ribbit/src/ts/events.ts

112 lines
2.8 KiB
TypeScript
Raw Normal View History

/*
* events.ts typed event emitter for the ribbit editor.
*/
import type { RibbitTheme } from './types';
export interface ContentPayload {
markdown: string;
html: string;
}
export interface ModeChangePayload {
current: string;
previous: string | null;
}
export interface ThemeChangePayload {
current: RibbitTheme;
previous: RibbitTheme;
}
export interface ReadyPayload {
markdown: string;
html: string;
mode: string;
theme: RibbitTheme;
}
export interface RibbitEventMap {
/*
* Content was modified. Fires on every edit.
*
* editor.on('change', ({ markdown }) => {
* localStorage.setItem('draft', markdown);
* });
*/
change: (payload: ContentPayload) => void;
/*
* Save requested via editor.save(), toolbar button, or Ctrl+S.
*
* editor.on('save', ({ markdown, html }) => {
* fetch('/api/save', { method: 'POST', body: markdown });
* });
*/
save: (payload: ContentPayload) => void;
/*
* Editor mode switched between view, edit, and wysiwyg.
*
* editor.on('modeChange', ({ current, previous }) => {
* toolbar.toggle(current !== 'view');
* main.classList.toggle('editing', current !== 'view');
* });
*/
modeChange: (payload: ModeChangePayload) => void;
/*
* Theme switched via editor.themes.set().
*
* editor.on('themeChange', ({ current, previous }) => {
* analytics.track('theme_switch', { from: previous.name, to: current.name });
* });
*/
themeChange: (payload: ThemeChangePayload) => void;
/*
* Editor initialized and first render complete.
*
* editor.on('ready', ({ mode, theme }) => {
* console.log(`Editor ready in ${mode} mode with ${theme.name} theme`);
* });
*/
ready: (payload: ReadyPayload) => void;
}
type EventName = keyof RibbitEventMap;
export class RibbitEmitter {
private listeners: Map<string, Set<Function>>;
constructor() {
this.listeners = new Map();
}
/**
* Register a callback for an event.
*/
on<K extends EventName>(event: K, callback: RibbitEventMap[K]): void {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event)!.add(callback);
}
/**
* Remove a previously registered callback.
*/
off<K extends EventName>(event: K, callback: RibbitEventMap[K]): void {
this.listeners.get(event)?.delete(callback);
}
/**
* Emit an event, calling all registered callbacks with the payload.
*/
emit<K extends EventName>(event: K, ...args: Parameters<RibbitEventMap[K]>): void {
for (const callback of this.listeners.get(event) || []) {
callback(...args);
}
}
}