Skip to content

Commit 88d6abc

Browse files
committed
feat: display yaml frontmatter as a table
1 parent fea4400 commit 88d6abc

File tree

1 file changed

+36
-3
lines changed

1 file changed

+36
-3
lines changed

server/utils/readme.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
import { marked, type Tokens } from 'marked'
1+
import type { ReadmeResponse, TocItem } from '#shared/types/readme'
2+
import type { Tokens } from 'marked'
3+
import matter from 'gray-matter'
4+
import { marked } from 'marked'
25
import sanitizeHtml from 'sanitize-html'
36
import { hasProtocol } from 'ufo'
4-
import type { ReadmeResponse, TocItem } from '#shared/types/readme'
57
import { convertBlobOrFileToRawUrl, type RepositoryInfo } from '#shared/utils/git-providers'
68
import { decodeHtmlEntities, stripHtmlTags } from '#shared/utils/html'
79
import { convertToEmoji } from '#shared/utils/emoji'
810
import { toProxiedImageUrl } from '#server/utils/image-proxy'
911

1012
import { highlightCodeSync } from './shiki'
13+
import { escapeHtml } from './docs/text'
1114

1215
/**
1316
* Playground provider configuration
@@ -403,13 +406,43 @@ function calculateSemanticDepth(depth: number, lastSemanticLevel: number) {
403406
return Math.min(depth + 2, maxAllowed)
404407
}
405408

409+
/**
410+
* Render YAML frontmatter as a GitHub-style key-value table.
411+
*/
412+
function renderFrontmatterTable(data: Record<string, unknown>): string {
413+
const entries = Object.entries(data)
414+
if (entries.length === 0) return ''
415+
416+
const rows = entries
417+
.map(([key, value]) => {
418+
const displayValue =
419+
typeof value === 'object' && value !== null ? JSON.stringify(value) : String(value ?? '')
420+
return `<tr><th>${escapeHtml(key)}</th><td>${escapeHtml(displayValue)}</td></tr>`
421+
})
422+
.join('\n')
423+
return `<table><thead><tr><th>Key</th><th>Value</th></tr></thead><tbody>\n${rows}\n</tbody></table>\n`
424+
}
425+
406426
export async function renderReadmeHtml(
407427
content: string,
408428
packageName: string,
409429
repoInfo?: RepositoryInfo,
410430
): Promise<ReadmeResponse> {
411431
if (!content) return { html: '', playgroundLinks: [], toc: [] }
412432

433+
// Parse and strip YAML frontmatter, render as table if present
434+
let markdownBody = content
435+
let frontmatterHtml = ''
436+
try {
437+
const { data, content: body } = matter(content)
438+
if (data && Object.keys(data).length > 0) {
439+
frontmatterHtml = renderFrontmatterTable(data)
440+
markdownBody = body
441+
}
442+
} catch {
443+
// If frontmatter parsing fails, render the full content as-is
444+
}
445+
413446
const shiki = await getShikiHighlighter()
414447
const renderer = new marked.Renderer()
415448

@@ -514,7 +547,7 @@ ${html}
514547

515548
marked.setOptions({ renderer })
516549

517-
const rawHtml = marked.parse(content) as string
550+
const rawHtml = frontmatterHtml + (marked.parse(markdownBody) as string)
518551

519552
const sanitized = sanitizeHtml(rawHtml, {
520553
allowedTags: ALLOWED_TAGS,

0 commit comments

Comments
 (0)