/* * theme-manager.ts — manages theme registration and activation for a Ribbit instance. */ import type { RibbitTheme } from './types'; import { HopDown } from './hopdown'; export class ThemeManager { private registered: Map; private disabled: Set; private active: RibbitTheme; private themeLink: HTMLLinkElement | null; private themesPath: string; private onSwitch: (theme: RibbitTheme) => void; constructor(initial: RibbitTheme, themesPath: string, onSwitch: (theme: RibbitTheme) => void) { this.registered = new Map(); this.disabled = new Set(); this.themeLink = null; this.themesPath = themesPath; this.onSwitch = onSwitch; this.active = initial; this.add(initial); } /** * Register a theme. Themes must be added before they can be enabled. */ add(theme: RibbitTheme): void { this.registered.set(theme.name, theme); } /** * Unregister a theme by name. Cannot remove the active theme. */ remove(name: string): void { if (this.active.name === name) { throw new Error(`Cannot remove the active theme "${name}".`); } this.registered.delete(name); } /** * Return the names of all registered and enabled themes. */ list(): string[] { return Array.from(this.registered.keys()).filter(name => !this.disabled.has(name)); } /** * Get a registered theme by name, or undefined if not found. */ get(name: string): RibbitTheme | undefined { return this.registered.get(name); } /** * Return the currently active theme. */ current(): RibbitTheme { return this.active; } /** * Switch to a registered theme by name. The theme must be * registered and enabled. Loads the theme's CSS and notifies * the editor to rebuild its converter. */ set(name: string): void { const theme = this.registered.get(name); if (!theme) { throw new Error(`Theme "${name}" is not registered. Call add() first.`); } if (this.disabled.has(name)) { throw new Error(`Theme "${name}" is disabled. Call enable() first.`); } this.active = theme; this.loadCSS(name); this.onSwitch(theme); } /** * Mark a theme as available for selection via set(). * Themes are enabled by default when added. */ enable(name: string): void { if (!this.registered.has(name)) { throw new Error(`Theme "${name}" is not registered. Call add() first.`); } this.disabled.delete(name); } /** * Mark a theme as unavailable for selection via set(). * Does not affect the current theme if it is already active. */ disable(name: string): void { if (!this.registered.has(name)) { throw new Error(`Theme "${name}" is not registered.`); } this.disabled.add(name); } private loadCSS(name: string): void { if (this.themeLink) { this.themeLink.remove(); } const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = `${this.themesPath}/${name}/theme.css`; document.head.appendChild(link); this.themeLink = link; } }