Skip to content

Commit cf42965

Browse files
committed
feat: Render module docs as Markdown with JSDoc link support
- Add parseApiMarkdown() to process {@link} and {@linkcode} tags - Convert JSDoc links to proper API documentation links - Render moduleDoc as Markdown instead of plain text - Add table styles for .module-doc (borders, padding, hover) - Add heading styles for .module-doc (controlled sizes h1-h6) - Update all Description/Examples components to support JSDoc links
1 parent 9e29307 commit cf42965

4 files changed

Lines changed: 270 additions & 25 deletions

File tree

lib/markdown.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,81 @@ export function parseMarkdown(content: string): string {
3434
return marked.parse(content, { async: false }) as string;
3535
}
3636

37+
/**
38+
* Options for parsing API documentation markdown
39+
*/
40+
export interface ApiMarkdownOptions {
41+
/** Map of type names to package names for cross-package linking */
42+
typeToPackage?: Map<string, string>;
43+
/** Set of local type names in the current package */
44+
localTypes?: Set<string>;
45+
/** Current package name (e.g., "builder") */
46+
currentPackage?: string;
47+
}
48+
49+
/**
50+
* Process JSDoc link tags ({@link name} and {@linkcode name}) to HTML links
51+
*
52+
* Supports formats:
53+
* - {@link TypeName} - creates a link with regular text
54+
* - {@linkcode TypeName} - creates a link with code formatting
55+
* - {@link TypeName description} - link with custom description text
56+
*/
57+
function processJsDocLinks(
58+
content: string,
59+
options: ApiMarkdownOptions,
60+
): string {
61+
const { typeToPackage, localTypes, currentPackage } = options;
62+
63+
// Match {@link name} or {@linkcode name} with optional description
64+
// Pattern: {@link(code)? name (optional description)}
65+
// Name must not contain } or whitespace
66+
const linkPattern = /\{@(link|linkcode)\s+([^\s}]+)(?:\s+([^}]+))?\}/g;
67+
68+
return content.replace(linkPattern, (_, tag, name, description) => {
69+
const isCode = tag === "linkcode";
70+
const displayText = description?.trim() || name;
71+
72+
// Determine the target URL
73+
let href: string | null = null;
74+
75+
// Check if it's a local type in the current package
76+
if (localTypes?.has(name) && currentPackage) {
77+
href = `/api/${currentPackage}#${name.toLowerCase()}`;
78+
} // Check if it's in another package
79+
else if (typeToPackage?.has(name)) {
80+
const targetPackage = typeToPackage.get(name)!;
81+
href = `/api/${targetPackage}#${name.toLowerCase()}`;
82+
} // Default: link to current package anchor (best effort)
83+
else if (currentPackage) {
84+
href = `/api/${currentPackage}#${name.toLowerCase()}`;
85+
}
86+
87+
// Format the display text
88+
const formattedText = isCode ? `<code>${displayText}</code>` : displayText;
89+
90+
// Return link or just formatted text if no href could be determined
91+
if (href) {
92+
return `<a href="${href}" class="type-link">${formattedText}</a>`;
93+
}
94+
return formattedText;
95+
});
96+
}
97+
98+
/**
99+
* Parse API documentation markdown with JSDoc link support
100+
*
101+
* This function processes JSDoc {@link} and {@linkcode} tags before
102+
* parsing as markdown, creating proper links to API documentation.
103+
*/
104+
export function parseApiMarkdown(
105+
content: string,
106+
options: ApiMarkdownOptions = {},
107+
): string {
108+
const processed = processJsDocLinks(content, options);
109+
return parseMarkdown(processed);
110+
}
111+
37112
/** Read and parse a markdown file */
38113
export async function readMarkdownFile(path: string): Promise<string> {
39114
const content = await Deno.readTextFile(path);

static/style.css

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,6 +1333,76 @@ body:has(.api-layout) .global-header {
13331333
line-height: 1.7;
13341334
}
13351335

1336+
.module-doc table {
1337+
width: 100%;
1338+
border-collapse: collapse;
1339+
margin: 1rem 0;
1340+
font-size: 0.875rem;
1341+
}
1342+
1343+
.module-doc th,
1344+
.module-doc td {
1345+
padding: 0.5rem 0.75rem;
1346+
text-align: left;
1347+
border-bottom: 1px solid var(--border);
1348+
}
1349+
1350+
.module-doc th {
1351+
background: var(--bg-primary);
1352+
font-weight: 600;
1353+
color: var(--text-heading);
1354+
}
1355+
1356+
.module-doc td {
1357+
color: var(--text-primary);
1358+
}
1359+
1360+
.module-doc td code {
1361+
font-size: 0.8rem;
1362+
color: var(--accent-light);
1363+
}
1364+
1365+
.module-doc tbody tr:hover {
1366+
background: var(--bg-primary);
1367+
}
1368+
1369+
.module-doc h1 {
1370+
font-size: 1.25rem;
1371+
font-weight: 600;
1372+
color: var(--text-heading);
1373+
margin: 1.5rem 0 0.75rem;
1374+
}
1375+
1376+
.module-doc h2 {
1377+
font-size: 1.1rem;
1378+
font-weight: 600;
1379+
color: var(--text-heading);
1380+
margin: 1.25rem 0 0.5rem;
1381+
}
1382+
1383+
.module-doc h3 {
1384+
font-size: 1rem;
1385+
font-weight: 600;
1386+
color: var(--text-heading);
1387+
margin: 1rem 0 0.5rem;
1388+
}
1389+
1390+
.module-doc h4,
1391+
.module-doc h5,
1392+
.module-doc h6 {
1393+
font-size: 0.9rem;
1394+
font-weight: 600;
1395+
color: var(--text-heading);
1396+
margin: 0.75rem 0 0.5rem;
1397+
}
1398+
1399+
.module-doc h1:first-child,
1400+
.module-doc h2:first-child,
1401+
.module-doc h3:first-child,
1402+
.module-doc h4:first-child {
1403+
margin-top: 0;
1404+
}
1405+
13361406
/* Installation Block */
13371407
.api-install {
13381408
margin-bottom: 2rem;

templates/api/ApiPage.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
loadPackageDoc,
1515
type PackageGroup,
1616
} from "../../data/api-pages.ts";
17+
import { parseApiMarkdown } from "../../lib/markdown.ts";
1718
import { Layout } from "../Layout.tsx";
1819
import { mainScript } from "../scripts.ts";
1920
import { ApiToc, DocNodeRenderer, PackageSidebar } from "./components.tsx";
@@ -207,9 +208,16 @@ export async function PackagePage({ packageName }: PackagePageProps) {
207208
</header>
208209

209210
{doc.moduleDoc && (
210-
<div class="module-doc">
211-
<p>{doc.moduleDoc}</p>
212-
</div>
211+
<div
212+
class="module-doc markdown-content"
213+
dangerouslySetInnerHTML={{
214+
__html: parseApiMarkdown(doc.moduleDoc, {
215+
typeToPackage,
216+
localTypes,
217+
currentPackage: packageName,
218+
}),
219+
}}
220+
/>
213221
)}
214222

215223
<div class="api-install">

0 commit comments

Comments
 (0)