# 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).