From a115d385ddd0c6cf5d7840d68a3e4d4fe7e54425 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 27 Apr 2022 13:40:00 +1000 Subject: [PATCH] merge linkify component to messagebody as this is the only one using it --- ts/components/conversation/Linkify.tsx | 95 ------------------- .../message/message-content/MessageBody.tsx | 95 ++++++++++++++++++- 2 files changed, 92 insertions(+), 98 deletions(-) delete mode 100644 ts/components/conversation/Linkify.tsx diff --git a/ts/components/conversation/Linkify.tsx b/ts/components/conversation/Linkify.tsx deleted file mode 100644 index ef50a69c6..000000000 --- a/ts/components/conversation/Linkify.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React from 'react'; - -import LinkifyIt from 'linkify-it'; - -import { RenderTextCallbackType } from '../../types/Util'; -import { updateConfirmModal } from '../../state/ducks/modalDialog'; -import { shell } from 'electron'; -import { MessageInteraction } from '../../interactions'; -import { useDispatch } from 'react-redux'; -import { LinkPreviews } from '../../util/linkPreviews'; - -const linkify = LinkifyIt(); - -type Props = { - text: string; - /** Allows you to customize now non-links are rendered. Simplest is just a . */ - renderNonLink?: RenderTextCallbackType; - isGroup: boolean; -}; - -const SUPPORTED_PROTOCOLS = /^(http|https):/i; - -const defaultRenderNonLink = ({ text }: { text: string }) => <>{text}; - -export const Linkify = (props: Props): JSX.Element => { - const { text, isGroup, renderNonLink } = props; - const results: Array = []; - let count = 1; - const dispatch = useDispatch(); - const matchData = linkify.match(text) || []; - let last = 0; - // disable click on elements so clicking a message containing a link doesn't - // select the message.The link will still be opened in the browser. - const handleClick = (e: any) => { - e.preventDefault(); - e.stopPropagation(); - - const url = e.target.href; - - const openLink = () => { - void shell.openExternal(url); - }; - - dispatch( - updateConfirmModal({ - title: window.i18n('linkVisitWarningTitle'), - message: window.i18n('linkVisitWarningMessage', url), - okText: window.i18n('open'), - cancelText: window.i18n('editMenuCopy'), - showExitIcon: true, - onClickOk: openLink, - onClickClose: () => { - dispatch(updateConfirmModal(null)); - }, - - onClickCancel: () => { - MessageInteraction.copyBodyToClipboard(url); - }, - }) - ); - }; - - const renderWith = renderNonLink || defaultRenderNonLink; - - if (matchData.length === 0) { - return renderWith({ text, key: 0, isGroup }); - } - - matchData.forEach((match: { index: number; url: string; lastIndex: number; text: string }) => { - if (last < match.index) { - const textWithNoLink = text.slice(last, match.index); - results.push(renderWith({ text: textWithNoLink, isGroup, key: count++ })); - } - - const { url, text: originalText } = match; - const isLink = SUPPORTED_PROTOCOLS.test(url) && !LinkPreviews.isLinkSneaky(url); - if (isLink) { - results.push( - - {originalText} - - ); - } else { - results.push(renderWith({ text: originalText, isGroup, key: count++ })); - } - - last = match.lastIndex; - }); - - if (last < text.length) { - results.push(renderWith({ text: text.slice(last), isGroup, key: count++ })); - } - - return <>{results}; -}; diff --git a/ts/components/conversation/message/message-content/MessageBody.tsx b/ts/components/conversation/message/message-content/MessageBody.tsx index 50f72c6f2..0ff3960a1 100644 --- a/ts/components/conversation/message/message-content/MessageBody.tsx +++ b/ts/components/conversation/message/message-content/MessageBody.tsx @@ -1,16 +1,24 @@ -import React from 'react'; +import React, { useCallback } from 'react'; +import { useDispatch } from 'react-redux'; +import { shell } from 'electron'; +import LinkifyIt from 'linkify-it'; + import { RenderTextCallbackType } from '../../../../types/Util'; import { getEmojiSizeClass, SizeClassType } from '../../../../util/emoji'; import { AddMentions } from '../../AddMentions'; import { AddNewLines } from '../../AddNewLines'; import { Emojify } from '../../Emojify'; -import { Linkify } from '../../Linkify'; +import { MessageInteraction } from '../../../../interactions'; +import { updateConfirmModal } from '../../../../state/ducks/modalDialog'; +import { LinkPreviews } from '../../../../util/linkPreviews'; + +const linkify = LinkifyIt(); type Props = { text: string; /** If set, all emoji will be the same size. Otherwise, just one emoji will be large. */ disableJumbomoji: boolean; - /** If set, links will be left alone instead of turned into clickable `` tags. */ + /** If set, links will be left alone instead of turned into clickable `` tags. Used in quotes, convo list item, etc */ disableLinks: boolean; isGroup: boolean; }; @@ -77,6 +85,7 @@ const JsxSelectable = (jsx: JSX.Element): JSX.Element => { ); }; + export const MessageBody = (props: Props) => { const { text, disableJumbomoji, disableLinks, isGroup } = props; const sizeClass: SizeClassType = disableJumbomoji ? 'default' : getEmojiSizeClass(text); @@ -113,3 +122,83 @@ export const MessageBody = (props: Props) => { /> ); }; + +type LinkifyProps = { + text: string; + /** Allows you to customize now non-links are rendered. Simplest is just a . */ + renderNonLink: RenderTextCallbackType; + isGroup: boolean; +}; + +const SUPPORTED_PROTOCOLS = /^(http|https):/i; + +const Linkify = (props: LinkifyProps): JSX.Element => { + const { text, isGroup, renderNonLink } = props; + const results: Array = []; + let count = 1; + const dispatch = useDispatch(); + const matchData = linkify.match(text) || []; + let last = 0; + + // disable click on elements so clicking a message containing a link doesn't + // select the message. The link will still be opened in the browser. + const handleClick = useCallback((e: any) => { + e.preventDefault(); + e.stopPropagation(); + + const url = e.target.href; + + const openLink = () => { + void shell.openExternal(url); + }; + + dispatch( + updateConfirmModal({ + title: window.i18n('linkVisitWarningTitle'), + message: window.i18n('linkVisitWarningMessage', url), + okText: window.i18n('open'), + cancelText: window.i18n('editMenuCopy'), + showExitIcon: true, + onClickOk: openLink, + onClickClose: () => { + dispatch(updateConfirmModal(null)); + }, + + onClickCancel: () => { + MessageInteraction.copyBodyToClipboard(url); + }, + }) + ); + }, []); + + if (matchData.length === 0) { + return renderNonLink({ text, key: 0, isGroup }); + } + + matchData.forEach((match: { index: number; url: string; lastIndex: number; text: string }) => { + if (last < match.index) { + const textWithNoLink = text.slice(last, match.index); + results.push(renderNonLink({ text: textWithNoLink, isGroup, key: count++ })); + } + + const { url, text: originalText } = match; + const isLink = SUPPORTED_PROTOCOLS.test(url) && !LinkPreviews.isLinkSneaky(url); + if (isLink) { + results.push( + + {originalText} + + ); + } else { + results.push(renderNonLink({ text: originalText, isGroup, key: count++ })); + } + + last = match.lastIndex; + }); + + if (last < text.length) { + results.push(renderNonLink({ text: text.slice(last), isGroup, key: count++ })); + } + + return <>{results}; +};