112 lines
2.8 KiB
TypeScript
112 lines
2.8 KiB
TypeScript
|
|
/*
|
||
|
|
* 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);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|