Dependency-free WYSIWYG markdown/html editor
Go to file
gsb 3368e719fd Vim keybindings for source edit mode
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
2026-04-29 07:37:09 +00:00
src Vim keybindings for source edit mode 2026-04-29 07:37:09 +00:00
test Vim keybindings for source edit mode 2026-04-29 07:37:09 +00:00
.gitignore Initial commit of ribbit library 2026-04-28 23:30:53 +00:00
jest.config.js Reimplement tests 2026-04-29 05:02:25 +00:00
LICENSE Initial commit 2026-04-28 16:21:33 -07:00
package-lock.json Reimplement tests 2026-04-29 05:02:25 +00:00
package.json Reimplement tests 2026-04-29 05:02:25 +00:00
README.md Add support for wysiwyg markdown preview 2026-04-29 05:01:51 +00:00
tsconfig.json Reimplement tests 2026-04-29 05:02:25 +00:00

ribbit

Zero-dependency WYSIWYG markdown editor for the browser.

Source Layout

  • src/ts/ — TypeScript source files
    • types.ts — shared interfaces (Tag, SourceToken, Converter, etc.)
    • tags.ts — tag definitions and inlineTag() factory
    • hopdown.ts — configurable markdown↔HTML converter (HopDown class)
    • macros.ts — macro parsing and Tag generation
    • ribbit.ts — Ribbit viewer, RibbitPlugin, utilities
    • ribbit-editor.ts — RibbitEditor with WYSIWYG support, public API exports
    • default-theme.ts — built-in theme definition
    • theme-manager.ts — theme registration and switching
    • events.ts — typed event emitter
  • src/static/ — CSS and static assets
    • ribbit-core.css — functional editor styles (always load)
    • themes/ribbit-default/theme.css — default theme

Build Output

dist/ribbit/
├── ribbit.js           # readable IIFE bundle + source map
├── ribbit.min.js       # minified bundle
├── ribbit-core.css     # functional styles
└── themes/
    └── ribbit-default/
        └── theme.css   # default theme (imports ribbit-core.css)

Usage

<link rel="stylesheet" href="ribbit/themes/ribbit-default/theme.css">
<article id="ribbit">your markdown here</article>

<script src="ribbit/ribbit.js"></script>
<script>
    const editor = new ribbit.Editor({
        on: {
            save: ({ markdown }) => {
                fetch('/api/save', { method: 'POST', body: markdown });
            },
        },
        macros: [
            {
                name: 'npc',
                toHTML: ({ keywords }) => {
                    const name = keywords.join(' ');
                    return `<a href="/NPC/${name}">${name}</a>`;
                },
            },
        ],
    });
    editor.run();
    editor.wysiwyg();
</script>

Custom Block Tags

const spoiler = {
    name: 'spoiler',
    match: (context) => {
        if (!/^\|{3,}/.test(context.lines[context.index])) return null;
        const content = [];
        let i = context.index + 1;
        while (i < context.lines.length && !/^\|{3,}/.test(context.lines[i]))
            content.push(context.lines[i++]);
        return { content: content.join('\n'), raw: '', consumed: i + 1 - context.index };
    },
    toHTML: (token, convert) =>
        '<details><summary>Spoiler</summary>' + convert.block(token.content) + '</details>',
    selector: 'DETAILS',
    toMarkdown: (element, convert) =>
        '\n\n|||\n' + convert.children(element).trim() + '\n|||\n\n',
};

const converter = new ribbit.HopDown({
    tags: { ...ribbit.defaultTags, 'DETAILS': spoiler },
});

Tests

npm test

Supported Markdown

Bold, italic, inline code, links, headings (h1-h6), unordered/ordered/nested lists, blockquotes, fenced code blocks with language, horizontal rules, GFM tables with column alignment, paragraphs, and macros (@name syntax).