diff --git a/app/components/Package/Playgrounds.vue b/app/components/Package/Playgrounds.vue index 199439e04..5193e9696 100644 --- a/app/components/Package/Playgrounds.vue +++ b/app/components/Package/Playgrounds.vue @@ -3,9 +3,22 @@ import type { PlaygroundLink } from '#shared/types' import { decodeHtmlEntities } from '~/utils/formatters' const props = defineProps<{ - links: PlaygroundLink[] + packageName: string + requestedVersion: string | null }>() +// Fetch README for specific version if requested, otherwise latest +const { data } = useLazyFetch( + () => { + const base = `/api/registry/readme/${props.packageName}` + const version = props.requestedVersion + return version ? `${base}/v/${version}` : base + }, + { default: () => ({ html: '', md: '', playgroundLinks: [], toc: [] }) }, +) + +const links = computed(() => data.value?.playgroundLinks ?? []) + // Map provider id to icon class const providerIcons: Record = { 'stackblitz': 'i-simple-icons:stackblitz', @@ -51,9 +64,9 @@ onClickOutside(dropdownRef, () => { }) // Single vs multiple -const hasSingleLink = computed(() => props.links.length === 1) -const hasMultipleLinks = computed(() => props.links.length > 1) -const firstLink = computed(() => props.links[0]) +const hasSingleLink = computed(() => links.value.length === 1) +const hasMultipleLinks = computed(() => links.value.length > 1) +const firstLink = computed(() => links.value[0]) function closeDropdown() { isOpen.value = false @@ -78,12 +91,12 @@ function handleKeydown(event: KeyboardEvent) { break case 'ArrowDown': event.preventDefault() - focusedIndex.value = (focusedIndex.value + 1) % props.links.length + focusedIndex.value = (focusedIndex.value + 1) % links.value.length focusMenuItem(focusedIndex.value) break case 'ArrowUp': event.preventDefault() - focusedIndex.value = focusedIndex.value <= 0 ? props.links.length - 1 : focusedIndex.value - 1 + focusedIndex.value = focusedIndex.value <= 0 ? links.value.length - 1 : focusedIndex.value - 1 focusMenuItem(focusedIndex.value) break case 'Home': @@ -93,8 +106,8 @@ function handleKeydown(event: KeyboardEvent) { break case 'End': event.preventDefault() - focusedIndex.value = props.links.length - 1 - focusMenuItem(props.links.length - 1) + focusedIndex.value = links.value.length - 1 + focusMenuItem(links.value.length - 1) break case 'Tab': closeDropdown() diff --git a/app/components/Package/Readme.vue b/app/components/Package/Readme.vue new file mode 100644 index 000000000..3bacadb28 --- /dev/null +++ b/app/components/Package/Readme.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/app/components/ReadmeTocDropdown.vue b/app/components/ReadmeTocDropdown.vue index 0fba743c9..d4a24c1f5 100644 --- a/app/components/ReadmeTocDropdown.vue +++ b/app/components/ReadmeTocDropdown.vue @@ -152,7 +152,6 @@ function handleKeydown(event: KeyboardEvent) { @click="toggle" @keydown="handleKeydown" classicon="i-carbon:list" - class="px-2.5" block > ( - () => { - const base = `/api/registry/readme/${packageName.value}` - const version = requestedVersion.value - return version ? `${base}/v/${version}` : base - }, - { default: () => ({ html: '', md: '', playgroundLinks: [], toc: [] }) }, -) - -//copy README file as Markdown -const { copied: copiedReadme, copy: copyReadme } = useClipboard({ - source: () => readmeData.value?.md ?? '', - copiedDuring: 2000, -}) - -// Track active TOC item based on scroll position -const tocItems = computed(() => readmeData.value?.toc ?? []) -const { activeId: activeTocId } = useActiveTocItem(tocItems) - // Check if package exists on JSR (only for scoped packages) const { data: jsrInfo } = useLazyFetch(() => `/api/jsr/${packageName.value}`, { default: () => ({ exists: false }), @@ -1213,53 +1192,12 @@ const showSkeleton = shallowRef(false) - -
-
-

