@@ -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+
401531function renderInlineMarkdown ( text : string ) : string {
402532 let html = escapeHTML ( text ) ;
403533 html = html . replace ( / \* \* ( .+ ?) \* \* / g, '<strong>$1</strong>' ) ;
0 commit comments