import React from 'react'; import { open } from '@tauri-apps/plugin-shell'; /** * Render inline markdown segments: **bold**, *italic*, `code`, [text](url). * External links open in the user's default browser via the Tauri shell plugin. */ export function renderInlineMarkdown(text: string, keyPrefix = 'i'): React.ReactNode[] { // Tokenize — order matters: links first (no recursion), then emphasis/code. const tokens: React.ReactNode[] = []; const linkRe = /\[([^\]]+)\]\(([^)]+)\)/g; let lastIndex = 0; let match: RegExpExecArray | null; let i = 0; const pushInline = (segment: string) => { const parts = segment.split(/(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`)/g); for (const part of parts) { if (!part) continue; if (part.startsWith('**') && part.endsWith('**')) { tokens.push({part.slice(2, -2)}); } else if (part.startsWith('*') && part.endsWith('*') && part.length > 2) { tokens.push({part.slice(1, -1)}); } else if (part.startsWith('`') && part.endsWith('`')) { tokens.push({part.slice(1, -1)}); } else { tokens.push(part); } } }; while ((match = linkRe.exec(text)) !== null) { if (match.index > lastIndex) pushInline(text.slice(lastIndex, match.index)); const [full, label, url] = match; tokens.push( { e.preventDefault(); open(url).catch(() => {}); }} className="whats-new-link" > {label} ); lastIndex = match.index + full.length; } if (lastIndex < text.length) pushInline(text.slice(lastIndex)); return tokens; } /** * Render a subset of GitHub-flavored Markdown used by our CHANGELOG: headings * (### / ####), bullets (- / *), blockquotes, horizontal rules, and inline * formatting (bold/italic/code/links). */ export function renderChangelogBody(body: string): React.ReactNode[] { const lines = body.split('\n'); const out: React.ReactNode[] = []; let bulletBuffer: React.ReactNode[] = []; let quoteBuffer: string[] = []; const flushBullets = () => { if (bulletBuffer.length === 0) return; out.push(); bulletBuffer = []; }; const flushQuote = () => { if (quoteBuffer.length === 0) return; out.push(
{renderInlineMarkdown(quoteBuffer.join(' '), `q-${out.length}`)}
); quoteBuffer = []; }; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const trimmed = line.trim(); if (trimmed === '') { flushBullets(); flushQuote(); continue; } if (trimmed === '---') { flushBullets(); flushQuote(); out.push(
); continue; } if (line.startsWith('### ')) { flushBullets(); flushQuote(); out.push(

{renderInlineMarkdown(line.slice(4), `h3-${i}`)}

); continue; } if (line.startsWith('#### ')) { flushBullets(); flushQuote(); out.push(

{renderInlineMarkdown(line.slice(5), `h4-${i}`)}

); continue; } if (line.startsWith('> ')) { flushBullets(); quoteBuffer.push(line.slice(2)); continue; } if (line.startsWith('- ') || line.startsWith('* ')) { flushQuote(); bulletBuffer.push(
  • {renderInlineMarkdown(line.slice(2), `li-${i}`)}
  • ); continue; } // Paragraph / plain line flushBullets(); flushQuote(); out.push(

    {renderInlineMarkdown(line, `p-${i}`)}

    ); } flushBullets(); flushQuote(); return out; }