From 8de32593b035d391261feb836135ea0d78720ffb Mon Sep 17 00:00:00 2001 From: Thomas Hallock Date: Mon, 15 Sep 2025 15:41:58 -0500 Subject: [PATCH] feat: add SVG post-processing to convert bead annotations to data attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parse bead:// link annotations and convert them to structured data attributes: - data-bead-type: "heaven" or "earth" - data-bead-column: column index (0-based) - data-bead-position: position within column for earth beads (0-3) - data-bead-active: "1" for active, "0" for inactive This enables easy JavaScript access to bead metadata for interactive features. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- apps/web/src/app/api/typst-svg/route.ts | 41 +++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/api/typst-svg/route.ts b/apps/web/src/app/api/typst-svg/route.ts index f614ec65..90aea90e 100644 --- a/apps/web/src/app/api/typst-svg/route.ts +++ b/apps/web/src/app/api/typst-svg/route.ts @@ -38,6 +38,40 @@ async function getFlashcardsTemplate(): Promise { } } +function processBeadAnnotations(svg: string): string { + // Parse bead annotations and add data attributes + return svg.replace( + /]*xlink:href="bead:\/\/([^"]*)"[^>]*>(.*?)<\/a>/gs, + (match, beadId, content) => { + // Parse the bead ID to extract metadata + const parts = beadId.split('-') + let beadType = '' + let column = '' + let position = '' + let active = '' + + if (parts[0] === 'heaven') { + beadType = 'heaven' + column = parts[1].replace('col', '') + active = parts[2].replace('active', '') + } else if (parts[0] === 'earth') { + beadType = 'earth' + column = parts[1].replace('col', '') + position = parts[2].replace('pos', '') + active = parts[3].replace('active', '') + } + + // Add data attributes to the content inside the link + const annotatedContent = content.replace( + /(<(?:path|rect|circle)[^>]*)(\/?>)/g, + `$1 data-bead-type="${beadType}" data-bead-column="${column}"${position ? ` data-bead-position="${position}"` : ''} data-bead-active="${active}"$2` + ) + + return annotatedContent + } + ) +} + function createTypstContent(config: TypstSVGRequest, template: string): string { const { number, @@ -105,9 +139,12 @@ export async function POST(request: NextRequest) { const typstContent = createTypstContent(config, template) // Generate SVG using typst.ts - const svg = await $typst.svg({ mainContent: typstContent }) + const rawSvg = await $typst.svg({ mainContent: typstContent }) - console.log('✅ Generated typst.ts SVG, length:', svg.length) + // Post-process to convert bead annotations to data attributes + const svg = processBeadAnnotations(rawSvg) + + console.log('✅ Generated and processed typst.ts SVG, length:', svg.length) return NextResponse.json({ svg,