Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions app/composables/useMarkdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ function stripAndEscapeHtml(text: string): string {
(match, codeSpan: string | undefined) => codeSpan ?? '',
)

// Strip unclosed HTML tags left by registry truncation (no closing '>').
// The tag branch must not cross a backtick, or it would swallow a later code span
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,
Expand All @@ -69,6 +76,7 @@ function stripAndEscapeHtml(text: string): string {
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;')
.trim()
}

// Parse simple inline markdown to HTML
Expand Down
4 changes: 3 additions & 1 deletion shared/utils/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ export function stripHtmlTags(text: string): string {
previous = result
result = result.replace(tagPattern, '')
} while (result !== previous)
return result
// Strip unclosed HTML tags left by registry truncation (no closing '>').
// The match must not cross a backtick, or it would swallow a later code span
return result.replace(/<\/?[a-z][^>`]*$/gi, '').trim()
}
/**
* Generate a GitHub-style slug from heading text.
Expand Down
19 changes: 18 additions & 1 deletion test/nuxt/composables/use-markdown.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,23 @@ describe('useMarkdown', () => {
const processed = useMarkdown({ text: '<b>bold</b> and **also bold**' })
expect(processed.value).toBe('bold and <strong>also bold</strong>')
})

it('strips unclosed HTML tags (truncated)', () => {
const processed = useMarkdown({
text: '<p> <a href="https://www.npmjs.com/package/vue-tsc"><img src="https://img.shields.io/npm/v/vue-tsc.svg?labelColor=18181B&color=1584FC" alt="NPM version"></a> <a href="https://github.com/vuejs/language-tools/blob/master/LICENSE"><img src="https://img.s',
})
expect(processed.value).toBe('')
})

it('strips a trailing unclosed tag but keeps preceding text', () => {
const processed = useMarkdown({ text: 'A library <img src="https://img.s' })
expect(processed.value).toBe('A library')
})

it('preserves a backtick code span after an unclosed tag', () => {
const processed = useMarkdown({ text: 'compare a <b and run `npm i`' })
expect(processed.value).toContain('<code>npm i</code>')
})
})

describe('HTML comment stripping', () => {
Expand Down Expand Up @@ -286,7 +303,7 @@ describe('useMarkdown', () => {

it('strips unclosed HTML comments (truncated)', () => {
const processed = useMarkdown({ text: 'A library <!-- automd:badges color=yel' })
expect(processed.value).toBe('A library ')
expect(processed.value).toBe('A library')
})
})

Expand Down
20 changes: 20 additions & 0 deletions test/unit/shared/utils/html.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,24 @@ describe('stripHtmlTags', () => {
const raw = '&lt;a href=&quot;url&quot;&gt;link&lt;/a&gt; and text'
expect(stripHtmlTags(decodeHtmlEntities(raw))).toBe('link and text')
})

it('removes unclosed HTML tags at the end of truncated text', () => {
expect(stripHtmlTags('A library <img src="https://img.s')).toBe('A library')
})

it('returns empty string when truncated text is only HTML tags', () => {
expect(
stripHtmlTags(
'<p> <a href="https://www.npmjs.com/package/vue-tsc"><img src="https://img.shields.io/npm/v/vue-tsc.svg"></a> <img src="https://img.s',
),
).toBe('')
})

it('leaves comparison text that is not a tag', () => {
expect(stripHtmlTags('a < b')).toBe('a < b')
})

it('keeps a backtick code span after an unclosed tag', () => {
expect(stripHtmlTags('run <img src="x `npm i`')).toBe('run <img src="x `npm i`')
})
})
Loading