|
1 | | -import React from "react" |
2 | | - |
3 | 1 | const URL_REGEX = /https?:\/\/[^\s]+/g |
| 2 | +const LINK_REGEX = /<a\s+([^>]*href\s*=\s*[^>]*)>/gi |
| 3 | + |
| 4 | +export function formatDescription(text: string): string { |
| 5 | + // we coerce all existing anchor tags to have target="_blank" rel="noopener noreferrer" and typography-link class |
| 6 | + const result = text.replace(LINK_REGEX, (_, attributes) => { |
| 7 | + let attrs = attributes |
| 8 | + |
| 9 | + if (!attrs.includes("target=")) { |
| 10 | + attrs += ' target="_blank"' |
| 11 | + } |
| 12 | + |
| 13 | + if (!attrs.includes("rel=")) { |
| 14 | + attrs += ' rel="noopener noreferrer"' |
| 15 | + } |
| 16 | + |
| 17 | + if (!attrs.includes("class=")) { |
| 18 | + attrs += ' class="typography-link"' |
| 19 | + } else if (!attrs.includes("typography-link")) { |
| 20 | + attrs = attrs.replace( |
| 21 | + /class\s*=\s*["']([^"']*)/gi, |
| 22 | + 'class="$1 typography-link', |
| 23 | + ) |
| 24 | + } |
| 25 | + |
| 26 | + return `<a ${attrs}>` |
| 27 | + }) |
4 | 28 |
|
5 | | -export function formatDescription(text: string): React.ReactNode { |
6 | | - const res: React.ReactNode[] = [] |
| 29 | + // then we format plain URLs that are not already inside an anchor tag |
| 30 | + return result.replace(URL_REGEX, (url, offset) => { |
| 31 | + const beforeUrl = result.slice(0, offset) |
| 32 | + const afterUrl = result.slice(offset + url.length) |
7 | 33 |
|
8 | | - let lastIndex = 0 |
9 | | - let match: RegExpExecArray | null |
| 34 | + const lastOpenTag = beforeUrl.lastIndexOf("<") |
| 35 | + const lastCloseTag = beforeUrl.lastIndexOf(">") |
| 36 | + const nextCloseTag = afterUrl.indexOf(">") |
10 | 37 |
|
11 | | - while ((match = URL_REGEX.exec(text)) !== null) { |
12 | | - if (match.index > lastIndex) { |
13 | | - res.push(text.slice(lastIndex, match.index)) |
| 38 | + if (lastOpenTag > lastCloseTag && nextCloseTag !== -1) { |
| 39 | + return url |
14 | 40 | } |
15 | 41 |
|
16 | | - res.push( |
17 | | - <a |
18 | | - href={match[0]} |
19 | | - target="_blank" |
20 | | - rel="noopener noreferrer" |
21 | | - className="typography-link" |
22 | | - > |
23 | | - {match[0].replace(/^https?:\/\//, "")} |
24 | | - </a>, |
25 | | - ) |
26 | | - |
27 | | - lastIndex = match.index + match[0].length |
28 | | - } |
29 | | - |
30 | | - if (lastIndex < text.length) { |
31 | | - res.push(text.slice(lastIndex)) |
32 | | - } |
33 | | - |
34 | | - return <>{res}</> |
| 42 | + const displayUrl = url.replace(/^https?:\/\//, "") |
| 43 | + return `<a href="${url}" target="_blank" rel="noopener noreferrer" class="typography-link">${displayUrl}</a>` |
| 44 | + }) |
35 | 45 | } |
0 commit comments