Enesis Editor

Enesis Editor

Block Markdown Editor

A high-performance block markdown editor built with Vue 3, TypeScript, ProseMirror, and the Lezer parsing framework.

Documents are structured as distinct, interactive **Blocks** rather than a single flat string. Each block is a self-contained ProseMirror editor instance that stores raw markdown as plain text — no AST conversion, no mark storage. A Lezer-based 3-stage parsing pipeline renders syntax as visual decorations in real time, enabling live-preview editing of Obsidian-style references, task states, callouts, headings, and inline formatting. The editor is designed for the philosophy that **plain text should stay plain text** — formatting is purely decorative, never structural. This makes it ideal for note-taking apps, knowledge bases, and any tool that needs to edit rich markdown with programmatic access to the raw source. ## Vision Enesis is being built as a complete block-editing substrate: | Phase | Feature | Status | |---|---|---|---| | 1 | Clean content model — raw markdown in/out, ProseMirror never parses formatting | **Done** | | 2 | Keyboard & boundary behavior — Enter, Backspace, Tab, arrows, pattern triggers | **Done** | | 3 | Focus & cursor control — programmatic focus, expose API | **Done** | | 4 | Core decoration system — Lezer-based inline tokens, cursor-reveal, caching | **Done** | | 5 | Block-level decorations — headings, blockquotes, tasks, callouts, properties | **Done** | | 6 | Code & math node views — CM6 fenced code blocks, KaTeX LaTeX rendering | **Done** | | 6.5 | Formatting handlers — toggle bold/italic/code/strike/highlight, insertLink, setHeading, toggleTask | **Done** | | 7 | Reference & tag insertion handlers — `insertPageRef`, `insertBlockRef`, `insertTag` | **Done** | | 8 | Reference interactivity — clickable `[[page]]`, `((block))`, `#tag` chips with preview | Planned | | 9 | Asset handling — image drop, upload protocol, inline preview | Planned | | 10 | Multi-block editing — shared toolbar, insertion zones, suggestion/mention menus, undo/redo | **Done** (shell + history) | | 11 | WCAG 2.1 AA compliance, screen reader support, RTL, high contrast | Planned | | 12 | History orchestration API across blocks | Planned | | Post-v1 | Third-party decoration plugin system | Planned | ## Design Principles - **Raw markdown in, raw markdown out.** ProseMirror never converts `**bold**` to ``. What you type is what's stored. - **Syntax through decorations, not marks.** Bold, italic, links, references, tags — all rendered via ProseMirror decorations, never via schema marks. This keeps the document model clean and pluggable. - **Lezer-based parsing.** All syntax features flow through a single `@lezer/markdown` AST walk with custom extensions for Obsidian-style elements. - **Block-level isolation.** Each block is an independent ProseMirror instance. Blocks coordinate via emitted events (`split`, `merge-previous`, `arrow-up-from-start`, etc.) — no shared state. ## Monorepo Structure ``` ├── apps/ │ └── dev/ Docs-style development site (Vue 3 + Vite) │ ├── src/ │ │ ├── components/ │ │ │ ├── LiveExample.vue Editable demo wrapper with source view │ │ │ └── AppLogo.vue Enesis brand mark │ │ ├── pages/ │ │ │ ├── index.vue Overview │ │ │ ├── basic-editing.vue Headings, paragraphs, rules │ │ │ ├── inline-marks.vue Bold, italic, code, strike, highlight │ │ │ ├── tasks.vue Task states & priorities │ │ │ ├── blockquotes.vue Blockquotes & callouts │ │ │ ├── links.vue Markdown links │ │ │ ├── refs-tags.vue Page refs, block refs, tags │ │ │ ├── code-blocks.vue Fenced code blocks │ │ │ ├── properties.vue Key::value properties & dates │ │ │ └── math.vue Inline & display LaTeX │ │ ├── App.vue Shell layout (Nuxt UI) with sidebar │ │ ├── main.ts App bootstrap (10 routes) │ │ └── style.css Tailwind v4 + Nuxt UI theme │ ├── public/ Static assets │ └── vite.config.ts Vite with Nuxt UI plugin │ ├── packages/ │ └── editor/ Core headless engine │ ├── src/ │ │ ├── index.ts Public API (Block + Editor + EditorToolbar components) │ │ ├── components/ │ │ │ ├── EditorBlock.vue Self-contained ProseMirror block editor with draggable bullet │ │ │ ├── Editor.vue Multi-block shell with insertion zones, drag-to-reorder, undo/redo │ │ │ ├── EditorInsertionZone.vue 32px hit target between blocks, hover-reveal, drag target │ │ │ └── EditorToolbar.vue Shared toolbar bound to active block │ │ ├── composables/ │ │ │ ├── useMarkdownDecorations.ts ProseMirror decoration plugin │ │ │ ├── useBlockKeyboardHandlers.ts Keyboard handler (Enter, Backspace, arrows) │ │ │ ├── useCodeBlockView.ts CM6 NodeView for fenced code blocks │ │ │ ├── useMathBlockView.ts KaTeX NodeView for display math │ │ │ ├── usePatternPlugin.ts Pattern detection ([[, ((, /, #) │ │ │ ├── usePasteHandler.ts Multi-line paste split handler │ │ │ └── useFocusRegistry.ts Cross-block focus management │ │ └── lib/ │ │ ├── block-parser.ts splitMarkdownIntoBlocks / serializeBlocks │ │ ├── content-model.ts Markdown ↔ ProseMirror conversion │ │ ├── schema.ts Minimal ProseMirror schema │ │ ├── auto-close-plugin.ts Auto-close for ```, **, _ │ │ ├── formatting.ts Formatting handlers (toggleBold, insertLink, etc.) │ │ ├── operation-history.ts EditorOperation types + undo/redo stack │ │ ├── theme.ts CSS-variable theme system with presets │ │ ├── katex.ts Dynamic KaTeX import + render helpers │ │ ├── logger.ts Namespace-based debug logger │ │ ├── markdown-parser.ts Lezer parser configuration │ │ ├── markdown-extensions.ts Custom Lezer extensions │ │ └── markdown-rules/ │ │ ├── engine.ts MarkdownRuleEngine (2-stage pipeline) │ │ ├── types.ts Shared type interfaces │ │ ├── inline-rules.ts Inline syntax rules │ │ ├── block-rules.ts Block syntax rules │ │ └── block-classifier.ts First-line regex classifier (paste only) │ ├── vite.config.ts Library build (ESM, tailwind, vue) │ └── vitest.config.ts Test runner configuration │ ├── .forgejo/ │ └── workflows/ │ └── deploy.yml CI: build + deploy to Codeberg Pages │ ├── package.json Workspace root ├── pnpm-workspace.yaml pnpm workspace definition ├── AGENTS.md Project conventions for AI coding agents └── biome.json Linting & formatting ``` ## Quick Start ```bash pnpm install pnpm dev # Start the dev sandbox at localhost:5173 pnpm test # Run editor unit tests (Vitest) pnpm check # Lint & format check (Biome) ``` ## Scripts | Script | Description | |---|---| | `pnpm dev` | Start Vite dev server for `@enesis/dev` sandbox | | `pnpm build` | Build `@enesis/editor` library (ESM + type declarations) | | `pnpm test` | Run unit tests for `@enesis/editor` | | `pnpm check` | Run Biome lint & format check across the workspace | ## Deployment On push to `master`, a Forgejo Actions workflow builds the dev app and deploys it to **Codeberg Pages** at `https://enesismd.codeberg.page/editor/`. ## Technology | Layer | Technology | |---|---| | Framework | Vue 3 (Composition API, `