ProseMirror Editor

ProseMirror Editor

WYSIWYG rich text editor built directly on the ProseMirror framework (no TipTap wrapper). Provides inline formatting, block structures, tables, images, and integrates with the Glossary, Search, and Spellcheck systems via a plugin architecture.

Overview

The ProseMirror Editor is the central authoring surface of the Palimpsest application. Authors write and edit content inside a ProseMirror EditorView that supports:

Architecture

The editor follows a layered architecture:

Route Layer          +page.svelte — content loading, persistence, draft lifecycle
     |
Component Layer      TextEditor.svelte — toolbar, reactive effects, lifecycle
     |
Wrapper Layer        ProseMirrorEditor.ts — clean API, plugin assembly, serialization
     |
Core PM Layer        schema.ts + commands.ts + pmUtils.ts + wordSelection.ts
     |
Plugin Layer         searchHighlight | textAnnotation | glossaryMark | contextMenu | fileHandler

Layer Responsibilities

Layer Responsibility
Route Loads project/content from backend, manages two-tier persistence (localStorage draft + backend sync), handles unsaved changes modal
Component Mounts ProseMirrorEditor, renders formatting toolbar, wires reactive effects (search, spellcheck, content sync), manages context menu UI
Wrapper Creates EditorState + EditorView, assembles plugins in correct order, exposes clean public API (serialization, command chain, search, spellcheck, glossary)
Core Document schema definition, 40+ formatting/structural commands, selection utilities, word boundary detection
Plugin Self-contained ProseMirror plugins using PluginKey/state/meta/apply or DOM event handling patterns

Document Schema

The schema is defined in schema.ts and extends ProseMirror's basic nodes and marks.

Nodes

Node Group Source Description
doc, paragraph, text, blockquote, heading, horizontal_rule, code_block, hard_break block/inline prosemirror-schema-basic Standard document structure
bullet_list, ordered_list, list_item block prosemirror-schema-list List structures
table, table_row, table_cell, table_header block prosemirror-tables Table structures with cell background attribute
details block custom Collapsible container (content: 'summary block*')
summary custom Summary line inside details (content: 'inline*')
image inline custom Inline draggable image with src, alt, title attrs

Marks

Mark Source Attrs Description
strong, em, code, link prosemirror-schema-basic standard Basic inline formatting
strikethrough custom none <s> / <del> / <strike> / text-decoration: line-through
underline custom none <u> / text-decoration: underline
highlight custom color <mark> with optional data-color attribute
glossaryLink custom termId, termSlug, color, hoverColor, enableHyperlink Applied by backend TermScanHandler; inclusive: false prevents mark extension at edges

Plugin Architecture

All plugins are assembled in ProseMirrorEditor.ts in a specific order:

Order Plugin Pattern Serialized Description
1 history() built-in no Undo/redo stack
2 keymap({...}) built-in no Keyboard shortcuts for marks and history
3 keymap(baseKeymap) built-in no Default ProseMirror keybindings
4 columnResizing() built-in no Table column drag handles
5 tableEditing() built-in no Table cell selection and editing
6 dropCursor() built-in no Visual cursor for drag-and-drop positioning
7 gapCursor() built-in no Cursor for positions between block nodes
8 fileHandler() DOM events no Image drag-and-drop and paste handling
9 contextMenu DOM events no Right-click context menu with word selection
10 placeholder decoration no Empty document placeholder text
11 searchHighlight PluginKey/meta/apply never Search match decorations
12 textAnnotation PluginKey/meta/apply never Spellcheck/grammar decorations
13 glossaryMark DOM events marks are Tooltip and click for glossary marks
Plugins 11 and 12 use Decoration.inline() overlays that are never serialized to the document. They exist purely as visual feedback. Plugin 13 handles marks that are part of the document model and are serialized on save.

Command System

The editor provides 40+ commands in commands.ts, organized by category:

All commands follow the standard ProseMirror (state, dispatch?, view?) => boolean signature via the fromPMCommand adapter.

CommandChain Fluent API

editor.chain().focus().toggleBold().run()

The CommandChain class provides a fluent builder for composing commands. Critically, it re-reads view.state before each command in the chain, ensuring subsequent commands see changes made by earlier ones.

Persistence

Content uses a two-tier persistence model managed by the route layer (+page.svelte):

Tier Storage Trigger Latency
Draft localStorage Debounced on content change (3s) Immediate
Sync Backend REST API (SQLite) Manual Ctrl+S or auto-save Network round-trip

Draft status lifecycle: idle -> saving -> draft_saved -> synced

Integration Points

Glossary System

Search System

Spellcheck System

Design Decisions

Decision Rationale
Direct ProseMirror (no TipTap) Full control over plugin pipeline, schema, and rendering; avoids TipTap's abstraction overhead and opinionated extension system
Decoration-only overlays for search/spellcheck Cleanly separates ephemeral visual feedback from persistent document content; prevents accidental serialization
inclusive: false on glossaryLink Prevents typing at mark edges from extending the glossary link to new text
Base64 data URLs for images Avoids external file references during Core license (local-only); Pro license will add cloud storage
Placeholder via widget decoration Visual-only UI state that does not enter the document model
CommandChain state refresh Each chained command re-reads view.state to see effects of the previous command; prevents stale state bugs with storedMarks and resolved positions

Security

Performance

Logging

Logger Name File Purpose
prosemirror ProseMirrorEditor.ts Parse errors, spellcheck lifecycle, glossary provider lifecycle
pm-commands commands.ts Selection debug info
search-highlight searchHighlight.ts Match count per scan, stub action invocations
file-handler fileHandler.ts Image processing errors
context-menu EditorContextMenu.svelte Context menu action dispatch

Dataflow Diagrams