A block markdown editor built for practical wisdom: Lezer-parsed, ProseMirror-rendered, structurally correct. It infers tasks, callouts, tables, and refs from what you type — and gets out of the way the moment you're focused on writing.

Zander Hawke 220126abda fix(deploy): SPA for codeberg pages 4 dienas atpakaļ
.forgejo 9a8ba93a2c fix(deploy): set `BASE_URL=/editor/` for Codeberg Pages subpath 1 nedēļu atpakaļ
apps 220126abda fix(deploy): SPA for codeberg pages 4 dienas atpakaļ
packages 1a264aa1e3 feat(editor): support GFM tables with inline markdown rendering 4 dienas atpakaļ
.editorconfig 9a21611c2f feat: implement core block editor with markdown decorations 1 mēnesi atpakaļ
.envrc 9a21611c2f feat: implement core block editor with markdown decorations 1 mēnesi atpakaļ
.gitignore f08f4c9854 fix: indent continuation lines on Shift+Enter in list blocks 5 dienas atpakaļ
AGENTS.md 1a264aa1e3 feat(editor): support GFM tables with inline markdown rendering 4 dienas atpakaļ
README.md 5c82c7b008 docs: update AGENTS and READMEs to match current architecture 5 dienas atpakaļ
biome.json fff9911f7c refactor(editor): fix TypeScript errors, path alias setup, and CI deploy 1 nedēļu atpakaļ
devenv.lock c263c03e55 chore: move to pnpm 1 mēnesi atpakaļ
devenv.nix c263c03e55 chore: move to pnpm 1 mēnesi atpakaļ
devenv.yaml c263c03e55 chore: move to pnpm 1 mēnesi atpakaļ
package.json 02c1a37d92 feat(editor): add unified suggestion menu, keyboard composable, and toolbar modal prompts 4 dienas atpakaļ
pnpm-lock.yaml 95b1a5380b fix(editor): resolve code review findings across shell, toolbar, and suggestion menu 4 dienas atpakaļ
pnpm-workspace.yaml bda12a084f fix(editor): fix pattern recognition for multi-line blocks and spaces in queries 1 mēnesi atpakaļ
skills-lock.json 9a21611c2f feat: implement core block editor with markdown decorations 1 mēnesi atpakaļ

README.md

Enesis 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 <strong>. 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/
│       │   │   ├── Block.vue           Self-contained ProseMirror block editor
│       │   │   ├── Editor.vue          Multi-block shell with insertion zones, undo/redo
│       │   │   ├── InsertionZone.vue   32px hit target between blocks, hover-reveal
│       │   │   └── 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

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, <script setup>)
Editor Engine ProseMirror (view, state, model, transform, keymap, gapcursor)
Parsing Lezer (@lezer/markdown + GFM + custom extensions)
Styling Tailwind CSS v4 + Nuxt UI v4
Type Safety TypeScript (strict: true)
Testing Vitest with jsdom
Formatting Biome
Package Manager pnpm workspaces
CI/CD Forgejo Actions → Codeberg Pages

License

MIT