Tons of refactoring, moving commands and slash commands into hooks
This commit is contained in:
parent
bf32d6d0bd
commit
b89aee97d7
@ -1,24 +1,14 @@
|
|||||||
import * as plugos from "../plugos/types";
|
import * as plugos from "../plugos/types";
|
||||||
import { EndpointHook } from "../plugos/feature/endpoint";
|
import { EndpointHookT } from "../plugos/hooks/endpoint";
|
||||||
import { CronHook } from "../plugos/feature/node_cron";
|
import { CronHookT } from "../plugos/hooks/node_cron";
|
||||||
import { EventHook } from "../plugos/feature/event";
|
import { EventHookT } from "../plugos/hooks/event";
|
||||||
|
import { CommandHookT } from "../webapp/hooks/command";
|
||||||
|
import { SlashCommandHookT } from "../webapp/hooks/slash_command";
|
||||||
|
|
||||||
export type CommandDef = {
|
export type SilverBulletHooks = CommandHookT &
|
||||||
name: string;
|
SlashCommandHookT &
|
||||||
|
EndpointHookT &
|
||||||
// Bind to keyboard shortcut
|
CronHookT &
|
||||||
key?: string;
|
EventHookT;
|
||||||
mac?: string;
|
|
||||||
|
|
||||||
// If to show in slash invoked menu and if so, with what label
|
|
||||||
// should match slashCommandRegexp
|
|
||||||
slashCommand?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SilverBulletHooks = {
|
|
||||||
command?: CommandDef | CommandDef[];
|
|
||||||
} & EndpointHook &
|
|
||||||
CronHook &
|
|
||||||
EventHook;
|
|
||||||
|
|
||||||
export type Manifest = plugos.Manifest<SilverBulletHooks>;
|
export type Manifest = plugos.Manifest<SilverBulletHooks>;
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
"clean": "rm -rf dist",
|
"clean": "rm -rf dist",
|
||||||
"plugs": "cd plugs && ../plugos/dist/plugos/plugos-bundle.js -w --dist dist */*.plug.yaml",
|
"plugs": "cd plugs && ../plugos/dist/plugos/plugos-bundle.js -w --dist dist */*.plug.yaml",
|
||||||
"server": "nodemon -w dist/server dist/server/server.js pages",
|
"server": "nodemon -w dist/server dist/server/server.js pages",
|
||||||
"test": "jest dist/test"
|
"test": "jest dist/test plugos/dist/test"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
@ -2,22 +2,22 @@
|
|||||||
|
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import yargs from "yargs";
|
import yargs from "yargs";
|
||||||
import { hideBin } from "yargs/helpers";
|
import {hideBin} from "yargs/helpers";
|
||||||
import { DiskPlugLoader } from "../plug_loader";
|
import {DiskPlugLoader} from "../plug_loader";
|
||||||
import { CronHook, NodeCronFeature } from "../feature/node_cron";
|
import {CronHookT, NodeCronHook} from "../hooks/node_cron";
|
||||||
import shellSyscalls from "../syscall/shell.node";
|
import shellSyscalls from "../syscalls/shell.node";
|
||||||
import { System } from "../system";
|
import {System} from "../system";
|
||||||
import { EndpointFeature, EndpointHook } from "../feature/endpoint";
|
import {EndpointHook, EndpointHookT} from "../hooks/endpoint";
|
||||||
import { safeRun } from "../util";
|
import {safeRun} from "../util";
|
||||||
import knex from "knex";
|
import knex from "knex";
|
||||||
import {
|
import {
|
||||||
ensureTable,
|
ensureTable,
|
||||||
storeReadSyscalls,
|
storeReadSyscalls,
|
||||||
storeWriteSyscalls,
|
storeWriteSyscalls,
|
||||||
} from "../syscall/store.knex_node";
|
} from "../syscalls/store.knex_node";
|
||||||
import { fetchSyscalls } from "../syscall/fetch.node";
|
import {fetchSyscalls} from "../syscalls/fetch.node";
|
||||||
import { EventFeature, EventHook } from "../feature/event";
|
import {EventHook, EventHookT} from "../hooks/event";
|
||||||
import { eventSyscalls } from "../syscall/event";
|
import {eventSyscalls} from "../syscalls/event";
|
||||||
|
|
||||||
let args = yargs(hideBin(process.argv))
|
let args = yargs(hideBin(process.argv))
|
||||||
.option("port", {
|
.option("port", {
|
||||||
@ -35,7 +35,7 @@ const plugPath = args._[0] as string;
|
|||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
type ServerHook = EndpointHook & CronHook & EventHook;
|
type ServerHook = EndpointHookT & CronHookT & EventHookT;
|
||||||
const system = new System<ServerHook>("server");
|
const system = new System<ServerHook>("server");
|
||||||
|
|
||||||
safeRun(async () => {
|
safeRun(async () => {
|
||||||
@ -52,11 +52,11 @@ safeRun(async () => {
|
|||||||
let plugLoader = new DiskPlugLoader(system, plugPath);
|
let plugLoader = new DiskPlugLoader(system, plugPath);
|
||||||
await plugLoader.loadPlugs();
|
await plugLoader.loadPlugs();
|
||||||
plugLoader.watcher();
|
plugLoader.watcher();
|
||||||
system.addFeature(new NodeCronFeature());
|
system.addHook(new NodeCronHook());
|
||||||
let eventFeature = new EventFeature();
|
let eventHook = new EventHook();
|
||||||
system.addFeature(eventFeature);
|
system.addHook(eventHook);
|
||||||
system.registerSyscalls("event", [], eventSyscalls(eventFeature));
|
system.registerSyscalls("event", [], eventSyscalls(eventHook));
|
||||||
system.addFeature(new EndpointFeature(app, ""));
|
system.addHook(new EndpointHook(app, ""));
|
||||||
system.registerSyscalls("shell", [], shellSyscalls("."));
|
system.registerSyscalls("shell", [], shellSyscalls("."));
|
||||||
system.registerSyscalls("fetch", [], fetchSyscalls());
|
system.registerSyscalls("fetch", [], fetchSyscalls());
|
||||||
system.registerSyscalls(
|
system.registerSyscalls(
|
||||||
|
@ -50,7 +50,6 @@ parentPort.on("message", (data: any) => {
|
|||||||
safeRun(async () => {
|
safeRun(async () => {
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case "load":
|
case "load":
|
||||||
console.log("Booting", data.name);
|
|
||||||
loadedFunctions.set(data.name, new VMScript(wrapScript(data.code)));
|
loadedFunctions.set(data.name, new VMScript(wrapScript(data.code)));
|
||||||
parentPort.postMessage({
|
parentPort.postMessage({
|
||||||
type: "inited",
|
type: "inited",
|
@ -48,7 +48,6 @@ self.addEventListener("message", (event: { data: WorkerMessage }) => {
|
|||||||
let data = messageEvent.data;
|
let data = messageEvent.data;
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case "load":
|
case "load":
|
||||||
console.log("Booting", data.name);
|
|
||||||
loadedFunctions.set(data.name!, new Function(wrapScript(data.code!)));
|
loadedFunctions.set(data.name!, new Function(wrapScript(data.code!)));
|
||||||
postMessage(
|
postMessage(
|
||||||
{
|
{
|
@ -1,13 +1,13 @@
|
|||||||
import { createSandbox } from "../environment/node_sandbox";
|
import { createSandbox } from "../environments/node_sandbox";
|
||||||
import { expect, test } from "@jest/globals";
|
import { expect, test } from "@jest/globals";
|
||||||
import { Manifest } from "../types";
|
import { Manifest } from "../types";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import request from "supertest";
|
import request from "supertest";
|
||||||
import { EndpointFeature, EndpointHook } from "./endpoint";
|
import { EndpointHook, EndpointHookT } from "./endpoint";
|
||||||
import { System } from "../system";
|
import { System } from "../system";
|
||||||
|
|
||||||
test("Run a plugos endpoint server", async () => {
|
test("Run a plugos endpoint server", async () => {
|
||||||
let system = new System<EndpointHook>("server");
|
let system = new System<EndpointHookT>("server");
|
||||||
let plug = await system.load(
|
let plug = await system.load(
|
||||||
"test",
|
"test",
|
||||||
{
|
{
|
||||||
@ -26,14 +26,14 @@ test("Run a plugos endpoint server", async () => {
|
|||||||
})()`,
|
})()`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as Manifest<EndpointHook>,
|
} as Manifest<EndpointHookT>,
|
||||||
createSandbox
|
createSandbox
|
||||||
);
|
);
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const port = 3123;
|
const port = 3123;
|
||||||
|
|
||||||
system.addFeature(new EndpointFeature(app, "/_"));
|
system.addHook(new EndpointHook(app, "/_"));
|
||||||
|
|
||||||
let server = app.listen(port, () => {
|
let server = app.listen(port, () => {
|
||||||
console.log(`Listening on port ${port}`);
|
console.log(`Listening on port ${port}`);
|
@ -1,4 +1,4 @@
|
|||||||
import { Feature, Manifest } from "../types";
|
import { Hook, Manifest } from "../types";
|
||||||
import { Express, NextFunction, Request, Response } from "express";
|
import { Express, NextFunction, Request, Response } from "express";
|
||||||
import { System } from "../system";
|
import { System } from "../system";
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ export type EndpointResponse = {
|
|||||||
body: any;
|
body: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EndpointHook = {
|
export type EndpointHookT = {
|
||||||
http?: EndPointDef | EndPointDef[];
|
http?: EndPointDef | EndPointDef[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ export type EndPointDef = {
|
|||||||
path: string;
|
path: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class EndpointFeature implements Feature<EndpointHook> {
|
export class EndpointHook implements Hook<EndpointHookT> {
|
||||||
private app: Express;
|
private app: Express;
|
||||||
private prefix: string;
|
private prefix: string;
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ export class EndpointFeature implements Feature<EndpointHook> {
|
|||||||
this.prefix = prefix;
|
this.prefix = prefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(system: System<EndpointHook>): void {
|
apply(system: System<EndpointHookT>): void {
|
||||||
this.app.use((req: Request, res: Response, next: NextFunction) => {
|
this.app.use((req: Request, res: Response, next: NextFunction) => {
|
||||||
if (!req.path.startsWith(this.prefix)) {
|
if (!req.path.startsWith(this.prefix)) {
|
||||||
return next();
|
return next();
|
||||||
@ -106,7 +106,7 @@ export class EndpointFeature implements Feature<EndpointHook> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
validateManifest(manifest: Manifest<EndpointHook>): string[] {
|
validateManifest(manifest: Manifest<EndpointHookT>): string[] {
|
||||||
let errors = [];
|
let errors = [];
|
||||||
for (const [name, functionDef] of Object.entries(manifest.functions)) {
|
for (const [name, functionDef] of Object.entries(manifest.functions)) {
|
||||||
if (!functionDef.http) {
|
if (!functionDef.http) {
|
@ -1,19 +1,19 @@
|
|||||||
import { Feature, Manifest } from "../types";
|
import { Hook, Manifest } from "../types";
|
||||||
import { System } from "../system";
|
import { System } from "../system";
|
||||||
|
|
||||||
// System events:
|
// System events:
|
||||||
// - plug:load (plugName: string)
|
// - plug:load (plugName: string)
|
||||||
|
|
||||||
export type EventHook = {
|
export type EventHookT = {
|
||||||
events?: string[];
|
events?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export class EventFeature implements Feature<EventHook> {
|
export class EventHook implements Hook<EventHookT> {
|
||||||
private system?: System<EventHook>;
|
private system?: System<EventHookT>;
|
||||||
|
|
||||||
async dispatchEvent(eventName: string, data?: any): Promise<any[]> {
|
async dispatchEvent(eventName: string, data?: any): Promise<any[]> {
|
||||||
if (!this.system) {
|
if (!this.system) {
|
||||||
throw new Error("EventFeature is not initialized");
|
throw new Error("Event hook is not initialized");
|
||||||
}
|
}
|
||||||
let promises: Promise<any>[] = [];
|
let promises: Promise<any>[] = [];
|
||||||
for (const plug of this.system.loadedPlugs.values()) {
|
for (const plug of this.system.loadedPlugs.values()) {
|
||||||
@ -31,7 +31,7 @@ export class EventFeature implements Feature<EventHook> {
|
|||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(system: System<EventHook>): void {
|
apply(system: System<EventHookT>): void {
|
||||||
this.system = system;
|
this.system = system;
|
||||||
this.system.on({
|
this.system.on({
|
||||||
plugLoaded: (name) => {
|
plugLoaded: (name) => {
|
||||||
@ -40,7 +40,7 @@ export class EventFeature implements Feature<EventHook> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
validateManifest(manifest: Manifest<EventHook>): string[] {
|
validateManifest(manifest: Manifest<EventHookT>): string[] {
|
||||||
let errors = [];
|
let errors = [];
|
||||||
for (const [name, functionDef] of Object.entries(manifest.functions)) {
|
for (const [name, functionDef] of Object.entries(manifest.functions)) {
|
||||||
if (functionDef.events && !Array.isArray(functionDef.events)) {
|
if (functionDef.events && !Array.isArray(functionDef.events)) {
|
@ -1,14 +1,14 @@
|
|||||||
import { Feature, Manifest } from "../types";
|
import { Hook, Manifest } from "../types";
|
||||||
import cron, { ScheduledTask } from "node-cron";
|
import cron, { ScheduledTask } from "node-cron";
|
||||||
import { safeRun } from "../util";
|
import { safeRun } from "../util";
|
||||||
import { System } from "../system";
|
import { System } from "../system";
|
||||||
|
|
||||||
export type CronHook = {
|
export type CronHookT = {
|
||||||
cron?: string | string[];
|
cron?: string | string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export class NodeCronFeature implements Feature<CronHook> {
|
export class NodeCronHook implements Hook<CronHookT> {
|
||||||
apply(system: System<CronHook>): void {
|
apply(system: System<CronHookT>): void {
|
||||||
let tasks: ScheduledTask[] = [];
|
let tasks: ScheduledTask[] = [];
|
||||||
system.on({
|
system.on({
|
||||||
plugLoaded: (name, plug) => {
|
plugLoaded: (name, plug) => {
|
||||||
@ -56,7 +56,7 @@ export class NodeCronFeature implements Feature<CronHook> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validateManifest(manifest: Manifest<CronHook>): string[] {
|
validateManifest(manifest: Manifest<CronHookT>): string[] {
|
||||||
let errors = [];
|
let errors = [];
|
||||||
for (const [name, functionDef] of Object.entries(manifest.functions)) {
|
for (const [name, functionDef] of Object.entries(manifest.functions)) {
|
||||||
if (!functionDef.cron) {
|
if (!functionDef.cron) {
|
@ -10,7 +10,7 @@
|
|||||||
"watch": "rm -rf .parcel-cache && parcel watch",
|
"watch": "rm -rf .parcel-cache && parcel watch",
|
||||||
"build": "parcel build",
|
"build": "parcel build",
|
||||||
"clean": "rm -rf dist",
|
"clean": "rm -rf dist",
|
||||||
"test": "jest dist"
|
"test": "jest dist/test"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
@ -28,9 +28,9 @@
|
|||||||
"test": {
|
"test": {
|
||||||
"source": [
|
"source": [
|
||||||
"runtime.test.ts",
|
"runtime.test.ts",
|
||||||
"feature/endpoint.test.ts",
|
"hooks/endpoint.test.ts",
|
||||||
"syscall/store.knex_node.test.ts",
|
"syscalls/store.knex_node.test.ts",
|
||||||
"syscall/store.dexie_browser.test.ts"
|
"syscalls/store.dexie_browser.test.ts"
|
||||||
],
|
],
|
||||||
"outputFormat": "commonjs",
|
"outputFormat": "commonjs",
|
||||||
"isLibrary": true,
|
"isLibrary": true,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
import watch from "node-watch";
|
import watch from "node-watch";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { createSandbox } from "./environment/node_sandbox";
|
import { createSandbox } from "./environments/node_sandbox";
|
||||||
import { safeRun } from "../server/util";
|
import { safeRun } from "../server/util";
|
||||||
import { System } from "./system";
|
import { System } from "./system";
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { createSandbox } from "./environment/node_sandbox";
|
import { createSandbox } from "./environments/node_sandbox";
|
||||||
import { expect, test } from "@jest/globals";
|
import { expect, test } from "@jest/globals";
|
||||||
import { System } from "./system";
|
import { System } from "./system";
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import {
|
|||||||
ControllerMessage,
|
ControllerMessage,
|
||||||
WorkerLike,
|
WorkerLike,
|
||||||
WorkerMessage,
|
WorkerMessage,
|
||||||
} from "./environment/worker";
|
} from "./environments/worker";
|
||||||
import { Plug } from "./plug";
|
import { Plug } from "./plug";
|
||||||
|
|
||||||
export type SandboxFactory<HookT> = (plug: Plug<HookT>) => Sandbox;
|
export type SandboxFactory<HookT> = (plug: Plug<HookT>) => Sandbox;
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
import { SysCallMapping } from "../system";
|
|
||||||
import { EventFeature } from "../feature/event";
|
|
||||||
|
|
||||||
export function eventSyscalls(eventFeature: EventFeature): SysCallMapping {
|
|
||||||
return {
|
|
||||||
async dispatch(ctx, eventName: string, data: any) {
|
|
||||||
return eventFeature.dispatchEvent(eventName, data);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
10
plugos/syscalls/event.ts
Normal file
10
plugos/syscalls/event.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { SysCallMapping } from "../system";
|
||||||
|
import { EventHook } from "../hooks/event";
|
||||||
|
|
||||||
|
export function eventSyscalls(eventHook: EventHook): SysCallMapping {
|
||||||
|
return {
|
||||||
|
async dispatch(ctx, eventName: string, data: any) {
|
||||||
|
return eventHook.dispatchEvent(eventName, data);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { createSandbox } from "../environment/node_sandbox";
|
import { createSandbox } from "../environments/node_sandbox";
|
||||||
import { expect, test } from "@jest/globals";
|
import { expect, test } from "@jest/globals";
|
||||||
import { System } from "../system";
|
import { System } from "../system";
|
||||||
import { storeSyscalls } from "./store.dexie_browser";
|
import { storeSyscalls } from "./store.dexie_browser";
|
@ -1,4 +1,4 @@
|
|||||||
import { createSandbox } from "../environment/node_sandbox";
|
import { createSandbox } from "../environments/node_sandbox";
|
||||||
import { expect, test } from "@jest/globals";
|
import { expect, test } from "@jest/globals";
|
||||||
import { System } from "../system";
|
import { System } from "../system";
|
||||||
import {
|
import {
|
@ -1,4 +1,4 @@
|
|||||||
import { Feature, Manifest, RuntimeEnvironment } from "./types";
|
import { Hook, Manifest, RuntimeEnvironment } from "./types";
|
||||||
import { EventEmitter } from "../common/event";
|
import { EventEmitter } from "../common/event";
|
||||||
import { SandboxFactory } from "./sandbox";
|
import { SandboxFactory } from "./sandbox";
|
||||||
import { Plug } from "./plug";
|
import { Plug } from "./plug";
|
||||||
@ -31,7 +31,7 @@ type Syscall = {
|
|||||||
export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
|
export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
|
||||||
protected plugs = new Map<string, Plug<HookT>>();
|
protected plugs = new Map<string, Plug<HookT>>();
|
||||||
protected registeredSyscalls = new Map<string, Syscall>();
|
protected registeredSyscalls = new Map<string, Syscall>();
|
||||||
protected enabledFeatures = new Set<Feature<HookT>>();
|
protected enabledHooks = new Set<Hook<HookT>>();
|
||||||
|
|
||||||
readonly runtimeEnv: RuntimeEnvironment;
|
readonly runtimeEnv: RuntimeEnvironment;
|
||||||
|
|
||||||
@ -40,8 +40,8 @@ export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
|
|||||||
this.runtimeEnv = env;
|
this.runtimeEnv = env;
|
||||||
}
|
}
|
||||||
|
|
||||||
addFeature(feature: Feature<HookT>) {
|
addHook(feature: Hook<HookT>) {
|
||||||
this.enabledFeatures.add(feature);
|
this.enabledHooks.add(feature);
|
||||||
feature.apply(this);
|
feature.apply(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
|
|||||||
}
|
}
|
||||||
// Validate
|
// Validate
|
||||||
let errors: string[] = [];
|
let errors: string[] = [];
|
||||||
for (const feature of this.enabledFeatures) {
|
for (const feature of this.enabledHooks) {
|
||||||
errors = [...errors, ...feature.validateManifest(manifest)];
|
errors = [...errors, ...feature.validateManifest(manifest)];
|
||||||
}
|
}
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"include": ["bin/*", "environment/*", "feature/**", "syscall/*", "*"],
|
"include": ["bin/*", "environments/*", "hooks/**", "syscalls/*", "*"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
@ -15,7 +15,7 @@ export type FunctionDef<HookT> = {
|
|||||||
|
|
||||||
export type RuntimeEnvironment = "client" | "server";
|
export type RuntimeEnvironment = "client" | "server";
|
||||||
|
|
||||||
export interface Feature<HookT> {
|
export interface Hook<HookT> {
|
||||||
validateManifest(manifest: Manifest<HookT>): string[];
|
validateManifest(manifest: Manifest<HookT>): string[];
|
||||||
|
|
||||||
apply(system: System<HookT>): void;
|
apply(system: System<HookT>): void;
|
||||||
|
@ -44,15 +44,10 @@ functions:
|
|||||||
- page:click
|
- page:click
|
||||||
insertToday:
|
insertToday:
|
||||||
path: "./dates.ts:insertToday"
|
path: "./dates.ts:insertToday"
|
||||||
command:
|
slashCommand:
|
||||||
name: Insert Current Date
|
name: today
|
||||||
slashCommand: today
|
|
||||||
welcome:
|
welcome:
|
||||||
path: "./server.ts:welcome"
|
path: "./server.ts:welcome"
|
||||||
events:
|
events:
|
||||||
- plug:load
|
- plug:load
|
||||||
env: server
|
env: server
|
||||||
# renderMD:
|
|
||||||
# path: "./markdown.ts:renderMD"
|
|
||||||
# command:
|
|
||||||
# name: Render Markdown
|
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
import {
|
import { EndpointRequest, EndpointResponse } from "../../plugos/hooks/endpoint";
|
||||||
EndpointRequest,
|
|
||||||
EndpointResponse,
|
|
||||||
} from "../../plugos/feature/endpoint";
|
|
||||||
|
|
||||||
export function endpointTest(req: EndpointRequest): EndpointResponse {
|
export function endpointTest(req: EndpointRequest): EndpointResponse {
|
||||||
console.log("I'm running on the server!", req);
|
console.log("I'm running on the server!", req);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Express } from "express";
|
import { Express } from "express";
|
||||||
import { SilverBulletHooks } from "../common/manifest";
|
import { SilverBulletHooks } from "../common/manifest";
|
||||||
import { EndpointFeature } from "../plugos/feature/endpoint";
|
import { EndpointHook } from "../plugos/hooks/endpoint";
|
||||||
import { readFile } from "fs/promises";
|
import { readFile } from "fs/promises";
|
||||||
import { System } from "../plugos/system";
|
import { System } from "../plugos/system";
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ export class ExpressServer {
|
|||||||
this.rootPath = rootPath;
|
this.rootPath = rootPath;
|
||||||
this.system = system;
|
this.system = system;
|
||||||
|
|
||||||
system.addFeature(new EndpointFeature(app, "/_"));
|
system.addHook(new EndpointHook(app, "/_"));
|
||||||
|
|
||||||
// Fallback, serve index.html
|
// Fallback, serve index.html
|
||||||
let cachedIndex: string | undefined = undefined;
|
let cachedIndex: string | undefined = undefined;
|
||||||
|
@ -11,9 +11,9 @@ import { stat } from "fs/promises";
|
|||||||
import { Cursor, cursorEffect } from "../webapp/cursorEffect";
|
import { Cursor, cursorEffect } from "../webapp/cursorEffect";
|
||||||
import { SilverBulletHooks } from "../common/manifest";
|
import { SilverBulletHooks } from "../common/manifest";
|
||||||
import { System } from "../plugos/system";
|
import { System } from "../plugos/system";
|
||||||
import { EventFeature } from "../plugos/feature/event";
|
import { EventHook } from "../plugos/hooks/event";
|
||||||
import spaceSyscalls from "./syscalls/space";
|
import spaceSyscalls from "./syscalls/space";
|
||||||
import { eventSyscalls } from "../plugos/syscall/event";
|
import { eventSyscalls } from "../plugos/syscalls/event";
|
||||||
|
|
||||||
export class PageApi implements ApiProvider {
|
export class PageApi implements ApiProvider {
|
||||||
openPages: Map<string, Page>;
|
openPages: Map<string, Page>;
|
||||||
@ -21,7 +21,7 @@ export class PageApi implements ApiProvider {
|
|||||||
rootPath: string;
|
rootPath: string;
|
||||||
connectedSockets: Set<Socket>;
|
connectedSockets: Set<Socket>;
|
||||||
private system: System<SilverBulletHooks>;
|
private system: System<SilverBulletHooks>;
|
||||||
private eventFeature: EventFeature;
|
private eventHook: EventHook;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
rootPath: string,
|
rootPath: string,
|
||||||
@ -34,10 +34,10 @@ export class PageApi implements ApiProvider {
|
|||||||
this.openPages = openPages;
|
this.openPages = openPages;
|
||||||
this.connectedSockets = connectedSockets;
|
this.connectedSockets = connectedSockets;
|
||||||
this.system = system;
|
this.system = system;
|
||||||
this.eventFeature = new EventFeature();
|
this.eventHook = new EventHook();
|
||||||
system.addFeature(this.eventFeature);
|
system.addHook(this.eventHook);
|
||||||
system.registerSyscalls("space", [], spaceSyscalls(this));
|
system.registerSyscalls("space", [], spaceSyscalls(this));
|
||||||
system.registerSyscalls("event", [], eventSyscalls(this.eventFeature));
|
system.registerSyscalls("event", [], eventSyscalls(this.eventHook));
|
||||||
}
|
}
|
||||||
|
|
||||||
async init(): Promise<void> {
|
async init(): Promise<void> {
|
||||||
@ -229,11 +229,8 @@ export class PageApi implements ApiProvider {
|
|||||||
" to disk and indexing."
|
" to disk and indexing."
|
||||||
);
|
);
|
||||||
await this.flushPageToDisk(pageName, page);
|
await this.flushPageToDisk(pageName, page);
|
||||||
await this.eventFeature.dispatchEvent(
|
await this.eventHook.dispatchEvent("page:saved", pageName);
|
||||||
"page:saved",
|
await this.eventHook.dispatchEvent("page:index", {
|
||||||
pageName
|
|
||||||
);
|
|
||||||
await this.eventFeature.dispatchEvent("page:index", {
|
|
||||||
name: pageName,
|
name: pageName,
|
||||||
text: page.text.sliceString(0),
|
text: page.text.sliceString(0),
|
||||||
});
|
});
|
||||||
@ -312,8 +309,8 @@ export class PageApi implements ApiProvider {
|
|||||||
this.openPages.delete(pageName);
|
this.openPages.delete(pageName);
|
||||||
}
|
}
|
||||||
// Trigger system events
|
// Trigger system events
|
||||||
await this.eventFeature.dispatchEvent("page:saved", pageName);
|
await this.eventHook.dispatchEvent("page:saved", pageName);
|
||||||
await this.eventFeature.dispatchEvent("page:index", {
|
await this.eventHook.dispatchEvent("page:index", {
|
||||||
name: pageName,
|
name: pageName,
|
||||||
text: text,
|
text: text,
|
||||||
});
|
});
|
||||||
@ -325,7 +322,7 @@ export class PageApi implements ApiProvider {
|
|||||||
clientConn.openPages.delete(pageName);
|
clientConn.openPages.delete(pageName);
|
||||||
// Cascading of this to all connected clients will be handled by file watcher
|
// Cascading of this to all connected clients will be handled by file watcher
|
||||||
await this.pageStore.deletePage(pageName);
|
await this.pageStore.deletePage(pageName);
|
||||||
await this.eventFeature.dispatchEvent("page:deleted", pageName);
|
await this.eventHook.dispatchEvent("page:deleted", pageName);
|
||||||
},
|
},
|
||||||
|
|
||||||
listPages: async (clientConn: ClientConnection): Promise<PageMeta[]> => {
|
listPages: async (clientConn: ClientConnection): Promise<PageMeta[]> => {
|
||||||
|
@ -9,8 +9,8 @@ import {hideBin} from "yargs/helpers";
|
|||||||
import {SilverBulletHooks} from "../common/manifest";
|
import {SilverBulletHooks} from "../common/manifest";
|
||||||
import {ExpressServer} from "./express_server";
|
import {ExpressServer} from "./express_server";
|
||||||
import {DiskPlugLoader} from "../plugos/plug_loader";
|
import {DiskPlugLoader} from "../plugos/plug_loader";
|
||||||
import {NodeCronFeature} from "../plugos/feature/node_cron";
|
import {NodeCronHook} from "../plugos/hooks/node_cron";
|
||||||
import shellSyscalls from "../plugos/syscall/shell.node";
|
import shellSyscalls from "../plugos/syscalls/shell.node";
|
||||||
import {System} from "../plugos/system";
|
import {System} from "../plugos/system";
|
||||||
|
|
||||||
let args = yargs(hideBin(process.argv))
|
let args = yargs(hideBin(process.argv))
|
||||||
@ -59,7 +59,7 @@ expressServer
|
|||||||
await plugLoader.loadPlugs();
|
await plugLoader.loadPlugs();
|
||||||
plugLoader.watcher();
|
plugLoader.watcher();
|
||||||
system.registerSyscalls("shell", ["shell"], shellSyscalls(pagesPath));
|
system.registerSyscalls("shell", ["shell"], shellSyscalls(pagesPath));
|
||||||
system.addFeature(new NodeCronFeature());
|
system.addHook(new NodeCronHook());
|
||||||
server.listen(port, () => {
|
server.listen(port, () => {
|
||||||
console.log(`Server listening on port ${port}`);
|
console.log(`Server listening on port ${port}`);
|
||||||
});
|
});
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
ensureTable,
|
ensureTable,
|
||||||
storeReadSyscalls,
|
storeReadSyscalls,
|
||||||
storeWriteSyscalls,
|
storeWriteSyscalls,
|
||||||
} from "../../plugos/syscall/store.knex_node";
|
} from "../../plugos/syscalls/store.knex_node";
|
||||||
|
|
||||||
type IndexItem = {
|
type IndexItem = {
|
||||||
page: string;
|
page: string;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { AppCommand } from "../types";
|
|
||||||
import { isMacLike } from "../util";
|
import { isMacLike } from "../util";
|
||||||
import { FilterList, Option } from "./filter";
|
import { FilterList, Option } from "./filter";
|
||||||
import { faPersonRunning } from "@fortawesome/free-solid-svg-icons";
|
import { faPersonRunning } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { AppCommand } from "../hooks/command";
|
||||||
|
|
||||||
export function CommandPalette({
|
export function CommandPalette({
|
||||||
commands,
|
commands,
|
||||||
@ -18,7 +18,6 @@ export function CommandPalette({
|
|||||||
hint: isMac && def.command.mac ? def.command.mac : def.command.key,
|
hint: isMac && def.command.mac ? def.command.mac : def.command.key,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log("Commands", options);
|
|
||||||
return (
|
return (
|
||||||
<FilterList
|
<FilterList
|
||||||
label="Run"
|
label="Run"
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
autocompletion,
|
autocompletion,
|
||||||
Completion,
|
|
||||||
CompletionContext,
|
|
||||||
completionKeymap,
|
completionKeymap,
|
||||||
CompletionResult,
|
CompletionResult,
|
||||||
} from "@codemirror/autocomplete";
|
} from "@codemirror/autocomplete";
|
||||||
@ -21,7 +19,7 @@ import {
|
|||||||
} from "@codemirror/view";
|
} from "@codemirror/view";
|
||||||
import React, { useEffect, useReducer } from "react";
|
import React, { useEffect, useReducer } from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import { createSandbox as createIFrameSandbox } from "../plugos/environment/iframe_sandbox";
|
import { createSandbox as createIFrameSandbox } from "../plugos/environments/iframe_sandbox";
|
||||||
import { AppEvent, AppEventDispatcher, ClickEvent } from "./app_event";
|
import { AppEvent, AppEventDispatcher, ClickEvent } from "./app_event";
|
||||||
import { CollabDocument, collabExtension } from "./collab";
|
import { CollabDocument, collabExtension } from "./collab";
|
||||||
import * as commands from "./commands";
|
import * as commands from "./commands";
|
||||||
@ -40,19 +38,15 @@ import customMarkdownStyle from "./style";
|
|||||||
import editorSyscalls from "./syscalls/editor";
|
import editorSyscalls from "./syscalls/editor";
|
||||||
import indexerSyscalls from "./syscalls/indexer";
|
import indexerSyscalls from "./syscalls/indexer";
|
||||||
import spaceSyscalls from "./syscalls/space";
|
import spaceSyscalls from "./syscalls/space";
|
||||||
import {
|
import { Action, AppViewState, initialViewState } from "./types";
|
||||||
Action,
|
|
||||||
AppCommand,
|
|
||||||
AppViewState,
|
|
||||||
initialViewState,
|
|
||||||
slashCommandRegexp,
|
|
||||||
} from "./types";
|
|
||||||
import { SilverBulletHooks } from "../common/manifest";
|
import { SilverBulletHooks } from "../common/manifest";
|
||||||
import { safeRun } from "./util";
|
import { safeRun } from "./util";
|
||||||
import { System } from "../plugos/system";
|
import { System } from "../plugos/system";
|
||||||
import { EventFeature } from "../plugos/feature/event";
|
import { EventHook } from "../plugos/hooks/event";
|
||||||
import { systemSyscalls } from "./syscalls/system";
|
import { systemSyscalls } from "./syscalls/system";
|
||||||
import { Panel } from "./components/panel";
|
import { Panel } from "./components/panel";
|
||||||
|
import { CommandHook } from "./hooks/command";
|
||||||
|
import { SlashCommandHook } from "./hooks/slash_command";
|
||||||
|
|
||||||
class PageState {
|
class PageState {
|
||||||
scrollTop: number;
|
scrollTop: number;
|
||||||
@ -67,22 +61,39 @@ class PageState {
|
|||||||
export class Editor implements AppEventDispatcher {
|
export class Editor implements AppEventDispatcher {
|
||||||
private system = new System<SilverBulletHooks>("client");
|
private system = new System<SilverBulletHooks>("client");
|
||||||
openPages = new Map<string, PageState>();
|
openPages = new Map<string, PageState>();
|
||||||
editorCommands = new Map<string, AppCommand>();
|
commandHook: CommandHook;
|
||||||
editorView?: EditorView;
|
editorView?: EditorView;
|
||||||
viewState: AppViewState;
|
viewState: AppViewState;
|
||||||
viewDispatch: React.Dispatch<Action>;
|
viewDispatch: React.Dispatch<Action>;
|
||||||
space: Space;
|
space: Space;
|
||||||
navigationResolve?: (val: undefined) => void;
|
|
||||||
pageNavigator: PathPageNavigator;
|
pageNavigator: PathPageNavigator;
|
||||||
private eventFeature: EventFeature;
|
eventHook: EventHook;
|
||||||
|
private slashCommandHook: SlashCommandHook;
|
||||||
|
|
||||||
constructor(space: Space, parent: Element) {
|
constructor(space: Space, parent: Element) {
|
||||||
this.space = space;
|
this.space = space;
|
||||||
this.viewState = initialViewState;
|
this.viewState = initialViewState;
|
||||||
this.viewDispatch = () => {};
|
this.viewDispatch = () => {};
|
||||||
|
|
||||||
this.eventFeature = new EventFeature();
|
// Event hook
|
||||||
this.system.addFeature(this.eventFeature);
|
this.eventHook = new EventHook();
|
||||||
|
this.system.addHook(this.eventHook);
|
||||||
|
|
||||||
|
// Command hook
|
||||||
|
this.commandHook = new CommandHook();
|
||||||
|
this.commandHook.on({
|
||||||
|
commandsUpdated: (commandMap) => {
|
||||||
|
this.viewDispatch({
|
||||||
|
type: "update-commands",
|
||||||
|
commands: commandMap,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.system.addHook(this.commandHook);
|
||||||
|
|
||||||
|
// Slash command hook
|
||||||
|
this.slashCommandHook = new SlashCommandHook(this);
|
||||||
|
this.system.addHook(this.slashCommandHook);
|
||||||
|
|
||||||
this.render(parent);
|
this.render(parent);
|
||||||
this.editorView = new EditorView({
|
this.editorView = new EditorView({
|
||||||
@ -142,14 +153,12 @@ export class Editor implements AppEventDispatcher {
|
|||||||
loadSystem: (systemJSON) => {
|
loadSystem: (systemJSON) => {
|
||||||
safeRun(async () => {
|
safeRun(async () => {
|
||||||
await this.system.replaceAllFromJSON(systemJSON, createIFrameSandbox);
|
await this.system.replaceAllFromJSON(systemJSON, createIFrameSandbox);
|
||||||
this.buildAllCommands();
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
plugLoaded: (plugName, plug) => {
|
plugLoaded: (plugName, plug) => {
|
||||||
safeRun(async () => {
|
safeRun(async () => {
|
||||||
console.log("Plug load", plugName);
|
console.log("Plug load", plugName);
|
||||||
await this.system.load(plugName, plug, createIFrameSandbox);
|
await this.system.load(plugName, plug, createIFrameSandbox);
|
||||||
this.buildAllCommands();
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
plugUnloaded: (plugName) => {
|
plugUnloaded: (plugName) => {
|
||||||
@ -161,39 +170,10 @@ export class Editor implements AppEventDispatcher {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (this.pageNavigator.getCurrentPage() === "") {
|
if (this.pageNavigator.getCurrentPage() === "") {
|
||||||
this.pageNavigator.navigate("start");
|
await this.pageNavigator.navigate("start");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildAllCommands() {
|
|
||||||
console.log("Loaded plugs, now updating editor commands");
|
|
||||||
this.editorCommands.clear();
|
|
||||||
for (let plug of this.system.loadedPlugs.values()) {
|
|
||||||
for (const [name, functionDef] of Object.entries(
|
|
||||||
plug.manifest!.functions
|
|
||||||
)) {
|
|
||||||
if (!functionDef.command) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const cmds = Array.isArray(functionDef.command)
|
|
||||||
? functionDef.command
|
|
||||||
: [functionDef.command];
|
|
||||||
for (let cmd of cmds) {
|
|
||||||
this.editorCommands.set(cmd.name, {
|
|
||||||
command: cmd,
|
|
||||||
run: () => {
|
|
||||||
return plug.invoke(name, []);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.viewDispatch({
|
|
||||||
type: "update-commands",
|
|
||||||
commands: this.editorCommands,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
flashNotification(message: string) {
|
flashNotification(message: string) {
|
||||||
let id = Math.floor(Math.random() * 1000000);
|
let id = Math.floor(Math.random() * 1000000);
|
||||||
this.viewDispatch({
|
this.viewDispatch({
|
||||||
@ -213,7 +193,7 @@ export class Editor implements AppEventDispatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async dispatchAppEvent(name: AppEvent, data?: any): Promise<any[]> {
|
async dispatchAppEvent(name: AppEvent, data?: any): Promise<any[]> {
|
||||||
return this.eventFeature.dispatchEvent(name, data);
|
return this.eventHook.dispatchEvent(name, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
get currentPage(): string | undefined {
|
get currentPage(): string | undefined {
|
||||||
@ -222,7 +202,7 @@ export class Editor implements AppEventDispatcher {
|
|||||||
|
|
||||||
createEditorState(pageName: string, doc: CollabDocument): EditorState {
|
createEditorState(pageName: string, doc: CollabDocument): EditorState {
|
||||||
let commandKeyBindings: KeyBinding[] = [];
|
let commandKeyBindings: KeyBinding[] = [];
|
||||||
for (let def of this.editorCommands.values()) {
|
for (let def of this.commandHook.editorCommands.values()) {
|
||||||
if (def.command.key) {
|
if (def.command.key) {
|
||||||
commandKeyBindings.push({
|
commandKeyBindings.push({
|
||||||
key: def.command.key,
|
key: def.command.key,
|
||||||
@ -257,7 +237,9 @@ export class Editor implements AppEventDispatcher {
|
|||||||
autocompletion({
|
autocompletion({
|
||||||
override: [
|
override: [
|
||||||
this.plugCompleter.bind(this),
|
this.plugCompleter.bind(this),
|
||||||
this.commandCompleter.bind(this),
|
this.slashCommandHook.slashCommandCompleter.bind(
|
||||||
|
this.slashCommandHook
|
||||||
|
),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
EditorView.lineWrapping,
|
EditorView.lineWrapping,
|
||||||
@ -361,39 +343,6 @@ export class Editor implements AppEventDispatcher {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
commandCompleter(ctx: CompletionContext): CompletionResult | null {
|
|
||||||
let prefix = ctx.matchBefore(slashCommandRegexp);
|
|
||||||
if (!prefix) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
let options: Completion[] = [];
|
|
||||||
for (let [name, def] of this.viewState.commands) {
|
|
||||||
if (!def.command.slashCommand) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
options.push({
|
|
||||||
label: def.command.slashCommand,
|
|
||||||
detail: name,
|
|
||||||
apply: () => {
|
|
||||||
this.editorView?.dispatch({
|
|
||||||
changes: {
|
|
||||||
from: prefix!.from,
|
|
||||||
to: ctx.pos,
|
|
||||||
insert: "",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
safeRun(async () => {
|
|
||||||
await def.run();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
from: prefix.from + 1,
|
|
||||||
options: options,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
focus() {
|
focus() {
|
||||||
this.editorView!.focus();
|
this.editorView!.focus();
|
||||||
}
|
}
|
||||||
@ -469,7 +418,7 @@ export class Editor implements AppEventDispatcher {
|
|||||||
editor.focus();
|
editor.focus();
|
||||||
if (page) {
|
if (page) {
|
||||||
safeRun(async () => {
|
safeRun(async () => {
|
||||||
editor.navigate(page);
|
await editor.navigate(page);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@ -497,7 +446,7 @@ export class Editor implements AppEventDispatcher {
|
|||||||
dispatch({ type: "start-navigate" });
|
dispatch({ type: "start-navigate" });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div id="editor"></div>
|
<div id="editor" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
75
webapp/hooks/command.ts
Normal file
75
webapp/hooks/command.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { Hook, Manifest } from "../../plugos/types";
|
||||||
|
import { System } from "../../plugos/system";
|
||||||
|
import { EventEmitter } from "../../common/event";
|
||||||
|
|
||||||
|
export type CommandDef = {
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
// Bind to keyboard shortcut
|
||||||
|
key?: string;
|
||||||
|
mac?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AppCommand = {
|
||||||
|
command: CommandDef;
|
||||||
|
run: () => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CommandHookT = {
|
||||||
|
command?: CommandDef;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CommandHookEvents = {
|
||||||
|
commandsUpdated(commandMap: Map<string, AppCommand>): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class CommandHook
|
||||||
|
extends EventEmitter<CommandHookEvents>
|
||||||
|
implements Hook<CommandHookT>
|
||||||
|
{
|
||||||
|
editorCommands = new Map<string, AppCommand>();
|
||||||
|
|
||||||
|
buildAllCommands(system: System<CommandHookT>) {
|
||||||
|
this.editorCommands.clear();
|
||||||
|
for (let plug of system.loadedPlugs.values()) {
|
||||||
|
for (const [name, functionDef] of Object.entries(
|
||||||
|
plug.manifest!.functions
|
||||||
|
)) {
|
||||||
|
if (!functionDef.command) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const cmd = functionDef.command;
|
||||||
|
this.editorCommands.set(cmd.name, {
|
||||||
|
command: cmd,
|
||||||
|
run: () => {
|
||||||
|
return plug.invoke(name, []);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.emit("commandsUpdated", this.editorCommands);
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(system: System<CommandHookT>): void {
|
||||||
|
this.buildAllCommands(system);
|
||||||
|
system.on({
|
||||||
|
plugLoaded: () => {
|
||||||
|
this.buildAllCommands(system);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
validateManifest(manifest: Manifest<CommandHookT>): string[] {
|
||||||
|
let 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 [];
|
||||||
|
}
|
||||||
|
}
|
111
webapp/hooks/slash_command.ts
Normal file
111
webapp/hooks/slash_command.ts
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import { Hook, Manifest } from "../../plugos/types";
|
||||||
|
import { System } from "../../plugos/system";
|
||||||
|
import {
|
||||||
|
Completion,
|
||||||
|
CompletionContext,
|
||||||
|
CompletionResult,
|
||||||
|
} from "@codemirror/autocomplete";
|
||||||
|
import { slashCommandRegexp } from "../types";
|
||||||
|
import { safeRun } from "../util";
|
||||||
|
import { Editor } from "../editor";
|
||||||
|
|
||||||
|
export type SlashCommandDef = {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AppSlashCommand = {
|
||||||
|
slashCommand: SlashCommandDef;
|
||||||
|
run: () => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SlashCommandHookT = {
|
||||||
|
slashCommand?: SlashCommandDef;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class SlashCommandHook implements Hook<SlashCommandHookT> {
|
||||||
|
slashCommands = new Map<string, AppSlashCommand>();
|
||||||
|
private editor: Editor;
|
||||||
|
|
||||||
|
constructor(editor: Editor) {
|
||||||
|
this.editor = editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
buildAllCommands(system: System<SlashCommandHookT>) {
|
||||||
|
this.slashCommands.clear();
|
||||||
|
for (let plug of system.loadedPlugs.values()) {
|
||||||
|
for (const [name, functionDef] of Object.entries(
|
||||||
|
plug.manifest!.functions
|
||||||
|
)) {
|
||||||
|
if (!functionDef.slashCommand) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const cmd = functionDef.slashCommand;
|
||||||
|
this.slashCommands.set(cmd.name, {
|
||||||
|
slashCommand: cmd,
|
||||||
|
run: () => {
|
||||||
|
return plug.invoke(name, []);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Completer for CodeMirror
|
||||||
|
public slashCommandCompleter(
|
||||||
|
ctx: CompletionContext
|
||||||
|
): CompletionResult | null {
|
||||||
|
let prefix = ctx.matchBefore(slashCommandRegexp);
|
||||||
|
if (!prefix) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let options: Completion[] = [];
|
||||||
|
for (let [name, def] of this.slashCommands.entries()) {
|
||||||
|
options.push({
|
||||||
|
label: def.slashCommand.name,
|
||||||
|
detail: name,
|
||||||
|
apply: () => {
|
||||||
|
// Delete slash command part
|
||||||
|
this.editor.editorView?.dispatch({
|
||||||
|
changes: {
|
||||||
|
from: prefix!.from,
|
||||||
|
to: ctx.pos,
|
||||||
|
insert: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// Replace with whatever the completion is
|
||||||
|
safeRun(async () => {
|
||||||
|
await def.run();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
// + 1 because of the '/'
|
||||||
|
from: prefix.from + 1,
|
||||||
|
options: options,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(system: System<SlashCommandHookT>): void {
|
||||||
|
this.buildAllCommands(system);
|
||||||
|
system.on({
|
||||||
|
plugLoaded: () => {
|
||||||
|
this.buildAllCommands(system);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
validateManifest(manifest: Manifest<SlashCommandHookT>): string[] {
|
||||||
|
let errors = [];
|
||||||
|
for (const [name, functionDef] of Object.entries(manifest.functions)) {
|
||||||
|
if (!functionDef.slashCommand) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const cmd = functionDef.slashCommand;
|
||||||
|
if (!cmd.name) {
|
||||||
|
errors.push(`Function ${name} has a command but no name`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import { Space } from "../space";
|
import { Space } from "../space";
|
||||||
import { SysCallMapping } from "../../plugos/system";
|
import { SysCallMapping } from "../../plugos/system";
|
||||||
import { transportSyscalls } from "../../plugos/syscall/transport";
|
import { transportSyscalls } from "../../plugos/syscalls/transport";
|
||||||
|
|
||||||
export default function indexerSyscalls(space: Space): SysCallMapping {
|
export default function indexerSyscalls(space: Space): SysCallMapping {
|
||||||
return transportSyscalls(
|
return transportSyscalls(
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CommandDef } from "../common/manifest";
|
import { AppCommand } from "./hooks/command";
|
||||||
|
|
||||||
export type PageMeta = {
|
export type PageMeta = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -7,11 +7,6 @@ export type PageMeta = {
|
|||||||
lastOpened?: number;
|
lastOpened?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AppCommand = {
|
|
||||||
command: CommandDef;
|
|
||||||
run: () => Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const slashCommandRegexp = /\/[\w\-]*/;
|
export const slashCommandRegexp = /\/[\w\-]*/;
|
||||||
|
|
||||||
export type Notification = {
|
export type Notification = {
|
||||||
|
Loading…
Reference in New Issue
Block a user