diff --git a/README.md b/README.md index 7502222..4c4f163 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![GitHub](https://img.shields.io/badge/GitHub-181717?logo=github&logoColor=white)](https://github.com/ExaDev/documents) [![CI](https://img.shields.io/github/actions/workflow/status/ExaDev/documents/ci.yml?branch=main)](https://github.com/ExaDev/documents/actions) -> A client-only, statically-built web UI for every conversion and editing tool in the [documents.js ecosystem](../README.md) — convert and edit docx, pptx, xlsx, odt, odp, ods, odg, pdf, and markdown documents entirely in the browser, with no server component. +> A client-only, statically-built web UI for every conversion and editing tool in the [documents.js ecosystem](../README.md) — convert and edit docx, pptx, xlsx, odt, odp, ods, odg, csv, svg, pdf, and markdown documents entirely in the browser, with no server component. Private (unpublished to npm); deployed as a static site to GitHub Pages. diff --git a/package.json b/package.json index f74f072..ce005bf 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "@vanilla-extract/recipes": "^0.5.7", "dexie": "^4.4.4", "dexie-react-hooks": "^4.4.0", - "documents.js": "^2.0.0", + "documents.js": "^2.3.0", "markdown-codec": "^2.0.0", "odf.js": "^3.0.1", "react": "^19.2.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7d4876e..54b8839 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -60,8 +60,8 @@ importers: specifier: ^4.4.0 version: 4.4.0(dexie@4.4.4)(react@19.2.8) documents.js: - specifier: ^2.0.0 - version: 2.0.0 + specifier: ^2.3.0 + version: 2.3.0 markdown-codec: specifier: ^2.0.0 version: 2.0.0 @@ -2434,8 +2434,8 @@ packages: resolution: {integrity: sha512-XDu/+fo56WrXrcR3f16xH5lYEQX+3b4W6kJELRNFwrrWxOqhQBQepXMkCi+niSrgCEIcfaC1IeaGPlZ8oj5gSw==} engines: {node: '>=20'} - documents.js@2.0.0: - resolution: {integrity: sha512-ryI83OYP6JimwjrWsU7DEV7fGZuLy2QDyEoO6WRZW+ZFPHtJh+EQ1Q3wtaHxnW9gHizjumh00rjtlxc2rx9HXQ==} + documents.js@2.3.0: + resolution: {integrity: sha512-FTQIA9PRiHYPjn2NFEVdUp3rlvfd7smFs+HU6WAYhPFC47V4TzHBkIWhq9ptIbhtpldAThESKh61eLYYQrHd5A==} engines: {node: '>=20'} hasBin: true @@ -4289,6 +4289,10 @@ packages: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} + temml@0.13.4: + resolution: {integrity: sha512-k1yolMBswx34Jw9hZn5Xh2/GBlwqlW+wiN7QaUYUMhSa1ypfti6rlCfSmCxWyooxH1G32C/Z+qVvgAsBOELZBQ==} + engines: {node: '>=18.13.0'} + temp-dir@2.0.0: resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} engines: {node: '>=8'} @@ -7329,7 +7333,7 @@ snapshots: dependencies: zod: 4.4.3 - documents.js@2.0.0: + documents.js@2.3.0: dependencies: byte-codec: 1.1.9 document-schema.js: 3.2.0 @@ -7338,6 +7342,7 @@ snapshots: odf.js: 3.0.1 ooxml.js: 2.16.0 pdf-codec: 2.2.35 + temml: 0.13.4 zod: 4.4.3 dom-helpers@5.2.1: @@ -9335,6 +9340,8 @@ snapshots: tagged-tag@1.0.0: {} + temml@0.13.4: {} + temp-dir@2.0.0: {} temp-dir@3.0.0: {} diff --git a/src/hooks/useInspect.ts b/src/hooks/useInspect.ts index dde2917..84c03ea 100644 --- a/src/hooks/useInspect.ts +++ b/src/hooks/useInspect.ts @@ -5,7 +5,7 @@ import { getRpcClient } from '../rpc/client'; import type { Diagnostic } from '../shared/diagnostics'; import { contentSummary } from '../shared/contentCounts'; -// Reads a ContentDocument directly from bytes via the content.read RPC endpoint -- no conversion, no PDF layout pass. Used by every content-backed preview (markdown, docx, odt, xlsx, ods, pptx, odp, odg, odf). +// Reads a ContentDocument directly from bytes via the content.read RPC endpoint -- no conversion, no PDF layout pass. Used by every content-backed preview (markdown, csv, svg, docx, odt, xlsx, ods, pptx, odp, odg, odf). export interface ReadContentInput { format: DocumentFormat; bytes: Uint8Array; diff --git a/src/routes/convert.tsx b/src/routes/convert.tsx index e44377a..de5b7e2 100644 --- a/src/routes/convert.tsx +++ b/src/routes/convert.tsx @@ -28,16 +28,18 @@ export const Route = createFileRoute('/convert')({ component: ConvertLayout, }); +// csv reads as a spreadsheet-kind ContentDocument (readCsvContent), so it previews through the same data grid as xlsx/ods. function isSheetFormat(format: string | null): boolean { - return format === 'xlsx' || format === 'ods'; + return format === 'xlsx' || format === 'ods' || format === 'csv'; } function isWordProcessingFormat(format: string | null): boolean { return format === 'docx' || format === 'odt'; } +// svg reads as a drawing-kind ContentDocument (readSvgContent), so it previews through the same pages/shapes/vectors renderer as odg. function isSlidesFormat(format: string | null): boolean { - return format === 'pptx' || format === 'odp' || format === 'odg'; + return format === 'pptx' || format === 'odp' || format === 'odg' || format === 'svg'; } // True for every format whose preview renders the ContentDocument natively via content.read rather than a PDF rendition. PDF itself is the only exception -- its "native" representation IS the PDF bytes rendered in an iframe. diff --git a/src/rpc/router.ts b/src/rpc/router.ts index 2d7c39f..499108c 100644 --- a/src/rpc/router.ts +++ b/src/rpc/router.ts @@ -8,6 +8,7 @@ import { DOCUMENT_FORMATS, extractSourceFontsForFormat, LayoutDocumentSchema, + readCsvContent, readDocxContent, readDocxExtras, readDocumentMetadata, @@ -19,6 +20,7 @@ import { readMarkdownContent, readPptxContent, readPdf, + readSvgContent, readXlsxContent, setDocumentMetadata, } from 'documents.js'; @@ -184,9 +186,11 @@ const SanitizedLayoutDocumentSchema = LayoutDocumentSchema.extend({ images: z.record(z.string(), SanitizedLayoutImageAssetSchema), }); -// Reads a ContentDocument directly from bytes, bypassing the conversion engine entirely -- no target build/encode, no PDF layout pass. Every format's standalone content reader is exported from documents.js (xlsx included since documents.js 2.0 -- before that, xlsx had to detour through the xlsx->ods bridge and read .content off the conversion result). +// Reads a ContentDocument directly from bytes, bypassing the conversion engine entirely -- no target build/encode, no PDF layout pass. Every format's standalone content reader is exported from documents.js (xlsx included since documents.js 2.0 -- before that, xlsx had to detour through the xlsx->ods bridge and read .content off the conversion result). markdown, csv, and svg are the plain-text formats: their readers take the decoded string, not a package, so each decodes its bytes up front the way markdown always has. function readContentForFormat(format: DocumentFormat, bytes: Uint8Array): ContentDocument { if (format === 'markdown') return readMarkdownContent(new TextDecoder().decode(bytes)); + if (format === 'csv') return readCsvContent(new TextDecoder().decode(bytes)); + if (format === 'svg') return readSvgContent(new TextDecoder().decode(bytes)); if (format === 'pdf') throw new Error('PDF has no standalone content reader'); const pkg = decodeDocumentPackage(format, bytes); switch (format) { diff --git a/src/shared/extensionToFormat.ts b/src/shared/extensionToFormat.ts index 58919ee..2fe6bc1 100644 --- a/src/shared/extensionToFormat.ts +++ b/src/shared/extensionToFormat.ts @@ -21,6 +21,8 @@ const EXTENSION_TO_FORMAT: Readonly> = { otg: 'odg', odf: 'odf', otf: 'odf', + csv: 'csv', + svg: 'svg', markdown: 'markdown', md: 'markdown', pdf: 'pdf',