diff --git a/app/pages/package/[[org]]/[name].vue b/app/pages/package/[[org]]/[name].vue index 092071131..6990e3046 100644 --- a/app/pages/package/[[org]]/[name].vue +++ b/app/pages/package/[[org]]/[name].vue @@ -5,6 +5,7 @@ import type { PackumentVersion, ProvenanceDetails, ReadmeResponse, + ReadmeMarkdownResponse, SkillsListResponse, } from '#shared/types' import type { JsrPackageInfo } from '#shared/types/jsr' @@ -106,15 +107,47 @@ const { data: readmeData } = useLazyFetch( const version = requestedVersion.value return version ? `${base}/v/${version}` : base }, - { default: () => ({ html: '', md: '', playgroundLinks: [], toc: [] }) }, + { default: () => ({ html: '', mdExists: false, playgroundLinks: [], toc: [] }) }, +) + +const { + data: readmeMarkdownData, + status: readmeMarkdownStatus, + execute: fetchReadmeMarkdown, +} = useLazyFetch( + () => { + const base = `/api/registry/readme/markdown/${packageName.value}` + const version = requestedVersion.value + return version ? `${base}/v/${version}` : base + }, + { + server: false, + immediate: false, + default: () => ({}), + }, ) //copy README file as Markdown const { copied: copiedReadme, copy: copyReadme } = useClipboard({ - source: () => readmeData.value?.md ?? '', + source: () => '', copiedDuring: 2000, }) +function prefetchReadmeMarkdown() { + if (readmeMarkdownStatus.value === 'idle') { + fetchReadmeMarkdown() + } +} + +async function copyReadmeHandler() { + await fetchReadmeMarkdown() + + const markdown = readmeMarkdownData.value?.markdown + if (!markdown) return + + await copyReadme(markdown) +} + // Track active TOC item based on scroll position const tocItems = computed(() => readmeData.value?.toc ?? []) const { activeId: activeTocId } = useActiveTocItem(tocItems) @@ -1238,12 +1271,14 @@ const showSkeleton = shallowRef(false)
Title + it('extracts toc from headings', async () => { + const markdown = `# Install\n\n## CLI\n\n## API` + const result = await renderReadmeHtml(markdown, 'test-pkg') + + expect(result.toc).toHaveLength(3) + expect(result.toc[0]).toMatchObject({ text: 'Install', depth: 1 }) + expect(result.toc[1]).toMatchObject({ text: 'CLI', depth: 2 }) + expect(result.toc[2]).toMatchObject({ text: 'API', depth: 2 }) + expect(result.toc.every(t => t.id.startsWith('user-content-'))).toBe(true) + }) +}) + +describe('HTML output', () => { + it('returns sanitized html', async () => { + const markdown = `# Title\n\nSome **bold** text and a [link](https://example.com).` + const result = await renderReadmeHtml(markdown, 'test-pkg') + + expect(result.html).toBe(`

Title

Some bold text and a link.

`) - }) }) })