- - {{ $t('package.readme.title') }} - -

-
- - - - {{ copiedReadme ? $t('common.copied') : $t('common.copy') }} - - - -
-
- - - -

- {{ $t('package.readme.no_readme') }} - {{ $t('package.readme.view_on_github') }} -

+
+
@@ -1471,14 +1409,6 @@ const showSkeleton = shallowRef(false) overflow-x: hidden; } -.areaReadme { - grid-area: readme; -} - -.areaReadme > :global(.readme) { - overflow-x: hidden; -} - .areaSidebar { grid-area: sidebar; } diff --git a/i18n/locales/en.json b/i18n/locales/en.json index e3f07511e..201fb53ff 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -236,6 +236,7 @@ "no_readme": "No README available.", "view_on_github": "View on GitHub", "toc_title": "Outline", + "load_error": "Failed to load README", "callout": { "note": "Note", "tip": "Tip", diff --git a/i18n/schema.json b/i18n/schema.json index 84cbbd10c..6d9aaa6fe 100644 --- a/i18n/schema.json +++ b/i18n/schema.json @@ -712,6 +712,9 @@ "toc_title": { "type": "string" }, + "load_error": { + "type": "string" + }, "callout": { "type": "object", "properties": { diff --git a/lunaria/files/en-GB.json b/lunaria/files/en-GB.json index 458a5f8f7..2e8654880 100644 --- a/lunaria/files/en-GB.json +++ b/lunaria/files/en-GB.json @@ -235,6 +235,7 @@ "no_readme": "No README available.", "view_on_github": "View on GitHub", "toc_title": "Outline", + "load_error": "Failed to load README", "callout": { "note": "Note", "tip": "Tip", diff --git a/lunaria/files/en-US.json b/lunaria/files/en-US.json index a4424e083..850aae18d 100644 --- a/lunaria/files/en-US.json +++ b/lunaria/files/en-US.json @@ -235,6 +235,7 @@ "no_readme": "No README available.", "view_on_github": "View on GitHub", "toc_title": "Outline", + "load_error": "Failed to load README", "callout": { "note": "Note", "tip": "Tip", diff --git a/server/api/registry/file/[...pkg].get.ts b/server/api/registry/file/[...pkg].get.ts index c1dd76c1b..7f2497c40 100644 --- a/server/api/registry/file/[...pkg].get.ts +++ b/server/api/registry/file/[...pkg].get.ts @@ -94,7 +94,7 @@ async function fetchFileContent( * - /api/registry/file/@scope/packageName/v/1.2.3/path/to/file.ts */ export default defineCachedEventHandler( - async event => { + async (event): Promise => { // Parse: [pkg, 'v', version, ...filePath] or [@scope, pkg, 'v', version, ...filePath] const pkgParamSegments = getRouterParam(event, 'pkg')?.split('/') ?? [] diff --git a/server/api/registry/readme/[...pkg].get.ts b/server/api/registry/readme/[...pkg].get.ts index ca64a55c0..82e8e0a08 100644 --- a/server/api/registry/readme/[...pkg].get.ts +++ b/server/api/registry/readme/[...pkg].get.ts @@ -56,7 +56,7 @@ async function fetchReadmeFromJsdelivr( * - /api/registry/readme/@scope/packageName/v/1.2.3 - scoped package, specific version */ export default defineCachedEventHandler( - async event => { + async (event): Promise => { // Parse package name and optional version from URL segments // Patterns: [pkg] or [pkg, 'v', version] or [@scope, pkg] or [@scope, pkg, 'v', version] const pkgParamSegments = getRouterParam(event, 'pkg')?.split('/') ?? [] @@ -107,7 +107,7 @@ export default defineCachedEventHandler( } if (!readmeContent || readmeContent === NPM_MISSING_README_SENTINEL) { - return { html: '', playgroundLinks: [], toc: [] } + return { html: '', playgroundLinks: [], toc: [], md: '' } } // Parse repository info for resolving relative URLs to GitHub