Skip to content

Commit f95b9cb

Browse files
committed
Merge remote-tracking branch 'origin/main' into feature/provenance
# Conflicts: # app/pages/[...package].vue
2 parents b9fd47e + 592982d commit f95b9cb

13 files changed

Lines changed: 570 additions & 654 deletions

File tree

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ import { hasProtocol } from 'ufo'
187187

188188
| Type | Convention | Example |
189189
| ---------------- | ------------------------ | ------------------------------ |
190-
| Vue components | PascalCase | `MarkdownText.vue` |
190+
| Vue components | PascalCase | `DateTime.vue` |
191191
| Pages | kebab-case | `search.vue`, `[...name].vue` |
192192
| Composables | camelCase + `use` prefix | `useNpmRegistry.ts` |
193193
| Server routes | kebab-case + method | `search.get.ts` |

app/components/Package/Card.vue

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ const isExactMatch = computed(() => {
1919
const name = props.result.package.name.toLowerCase()
2020
return query === name
2121
})
22+
23+
// Process package description
24+
const pkgDescription = useMarkdown(() => ({
25+
text: props.result.package.description ?? '',
26+
plain: true,
27+
packageName: props.result.package.name,
28+
}))
2229
</script>
2330

2431
<template>
@@ -74,11 +81,8 @@ const isExactMatch = computed(() => {
7481
</div>
7582
<div class="flex justify-start items-start gap-4 sm:gap-8">
7683
<div class="min-w-0">
77-
<p
78-
v-if="result.package.description"
79-
class="text-fg-muted text-xs sm:text-sm line-clamp-2 mb-2 sm:mb-3"
80-
>
81-
<MarkdownText :text="result.package.description" plain />
84+
<p v-if="pkgDescription" class="text-fg-muted text-xs sm:text-sm line-clamp-2 mb-2 sm:mb-3">
85+
<span v-html="pkgDescription" />
8286
</p>
8387
<div class="flex flex-wrap items-center gap-x-3 sm:gap-x-4 gap-y-2 text-xs text-fg-subtle">
8488
<dl v-if="showPublisher || result.package.date" class="flex items-center gap-4 m-0">
Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1-
<script setup lang="ts">
21
import { decodeHtmlEntities } from '~/utils/formatters'
32

4-
const props = defineProps<{
3+
interface UseMarkdownOptions {
54
text: string
65
/** When true, renders link text without the anchor tag (useful when inside another link) */
76
plain?: boolean
87
/** Package name to strip from the beginning of the description (if present) */
98
packageName?: string
10-
}>()
9+
}
10+
11+
/** @public */
12+
export function useMarkdown(options: MaybeRefOrGetter<UseMarkdownOptions>) {
13+
return computed(() => parseMarkdown(toValue(options)))
14+
}
1115

1216
// Strip markdown image badges from text
1317
function stripMarkdownImages(text: string): string {
@@ -22,7 +26,7 @@ function stripMarkdownImages(text: string): string {
2226
}
2327

2428
// Strip HTML tags and escape remaining HTML to prevent XSS
25-
function stripAndEscapeHtml(text: string): string {
29+
function stripAndEscapeHtml(text: string, packageName?: string): string {
2630
// First decode any HTML entities in the input
2731
let stripped = decodeHtmlEntities(text)
2832

@@ -33,13 +37,13 @@ function stripAndEscapeHtml(text: string): string {
3337
// Only match tags that start with a letter or / (to avoid matching things like "a < b > c")
3438
stripped = stripped.replace(/<\/?[a-z][^>]*>/gi, '')
3539

36-
if (props.packageName) {
40+
if (packageName) {
3741
// Trim first to handle leading/trailing whitespace from stripped HTML
3842
stripped = stripped.trim()
3943
// Collapse multiple whitespace into single space
4044
stripped = stripped.replace(/\s+/g, ' ')
4145
// Escape special regex characters in package name
42-
const escapedName = props.packageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
46+
const escapedName = packageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
4347
// Match package name at the start, optionally followed by: space, dash, colon, hyphen, or just space
4448
const namePattern = new RegExp(`^${escapedName}\\s*[-:—]?\\s*`, 'i')
4549
stripped = stripped.replace(namePattern, '').trim()
@@ -55,11 +59,11 @@ function stripAndEscapeHtml(text: string): string {
5559
}
5660

5761
// Parse simple inline markdown to HTML
58-
function parseMarkdown(text: string): string {
62+
function parseMarkdown({ text, packageName, plain }: UseMarkdownOptions): string {
5963
if (!text) return ''
6064

6165
// First strip HTML tags and escape remaining HTML
62-
let html = stripAndEscapeHtml(text)
66+
let html = stripAndEscapeHtml(text, packageName)
6367

6468
// Bold: **text** or __text__
6569
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
@@ -78,7 +82,7 @@ function parseMarkdown(text: string): string {
7882
// Links: [text](url) - only allow https, mailto
7983
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, text, url) => {
8084
// In plain mode, just render the link text without the anchor
81-
if (props.plain) {
85+
if (plain) {
8286
return text
8387
}
8488
const decodedUrl = url.replace(/&amp;/g, '&')
@@ -94,11 +98,3 @@ function parseMarkdown(text: string): string {
9498

9599
return html
96100
}
97-
98-
const html = computed(() => parseMarkdown(props.text))
99-
</script>
100-
101-
<template>
102-
<!-- eslint-disable-next-line vue/no-v-html -->
103-
<span v-html="html" />
104-
</template>

0 commit comments

Comments
 (0)