Design document for replacing the WYSIWYG innerHTML rebuild
approach with a styled-source model where the editor always
contains markdown text with CSS styling. No mode conversions
during editing, no innerHTML rebuild, no round-trip bugs.
npm run dev starts a dev server on port 8080 serving the test
page and ribbit dist files. Watches src/ and test/integration/
for changes, rebuilds automatically, and notifies connected
browsers to reload via EventSource on port 8081.
The test page includes a livereload script that auto-reloads
when the dev server signals a rebuild.
dist/ribbit/ribbit-core.js — editor without extra features
dist/ribbit/ribbit.js — editor with all features
Added a theme feature flag for vim keybindings
VimHandler activates in source (edit) mode only. Two modes:
- Insert: standard typing, Esc enters normal mode
- Normal: vim navigation and editing, i/a/o/O enter insert
Normal mode commands:
h/j/k/l: cursor movement
w/b: word forward/back
0/$: line start/end
gg/G: document start/end
i/a/o/O: enter insert mode
x: delete char
dd: delete line
u: undo
Ctrl+r: redo
New: macros.ts with MacroDef, parseBlockMacro, matchInlineMacro,
buildMacroTags, processInlineMacros.
Macro syntax:
@user — bare, no args
@user() — empty parens, same as bare
@npc(Goblin King) — self-closing with args
@style(box center — block: no closing paren on first line
Content here. — content on subsequent lines
) — closing paren on its own line
Unknown macro names now render as an error:
<span class="ribbit-error">Unknown macro: @bogus</span>
The verbatim keyword causes the contents to render as literals and also
preserves line breaks.
New events with structured payloads:
change({ markdown, html })
Fires on every content edit.
save({ markdown, html })
Fires when editor.save() is called. Consumer handles persistence.
modeChange({ current, previous })
Fires on VIEW/EDIT/WYSIWYG transitions.
themeChange({ current, previous })
Fires when themes.set() switches the active theme.
ready({ markdown, html, mode, theme })
Fires after editor.run() completes first render.
Events can be registered in the constructor via the 'on' setting
or at any time via editor.on(event, callback) / editor.off().
202/202 tests passing.