Visual Editor Implementation Plan
Overview
Replace the current plain textarea editor with a GitBook-style visual (WYSIWYG) block editor using Plate (Slate.js-based). The new editor will provide slash commands, drag-and-drop blocks, inline formatting toolbar, and real-time visual rendering — while outputting standard Markdown/MDX compatible with Docusaurus.
Current State
- Library:
@uiw/react-md-editorv4.0.11 (onlyMDEditor.Markdownused for preview) - Editor: Plain
<textarea>with monospace font — no toolbar, no syntax highlighting - Views: Edit (raw text) / Preview (rendered markdown) / Docs View (iframe)
- Key file:
src/components/Editor/MarkdownEditor.tsx
Library Selection: Plate
Candidates Evaluated
| Library | GitBook-like UX | MDX/Markdown Fidelity | Dev Effort | Maturity |
|---|---|---|---|---|
| Plate (Slate.js) | Build it (plugins) | Native MDX serialization | Medium | High |
| BlockNote (Tiptap) | Built-in | Lossy markdown | Low | Pre-1.0 |
| Tiptap (ProseMirror) | Build it all | Custom work | Highest | Highest |
| MDXEditor (Lexical) | No (traditional WYSIWYG) | Native MDX + admonitions | Lowest | Medium |
| Milkdown (ProseMirror) | Build it all | Lossless markdown | High | Medium |
Why Plate
- Native MDX serialization —
@platejs/markdownconverts custom elements to MDX tags, which Docusaurus renders directly - Plugin ecosystem — 50+ plugins: tables (resizable), code blocks, slash commands, drag-and-drop, floating toolbar
- Notion/GitBook-like UX — achievable by combining slash menu + drag-and-drop + block selection plugins
- Docusaurus admonitions — custom plugin can handle
:::note,:::tip,:::warning,:::danger,:::info - Front matter — structured metadata panel (sidebar or top section)
- MIT license — compatible with BUSL-1.1
- React-native — fits our React 19 + TypeScript stack
- shadcn/ui components — modern, customizable UI
Why Not Others
- BlockNote: Closest to GitBook UX out of the box, but markdown conversion is lossy. Every custom block needs manual serialization — risky for Docusaurus
.md/.mdxround-trips. Also pre-1.0 (API instability). - Tiptap: Most stable foundation (ProseMirror), but requires building everything from scratch — block UX, markdown serialization, MDX support. Highest effort.
- MDXEditor: Best MDX support (Lexical-based), admonitions and front matter built-in, but not block-based — traditional WYSIWYG, no drag-drop/slash commands.
- Milkdown: Lossless markdown round-tripping, remark ecosystem, but bare-bones React integration — entire UI must be built manually.
Implementation Phases
Phase 1: Foundation — Replace textarea with Plate editor
Goal: Basic visual editing with markdown round-tripping.
-
Install dependencies
npm install @udecode/plate @platejs/basic-nodes @platejs/markdown
npm install @platejs/heading @platejs/list @platejs/link
npm install @platejs/horizontal-rule @platejs/autoformat -
Create
src/components/Editor/PlateEditor.tsx- Basic Plate editor with plugins: paragraph, heading (h1-h6), bold, italic, strikethrough, inline code, link, blockquote, ordered/unordered list, horizontal rule
- Markdown deserialization: file content (string) → Plate value (Slate nodes)
- Markdown serialization: Plate value → markdown string
- Props:
value,onChange,readOnly
-
Create
src/components/Editor/PlateEditor.module.css- Editor styling with Infima CSS variables
- Light/dark mode support
-
Modify
src/components/Editor/MarkdownEditor.tsx- Replace
<textarea>with<PlateEditor>in Edit mode - Keep Preview mode (
MDEditor.Markdownfrom@uiw/react-md-editor) - Keep Docs View mode (iframe, unchanged)
- Wire dirty state:
onChangefrom PlateEditor → unsaved indicator - Save: serialize Plate value → markdown string → send to API
- Replace
-
Keep
@uiw/react-md-editor— still needed for Preview mode rendering
Phase 2: Rich Block Types
Goal: Full documentation editing capabilities.
| Block | Plugin | Serialization |
|---|---|---|
| Tables | @platejs/table | Standard markdown table syntax |
| Code blocks | @platejs/code-block | Fenced code blocks (```lang) |
| Images | @platejs/media |  markdown |
| Admonitions | Custom plugin | :::note\ncontent\n::: (Docusaurus syntax) |
| Front matter | Custom component | YAML front matter (structured editor panel) |
- Admonition plugin (
src/components/Editor/plugins/admonition-plugin.ts): Visual block with colored left border + icon fornote,tip,warning,danger,info - Front matter panel: Parse YAML on load, show as editable form fields (title, sidebar_position, etc.), serialize back on save
Phase 3: Block Editor UX (GitBook-like)
Goal: Intuitive block manipulation experience.
| Feature | Plugin | Description |
|---|---|---|
| Slash commands | @platejs/slash-command | Type / to open block insertion menu |
| Drag-and-drop | @platejs/dnd | Block handles for reordering |
| Floating toolbar | @platejs/floating-toolbar | Text selection shows formatting options |
| Block selection | @platejs/block-selection | Click handle to select entire blocks |
| Markdown shortcuts | @platejs/autoformat | # → heading, * → list, ``` → code block |
Slash menu categories:
- Text: Paragraph, Heading 1-3
- Lists: Bullet list, Ordered list
- Media: Image, Code block
- Layout: Table, Horizontal rule
- Docusaurus: Note, Tip, Warning, Danger, Info (admonitions)
Phase 4: Polish
Goal: Production-ready editor experience.
- Dark mode — CSS variables integration with Infima
[data-theme='dark'] - Keyboard shortcuts — Ctrl+B (bold), Ctrl+I (italic), Ctrl+K (link), Ctrl+S (save)
- Undo/Redo — Plate built-in history plugin
- Read-only mode —
readOnlyprop when user lacks write permission - Performance — Lazy loading of heavy plugins, virtualization for large docs
Files to Create/Modify
| File | Action |
|---|---|
src/components/Editor/PlateEditor.tsx | Create — Main Plate editor component |
src/components/Editor/PlateEditor.module.css | Create — Editor styles |
src/components/Editor/plugins/admonition-plugin.ts | Create — Docusaurus admonition block |
src/components/Editor/plugins/frontmatter-plugin.ts | Create — Front matter handling |
src/components/Editor/MarkdownEditor.tsx | Modify — Replace textarea with PlateEditor |
package.json | Modify — Add Plate dependencies |
Critical Requirements
- Lossless round-tripping: Edit → Save → Reopen must preserve all content
- Front matter preservation: YAML metadata must survive edit cycles
- Admonition syntax:
:::typeblocks must serialize correctly for Docusaurus - MDX compatibility: Custom components in
.mdxfiles should not be corrupted - Existing features preserved: Preview mode, Docs View, build integration, Ctrl+S save
Verification Checklist
- Existing
.md/.mdxfiles load correctly in the visual editor - Edit content visually → Save → Reopen → Content preserved (no data loss)
- Admonitions (
:::noteetc.) render as visual blocks and serialize back correctly - Front matter preserved through edit cycles
- Tables, code blocks, images round-trip correctly
- Slash menu works, drag-drop reorders blocks, floating toolbar formats text
- Dark mode styling consistent with rest of the app
- Read-only mode disables editing when user lacks write access
-
npm run typecheckpasses -
npm run buildsucceeds - Preview and Docs View modes still work
References
- Plate docs: https://platejs.org
- Plate GitHub: https://github.com/udecode/plate
- @platejs/markdown: https://platejs.org/docs/markdown
- BlockNote (alternative): https://blocknotejs.org
- MDXEditor (alternative): https://mdxeditor.dev
- GitBook editor: https://gitbook.com/docs/creating-content/blocks