From 96b235427922bd120f0c2bbf37ceccbc6b37403e Mon Sep 17 00:00:00 2001 From: Alphin Tom Date: Fri, 8 Aug 2025 20:45:39 +0200 Subject: [PATCH] feat(cli): render additional slide elements --- cli/src/osf.ts | 80 ++++++++++++++++++++++++++++++++++++------- cli/tests/cli.test.ts | 38 ++++++++++++++++++++ 2 files changed, 106 insertions(+), 12 deletions(-) diff --git a/cli/src/osf.ts b/cli/src/osf.ts index 91ecb7d..27f1519 100644 --- a/cli/src/osf.ts +++ b/cli/src/osf.ts @@ -12,6 +12,8 @@ import { SheetBlock, OSFValue, TextRun, + ContentBlock, + Paragraph, } from 'omniscript-parser'; // Type definitions for formula handling @@ -45,6 +47,70 @@ function extractText(run: TextRun): string { return ''; } +// Helper function to render TextRun objects to HTML +function renderTextRun(run: TextRun): string { + if (typeof run === 'string') { + return run; + } + if ('type' in run) { + switch (run.type) { + case 'link': + return `${run.text}`; + case 'image': + return `${run.alt}`; + } + } + if ('text' in run) { + let text = run.text; + if (run.bold) text = `${text}`; + if (run.italic) text = `${text}`; + if (run.underline) text = `${text}`; + return text; + } + return ''; +} + +// Render content blocks within slides to HTML +function renderContentBlock(block: ContentBlock, indent = ' '): string { + switch (block.type) { + case 'paragraph': { + const text = block.content.map(renderTextRun).join(''); + return `${indent}

${text}

`; + } + case 'unordered_list': { + const items = block.items + .map(item => `${indent}
  • ${item.content.map(renderTextRun).join('')}
  • `) + .join('\n'); + return `${indent}`; + } + case 'ordered_list': { + const items = block.items + .map(item => `${indent}
  • ${item.content.map(renderTextRun).join('')}
  • `) + .join('\n'); + return `${indent}
      \n${items}\n${indent}
    `; + } + case 'blockquote': { + const inner = block.content + .map((p: Paragraph) => renderContentBlock(p, indent + ' ')) + .join('\n'); + return `${indent}
    \n${inner}\n${indent}
    `; + } + case 'code': { + const langClass = block.language ? ` class="language-${block.language}"` : ''; + const escaped = block.content + .replace(/&/g, '&') + .replace(//g, '>'); + return `${indent}
    ${escaped}
    `; + } + case 'image': { + return `${indent}${block.alt}`; + } + default: + return ''; + } +} + // Helper function to convert OSFValue to CellValue function toSpreadsheetData(data: Record | undefined): SpreadsheetData { if (!data) return {}; @@ -453,18 +519,8 @@ function renderHtml(doc: OSFDocument): string { } if (slide.content && Array.isArray(slide.content)) { parts.push('
    '); - for (const block of slide.content) { - if (block.type === 'unordered_list') { - parts.push('
      '); - for (const item of block.items) { - const itemText = item.content.map(extractText).join(''); - parts.push(`
    • ${itemText}
    • `); - } - parts.push('
    '); - } else if (block.type === 'paragraph') { - const paragraphText = block.content.map(extractText).join(''); - parts.push(`

    ${paragraphText}

    `); - } + for (const contentBlock of slide.content) { + parts.push(renderContentBlock(contentBlock)); } parts.push('
    '); } diff --git a/cli/tests/cli.test.ts b/cli/tests/cli.test.ts index b0f85a2..1468962 100644 --- a/cli/tests/cli.test.ts +++ b/cli/tests/cli.test.ts @@ -60,6 +60,23 @@ const FORMULA_TEST_OSF = `@meta { formula (2,4): "=C1+C2"; }`; +const RICH_CONTENT_OSF = `@slide { + title: "Complex Slide"; + + This is a paragraph with a [link](https://example.com). + + 1. Ordered item 1 + 2. Ordered item 2 + + > A blockquote + + \`\`\`javascript + console.log("Hello, World!"); + \`\`\` + + ![Alt text](https://example.com/image.png) +}`; + const INVALID_OSF = `@meta { title: "Unclosed Block" // Missing closing brace`; @@ -190,6 +207,27 @@ describe('OSF CLI', () => { expect(result).toContain(''); }); + it('should render advanced slide content types to HTML', () => { + const richFile = join(TEST_FIXTURES_DIR, 'rich.osf'); + writeFileSync(richFile, RICH_CONTENT_OSF, 'utf8'); + + try { + const result = execSync(`node "${CLI_PATH}" render "${richFile}"`, { + encoding: 'utf8', + }); + + expect(result).toContain('
      '); + expect(result).toContain('
      '); + expect(result).toContain('
      ');
      +        expect(result).toContain('link');
      +      } finally {
      +        if (existsSync(richFile)) {
      +          unlinkSync(richFile);
      +        }
      +      }
      +    });
      +
           it('should render OSF with computed formula values to HTML', () => {
             const formulaFile = join(TEST_FIXTURES_DIR, 'formula_test.osf');
             writeFileSync(formulaFile, FORMULA_TEST_OSF, 'utf8');