Skip to content

Commit a76bd69

Browse files
committed
fix: strip HTML tags from search result descriptions
After decoding HTML entities, descriptions containing entity-encoded HTML (e.g. <a>) were displaying reconstructed tags as raw text. Apply stripHtmlTags after decodeHtmlEntities in PackageSelector and TableRow components. Fixes #1681
1 parent 71eba9d commit a76bd69

File tree

4 files changed

+30
-3
lines changed

4 files changed

+30
-3
lines changed

app/components/Compare/PackageSelector.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ onClickOutside(containerRef, () => {
301301
v-if="result.description"
302302
class="text-xs text-fg-muted truncate mt-0.5 w-full block"
303303
>
304-
{{ decodeHtmlEntities(result.description) }}
304+
{{ stripHtmlTags(decodeHtmlEntities(result.description)) }}
305305
</span>
306306
</ButtonBase>
307307
</div>

app/components/Package/TableRow.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ const allMaintainersText = computed(() => {
6969
v-if="isColumnVisible('description')"
7070
class="py-2 px-3 text-sm text-fg-muted max-w-xs truncate"
7171
>
72-
{{ decodeHtmlEntities(pkg.description || '-') }}
72+
{{ stripHtmlTags(decodeHtmlEntities(pkg.description || '-')) }}
7373
</td>
7474

7575
<!-- Downloads -->

shared/utils/html.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,7 @@ const htmlEntities: Record<string, string> = {
1111
export function decodeHtmlEntities(text: string): string {
1212
return text.replace(/&(?:amp|lt|gt|quot|apos|nbsp|#39);/g, match => htmlEntities[match] || match)
1313
}
14+
15+
export function stripHtmlTags(text: string): string {
16+
return text.replace(/<[^>]*>/g, '')
17+
}

test/unit/shared/utils/html.spec.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, it } from 'vitest'
2-
import { decodeHtmlEntities } from '../../../../shared/utils/html'
2+
import { decodeHtmlEntities, stripHtmlTags } from '../../../../shared/utils/html'
33

44
describe('decodeHtmlEntities', () => {
55
it.each([
@@ -26,3 +26,26 @@ describe('decodeHtmlEntities', () => {
2626
expect(decodeHtmlEntities('&unknown;')).toBe('&unknown;')
2727
})
2828
})
29+
30+
describe('stripHtmlTags', () => {
31+
it('removes simple HTML tags', () => {
32+
expect(stripHtmlTags('<b>bold</b>')).toBe('bold')
33+
})
34+
35+
it('removes anchor tags keeping text content', () => {
36+
expect(stripHtmlTags('<a href="https://example.com">link</a>')).toBe('link')
37+
})
38+
39+
it('removes self-closing tags', () => {
40+
expect(stripHtmlTags('before<br/>after')).toBe('beforeafter')
41+
})
42+
43+
it('leaves plain text unchanged', () => {
44+
expect(stripHtmlTags('no tags here')).toBe('no tags here')
45+
})
46+
47+
it('works with decodeHtmlEntities to clean descriptions', () => {
48+
const raw = '&lt;a href=&quot;url&quot;&gt;link&lt;/a&gt; and text'
49+
expect(stripHtmlTags(decodeHtmlEntities(raw))).toBe('link and text')
50+
})
51+
})

0 commit comments

Comments
 (0)