Skip to content

Commit 190fa9d

Browse files
committed
Render API descriptions via content-collections, drop /generated/api fetch
Move the per-component API JSON load from the [name] layout's `import.meta.glob('/generated/api/*.json')` into the components content-collection transform. JSDoc descriptions are now compiled to HTML with remark-gfm so backticks and links render as inline code and anchors, surfaced as `descriptionHtml` on each PropertyInfo. The page renders `{@html row.descriptionHtml}` with a fallback to the raw text, and a `.prose-inline` rule scopes link/code styling without bleeding block-level prose resets. Eliminates the dev-server 403s caused by `/generated/api/*.json` falling outside `server.fs.allow`, since the data now ships through the collection rather than a runtime URL fetch.
1 parent afa67f6 commit 190fa9d

5 files changed

Lines changed: 51 additions & 30 deletions

File tree

docs/content-collections.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const components = defineCollection({
3232
order: z.number().optional(),
3333
content: z.string()
3434
}),
35-
transform: async (doc) => {
35+
transform: async (doc, context) => {
3636
const { filePath, fileName, directory, path } = doc._meta;
3737

3838
const name = doc.name ?? toPascalCase(fileName.replace('.md', ''));
@@ -89,9 +89,31 @@ const components = defineCollection({
8989
const defaultExample = usageExample ?? catalogFirstExample;
9090

9191
const apiPath = join(process.cwd(), `generated/api/${path}.json`);
92+
let api: any = null;
9293
if (existsSync(apiPath)) {
9394
try {
94-
const api = JSON.parse(readFileSync(apiPath, 'utf-8'));
95+
api = JSON.parse(readFileSync(apiPath, 'utf-8'));
96+
97+
const renderInline = async (text: string) => {
98+
const html = await compileMarkdown(
99+
context,
100+
{ ...doc, content: text } as any,
101+
{ remarkPlugins: [remarkGfm] }
102+
);
103+
return html
104+
.replace(/^<p>/, '')
105+
.replace(/<\/p>\s*$/, '')
106+
.trim();
107+
};
108+
109+
const walk = async (props: any[] = []) => {
110+
for (const p of props) {
111+
if (p.description) p.descriptionHtml = await renderInline(p.description);
112+
if (p.properties) await walk(p.properties);
113+
}
114+
};
115+
await walk(api.properties);
116+
95117
if (api.properties?.length) {
96118
toc.push({ id: 'api-reference', text: 'API Reference', level: 2 });
97119
}
@@ -111,8 +133,8 @@ const components = defineCollection({
111133
source,
112134
sourceUrl,
113135
defaultExample,
114-
toc
115-
// html: await compileMarkdown(context, doc)
136+
toc,
137+
api
116138
};
117139
}
118140
});

docs/src/app.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,14 @@ code:not(pre > code):not(.custom) {
8282
@apply text-sm font-medium bg-primary/5 text-primary font-pixel px-2 rounded border border-primary/50;
8383
}
8484

85+
/* Inline-only rendered markdown (e.g. API property descriptions) */
86+
.prose-inline a {
87+
@apply text-primary underline underline-offset-2 hover:text-primary/80;
88+
}
89+
.prose-inline code:not(pre > code):not(.custom) {
90+
@apply text-xs px-1 py-0;
91+
}
92+
8593
/* Code block figure container */
8694
figure[data-rehype-pretty-code-figure] {
8795
/* Title/filename display - hidden, handled by pre.svelte component */

docs/src/lib/api-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export interface PropertyInfo {
77
type: string;
88
required: boolean;
99
description?: string;
10+
descriptionHtml?: string;
1011
default?: string;
1112
tags?: Record<string, string>;
1213
properties?: PropertyInfo[]; // For nested object types

docs/src/routes/docs/components/[name]/+layout.ts

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { ComponentAPI } from '$lib/api-types.js';
21
import type { ComponentCatalog } from '$examples/catalog/types.js';
32
import { getMarkdownComponent, loadExamplesFromMarkdown } from '$lib/markdown/utils.js';
43
import type { Examples } from '$lib/types.js';
@@ -7,10 +6,6 @@ export const load = async ({ params, url, parent }) => {
76
// Get examples from parent layout
87
const parentData = await parent();
98

10-
const allAPIs = import.meta.glob('/generated/api/*.json', {
11-
import: 'default'
12-
});
13-
149
const allCatalogs = import.meta.glob('/src/examples/catalog/*.json', {
1510
import: 'default'
1611
});
@@ -49,24 +44,10 @@ export const load = async ({ params, url, parent }) => {
4944
// Page examples take precedence
5045
const examples: Examples = { ...parentData.examples, ...pageExamples };
5146

52-
// Load component API
53-
let api: ComponentAPI | null = null;
54-
const apiPath = `/generated/api/${params.name}.json`;
55-
if (allAPIs[apiPath]) {
56-
try {
57-
api = (await allAPIs[apiPath]()) as ComponentAPI;
58-
} catch (error) {
59-
console.warn(`Failed to load API file for component: ${params.name}`, error);
60-
}
61-
} else {
62-
console.warn(`No API file found for component: ${params.name}`);
63-
}
64-
6547
return {
6648
PageComponent,
6749
metadata,
6850
catalog,
69-
examples,
70-
api
51+
examples
7152
};
7253
};

docs/src/routes/docs/components/[name]/+page.svelte

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
<script lang="ts">
22
import { Table } from 'svelte-ux';
3+
import type { Component } from 'content-collections';
34
45
import { h2 as H2 } from '$lib/markdown/blueprints/default/blueprint.svelte';
56
import { tableCell } from '@layerstack/svelte-table';
67
import ExampleListing from '$lib/components/ExampleListing.svelte';
78
import { page } from '$app/state';
89
910
import RelatedLink from '$lib/components/RelatedLink.svelte';
11+
import type { PropertyInfo } from '$lib/api-types.js';
1012
1113
let { data } = $props();
12-
const { PageComponent, metadata, api, catalog } = $derived(data);
14+
const { PageComponent, catalog } = $derived(data);
15+
const metadata = $derived(data.metadata as Component);
16+
const api = $derived(metadata.api);
1317
</script>
1418

1519
<!-- Markdown page -->
@@ -39,7 +43,8 @@
3943
}}
4044
>
4145
<tbody slot="data" let:columns let:data let:getCellValue let:getCellContent>
42-
{#each data ?? [] as rowData, rowIndex}
46+
{#each (data ?? []) as rowData, rowIndex (rowIndex)}
47+
{@const row = rowData as PropertyInfo}
4348
<tr class="hover:bg-surface-content/5 border-b">
4449
{#each columns as column (column.name)}
4550
{@const value = getCellValue(column, rowData, rowIndex)}
@@ -50,7 +55,7 @@
5055
<span class="text-xs font-pixel bg-surface-content/10 px-2 py-1 rounded border"
5156
>{value}</span
5257
>
53-
{#if rowData.required}
58+
{#if row.required}
5459
<span
5560
class="bg-danger/10 px-1 py-0.5 font-medium rounded border border-danger text-danger text-xs"
5661
>required</span
@@ -60,10 +65,14 @@
6065
{:else if column.name === 'type'}
6166
<span class="font-pixel text-xs text-surface-content/70">{value}</span>
6267
{:else if column.name === 'description'}
63-
<span class="whitespace-pre-line">{value}</span>
64-
{#if rowData.default != null}
68+
{#if row.descriptionHtml}
69+
<span class="prose-inline">{@html row.descriptionHtml}</span>
70+
{:else}
71+
<span class="whitespace-pre-line">{value}</span>
72+
{/if}
73+
{#if row.default != null}
6574
<div class="mt-2 text-surface-content/70">
66-
default: <span class="font-pixel">{rowData.default}</span>
75+
default: <span class="font-pixel">{row.default}</span>
6776
</div>
6877
{/if}
6978
{:else}

0 commit comments

Comments
 (0)