Skip to content

Commit 4e6e168

Browse files
vmrjnvcdanielroe
andauthored
fix: preserve backtick spans in markdown (#1608)
Co-authored-by: Daniel Roe <daniel@roe.dev>
1 parent 9774269 commit 4e6e168

File tree

2 files changed

+56
-4
lines changed

2 files changed

+56
-4
lines changed

app/composables/useMarkdown.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,18 @@ function stripAndEscapeHtml(text: string, packageName?: string): string {
3232
// Then strip markdown image badges
3333
stripped = stripMarkdownImages(stripped)
3434

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

3942
// Strip HTML comments: <!-- ... --> (including unclosed comments from truncation)
40-
stripped = stripped.replace(/<!--[\s\S]*?(-->|$)/g, '')
43+
stripped = stripped.replace(
44+
/(`[^`]*`)|<!--[\s\S]*?(-->|$)/g,
45+
(match, codeSpan: string | undefined) => codeSpan ?? '',
46+
)
4147

4248
if (packageName) {
4349
// Trim first to handle leading/trailing whitespace from stripped HTML

test/nuxt/composables/use-markdown.spec.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,4 +350,50 @@ describe('useMarkdown', () => {
350350
expect(processed.value).toBe('A library ')
351351
})
352352
})
353+
354+
describe('HTML tags inside backtick spans (regression #1478)', () => {
355+
it('preserves HTML tags inside backtick code spans', () => {
356+
const processed = useMarkdown({ text: 'Use `<div>` for layout' })
357+
expect(processed.value).toBe('Use <code>&lt;div&gt;</code> for layout')
358+
})
359+
360+
it('preserves multiple HTML tags inside one backtick span', () => {
361+
const processed = useMarkdown({ text: 'Use `<div><span>test</span></div>` element' })
362+
expect(processed.value).toBe(
363+
'Use <code>&lt;div&gt;&lt;span&gt;test&lt;/span&gt;&lt;/div&gt;</code> element',
364+
)
365+
})
366+
367+
it('preserves backtick spans while stripping bare HTML tags', () => {
368+
const processed = useMarkdown({ text: '`<a>` some <b>bold</b> text `<c>`' })
369+
expect(processed.value).toBe('<code>&lt;a&gt;</code> some bold text <code>&lt;c&gt;</code>')
370+
})
371+
372+
it('strips HTML tags outside backticks but keeps backtick content', () => {
373+
const processed = useMarkdown({ text: '<b>hello</b> and `<input type="text">` world' })
374+
expect(processed.value).toBe(
375+
'hello and <code>&lt;input type=&quot;text&quot;&gt;</code> world',
376+
)
377+
})
378+
379+
it('handles backtick span with self-closing tag', () => {
380+
const processed = useMarkdown({ text: 'Use `<br/>` for line breaks' })
381+
expect(processed.value).toBe('Use <code>&lt;br/&gt;</code> for line breaks')
382+
})
383+
384+
it('handles backtick spans without HTML inside', () => {
385+
const processed = useMarkdown({ text: '`code` and <b>stripped</b>' })
386+
expect(processed.value).toBe('<code>code</code> and stripped')
387+
})
388+
389+
it('preserves HTML comments inside backtick spans', () => {
390+
const processed = useMarkdown({ text: 'Use `<!-- comment -->` syntax' })
391+
expect(processed.value).toBe('Use <code>&lt;!-- comment --&gt;</code> syntax')
392+
})
393+
394+
it('strips HTML comments outside backtick spans', () => {
395+
const processed = useMarkdown({ text: '`<div>` <!-- badge --> is an element' })
396+
expect(processed.value).toBe('<code>&lt;div&gt;</code> is an element')
397+
})
398+
})
353399
})

0 commit comments

Comments
 (0)