Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions app/composables/useMarkdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,19 @@ function stripAndEscapeHtml(text: string, packageName?: string): string {
// Then strip markdown image badges
stripped = stripMarkdownImages(stripped)

// Then strip actual HTML tags (keep their text content)
// Only match tags that start with a letter or / (to avoid matching things like "a < b > c")
stripped = stripped.replace(/<\/?[a-z][^>]*>/gi, '')
// Strip actual HTML tags (keep their text content), but leave tags inside backtick spans
// The alternation matches a backtick span first — if that branch wins the match is kept as-is
stripped = stripped.replace(
/(`[^`]*`)|<\/?[a-z][^>]*>/gi,
(match, codeSpan: string | undefined) => codeSpan ?? '',
)

// Strip HTML comments: <!-- ... --> (including unclosed comments from truncation)
stripped = stripped.replace(/<!--[\s\S]*?(-->|$)/g, '')
// Strip HTML comments: <!-- ... --> (including unclosed comments from truncation)
Comment thread
danielroe marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
stripped = stripped.replace(
/(`[^`]*`)|<!--[\s\S]*?(-->|$)/g,
(match, codeSpan: string | undefined) => codeSpan ?? '',
)

if (packageName) {
// Trim first to handle leading/trailing whitespace from stripped HTML
Expand Down
46 changes: 46 additions & 0 deletions test/nuxt/composables/use-markdown.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,4 +350,50 @@ describe('useMarkdown', () => {
expect(processed.value).toBe('A library ')
})
})

describe('HTML tags inside backtick spans (regression #1478)', () => {
it('preserves HTML tags inside backtick code spans', () => {
const processed = useMarkdown({ text: 'Use `<div>` for layout' })
expect(processed.value).toBe('Use <code>&lt;div&gt;</code> for layout')
})

it('preserves multiple HTML tags inside one backtick span', () => {
const processed = useMarkdown({ text: 'Use `<div><span>test</span></div>` element' })
expect(processed.value).toBe(
'Use <code>&lt;div&gt;&lt;span&gt;test&lt;/span&gt;&lt;/div&gt;</code> element',
)
})

it('preserves backtick spans while stripping bare HTML tags', () => {
const processed = useMarkdown({ text: '`<a>` some <b>bold</b> text `<c>`' })
expect(processed.value).toBe('<code>&lt;a&gt;</code> some bold text <code>&lt;c&gt;</code>')
})

it('strips HTML tags outside backticks but keeps backtick content', () => {
const processed = useMarkdown({ text: '<b>hello</b> and `<input type="text">` world' })
expect(processed.value).toBe(
'hello and <code>&lt;input type=&quot;text&quot;&gt;</code> world',
)
})

it('handles backtick span with self-closing tag', () => {
const processed = useMarkdown({ text: 'Use `<br/>` for line breaks' })
expect(processed.value).toBe('Use <code>&lt;br/&gt;</code> for line breaks')
})

it('handles backtick spans without HTML inside', () => {
const processed = useMarkdown({ text: '`code` and <b>stripped</b>' })
expect(processed.value).toBe('<code>code</code> and stripped')
})

it('preserves HTML comments inside backtick spans', () => {
const processed = useMarkdown({ text: 'Use `<!-- comment -->` syntax' })
expect(processed.value).toBe('Use <code>&lt;!-- comment --&gt;</code> syntax')
})

it('strips HTML comments outside backtick spans', () => {
const processed = useMarkdown({ text: '`<div>` <!-- badge --> is an element' })
expect(processed.value).toBe('<code>&lt;div&gt;</code> is an element')
})
})
})
Loading