import React from 'react'; import _truncate from 'lodash/truncate'; import * as linkify from 'linkifyjs'; import type { PluginArg } from 'linkifyjs'; import { createTokenClass, registerPlugin, init } from 'linkifyjs'; import 'linkify-plugin-mention'; // Initialize linkify init(); // Register hashtag plugin for linkifyjs 4.x const HashtagToken = createTokenClass('hashtag', { isLink: true, toHref() { return this.toString(); }, }); registerPlugin('hashtag', ({ scanner, parser }: PluginArg) => { const { POUND, UNDERSCORE, LOCALHOST } = scanner.tokens as Record; const { domain } = scanner.tokens.groups; // # const Hash = parser.start.tt(POUND); // Valid hashtag const Hashtag = Hash.tt(UNDERSCORE, HashtagToken); Hash.ta(domain, Hashtag); // Continue hashtag Hashtag.ta(domain, Hashtag); Hashtag.tt(UNDERSCORE, Hashtag); // Support for _ in hashtags (like original implementation) Hashtag.tt(LOCALHOST, Hashtag); }); type ClickCallback = (word: string) => void; type Word = string | JSX.Element; type WordArray = Array; type WordArrayArray = Array; type CustomAnchorProps = { type: 'mention' | 'hashtag'; value: string; word: string; clickCallback?: ClickCallback; parentClass?: string; }; const CustomAnchor = ({ type, word, parentClass, value, clickCallback = () => {} }: CustomAnchorProps) => ( {!word.startsWith(value) && word.slice(0, word.indexOf(value))} clickCallback(value.substring(1))} className={`${parentClass}__${type}`}> {value} {!word.endsWith(value) && word.slice(word.indexOf(value) + value.length)} ); const renderWord = ( word: string, key: string, parentClass?: string, onClickMention?: ClickCallback, onClickHashtag?: ClickCallback, ): Word => { const [link] = linkify.find(word); if (!link) return word; const { type, value, href } = link; if (onClickMention && type === 'mention') { return ( ); } if (onClickHashtag && type === 'hashtag') { return ( ); } if (type === 'email' || type === 'url') { return ( {type === 'email' ? value : _truncate(value.replace(/(http(s?):\/\/)?(www\.)?/, ''), { length: 33 })} ); } return word; }; export const textRenderer = ( text?: string, parentClass?: string, onClickMention?: ClickCallback, onClickHashtag?: ClickCallback, ) => { if (!text) return <>; const renderedText = text .split(/\r\n|\r|\n/) // first break on line .map((line, i) => line .split(' ') // break for each word .map((word, j) => renderWord(word, `item-${i}-${j}`, parentClass, onClickMention, onClickHashtag)) .reduce((acc, elem) => (acc.length ? [acc, ' ', elem] : [elem]), []), ) .reduce((acc, elem) => (acc.length ? [acc, '\n', elem] : [elem]), []); return <>{renderedText}; };