Skip to content

Commit 0d9b25b

Browse files
committed
refactor: split out package readme component
1 parent ee73e57 commit 0d9b25b

3 files changed

Lines changed: 110 additions & 86 deletions

File tree

app/components/Package/Playgrounds.vue

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,22 @@ import type { PlaygroundLink } from '#shared/types'
33
import { decodeHtmlEntities } from '~/utils/formatters'
44
55
const props = defineProps<{
6-
links: PlaygroundLink[]
6+
packageName: string
7+
requestedVersion: string | null
78
}>()
89
10+
// Fetch README for specific version if requested, otherwise latest
11+
const { data } = useLazyFetch<ReadmeResponse>(
12+
() => {
13+
const base = `/api/registry/readme/${props.packageName}`
14+
const version = props.requestedVersion
15+
return version ? `${base}/v/${version}` : base
16+
},
17+
{ default: () => ({ html: '', md: '', playgroundLinks: [], toc: [] }) },
18+
)
19+
20+
const links = computed<PlaygroundLink[]>(() => data.value?.playgroundLinks ?? [])
21+
922
// Map provider id to icon class
1023
const providerIcons: Record<string, string> = {
1124
'stackblitz': 'i-simple-icons:stackblitz',
@@ -51,9 +64,9 @@ onClickOutside(dropdownRef, () => {
5164
})
5265
5366
// Single vs multiple
54-
const hasSingleLink = computed(() => props.links.length === 1)
55-
const hasMultipleLinks = computed(() => props.links.length > 1)
56-
const firstLink = computed(() => props.links[0])
67+
const hasSingleLink = computed(() => links.value.length === 1)
68+
const hasMultipleLinks = computed(() => links.value.length > 1)
69+
const firstLink = computed(() => links.value[0])
5770
5871
function closeDropdown() {
5972
isOpen.value = false
@@ -78,12 +91,12 @@ function handleKeydown(event: KeyboardEvent) {
7891
break
7992
case 'ArrowDown':
8093
event.preventDefault()
81-
focusedIndex.value = (focusedIndex.value + 1) % props.links.length
94+
focusedIndex.value = (focusedIndex.value + 1) % links.value.length
8295
focusMenuItem(focusedIndex.value)
8396
break
8497
case 'ArrowUp':
8598
event.preventDefault()
86-
focusedIndex.value = focusedIndex.value <= 0 ? props.links.length - 1 : focusedIndex.value - 1
99+
focusedIndex.value = focusedIndex.value <= 0 ? links.value.length - 1 : focusedIndex.value - 1
87100
focusMenuItem(focusedIndex.value)
88101
break
89102
case 'Home':
@@ -93,8 +106,8 @@ function handleKeydown(event: KeyboardEvent) {
93106
break
94107
case 'End':
95108
event.preventDefault()
96-
focusedIndex.value = props.links.length - 1
97-
focusMenuItem(props.links.length - 1)
109+
focusedIndex.value = links.value.length - 1
110+
focusMenuItem(links.value.length - 1)
98111
break
99112
case 'Tab':
100113
closeDropdown()

app/components/Package/Readme.vue

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<script setup lang="ts">
2+
import Readme from '../Readme.vue'
3+
4+
const props = defineProps<{
5+
packageName: string
6+
requestedVersion: string | null
7+
repositoryUrl: string | null
8+
}>()
9+
10+
// Fetch README for specific version if requested, otherwise latest
11+
const { data } = useLazyFetch<ReadmeResponse>(
12+
() => {
13+
const base = `/api/registry/readme/${props.packageName}`
14+
const version = props.requestedVersion
15+
return version ? `${base}/v/${version}` : base
16+
},
17+
{ default: () => ({ html: '', md: '', playgroundLinks: [], toc: [] }) },
18+
)
19+
20+
// Track active TOC item based on scroll position
21+
const tocItems = computed(() => data.value?.toc ?? [])
22+
const { activeId: activeTocId } = useActiveTocItem(tocItems)
23+
24+
//copy README file as Markdown
25+
const { copied: copiedReadme, copy: copyReadme } = useClipboard({
26+
source: () => data.value?.md ?? '',
27+
copiedDuring: 2000,
28+
})
29+
</script>
30+
31+
<template>
32+
<div class="flex flex-wrap items-center justify-between mb-3 px-1">
33+
<h2 id="readme-heading" class="group text-xs text-fg-subtle uppercase tracking-wider">
34+
<LinkBase to="#readme">
35+
{{ $t('package.readme.title') }}
36+
</LinkBase>
37+
</h2>
38+
<div class="flex gap-2">
39+
<!-- Copy readme as Markdown button -->
40+
<TooltipApp v-if="data?.md" :text="$t('package.readme.copy_as_markdown')" position="bottom">
41+
<ButtonBase
42+
@click="copyReadme()"
43+
:aria-pressed="copiedReadme"
44+
:aria-label="copiedReadme ? $t('common.copied') : $t('package.readme.copy_as_markdown')"
45+
:classicon="copiedReadme ? 'i-carbon:checkmark' : 'i-simple-icons:markdown'"
46+
>
47+
{{ copiedReadme ? $t('common.copied') : $t('common.copy') }}
48+
</ButtonBase>
49+
</TooltipApp>
50+
<ReadmeTocDropdown
51+
v-if="data?.toc && data.toc.length > 1"
52+
:toc="data.toc"
53+
:active-id="activeTocId"
54+
/>
55+
</div>
56+
</div>
57+
58+
<!-- eslint-disable vue/no-v-html -- HTML is sanitized server-side -->
59+
<Readme v-if="data?.html" :html="data.html" />
60+
<p v-else class="text-fg-muted italic">
61+
{{ $t('package.readme.no_readme') }}
62+
<a
63+
v-if="repositoryUrl"
64+
:href="repositoryUrl"
65+
target="_blank"
66+
rel="noopener noreferrer"
67+
class="link text-fg underline underline-offset-4 decoration-fg-subtle hover:(decoration-fg text-fg) transition-colors duration-200"
68+
>{{ $t('package.readme.view_on_github') }}</a
69+
>
70+
</p>
71+
</template>
72+
73+
<style module>
74+
.areaReadme {
75+
grid-area: readme;
76+
}
77+
78+
.areaReadme > :global(.readme) {
79+
overflow-x: hidden;
80+
}
81+
</style>

app/pages/package/[[org]]/[name].vue

Lines changed: 8 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import type {
44
PackageVersionInfo,
55
PackumentVersion,
66
ProvenanceDetails,
7-
ReadmeResponse,
87
SkillsListResponse,
98
} from '#shared/types'
109
import type { JsrPackageInfo } from '#shared/types/jsr'
@@ -98,26 +97,6 @@ if (import.meta.server) {
9897
assertValidPackageName(packageName.value)
9998
}
10099
101-
// Fetch README for specific version if requested, otherwise latest
102-
const { data: readmeData } = useLazyFetch<ReadmeResponse>(
103-
() => {
104-
const base = `/api/registry/readme/${packageName.value}`
105-
const version = requestedVersion.value
106-
return version ? `${base}/v/${version}` : base
107-
},
108-
{ default: () => ({ html: '', md: '', playgroundLinks: [], toc: [] }) },
109-
)
110-
111-
//copy README file as Markdown
112-
const { copied: copiedReadme, copy: copyReadme } = useClipboard({
113-
source: () => readmeData.value?.md ?? '',
114-
copiedDuring: 2000,
115-
})
116-
117-
// Track active TOC item based on scroll position
118-
const tocItems = computed(() => readmeData.value?.toc ?? [])
119-
const { activeId: activeTocId } = useActiveTocItem(tocItems)
120-
121100
// Check if package exists on JSR (only for scoped packages)
122101
const { data: jsrInfo } = useLazyFetch<JsrPackageInfo>(() => `/api/jsr/${packageName.value}`, {
123102
default: () => ({ exists: false }),
@@ -1213,53 +1192,12 @@ const showSkeleton = shallowRef(false)
12131192
</ClientOnly>
12141193
</div>
12151194

1216-
<!-- README -->
1217-
<section id="readme" class="min-w-0 scroll-mt-20" :class="$style.areaReadme">
1218-
<div class="flex flex-wrap items-center justify-between mb-3 px-1">
1219-
<h2 id="readme-heading" class="group text-xs text-fg-subtle uppercase tracking-wider">
1220-
<LinkBase to="#readme">
1221-
{{ $t('package.readme.title') }}
1222-
</LinkBase>
1223-
</h2>
1224-
<div class="flex gap-2">
1225-
<!-- Copy readme as Markdown button -->
1226-
<TooltipApp
1227-
v-if="readmeData?.md"
1228-
:text="$t('package.readme.copy_as_markdown')"
1229-
position="bottom"
1230-
>
1231-
<ButtonBase
1232-
@click="copyReadme()"
1233-
:aria-pressed="copiedReadme"
1234-
:aria-label="
1235-
copiedReadme ? $t('common.copied') : $t('package.readme.copy_as_markdown')
1236-
"
1237-
:classicon="copiedReadme ? 'i-carbon:checkmark' : 'i-simple-icons:markdown'"
1238-
>
1239-
{{ copiedReadme ? $t('common.copied') : $t('common.copy') }}
1240-
</ButtonBase>
1241-
</TooltipApp>
1242-
<ReadmeTocDropdown
1243-
v-if="readmeData?.toc && readmeData.toc.length > 1"
1244-
:toc="readmeData.toc"
1245-
:active-id="activeTocId"
1246-
/>
1247-
</div>
1248-
</div>
1249-
1250-
<!-- eslint-disable vue/no-v-html -- HTML is sanitized server-side -->
1251-
<Readme v-if="readmeData?.html" :html="readmeData.html" />
1252-
<p v-else class="text-fg-muted italic">
1253-
{{ $t('package.readme.no_readme') }}
1254-
<a
1255-
v-if="repositoryUrl"
1256-
:href="repositoryUrl"
1257-
target="_blank"
1258-
rel="noopener noreferrer"
1259-
class="link text-fg underline underline-offset-4 decoration-fg-subtle hover:(decoration-fg text-fg) transition-colors duration-200"
1260-
>{{ $t('package.readme.view_on_github') }}</a
1261-
>
1262-
</p>
1195+
<section id="readme" class="min-w-0 scroll-mt-20">
1196+
<PackageReadme
1197+
:package-name="pkg.name"
1198+
:requested-version="resolvedVersion ?? null"
1199+
:repository-url="repositoryUrl"
1200+
/>
12631201

12641202
<section
12651203
v-if="hasProvenance(displayVersion) && isMounted"
@@ -1320,8 +1258,8 @@ const showSkeleton = shallowRef(false)
13201258

13211259
<!-- Playground links -->
13221260
<PackagePlaygrounds
1323-
v-if="readmeData?.playgroundLinks?.length"
1324-
:links="readmeData.playgroundLinks"
1261+
:package-name="pkg.name"
1262+
:requested-version="resolvedVersion ?? null"
13251263
/>
13261264

13271265
<PackageCompatibility :engines="displayVersion?.engines" />
@@ -1471,14 +1409,6 @@ const showSkeleton = shallowRef(false)
14711409
overflow-x: hidden;
14721410
}
14731411
1474-
.areaReadme {
1475-
grid-area: readme;
1476-
}
1477-
1478-
.areaReadme > :global(.readme) {
1479-
overflow-x: hidden;
1480-
}
1481-
14821412
.areaSidebar {
14831413
grid-area: sidebar;
14841414
}

0 commit comments

Comments
 (0)