diff --git a/app/composables/useMarkdown.ts b/app/composables/useMarkdown.ts index bcf8678096..81fb9e90a4 100644 --- a/app/composables/useMarkdown.ts +++ b/app/composables/useMarkdown.ts @@ -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( /(`[^`]*`)||$)/g, @@ -69,6 +76,7 @@ function stripAndEscapeHtml(text: string): string { .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, ''') + .trim() } // Parse simple inline markdown to HTML diff --git a/shared/utils/html.ts b/shared/utils/html.ts index 1f9de45c67..d3605d02c4 100644 --- a/shared/utils/html.ts +++ b/shared/utils/html.ts @@ -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. diff --git a/test/nuxt/composables/use-markdown.spec.ts b/test/nuxt/composables/use-markdown.spec.ts index 2002a5ad30..12ef5491f1 100644 --- a/test/nuxt/composables/use-markdown.spec.ts +++ b/test/nuxt/composables/use-markdown.spec.ts @@ -256,6 +256,23 @@ describe('useMarkdown', () => { const processed = useMarkdown({ text: 'bold and **also bold**' }) expect(processed.value).toBe('bold and also bold') }) + + it('strips unclosed HTML tags (truncated)', () => { + const processed = useMarkdown({ + text: '

NPM version { + const processed = useMarkdown({ text: 'compare a npm i') + }) }) describe('HTML comment stripping', () => { @@ -286,7 +303,7 @@ describe('useMarkdown', () => { it('strips unclosed HTML comments (truncated)', () => { const processed = useMarkdown({ text: 'A library