Skip to content

Commit dc10447

Browse files
committed
feat: render tables and blockquotes in playground
1 parent 7d802da commit dc10447

1 file changed

Lines changed: 131 additions & 1 deletion

File tree

app/playground/page.tsx

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ function generatePreviewHTML(doc: any): string {
295295
html += '<div class="mb-8 p-6 border-2 border-blue-500 bg-blue-900 bg-opacity-20 rounded">';
296296
html += `<h2 class="text-2xl font-bold mb-4">${block.title || 'Slide'}</h2>`;
297297
if (block.content) {
298-
html += convertMarkdownToHTML(block.content.map((c: any) => c.text || '').join('\n'));
298+
html += renderSlideContentHTML(block.content);
299299
}
300300
html += '</div>';
301301
break;
@@ -321,6 +321,33 @@ function generatePreviewHTML(doc: any): string {
321321
html += `<pre class="text-sm bg-black bg-opacity-50 p-4 rounded overflow-x-auto">${block.code}</pre>`;
322322
html += '</div>';
323323
break;
324+
325+
case 'table':
326+
html += '<div class="mb-8 overflow-x-auto">';
327+
if (block.caption) {
328+
html += `<p class="text-sm text-gray-400 italic mb-2">${block.caption}</p>`;
329+
}
330+
html += '<table class="min-w-full border border-gray-700 text-sm">';
331+
html += '<thead class="bg-gray-800"><tr>';
332+
block.headers.forEach((header: string) => {
333+
html += `<th class="px-3 py-2 text-left font-semibold border border-gray-700">${escapeHTML(
334+
header
335+
)}</th>`;
336+
});
337+
html += '</tr></thead><tbody>';
338+
block.rows.forEach((row: any, rowIndex: number) => {
339+
const rowClass =
340+
block.style === 'striped' && rowIndex % 2 === 1 ? ' bg-gray-800/60' : '';
341+
html += `<tr class="${rowClass}">`;
342+
row.cells.forEach((cell: any) => {
343+
html += `<td class="px-3 py-2 border border-gray-700">${escapeHTML(
344+
cell.text
345+
)}</td>`;
346+
});
347+
html += '</tr>';
348+
});
349+
html += '</tbody></table></div>';
350+
break;
324351

325352
case 'osfcode':
326353
html += '<div class="mb-8">';
@@ -341,6 +368,7 @@ function convertMarkdownToHTML(text: string): string {
341368
let html = '';
342369
let paragraph: string[] = [];
343370
let listItems: string[] = [];
371+
let blockquoteLines: string[] = [];
344372

345373
const flushParagraph = () => {
346374
if (paragraph.length > 0) {
@@ -358,18 +386,29 @@ function convertMarkdownToHTML(text: string): string {
358386
}
359387
};
360388

389+
const flushBlockquote = () => {
390+
if (blockquoteLines.length > 0) {
391+
html += `<blockquote class="border-l-4 border-gray-600 pl-4 italic text-gray-300 my-3">${blockquoteLines
392+
.map((line) => `<p>${renderInlineMarkdown(line)}</p>`)
393+
.join('')}</blockquote>`;
394+
blockquoteLines = [];
395+
}
396+
};
397+
361398
for (const rawLine of lines) {
362399
const line = rawLine.trim();
363400
if (!line) {
364401
flushParagraph();
365402
flushList();
403+
flushBlockquote();
366404
continue;
367405
}
368406

369407
const headingMatch = /^(#{1,3})\s+(.+)$/.exec(line);
370408
if (headingMatch) {
371409
flushParagraph();
372410
flushList();
411+
flushBlockquote();
373412
const level = headingMatch[1].length;
374413
const size =
375414
level === 1 ? 'text-3xl' : level === 2 ? 'text-2xl' : 'text-xl';
@@ -382,22 +421,113 @@ function convertMarkdownToHTML(text: string): string {
382421
const listMatch = /^[-*]\s+(.+)$/.exec(line);
383422
if (listMatch) {
384423
flushParagraph();
424+
flushBlockquote();
385425
listItems.push(listMatch[1]);
386426
continue;
387427
}
388428

429+
const quoteMatch = /^>\s?(.+)$/.exec(line);
430+
if (quoteMatch) {
431+
flushParagraph();
432+
flushList();
433+
blockquoteLines.push(quoteMatch[1]);
434+
continue;
435+
}
436+
389437
if (listItems.length > 0) {
390438
flushList();
391439
}
440+
if (blockquoteLines.length > 0) {
441+
flushBlockquote();
442+
}
392443
paragraph.push(line);
393444
}
394445

395446
flushParagraph();
396447
flushList();
448+
flushBlockquote();
449+
450+
return html;
451+
}
452+
453+
function renderSlideContentHTML(contentBlocks: any[]): string {
454+
let html = '';
455+
456+
for (const block of contentBlocks) {
457+
if (block.type === 'unordered_list') {
458+
html += '<ul class="list-disc pl-6 my-2">';
459+
for (const item of block.items) {
460+
html += `<li>${renderRuns(item.content)}</li>`;
461+
}
462+
html += '</ul>';
463+
} else if (block.type === 'ordered_list') {
464+
html += '<ol class="list-decimal pl-6 my-2">';
465+
for (const item of block.items) {
466+
html += `<li>${renderRuns(item.content)}</li>`;
467+
}
468+
html += '</ol>';
469+
} else if (block.type === 'blockquote') {
470+
html += '<blockquote class="border-l-4 border-blue-500 pl-4 italic text-gray-300 my-3">';
471+
for (const paragraph of block.content) {
472+
html += `<p>${renderRuns(paragraph.content)}</p>`;
473+
}
474+
html += '</blockquote>';
475+
} else if (block.type === 'paragraph') {
476+
const rawText = runsToText(block.content).trim();
477+
const headingMatch = /^(#{1,3})\s+(.+)$/.exec(rawText);
478+
if (headingMatch) {
479+
const level = headingMatch[1].length;
480+
const size =
481+
level === 1 ? 'text-3xl' : level === 2 ? 'text-2xl' : 'text-xl';
482+
html += `<h${level} class="${size} font-bold mt-4 mb-2">${renderInlineMarkdown(
483+
headingMatch[2]
484+
)}</h${level}>`;
485+
} else {
486+
html += `<p class="my-2">${renderRuns(block.content)}</p>`;
487+
}
488+
}
489+
}
397490

398491
return html;
399492
}
400493

494+
function renderRuns(runs: any[]): string {
495+
return runs
496+
.map((run) => {
497+
if (typeof run === 'string') {
498+
return escapeHTML(run);
499+
}
500+
if (run.type === 'link') {
501+
const text = escapeHTML(run.text || '');
502+
const url = escapeHTML(run.url || '#');
503+
return `<a class="underline text-blue-400" href="${url}" target="_blank" rel="noopener noreferrer">${text}</a>`;
504+
}
505+
if (run.type === 'image') {
506+
const alt = escapeHTML(run.alt || '');
507+
const url = escapeHTML(run.url || '');
508+
return `<img class="inline-block max-h-32" src="${url}" alt="${alt}" />`;
509+
}
510+
let content = escapeHTML(run.text || '');
511+
if (run.bold) content = `<strong>${content}</strong>`;
512+
if (run.italic) content = `<em>${content}</em>`;
513+
if (run.underline) content = `<span class="underline">${content}</span>`;
514+
if (run.strike) content = `<span class="line-through">${content}</span>`;
515+
return content;
516+
})
517+
.join('');
518+
}
519+
520+
function runsToText(runs: any[]): string {
521+
return runs
522+
.map((run) => {
523+
if (typeof run === 'string') return run;
524+
if (run.type === 'link') return run.text || '';
525+
if (run.type === 'image') return run.alt || '';
526+
return run.text || '';
527+
})
528+
.join('');
529+
}
530+
401531
function renderInlineMarkdown(text: string): string {
402532
let html = escapeHTML(text);
403533
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');

0 commit comments

Comments
 (0)