ObsiViewer/docs/EXCALIDRAW_IMPLEMENTATION.md

6.7 KiB

Excalidraw Implementation - Obsidian Format Support

Overview

ObsiViewer now fully supports Obsidian's Excalidraw plugin format, including:

  • Reading .excalidraw.md files with LZ-String compressed data
  • Writing files in Obsidian-compatible format
  • Preserving front matter and metadata
  • Round-trip compatibility (ObsiViewer ⇄ Obsidian)
  • Backward compatibility with legacy flat JSON format
  • Migration tool for converting old files

File Format

Obsidian Format (.excalidraw.md)

---
excalidraw-plugin: parsed
tags: [excalidraw]
---
==⚠  Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠==

# Excalidraw Data

## Text Elements
%%
## Drawing
```compressed-json
<LZ-STRING_BASE64_COMPRESSED_DATA>

%%


The `compressed-json` block contains the Excalidraw scene data compressed using **LZ-String** (`compressToBase64`/`decompressFromBase64`).

### Legacy Format (`.excalidraw`, `.json`)

Plain JSON format:
```json
{
  "elements": [...],
  "appState": {...},
  "files": {...}
}

Architecture

Backend (server/)

excalidraw-obsidian.mjs

Core utilities for parsing and serializing Excalidraw files:

  • parseObsidianExcalidrawMd(md) - Parse Obsidian format, decompress LZ-String
  • parseFlatJson(text) - Parse legacy JSON format
  • toObsidianExcalidrawMd(data, frontMatter?) - Convert to Obsidian format
  • extractFrontMatter(md) - Extract YAML front matter
  • parseExcalidrawAny(text) - Auto-detect and parse any format
  • isValidExcalidrawScene(data) - Validate scene structure

API Routes (server/index.mjs)

GET /api/files?path=<file>

  • Accepts path as query parameter (properly URL-encoded)
  • Returns parsed Excalidraw scene as JSON
  • Supports .excalidraw.md, .excalidraw, .json
  • Sets ETag header for conflict detection

PUT /api/files?path=<file>

  • Accepts path as query parameter
  • Content-Type: application/json (Excalidraw scene)
  • Automatically converts to Obsidian format for .excalidraw.md files
  • Preserves existing front matter
  • Supports If-Match header for conflict detection
  • Atomic write with backup (.bak)

PUT /api/files/blob?path=<file>

  • For binary sidecars (PNG/SVG exports)
  • Accepts path as query parameter

Frontend (src/app/features/drawings/)

excalidraw-io.service.ts

Frontend parsing and serialization service:

  • parseObsidianMd(md) - Parse Obsidian format
  • parseFlatJson(text) - Parse legacy format
  • toObsidianMd(data, frontMatter?) - Convert to Obsidian format
  • parseAny(text) - Auto-detect format
  • isValidScene(data) - Validate scene

drawings-file.service.ts

HTTP service for file operations:

  • get(path) - Load Excalidraw file
  • put(path, scene) - Save with conflict detection
  • putForce(path, scene) - Force overwrite
  • putBinary(path, blob, mime) - Save binary sidecar

All methods use query parameters for proper URL encoding.

drawings-editor.component.ts

Editor component with:

  • Auto-save (debounced)
  • Conflict detection and resolution
  • Manual save (Ctrl+S)
  • Export to PNG/SVG
  • Theme synchronization

Usage

Opening Files

Files are automatically detected and parsed:

// Backend automatically detects format
GET /api/files?path=drawing.excalidraw.md
// Returns: { elements: [...], appState: {...}, files: {...} }

Saving Files

The backend automatically converts to Obsidian format:

// Frontend sends JSON
PUT /api/files?path=drawing.excalidraw.md
Content-Type: application/json
{ elements: [...], appState: {...}, files: {...} }

// Backend writes Obsidian format with LZ-String compression

Migration

Convert old flat JSON files to Obsidian format:

# Dry run (preview changes)
npm run migrate:excalidraw:dry

# Apply migration
npm run migrate:excalidraw

# Custom vault path
node server/migrate-excalidraw.mjs --vault-path=/path/to/vault

The migration script:

  • Scans for .excalidraw and .json files
  • Validates Excalidraw structure
  • Converts to .excalidraw.md with Obsidian format
  • Creates .bak backups
  • Removes original files

Testing

Unit Tests

# Run backend utility tests
npm run test:excalidraw

Tests cover:

  • Front matter extraction
  • Obsidian format parsing
  • LZ-String compression/decompression
  • Round-trip conversion
  • Legacy JSON parsing
  • Edge cases (empty scenes, special characters, large files)

E2E Tests

# Run Playwright tests
npm run test:e2e -- excalidraw.spec.ts

Tests cover:

  • Editor loading
  • API endpoints with query params
  • Obsidian format parsing
  • File structure validation

Compatibility

Obsidian → ObsiViewer

Open .excalidraw.md files created in Obsidian
Render drawings correctly
Preserve all metadata and front matter

ObsiViewer → Obsidian

Save in Obsidian-compatible format
Files open correctly in Obsidian
No warnings or data loss
Front matter preserved

Legacy Support

Open old flat JSON files
Automatically convert on save
Migration tool available

Key Changes from Previous Implementation

Backend

  • Removed zlib decompression (wrong algorithm)
  • Added LZ-String support (lz-string package)
  • Removed splat routes (/api/files/*splat)
  • Added query param routes (/api/files?path=...)
  • Proper URL encoding/decoding
  • Front matter preservation
  • Atomic writes with backups

Frontend

  • Created ExcalidrawIoService for parsing
  • Updated all HTTP calls to use query params
  • Proper encodeURIComponent usage
  • No more 400 errors on special characters

Troubleshooting

400 Bad Request

Cause: Path not properly encoded or missing query parameter
Fix: Ensure using ?path= query param, not URL path

Invalid Excalidraw Format

Cause: Corrupted compressed data or wrong compression algorithm
Fix: Check file was created with LZ-String, not zlib

Conflict Detected (409)

Cause: File modified externally while editing
Fix: Use "Reload from disk" or "Overwrite" buttons in UI

File Opens in Obsidian but Not ObsiViewer

Cause: Unsupported Excalidraw version or corrupted data
Fix: Check console for parsing errors, validate JSON structure

Dependencies

  • Backend: lz-string (^1.5.0)
  • Frontend: lz-string (^1.5.0)

Future Enhancements

  • Support for Excalidraw libraries
  • Embedded images optimization
  • Collaborative editing
  • Version history
  • Template system

References