@kevinpeckham/woof-editor

A WYSIWYG surface using Svelte 5 and contenteditable html that serializes every edit back through the barkdown property-tested round-trip codec. What you edit is exactly the markdown you save, with no drift.


Live playground

Editor

  • CtrlB Bold
  • CtrlI Italic
  • Ctrl2 Heading 2
  • Ctrl3 Heading 3
  • Ctrl4 Heading 4
  • Ctrl0 Paragraph
  • CtrlZ Undo
  • CtrlZ Redo

Serialized Markdown (live)

# Try Editing This

Every edit round-trips through **barkdown**'s codec — *italic*, ~~strikethrough~~, `inline code`, and a [link](https://github.com/kevinpeckham/woof-editor) all serialize back to the exact markdown a parser would produce.

Select text for the bubble menu, right-click for the context menu, or click the gutter chip to the left of any block to change its type.

## Two kinds of lists

- Bulleted items work as expected
- So does **emphasis** inside a bullet
- Undo, redo, and save are wired to the toolbar above

1. Numbered lists too
2. Keyboard shortcuts are listed below the editor

> Round-trip means the markdown you started with and the markdown you get back after editing are structurally identical — no drift, no diff noise.

```ts
const editor = new MarkdownEditorState({ markdown: "# Hello" });
```

This editor understands footnotes, too[^1].

[^1]: Click the footnote number to edit its definition inline.

Styling Notes

This page styles the document via the class prop. Menus are themed with two CSS variables.


Why Another Markdown WYSIWYG

Most contenteditable markdown editors round-trip lossily: markdown renders to DOM, you edit the DOM, and a separate serializer writes markdown back out through a different path than the one that read it in. Content drifts a little on every save — extra whitespace, quote-style changes, list markers that flip.

woof-editor serializes through @kevinpeckham/barkdown instead, whose DOM → markdown writer is property-tested to invert marked's parser: toMarkdown(toDom(md)) === md for every canonical input.

That guarantee is what lets you store editor output as canonical markdown in a database or a git repo — CMS bodies that survive edit → save → reload cycles without accumulating cruft.

Usage

<script lang="ts">
  import { MarkdownEditor, MarkdownEditorState } from "@kevinpeckham/woof-editor";

  const editor = new MarkdownEditorState({
    markdown: "# Hello\n\nEditable markdown, round-tripped through barkdown.",
  });

  async function save() {
    await fetch("/api/articles/123", {
      method: "PUT",
      body: JSON.stringify({ markdown: editor.markdownCurrent }),
    });
    editor.markAsSaved(); // flips `hasEdits` to false without re-seeding the DOM
  }
</script>

<MarkdownEditor {editor} />

<button disabled={!editor.canUndo} onclick={() => editor.undo()}>Undo</button>
<button disabled={!editor.canRedo} onclick={() => editor.redo()}>Redo</button>
<button disabled={!editor.hasEdits} onclick={save}>Save</button>