Something went wrong. Try again.
Automerge backed Markdown editor for the Atmosphere
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120// src/plugin/events.ts
/** * Reference to an event subscription, used to unsubscribe. * * This is an opaque interface - the internal structure is not part of the * public API. Pass this to `Events.offref()` to unsubscribe. * * @public */// eslint-disable-next-line @typescript-eslint/no-empty-object-typeexport interface EventRef {}
/** * Internal implementation of EventRef with the actual fields. * Exported for internal Reams code, but NOT re-exported from the `obsidian` * compatibility module. External plugins should treat EventRef as opaque. */export interface EventRefImpl extends EventRef { /** Events instance */ e: Events /** Event name */ n: string /** Callback function */ fn: (...data: unknown[]) => unknown}
/** * Events base class providing pub/sub functionality. * Matches Obsidian's Events API. */export class Events { // Use ES2022 private field syntax so it doesn't leak into .d.ts #eventMap: Map<string, Array<(...data: unknown[]) => unknown>> = new Map()
/** * Subscribe to an event. * @param name - Event name * @param callback - Handler function * @param ctx - Optional context (this binding) - not used in Reams * @returns EventRef for unsubscribing */ // eslint-disable-next-line @typescript-eslint/no-explicit-any on(name: string, callback: (...data: unknown[]) => unknown, ctx?: any): EventRef { const boundCallback = ctx ? callback.bind(ctx) : callback const callbacks = this.#eventMap.get(name) ?? [] callbacks.push(boundCallback) this.#eventMap.set(name, callbacks) // Return EventRefImpl cast to EventRef (opaque to external code) return { e: this, n: name, fn: boundCallback } as EventRef }
/** * Unsubscribe from an event by callback reference. */ off(name: string, callback: (...data: unknown[]) => unknown): void { const callbacks = this.#eventMap.get(name) if (!callbacks) return const index = callbacks.indexOf(callback) if (index !== -1) { callbacks.splice(index, 1) } }
/** * Unsubscribe using an EventRef. */ offref(ref: EventRef): void { // Cast back to internal type to access fields const impl = ref as EventRefImpl if (impl.e === this) { this.off(impl.n, impl.fn) } }
/** * Trigger an event with arguments. * Iterates over a shallow copy to allow safe subscribe/unsubscribe during dispatch. */ trigger(name: string, ...data: unknown[]): void { const callbacks = this.#eventMap.get(name) if (!callbacks) return // Iterate over a copy to allow modifications during dispatch for (const cb of [...callbacks]) { cb(...data) } }
/** Dispatch a host-owned event without allowing one observer to block others. */ protected triggerIsolated(name: string, ...data: unknown[]): void { const callbacks = this.#eventMap.get(name) if (!callbacks) return for (const cb of [...callbacks]) { try { const result = cb(...data) if (result instanceof Promise) { void result.catch(error => { // Async observers may reject after their plugin has unloaded and // cancelled its workers. Keep detached callbacks out of the host. if (this.#eventMap.get(name)?.includes(cb)) console.error(`Event '${name}' observer rejected:`, error) }) } } catch { // Plugin observers are isolated at host-managed dispatch boundaries. } } }
/** * Trigger an event using an EventRef. */ tryTrigger(evt: EventRef, args: unknown[]): void { // Cast back to internal type to access fields const impl = evt as EventRefImpl if (impl.e === this) { impl.fn(...args) } }}