You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-desktop/ts/components/conversation/Linkify.tsx

96 lines
2.7 KiB
TypeScript

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 <span>. */
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<any> = [];
let count = 1;
const dispatch = useDispatch();
const matchData = linkify.match(text) || [];
let last = 0;
// disable click on <a> 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(
<a key={count++} href={url} onClick={handleClick}>
{originalText}
</a>
);
} 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}</>;
};