···33 text: string
44 /** When true, renders link text without the anchor tag (useful when inside another link) */
55 plain?: boolean
66+ /** Package name to strip from the beginning of the description (if present) */
77+ packageName?: string
68}>()
7988-// Escape HTML to prevent XSS
99-function escapeHtml(text: string): string {
1010- return text
1010+// Strip markdown image badges from text
1111+function stripMarkdownImages(text: string): string {
1212+ // Remove linked images: [](link-url) - handles incomplete URLs too
1313+ // Using {0,500} instead of * to prevent ReDoS on pathological inputs
1414+ text = text.replace(/\[!\[[^\]]{0,500}\]\([^)]{0,2000}\)\]\([^)]{0,2000}\)?/g, '')
1515+ // Remove standalone images: 
1616+ text = text.replace(/!\[[^\]]{0,500}\]\([^)]{0,2000}\)/g, '')
1717+ // Remove any leftover empty links or broken markdown link syntax
1818+ text = text.replace(/\[\]\([^)]{0,2000}\)?/g, '')
1919+ return text.trim()
2020+}
2121+2222+// Strip HTML tags and escape remaining HTML to prevent XSS
2323+function stripAndEscapeHtml(text: string): string {
2424+ // First strip markdown image badges
2525+ let stripped = stripMarkdownImages(text)
2626+2727+ // Then strip actual HTML tags (keep their text content)
2828+ // Only match tags that start with a letter or / (to avoid matching things like "a < b > c")
2929+ stripped = stripped.replace(/<\/?[a-z][^>]*>/gi, '')
3030+3131+ if (props.packageName) {
3232+ // Trim first to handle leading/trailing whitespace from stripped HTML
3333+ stripped = stripped.trim()
3434+ // Collapse multiple whitespace into single space
3535+ stripped = stripped.replace(/\s+/g, ' ')
3636+ // Escape special regex characters in package name
3737+ const escapedName = props.packageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
3838+ // Match package name at the start, optionally followed by: space, dash, colon, hyphen, or just space
3939+ const namePattern = new RegExp(`^${escapedName}\\s*[-:—]?\\s*`, 'i')
4040+ stripped = stripped.replace(namePattern, '').trim()
4141+ }
4242+4343+ // Then escape any remaining HTML entities
4444+ return stripped
1145 .replace(/&/g, '&')
1246 .replace(/</g, '<')
1347 .replace(/>/g, '>')
···1953function parseMarkdown(text: string): string {
2054 if (!text) return ''
21552222- // First escape HTML
2323- let html = escapeHtml(text)
5656+ // First strip HTML tags and escape remaining HTML
5757+ let html = stripAndEscapeHtml(text)
24582559 // Bold: **text** or __text__
2660 html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')