import type { Hook, Manifest } from "../types.ts"; import type { System } from "../system.ts"; import { EventEmitter } from "../event.ts"; import { throttle } from "@silverbulletmd/silverbullet/lib/async"; import type { Command, CommandHookEvents } from "../../types/command.ts"; import type { CommandHookT } from "@silverbulletmd/silverbullet/type/manifest"; export class CommandHook extends EventEmitter implements Hook { system?: System; public throttledBuildAllCommandsAndEmit = throttle(() => { this.buildAllCommandsAndEmit(); }, 200); constructor( private readOnly: boolean, private additionalCommands: Map, ) { super(); } /** * Build the command map */ buildAllCommands(): Map { const commands = new Map(); // Add commands from plugs if (!this.system) { // Not initialized yet return commands; } for (const plug of this.system.loadedPlugs.values()) { for (const [name, functionDef] of Object.entries( plug.manifest!.functions, )) { if (!functionDef.command) { continue; } const cmd = functionDef.command; if (cmd.requireMode === "rw" && this.readOnly) { // Bit hacky, but don't expose commands that require write mode in read-only mode continue; } commands.set(cmd.name, { ...cmd, run: (args?: string[]) => { return plug.invoke(name, [cmd, ...(args ?? [])]); }, }); } } for (const [name, cmd] of this.additionalCommands) { if (commands.has(name)) { // Existing command, let's do some inline patching const existingCommand = commands.get(name)!; const command: Command = { ...existingCommand, ...cmd, }; if (cmd.run) { command.run = cmd.run; } commands.set(name, command); } else { // New command, let's just set commands.set(name, cmd); } } return commands; } public buildAllCommandsAndEmit() { this.emit("commandsUpdated", this.buildAllCommands()); } apply(system: System): void { this.system = system; system.on({ plugLoaded: () => { this.throttledBuildAllCommandsAndEmit(); }, }); // On next tick setTimeout(() => { this.throttledBuildAllCommandsAndEmit(); }); } validateManifest(manifest: Manifest): string[] { const errors = []; for (const [name, functionDef] of Object.entries(manifest.functions)) { if (!functionDef.command) { continue; } const cmd = functionDef.command; if (!cmd.name) { errors.push(`Function ${name} has a command but no name`); } } return []; } }