Dependency-free WYSIWYG markdown/html editor
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. |
||
|---|---|---|
| examples/flask-collab | ||
| src | ||
| test | ||
| .gitignore | ||
| jest.config.js | ||
| LICENSE | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| TOKENIZER_DESIGN.md | ||
| tsconfig.json | ||
ribbit
Zero-dependency WYSIWYG markdown editor for the browser.
Source Layout
src/ts/— TypeScript source filestypes.ts— shared interfaces (Tag, SourceToken, Converter, etc.)tags.ts— tag definitions andinlineTag()factoryhopdown.ts— configurable markdown↔HTML converter (HopDown class)macros.ts— macro parsing and Tag generationribbit.ts— Ribbit viewer, RibbitPlugin, utilitiesribbit-editor.ts— RibbitEditor with WYSIWYG support, public API exportsdefault-theme.ts— built-in theme definitiontheme-manager.ts— theme registration and switchingevents.ts— typed event emitter
src/static/— CSS and static assetsribbit-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).