/* * 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>; constructor() { this.listeners = new Map(); } /** * Register a callback for an event. */ on(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(event: K, callback: RibbitEventMap[K]): void { this.listeners.get(event)?.delete(callback); } /** * Emit an event, calling all registered callbacks with the payload. */ emit(event: K, ...args: Parameters): void { for (const callback of this.listeners.get(event) || []) { callback(...args); } } }