|
1 | | -const URL_REGEX = /https?:\/\/[^\s]+/g |
2 | | -const LINK_REGEX = /<a\s+([^>]*href\s*=\s*[^>]*)>/gi |
| 1 | +// Combined regex that matches either existing anchor tags OR standalone URLs |
| 2 | +const COMBINED_REGEX = |
| 3 | + /(<a\s+[^>]*href\s*=\s*[^>]*>.*?<\/a>)|(https?:\/\/[^\s]+)/gi |
3 | 4 |
|
4 | 5 | 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', |
| 6 | + return text.replace(COMBINED_REGEX, (match, anchorTag, standaloneUrl) => { |
| 7 | + if (anchorTag) { |
| 8 | + // Handle existing anchor tag |
| 9 | + const linkMatch = anchorTag.match( |
| 10 | + /<a\s+([^>]*href\s*=\s*[^>]*)>(.*?)<\/a>/i, |
| 11 | + ) |
| 12 | + if (!linkMatch) return anchorTag |
| 13 | + |
| 14 | + const [, attributes, content] = linkMatch |
| 15 | + let attrs = attributes |
| 16 | + |
| 17 | + if (!attrs.includes("rel=")) { |
| 18 | + attrs += ' rel="noopener noreferrer"' |
| 19 | + } |
| 20 | + |
| 21 | + if (!attrs.includes("target=")) { |
| 22 | + attrs += ' target="_blank"' |
| 23 | + } |
| 24 | + |
| 25 | + if (!attrs.includes("class=")) { |
| 26 | + attrs += ' class=" typography-link"' |
| 27 | + } else if (!attrs.includes("typography-link")) { |
| 28 | + attrs = attrs.replace( |
| 29 | + /class\s*=\s*["']([^"']*)/gi, |
| 30 | + 'class="$1 typography-link', |
| 31 | + ) |
| 32 | + } |
| 33 | + |
| 34 | + // Format URL content to show just domain |
| 35 | + const urlContent = content.replace( |
| 36 | + /https?:\/\/[^\s]+/g, |
| 37 | + (url: string) => { |
| 38 | + return url.replace(/^https?:\/\//, "") |
| 39 | + }, |
23 | 40 | ) |
24 | | - } |
25 | | - |
26 | | - return `<a ${attrs}>` |
27 | | - }) |
28 | | - |
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) |
33 | | - |
34 | | - const lastOpenTag = beforeUrl.lastIndexOf("<") |
35 | | - const lastCloseTag = beforeUrl.lastIndexOf(">") |
36 | | - const nextCloseTag = afterUrl.indexOf(">") |
37 | 41 |
|
38 | | - if (lastOpenTag > lastCloseTag && nextCloseTag !== -1) { |
39 | | - return url |
| 42 | + return `<a ${attrs}>${urlContent}</a>` |
| 43 | + } else if (standaloneUrl) { |
| 44 | + // Handle standalone URL |
| 45 | + const displayUrl = standaloneUrl.replace(/^https?:\/\//, "") |
| 46 | + return `<a href="${standaloneUrl}" target="_blank" rel="noopener noreferrer" class="typography-link">${displayUrl}</a>` |
40 | 47 | } |
41 | 48 |
|
42 | | - const displayUrl = url.replace(/^https?:\/\//, "") |
43 | | - return `<a href="${url}" target="_blank" rel="noopener noreferrer" class="typography-link">${displayUrl}</a>` |
| 49 | + return match |
44 | 50 | }) |
45 | 51 | } |
0 commit comments