Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 68 additions & 12 deletions cli/src/osf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
SheetBlock,
OSFValue,
TextRun,
ContentBlock,
Paragraph,
} from 'omniscript-parser';

// Type definitions for formula handling
Expand Down Expand Up @@ -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 `<a href="${run.url}">${run.text}</a>`;
case 'image':
return `<img src="${run.url}" alt="${run.alt}" />`;
}
}
if ('text' in run) {
let text = run.text;
if (run.bold) text = `<strong>${text}</strong>`;
if (run.italic) text = `<em>${text}</em>`;
if (run.underline) text = `<u>${text}</u>`;
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}<p>${text}</p>`;
}
case 'unordered_list': {
const items = block.items
.map(item => `${indent} <li>${item.content.map(renderTextRun).join('')}</li>`)
.join('\n');
return `${indent}<ul>\n${items}\n${indent}</ul>`;
}
case 'ordered_list': {
const items = block.items
.map(item => `${indent} <li>${item.content.map(renderTextRun).join('')}</li>`)
.join('\n');
return `${indent}<ol>\n${items}\n${indent}</ol>`;
}
case 'blockquote': {
const inner = block.content
.map((p: Paragraph) => renderContentBlock(p, indent + ' '))
.join('\n');
return `${indent}<blockquote>\n${inner}\n${indent}</blockquote>`;
}
case 'code': {
const langClass = block.language ? ` class="language-${block.language}"` : '';
const escaped = block.content
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
return `${indent}<pre><code${langClass}>${escaped}</code></pre>`;
}
case 'image': {
return `${indent}<img src="${block.url}" alt="${block.alt}" />`;
}
default:
return '';
}
}

// Helper function to convert OSFValue to CellValue
function toSpreadsheetData(data: Record<string, OSFValue> | undefined): SpreadsheetData {
if (!data) return {};
Expand Down Expand Up @@ -453,18 +519,8 @@ function renderHtml(doc: OSFDocument): string {
}
if (slide.content && Array.isArray(slide.content)) {
parts.push(' <div class="slide-content">');
for (const block of slide.content) {
if (block.type === 'unordered_list') {
parts.push(' <ul>');
for (const item of block.items) {
const itemText = item.content.map(extractText).join('');
parts.push(` <li>${itemText}</li>`);
}
parts.push(' </ul>');
} else if (block.type === 'paragraph') {
const paragraphText = block.content.map(extractText).join('');
parts.push(` <p>${paragraphText}</p>`);
}
for (const contentBlock of slide.content) {
parts.push(renderContentBlock(contentBlock));
}
parts.push(' </div>');
}
Expand Down
38 changes: 38 additions & 0 deletions cli/tests/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`;
Expand Down Expand Up @@ -190,6 +207,27 @@ describe('OSF CLI', () => {
expect(result).toContain('<table>');
});

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('<ol>');
expect(result).toContain('<blockquote>');
expect(result).toContain('<pre><code');
expect(result).toContain('<img src="https://example.com/image.png" alt="Alt text" />');
expect(result).toContain('<a href="https://example.com">link</a>');
} 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');
Expand Down
Loading