# 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 ```html
your markdown here
``` ## Custom Block Tags ```javascript 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) => '
Spoiler' + convert.block(token.content) + '
', 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).