Vditor Editor#
Overview#
Vditor is the Markdown editor library powering all note creation and editing in Blinko. The main React wrapper is Editor at app/src/components/Common/Editor/index.tsx, which combines Vditor's core with a MobX store, four custom hooks, a modular toolbar, and drag-and-drop file support.
The component supports three modes β create, edit, and comment β and is used in card inline editing, the fullscreen editor, and the comment flow .
Initialization & Configuration#
Initialization is handled by useEditorInit in hooks/useEditor.ts. Vditor is instantiated against the DOM element with ID vditor-{mode} (e.g. vditor-create).
Key configuration choices:
- CDN: Uses a local CDN endpoint rather than a remote one, with highlight CSS pre-loaded before instantiation to prevent style-timing races .
- Toolbar: Separate toolbar configs for PC and mobile; the
showTopToolbarprop portals the toolbar to#editor-top-toolbaror#editor-top-toolbar-{noteId}instead of rendering it inline . - Theme: Sourced from
UserStoreand applied without destroying the Vditor instance β CSS and config are updated reactively when the theme changes . - Content rendering: The
after()callback fires post-init and renders code blocks, MathJax, Mermaid, Graphviz, PlantUML, ECharts, flowchart, mindmap, SMILES, and ABC notation .
Error handling (added in PR #1079): Vditor instantiation is wrapped in a try-catch block with console.error('[Vditor Error]...') logging, preventing unhandled failures from crashing the app.
Content prefill (added in PR #891): The initialData?: { file?: File, text?: string } prop enables pre-seeding the editor. When mode === 'create' and initialData.text is set, that text becomes the initial editor content and is passed to onChange on mount; initialData.file is uploaded automatically via store.uploadFiles .
State Management (EditorStore)#
The EditorStore (editorStore.tsx) is a MobX store local to each Editor instance. Key state:
| Property | Description |
|---|---|
viewMode | Persisted to localStorage, defaults to 'ir' |
isFullscreen | Controls fullscreen layout; also toggles document.body.overflow |
noteType | The type of note being created/edited |
files | Upload file list with embedded progress state |
references | Array of referenced note IDs |
Mobile height adjustment: adjustMobileEditorHeight() is called on keydown events on mobile, clamping all .vditor-reset elements to window.innerHeight - 200px to prevent the virtual keyboard from obscuring the editor.
Height, Layout & Stability#
Fullscreen height is calculated dynamically by useEditorEvents using a dual-observer pattern:
- A
ResizeObserverwatches the attachment and reference containers for size changes. - A
MutationObserverwatches DOM mutations (childList, subtree, attributes). - Both trigger a
requestAnimationFrame-batched recalculation.
The height formula applied to .vditor-reset elements:
calc(100vh - (toolbarHeight:50 + attachmentHeight + referenceHeight + padding:40)px)
useEditorHeight separately fires the onHeightChange callback whenever noteType, content, files.length, or viewMode change.
CSS stability fixes (PR #1079):
.vditor-contentminimum height raised to150px !important.- Explicit min-heights added for
.vditor-ir,.vditor-sv,.vditor-wysiwyg(80β120px). - Global
--doc-heightCSS variable given a100vhfallback;html,body, and.h-mobile-fullgetmin-height: 500pxto prevent page collapse.
Fullscreen Editor Flow#
FullscreenEditor (app/src/components/BlinkoCard/FullscreenEditor.tsx) provides a dedicated full-viewport editing experience, rendered as a createPortal onto document.body to escape parent container constraints .
Two-mode UX:
- Preview mode (default on open): renders via
MarkdownRender; double-click switches to edit mode . - Edit mode: renders
BlinkoEditorwithshowTopToolbar={true}, placing toolbar controls in the header bar .
Key behaviors:
- On open, fresh note data is fetched from the server via
blinko.noteDetail.call({ id })and deep-cloned intoblinko.curSelectedNote. - ESC key handling: Edit β Preview β Close, with an explicit check to defer to PhotoView's image preview if it is open .
- Mobile nav bars (
.blinko-mobile-header,.blinko-bottom-bar) are hidden while fullscreen is active and restored on close . - View mode override: entering edit mode forces
wysiwyg(stored in localStorage); the original value is restored on close . - Max width:
1000pxin wysiwyg/ir modes,1200pxin split-view (sv) mode to accommodate the side-by-side panes . - Toolbar container is identified by
#editor-top-toolbar-{noteId}, making each fullscreen instance independent .
Event Bus Integration#
The editor communicates with the rest of the app via eventBus (@/lib/event). Key events:
| Event | Direction | Effect |
|---|---|---|
editor:clear | β Editor | Clears editor content |
editor:insert | β Editor | Inserts text at cursor |
editor:replace | β Editor | Replaces editor content |
editor:focus | β Editor | Focuses the editor |
editor:setViewMode | β Editor | Switches view mode (ir/sv/wysiwyg) |
editor:setFullScreen | β Editor | Enters/exits fullscreen |
plugin:closeToolBarContent | β Editor | Closes an open plugin toolbar popover |
Events are handled in useEditorEvents and Editor . Fullscreen toggle is triggered programmatically via eventBus.emit('editor:setFullScreen', !store.isFullscreen) from the FullScreenButton component .
Key Source Files#
| File | Purpose |
|---|---|
app/src/components/Common/Editor/index.tsx | Main Editor React component |
app/src/components/Common/Editor/hooks/useEditor.ts | useEditorInit, useEditorEvents, useEditorFiles, useEditorHeight hooks |
app/src/components/Common/Editor/editorStore.tsx | MobX EditorStore β viewMode, fullscreen, files, mobile height |
app/src/components/BlinkoCard/FullscreenEditor.tsx | Full-viewport editor with preview/edit mode toggle |
app/src/styles/vditor.css | Custom Vditor CSS overrides (min-heights, layout fixes) |