2024-07-30 23:33:33 +08:00
|
|
|
import type { Hook, Manifest } from "../../lib/plugos/types.ts";
|
|
|
|
import type { System } from "../../lib/plugos/system.ts";
|
2024-02-09 04:00:45 +08:00
|
|
|
import { EventEmitter } from "../../lib/plugos/event.ts";
|
2024-07-30 23:33:33 +08:00
|
|
|
import type { ObjectValue } from "../../plug-api/types.ts";
|
2024-08-02 22:47:36 +08:00
|
|
|
import type { FrontmatterConfig } from "../../plugs/template/types.ts";
|
2024-02-09 04:00:45 +08:00
|
|
|
import { throttle } from "../../lib/async.ts";
|
2024-07-30 23:33:33 +08:00
|
|
|
import type { AppCommand, CommandHookEvents } from "../../lib/command.ts";
|
|
|
|
import type { CommandHookT } from "$lib/manifest.ts";
|
2022-03-29 17:21:32 +08:00
|
|
|
|
2022-10-10 20:50:21 +08:00
|
|
|
export class CommandHook extends EventEmitter<CommandHookEvents>
|
|
|
|
implements Hook<CommandHookT> {
|
2022-03-29 17:21:32 +08:00
|
|
|
editorCommands = new Map<string, AppCommand>();
|
2024-01-21 02:16:07 +08:00
|
|
|
system!: System<CommandHookT>;
|
2022-03-29 17:21:32 +08:00
|
|
|
|
2024-02-07 21:50:01 +08:00
|
|
|
constructor(
|
|
|
|
private readOnly: boolean,
|
|
|
|
private additionalCommandsMap: Map<string, AppCommand>,
|
|
|
|
) {
|
2024-02-06 23:51:04 +08:00
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
2024-01-21 02:16:07 +08:00
|
|
|
throttledBuildAllCommands = throttle(() => {
|
|
|
|
this.buildAllCommands().catch(console.error);
|
2024-01-21 05:53:51 +08:00
|
|
|
}, 200);
|
2024-01-21 02:16:07 +08:00
|
|
|
|
|
|
|
async buildAllCommands() {
|
2022-03-29 17:21:32 +08:00
|
|
|
this.editorCommands.clear();
|
2024-01-21 02:16:07 +08:00
|
|
|
for (const plug of this.system.loadedPlugs.values()) {
|
2022-10-10 20:50:21 +08:00
|
|
|
for (
|
|
|
|
const [name, functionDef] of Object.entries(
|
|
|
|
plug.manifest!.functions,
|
|
|
|
)
|
|
|
|
) {
|
2022-03-29 17:21:32 +08:00
|
|
|
if (!functionDef.command) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const cmd = functionDef.command;
|
2024-02-06 23:51:04 +08:00
|
|
|
if (cmd.requireMode === "rw" && this.readOnly) {
|
2024-01-27 00:05:10 +08:00
|
|
|
// Bit hacky, but don't expose commands that require write mode in read-only mode
|
|
|
|
continue;
|
|
|
|
}
|
2022-03-29 17:21:32 +08:00
|
|
|
this.editorCommands.set(cmd.name, {
|
|
|
|
command: cmd,
|
2023-11-26 01:57:00 +08:00
|
|
|
run: (args?: string[]) => {
|
2023-12-22 20:22:25 +08:00
|
|
|
return plug.invoke(name, [cmd, ...args ?? []]);
|
2022-03-29 17:21:32 +08:00
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2024-01-21 02:16:07 +08:00
|
|
|
await this.loadPageTemplateCommands();
|
2024-02-07 21:50:01 +08:00
|
|
|
for (const [name, cmd] of this.additionalCommandsMap) {
|
|
|
|
this.editorCommands.set(name, cmd);
|
|
|
|
}
|
2022-06-21 00:30:45 +08:00
|
|
|
this.emit("commandsUpdated", this.editorCommands);
|
2022-03-29 17:21:32 +08:00
|
|
|
}
|
|
|
|
|
2024-01-21 02:16:07 +08:00
|
|
|
async loadPageTemplateCommands() {
|
|
|
|
// This relies on two plugs being loaded: index and template
|
|
|
|
const indexPlug = this.system.loadedPlugs.get("index");
|
|
|
|
const templatePlug = this.system.loadedPlugs.get("template");
|
|
|
|
if (!indexPlug || !templatePlug) {
|
|
|
|
// Index and template plugs not yet loaded, let's wait
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Query all page templates that have a command configured
|
|
|
|
const templateCommands: ObjectValue<FrontmatterConfig>[] = await indexPlug
|
|
|
|
.invoke(
|
|
|
|
"queryObjects",
|
|
|
|
["template", {
|
|
|
|
// where hooks.newPage.command or hooks.snippet.command
|
|
|
|
filter: ["or", [
|
|
|
|
"attr",
|
|
|
|
["attr", ["attr", "hooks"], "newPage"],
|
|
|
|
"command",
|
|
|
|
], [
|
|
|
|
"attr",
|
|
|
|
["attr", ["attr", "hooks"], "snippet"],
|
|
|
|
"command",
|
|
|
|
]],
|
|
|
|
}],
|
|
|
|
);
|
|
|
|
|
|
|
|
// console.log("Template commands", templateCommands);
|
|
|
|
|
|
|
|
for (const page of templateCommands) {
|
|
|
|
try {
|
|
|
|
if (page.hooks!.newPage) {
|
2024-08-02 22:47:36 +08:00
|
|
|
const newPageConfig = page.hooks!.newPage;
|
2024-01-21 02:16:07 +08:00
|
|
|
const cmdDef = {
|
|
|
|
name: newPageConfig.command!,
|
|
|
|
key: newPageConfig.key,
|
|
|
|
mac: newPageConfig.mac,
|
|
|
|
};
|
|
|
|
this.editorCommands.set(newPageConfig.command!, {
|
|
|
|
command: cmdDef,
|
|
|
|
run: () => {
|
|
|
|
return templatePlug.invoke("newPageCommand", [cmdDef, page.ref]);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (page.hooks!.snippet) {
|
2024-08-02 22:47:36 +08:00
|
|
|
const snippetConfig = page.hooks!.snippet;
|
2024-01-21 02:16:07 +08:00
|
|
|
const cmdDef = {
|
|
|
|
name: snippetConfig.command!,
|
|
|
|
key: snippetConfig.key,
|
|
|
|
mac: snippetConfig.mac,
|
|
|
|
};
|
|
|
|
this.editorCommands.set(snippetConfig.command!, {
|
|
|
|
command: cmdDef,
|
|
|
|
run: () => {
|
|
|
|
return templatePlug.invoke("insertSnippetTemplate", [
|
|
|
|
{ templatePage: page.ref },
|
|
|
|
]);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} catch (e: any) {
|
|
|
|
console.error("Error loading command from", page.ref, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// console.log("Page template commands", pageTemplateCommands);
|
|
|
|
}
|
|
|
|
|
2022-03-29 17:21:32 +08:00
|
|
|
apply(system: System<CommandHookT>): void {
|
2024-01-21 02:16:07 +08:00
|
|
|
this.system = system;
|
2022-03-29 17:21:32 +08:00
|
|
|
system.on({
|
|
|
|
plugLoaded: () => {
|
2024-01-21 02:16:07 +08:00
|
|
|
this.throttledBuildAllCommands();
|
2022-03-29 17:21:32 +08:00
|
|
|
},
|
|
|
|
});
|
2023-07-14 20:22:26 +08:00
|
|
|
// On next tick
|
|
|
|
setTimeout(() => {
|
2024-01-21 02:16:07 +08:00
|
|
|
this.throttledBuildAllCommands();
|
2023-07-14 20:22:26 +08:00
|
|
|
});
|
2022-03-29 17:21:32 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
validateManifest(manifest: Manifest<CommandHookT>): string[] {
|
2023-05-24 02:53:53 +08:00
|
|
|
const errors = [];
|
2022-03-29 17:21:32 +08:00
|
|
|
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 [];
|
|
|
|
}
|
|
|
|
}
|