Work
parent
cb2d3f8652
commit
76636dd9b1
|
@ -2,7 +2,7 @@ import { SpacePrimitives } from "./space_primitives";
|
|||
import { EventHook } from "@plugos/plugos/hooks/event";
|
||||
import { PageMeta } from "../types";
|
||||
import { Plug } from "@plugos/plugos/plug";
|
||||
import { trashPrefix } from "./constants";
|
||||
import { plugPrefix, trashPrefix } from "./constants";
|
||||
|
||||
export class EventedSpacePrimitives implements SpacePrimitives {
|
||||
constructor(private wrapped: SpacePrimitives, private eventHook: EventHook) {}
|
||||
|
@ -41,7 +41,7 @@ export class EventedSpacePrimitives implements SpacePrimitives {
|
|||
lastModified
|
||||
);
|
||||
// This can happen async
|
||||
if (!pageName.startsWith(trashPrefix)) {
|
||||
if (!pageName.startsWith(trashPrefix) && !pageName.startsWith(plugPrefix)) {
|
||||
this.eventHook
|
||||
.dispatchEvent("page:saved", pageName)
|
||||
.then(() => {
|
||||
|
|
|
@ -13,8 +13,6 @@ export type SpaceEvents = {
|
|||
pageChanged: (meta: PageMeta) => void;
|
||||
pageDeleted: (name: string) => void;
|
||||
pageListUpdated: (pages: Set<PageMeta>) => void;
|
||||
plugLoaded: (plugName: string, plug: Manifest) => void;
|
||||
plugUnloaded: (plugName: string) => void;
|
||||
};
|
||||
|
||||
export class Space extends EventEmitter<SpaceEvents> {
|
||||
|
@ -25,70 +23,45 @@ export class Space extends EventEmitter<SpaceEvents> {
|
|||
|
||||
constructor(private space: SpacePrimitives, private trashEnabled = true) {
|
||||
super();
|
||||
this.on({
|
||||
pageCreated: async (pageMeta) => {
|
||||
if (pageMeta.name.startsWith(plugPrefix)) {
|
||||
let pageData = await this.readPage(pageMeta.name);
|
||||
this.emit(
|
||||
"plugLoaded",
|
||||
pageMeta.name.substring(plugPrefix.length),
|
||||
JSON.parse(pageData.text)
|
||||
);
|
||||
this.watchPage(pageMeta.name);
|
||||
}
|
||||
},
|
||||
pageChanged: async (pageMeta) => {
|
||||
if (pageMeta.name.startsWith(plugPrefix)) {
|
||||
let pageData = await this.readPage(pageMeta.name);
|
||||
this.emit(
|
||||
"plugLoaded",
|
||||
pageMeta.name.substring(plugPrefix.length),
|
||||
JSON.parse(pageData.text)
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public updatePageListAsync() {
|
||||
safeRun(async () => {
|
||||
let newPageList = await this.space.fetchPageList();
|
||||
let deletedPages = new Set<string>(this.pageMetaCache.keys());
|
||||
newPageList.pages.forEach((meta) => {
|
||||
const pageName = meta.name;
|
||||
const oldPageMeta = this.pageMetaCache.get(pageName);
|
||||
const newPageMeta = {
|
||||
name: pageName,
|
||||
lastModified: meta.lastModified,
|
||||
};
|
||||
if (
|
||||
!oldPageMeta &&
|
||||
(pageName.startsWith(plugPrefix) || !this.initialPageListLoad)
|
||||
) {
|
||||
this.emit("pageCreated", newPageMeta);
|
||||
} else if (
|
||||
oldPageMeta &&
|
||||
oldPageMeta.lastModified !== newPageMeta.lastModified &&
|
||||
(!this.trashEnabled ||
|
||||
(this.trashEnabled && !pageName.startsWith(trashPrefix)))
|
||||
) {
|
||||
this.emit("pageChanged", newPageMeta);
|
||||
}
|
||||
// Page found, not deleted
|
||||
deletedPages.delete(pageName);
|
||||
|
||||
// Update in cache
|
||||
this.pageMetaCache.set(pageName, newPageMeta);
|
||||
});
|
||||
|
||||
for (const deletedPage of deletedPages) {
|
||||
this.pageMetaCache.delete(deletedPage);
|
||||
this.emit("pageDeleted", deletedPage);
|
||||
public async updatePageList() {
|
||||
let newPageList = await this.space.fetchPageList();
|
||||
let deletedPages = new Set<string>(this.pageMetaCache.keys());
|
||||
newPageList.pages.forEach((meta) => {
|
||||
const pageName = meta.name;
|
||||
const oldPageMeta = this.pageMetaCache.get(pageName);
|
||||
const newPageMeta = {
|
||||
name: pageName,
|
||||
lastModified: meta.lastModified,
|
||||
};
|
||||
if (
|
||||
!oldPageMeta &&
|
||||
(pageName.startsWith(plugPrefix) || !this.initialPageListLoad)
|
||||
) {
|
||||
this.emit("pageCreated", newPageMeta);
|
||||
} else if (
|
||||
oldPageMeta &&
|
||||
oldPageMeta.lastModified !== newPageMeta.lastModified &&
|
||||
(!this.trashEnabled ||
|
||||
(this.trashEnabled && !pageName.startsWith(trashPrefix)))
|
||||
) {
|
||||
this.emit("pageChanged", newPageMeta);
|
||||
}
|
||||
// Page found, not deleted
|
||||
deletedPages.delete(pageName);
|
||||
|
||||
this.emit("pageListUpdated", this.listPages());
|
||||
this.initialPageListLoad = false;
|
||||
// Update in cache
|
||||
this.pageMetaCache.set(pageName, newPageMeta);
|
||||
});
|
||||
|
||||
for (const deletedPage of deletedPages) {
|
||||
this.pageMetaCache.delete(deletedPage);
|
||||
this.emit("pageDeleted", deletedPage);
|
||||
}
|
||||
|
||||
this.emit("pageListUpdated", this.listPages());
|
||||
this.initialPageListLoad = false;
|
||||
}
|
||||
|
||||
watch() {
|
||||
|
@ -109,7 +82,7 @@ export class Space extends EventEmitter<SpaceEvents> {
|
|||
}
|
||||
});
|
||||
}, pageWatchInterval);
|
||||
this.updatePageListAsync();
|
||||
this.updatePageList().catch(console.error);
|
||||
}
|
||||
|
||||
async deletePage(name: string, deleteDate?: number): Promise<void> {
|
||||
|
@ -152,14 +125,18 @@ export class Space extends EventEmitter<SpaceEvents> {
|
|||
return this.space.invokeFunction(plug, env, name, args);
|
||||
}
|
||||
|
||||
listPages(): Set<PageMeta> {
|
||||
return new Set(
|
||||
[...this.pageMetaCache.values()].filter(
|
||||
(pageMeta) =>
|
||||
!pageMeta.name.startsWith(trashPrefix) &&
|
||||
!pageMeta.name.startsWith(plugPrefix)
|
||||
)
|
||||
);
|
||||
listPages(unfiltered = false): Set<PageMeta> {
|
||||
if (unfiltered) {
|
||||
return new Set(this.pageMetaCache.values());
|
||||
} else {
|
||||
return new Set(
|
||||
[...this.pageMetaCache.values()].filter(
|
||||
(pageMeta) =>
|
||||
!pageMeta.name.startsWith(trashPrefix) &&
|
||||
!pageMeta.name.startsWith(plugPrefix)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
listTrash(): Set<PageMeta> {
|
||||
|
|
|
@ -62,6 +62,14 @@ export function hideLhs(): Promise<void> {
|
|||
return syscall("editor.hideLhs");
|
||||
}
|
||||
|
||||
export function showBhs(html: string, flex = 1): Promise<void> {
|
||||
return syscall("editor.showBhs", html, flex);
|
||||
}
|
||||
|
||||
export function hideBhs(): Promise<void> {
|
||||
return syscall("editor.hideBhs");
|
||||
}
|
||||
|
||||
export function insertAtPos(text: string, pos: number): Promise<void> {
|
||||
return syscall("editor.insertAtPos", text, pos);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { syscall } from "./syscall";
|
||||
import { PageMeta } from "../common/types";
|
||||
|
||||
export async function listPages(): Promise<PageMeta[]> {
|
||||
return syscall("space.listPages");
|
||||
export async function listPages(unfiltered = false): Promise<PageMeta[]> {
|
||||
return syscall("space.listPages", unfiltered);
|
||||
}
|
||||
|
||||
export async function readPage(
|
||||
|
|
|
@ -7,3 +7,7 @@ export async function invokeFunction(
|
|||
): Promise<any> {
|
||||
return syscall("system.invokeFunction", env, name, ...args);
|
||||
}
|
||||
|
||||
export async function reloadPlugs() {
|
||||
return syscall("system.reloadPlugs");
|
||||
}
|
||||
|
|
|
@ -20,6 +20,10 @@ async function bundle(
|
|||
(await readFile(manifestPath)).toString()
|
||||
) as Manifest<any>;
|
||||
|
||||
if (!manifest.name) {
|
||||
throw new Error(`Missing 'name' in ${manifestPath}`);
|
||||
}
|
||||
|
||||
for (let [name, def] of Object.entries(manifest.functions)) {
|
||||
let jsFunctionName = "default",
|
||||
filePath = path.join(rootPath, def.path!);
|
||||
|
|
|
@ -9,8 +9,8 @@ import { System } from "../system";
|
|||
test("Run a plugos endpoint server", async () => {
|
||||
let system = new System<EndpointHookT>("server");
|
||||
let plug = await system.load(
|
||||
"test",
|
||||
{
|
||||
name: "test",
|
||||
functions: {
|
||||
testhandler: {
|
||||
http: {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Hook, Manifest } from "../types";
|
||||
import { System } from "../system";
|
||||
import { safeRun } from "../util";
|
||||
import { EventEmitter } from "events";
|
||||
|
||||
// System events:
|
||||
// - plug:load (plugName: string)
|
||||
|
@ -11,6 +12,14 @@ export type EventHookT = {
|
|||
|
||||
export class EventHook implements Hook<EventHookT> {
|
||||
private system?: System<EventHookT>;
|
||||
public localListeners: Map<string, ((data: any) => any)[]> = new Map();
|
||||
|
||||
addLocalListener(eventName: string, callback: (data: any) => any) {
|
||||
if (!this.localListeners.has(eventName)) {
|
||||
this.localListeners.set(eventName, []);
|
||||
}
|
||||
this.localListeners.get(eventName)!.push(callback);
|
||||
}
|
||||
|
||||
async dispatchEvent(eventName: string, data?: any): Promise<any[]> {
|
||||
if (!this.system) {
|
||||
|
@ -32,15 +41,25 @@ export class EventHook implements Hook<EventHookT> {
|
|||
}
|
||||
}
|
||||
}
|
||||
let localListeners = this.localListeners.get(eventName);
|
||||
if (localListeners) {
|
||||
for (let localListener of localListeners) {
|
||||
let result = await Promise.resolve(localListener(data));
|
||||
if (result) {
|
||||
responses.push(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return responses;
|
||||
}
|
||||
|
||||
apply(system: System<EventHookT>): void {
|
||||
this.system = system;
|
||||
this.system.on({
|
||||
plugLoaded: (name) => {
|
||||
plugLoaded: (plug) => {
|
||||
safeRun(async () => {
|
||||
await this.dispatchEvent("plug:load", name);
|
||||
await this.dispatchEvent("plug:load", plug.name);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -11,10 +11,10 @@ export class NodeCronHook implements Hook<CronHookT> {
|
|||
apply(system: System<CronHookT>): void {
|
||||
let tasks: ScheduledTask[] = [];
|
||||
system.on({
|
||||
plugLoaded: (name, plug) => {
|
||||
plugLoaded: () => {
|
||||
reloadCrons();
|
||||
},
|
||||
plugUnloaded(name, plug) {
|
||||
plugUnloaded() {
|
||||
reloadCrons();
|
||||
},
|
||||
});
|
||||
|
|
|
@ -3,11 +3,7 @@ import watch from "node-watch";
|
|||
import path from "path";
|
||||
import { createSandbox } from "./environments/node_sandbox";
|
||||
import { System } from "./system";
|
||||
|
||||
function extractPlugName(localPath: string): string {
|
||||
const baseName = path.basename(localPath);
|
||||
return baseName.substring(0, baseName.length - ".plug.json".length);
|
||||
}
|
||||
import { Manifest } from "./types";
|
||||
|
||||
export class DiskPlugLoader<HookT> {
|
||||
private system: System<HookT>;
|
||||
|
@ -27,13 +23,13 @@ export class DiskPlugLoader<HookT> {
|
|||
.then(async () => {
|
||||
try {
|
||||
// let localPath = path.join(this.plugPath, filename);
|
||||
const plugName = extractPlugName(localPath);
|
||||
console.log("Change detected for", plugName);
|
||||
console.log("Change detected for", localPath);
|
||||
try {
|
||||
await fs.stat(localPath);
|
||||
} catch (e) {
|
||||
// Likely removed
|
||||
await this.system.unload(plugName);
|
||||
console.log("Plug removed, TODO: Unload");
|
||||
return;
|
||||
}
|
||||
const plugDef = await this.loadPlugFromFile(localPath);
|
||||
} catch (e) {
|
||||
|
@ -47,12 +43,11 @@ export class DiskPlugLoader<HookT> {
|
|||
|
||||
private async loadPlugFromFile(localPath: string) {
|
||||
const plug = await fs.readFile(localPath, "utf8");
|
||||
const plugName = extractPlugName(localPath);
|
||||
|
||||
console.log("Now loading plug", plugName);
|
||||
try {
|
||||
const plugDef = JSON.parse(plug);
|
||||
await this.system.load(plugName, plugDef, createSandbox);
|
||||
const plugDef: Manifest<HookT> = JSON.parse(plug);
|
||||
console.log("Now loading plug", plugDef.name);
|
||||
await this.system.load(plugDef, createSandbox);
|
||||
return plugDef;
|
||||
} catch (e) {
|
||||
console.error("Could not parse plugin file", e);
|
||||
|
|
|
@ -23,8 +23,8 @@ test("Run a Node sandbox", async () => {
|
|||
},
|
||||
});
|
||||
let plug = await system.load(
|
||||
"test",
|
||||
{
|
||||
name: "test",
|
||||
requiredPermissions: ["dangerous"],
|
||||
functions: {
|
||||
addTen: {
|
||||
|
|
|
@ -34,9 +34,7 @@ export function esbuildSyscalls(): SysCallMapping {
|
|||
}
|
||||
|
||||
await writeFile(`${tmpDir}/${filename}`, code);
|
||||
console.log("Dir", tmpDir);
|
||||
let jsCode = await compile(`${tmpDir}/${filename}`, "", false, ["yaml"]);
|
||||
// console.log("JS code", jsCode);
|
||||
await rm(tmpDir, { recursive: true });
|
||||
return jsCode;
|
||||
},
|
||||
|
|
|
@ -10,8 +10,8 @@ test("Test store", async () => {
|
|||
let system = new System("server");
|
||||
system.registerSyscalls([], storeSyscalls("test", "test"));
|
||||
let plug = await system.load(
|
||||
"test",
|
||||
{
|
||||
name: "test",
|
||||
functions: {
|
||||
test1: {
|
||||
code: `(() => {
|
||||
|
|
|
@ -17,8 +17,8 @@ test("Test store", async () => {
|
|||
let system = new System("server");
|
||||
system.registerSyscalls([], storeSyscalls(db, "test_table"));
|
||||
let plug = await system.load(
|
||||
"test",
|
||||
{
|
||||
name: "test",
|
||||
functions: {
|
||||
test1: {
|
||||
code: `(() => {
|
||||
|
|
|
@ -7,11 +7,11 @@ export interface SysCallMapping {
|
|||
[key: string]: (ctx: SyscallContext, ...args: any) => Promise<any> | any;
|
||||
}
|
||||
|
||||
export type SystemJSON<HookT> = { [key: string]: Manifest<HookT> };
|
||||
export type SystemJSON<HookT> = Manifest<HookT>[];
|
||||
|
||||
export type SystemEvents<HookT> = {
|
||||
plugLoaded: (name: string, plug: Plug<HookT>) => void;
|
||||
plugUnloaded: (name: string, plug: Plug<HookT>) => void;
|
||||
plugLoaded: (plug: Plug<HookT>) => void;
|
||||
plugUnloaded: (name: string) => void;
|
||||
};
|
||||
|
||||
export type SyscallContext = {
|
||||
|
@ -83,10 +83,10 @@ export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
|
|||
}
|
||||
|
||||
async load(
|
||||
name: string,
|
||||
manifest: Manifest<HookT>,
|
||||
sandboxFactory: SandboxFactory<HookT>
|
||||
): Promise<Plug<HookT>> {
|
||||
const name = manifest.name;
|
||||
if (this.plugs.has(name)) {
|
||||
await this.unload(name);
|
||||
}
|
||||
|
@ -100,29 +100,31 @@ export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
|
|||
}
|
||||
// Ok, let's load this thing!
|
||||
const plug = new Plug(this, name, sandboxFactory);
|
||||
console.log("Loading", name);
|
||||
await plug.load(manifest);
|
||||
this.plugs.set(name, plug);
|
||||
this.emit("plugLoaded", name, plug);
|
||||
this.emit("plugLoaded", plug);
|
||||
return plug;
|
||||
}
|
||||
|
||||
async unload(name: string) {
|
||||
console.log("Unloading", name);
|
||||
const plug = this.plugs.get(name);
|
||||
if (!plug) {
|
||||
throw Error(`Plug ${name} not found`);
|
||||
}
|
||||
await plug.stop();
|
||||
this.emit("plugUnloaded", name, plug);
|
||||
this.emit("plugUnloaded", name);
|
||||
this.plugs.delete(name);
|
||||
}
|
||||
|
||||
toJSON(): SystemJSON<HookT> {
|
||||
let plugJSON: { [key: string]: Manifest<HookT> } = {};
|
||||
let plugJSON: Manifest<HookT>[] = [];
|
||||
for (let [name, plug] of this.plugs) {
|
||||
if (!plug.manifest) {
|
||||
continue;
|
||||
}
|
||||
plugJSON[name] = plug.manifest;
|
||||
plugJSON.push(plug.manifest);
|
||||
}
|
||||
return plugJSON;
|
||||
}
|
||||
|
@ -132,9 +134,9 @@ export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
|
|||
sandboxFactory: SandboxFactory<HookT>
|
||||
) {
|
||||
await this.unloadAll();
|
||||
for (let [name, manifest] of Object.entries(json)) {
|
||||
console.log("Loading plug", name);
|
||||
await this.load(name, manifest, sandboxFactory);
|
||||
for (let manifest of json) {
|
||||
console.log("Loading plug", manifest.name);
|
||||
await this.load(manifest, sandboxFactory);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { System } from "./system";
|
||||
|
||||
export interface Manifest<HookT> {
|
||||
name: string;
|
||||
requiredPermissions?: string[];
|
||||
functions: {
|
||||
[key: string]: FunctionDef<HookT>;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
name: core
|
||||
syntax:
|
||||
HashTag:
|
||||
firstCharacters:
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
name: emoji
|
||||
functions:
|
||||
emojiCompleter:
|
||||
path: "./emoji.ts:emojiCompleter"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
name: ghost
|
||||
functions:
|
||||
downloadAllPostsCommand:
|
||||
path: "./ghost.ts:downloadAllPostsCommand"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
name: git
|
||||
requiredPermissions:
|
||||
- shell
|
||||
functions:
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
name: markdown
|
||||
functions:
|
||||
toggle:
|
||||
path: "./markdown.ts:togglePreview"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
name: mattermost
|
||||
functions:
|
||||
test:
|
||||
path: mattermost.ts:savedPostsQueryProvider
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
functions:
|
||||
compile:
|
||||
path: "./plugger.ts:compileCommand"
|
||||
command:
|
||||
name: "Plugger: Compile"
|
||||
compileJS:
|
||||
path: "./plugger.ts:compileJS"
|
||||
env: server
|
|
@ -1,85 +0,0 @@
|
|||
import type { Manifest } from "@silverbulletmd/common/manifest";
|
||||
import {
|
||||
addParentPointers,
|
||||
collectNodesOfType,
|
||||
findNodeOfType,
|
||||
} from "@silverbulletmd/common/tree";
|
||||
import {
|
||||
getCurrentPage,
|
||||
getText,
|
||||
} from "@silverbulletmd/plugos-silverbullet-syscall/editor";
|
||||
import { parseMarkdown } from "@silverbulletmd/plugos-silverbullet-syscall/markdown";
|
||||
import { writePage } from "@silverbulletmd/plugos-silverbullet-syscall/space";
|
||||
import { invokeFunction } from "@silverbulletmd/plugos-silverbullet-syscall/system";
|
||||
import YAML from "yaml";
|
||||
import { extractMeta } from "../query/data";
|
||||
|
||||
export async function compileCommand() {
|
||||
let text = await getText();
|
||||
let tree = await parseMarkdown(text);
|
||||
addParentPointers(tree);
|
||||
let allHeaders = collectNodesOfType(tree, "ATXHeading2");
|
||||
let manifest: Manifest = {
|
||||
functions: {},
|
||||
};
|
||||
for (let t of allHeaders) {
|
||||
let parent = t.parent!;
|
||||
let headerIdx = parent.children!.indexOf(t);
|
||||
let headerTitle = t.children![1].text!.trim();
|
||||
if (!headerTitle.startsWith("function ")) {
|
||||
continue;
|
||||
}
|
||||
let functionName = headerTitle
|
||||
.substring("function ".length)
|
||||
.replace(/[^\w]/g, "_");
|
||||
let meta: any;
|
||||
let code: string | undefined;
|
||||
let language = "js";
|
||||
for (let i = headerIdx + 1; i < parent.children!.length; i++) {
|
||||
let child = parent.children![i];
|
||||
if (child.type === "FencedCode") {
|
||||
let codeInfo = findNodeOfType(child, "CodeInfo")!.children![0].text!;
|
||||
let codeText = findNodeOfType(child, "CodeText")!.children![0].text!;
|
||||
if (codeInfo === "yaml") {
|
||||
meta = YAML.parse(codeText);
|
||||
continue;
|
||||
}
|
||||
if (codeInfo === "typescript" || codeInfo === "ts") {
|
||||
language = "ts";
|
||||
}
|
||||
code = codeText;
|
||||
}
|
||||
|
||||
if (child.type?.startsWith("ATXHeading")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (code) {
|
||||
let compiled = await invokeFunction(
|
||||
"server",
|
||||
"compileJS",
|
||||
`file.${language}`,
|
||||
code
|
||||
);
|
||||
manifest.functions[functionName] = meta;
|
||||
manifest.functions[functionName].code = compiled;
|
||||
}
|
||||
}
|
||||
|
||||
let pageMeta = extractMeta(tree);
|
||||
|
||||
if (pageMeta.name) {
|
||||
await writePage(
|
||||
`_plug/${pageMeta.name}`,
|
||||
JSON.stringify(manifest, null, 2)
|
||||
);
|
||||
console.log("Wrote this plug", manifest);
|
||||
}
|
||||
}
|
||||
|
||||
export async function compileJS(
|
||||
filename: string,
|
||||
code: string
|
||||
): Promise<string> {
|
||||
return self.syscall("esbuild.compile", filename, code);
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
name: plugmd
|
||||
functions:
|
||||
updatePlugsCommand:
|
||||
path: ./plugmd.ts:updatePlugsCommand
|
||||
command:
|
||||
name: "Plugs: Update"
|
||||
key: "Ctrl-Shift-p"
|
||||
mac: "Cmd-Shift-p"
|
||||
updatePlugs:
|
||||
path: ./plugmd.ts:updatePlugs
|
||||
env: server
|
||||
compile:
|
||||
path: "./plugmd.ts:compileCommand"
|
||||
command:
|
||||
name: "Plug: Compile"
|
||||
mac: "Cmd-Shift-c"
|
||||
key: "Ctrl-Shift-c"
|
||||
compileJS:
|
||||
path: "./plugmd.ts:compileJS"
|
||||
env: server
|
||||
|
||||
getPlugPlugMd:
|
||||
path: "./plugmd.ts:getPlugPlugMd"
|
||||
events:
|
||||
- get-plug:plugmd
|
|
@ -0,0 +1,174 @@
|
|||
import { dispatch } from "@plugos/plugos-syscall/event";
|
||||
import type { Manifest } from "@silverbulletmd/common/manifest";
|
||||
import {
|
||||
addParentPointers,
|
||||
collectNodesOfType,
|
||||
findNodeOfType,
|
||||
} from "@silverbulletmd/common/tree";
|
||||
import {
|
||||
getCurrentPage,
|
||||
getText,
|
||||
hideBhs,
|
||||
showBhs,
|
||||
} from "@silverbulletmd/plugos-silverbullet-syscall/editor";
|
||||
import { parseMarkdown } from "@silverbulletmd/plugos-silverbullet-syscall/markdown";
|
||||
import {
|
||||
deletePage,
|
||||
listPages,
|
||||
readPage,
|
||||
writePage,
|
||||
} from "@silverbulletmd/plugos-silverbullet-syscall/space";
|
||||
import {
|
||||
invokeFunction,
|
||||
reloadPlugs,
|
||||
} from "@silverbulletmd/plugos-silverbullet-syscall/system";
|
||||
import YAML from "yaml";
|
||||
import { extractMeta } from "../query/data";
|
||||
|
||||
export async function compileCommand() {
|
||||
let text = await getText();
|
||||
try {
|
||||
let manifest = await compileDefinition(text);
|
||||
await writePage(
|
||||
`_plug/${manifest.name}`,
|
||||
JSON.stringify(manifest, null, 2)
|
||||
);
|
||||
console.log("Wrote this plug", manifest);
|
||||
await hideBhs();
|
||||
// Important not to await!
|
||||
reloadPlugs();
|
||||
} catch (e: any) {
|
||||
await showBhs(e.message);
|
||||
// console.error("Got this error from compiler", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function compileDefinition(text: string): Promise<Manifest> {
|
||||
let tree = await parseMarkdown(text);
|
||||
|
||||
let pageMeta = extractMeta(tree);
|
||||
|
||||
if (!pageMeta.name) {
|
||||
throw new Error("No 'name' specified in page meta");
|
||||
}
|
||||
|
||||
addParentPointers(tree);
|
||||
let allHeaders = collectNodesOfType(tree, "ATXHeading2");
|
||||
let manifest: Manifest = {
|
||||
name: pageMeta.name,
|
||||
functions: {},
|
||||
};
|
||||
for (let t of allHeaders) {
|
||||
let parent = t.parent!;
|
||||
let headerIdx = parent.children!.indexOf(t);
|
||||
let headerTitle = t.children![1].text!.trim();
|
||||
if (!headerTitle.startsWith("function ")) {
|
||||
continue;
|
||||
}
|
||||
let functionName = headerTitle
|
||||
.substring("function ".length)
|
||||
.replace(/[^\w]/g, "_");
|
||||
let meta: any;
|
||||
let code: string | undefined;
|
||||
let language = "js";
|
||||
for (let i = headerIdx + 1; i < parent.children!.length; i++) {
|
||||
let child = parent.children![i];
|
||||
if (child.type === "FencedCode") {
|
||||
let codeInfo = findNodeOfType(child, "CodeInfo")!.children![0].text!;
|
||||
let codeText = findNodeOfType(child, "CodeText")!.children![0].text!;
|
||||
if (codeInfo === "yaml") {
|
||||
meta = YAML.parse(codeText);
|
||||
continue;
|
||||
}
|
||||
if (codeInfo === "typescript" || codeInfo === "ts") {
|
||||
language = "ts";
|
||||
}
|
||||
code = codeText;
|
||||
}
|
||||
|
||||
if (child.type?.startsWith("ATXHeading")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (code) {
|
||||
let compiled = await invokeFunction(
|
||||
"server",
|
||||
"compileJS",
|
||||
`file.${language}`,
|
||||
code
|
||||
);
|
||||
manifest.functions[functionName] = meta;
|
||||
manifest.functions[functionName].code = compiled;
|
||||
}
|
||||
}
|
||||
return manifest;
|
||||
}
|
||||
|
||||
export async function compileJS(
|
||||
filename: string,
|
||||
code: string
|
||||
): Promise<string> {
|
||||
return self.syscall("esbuild.compile", filename, code);
|
||||
}
|
||||
|
||||
async function listPlugs(): Promise<string[]> {
|
||||
let unfilteredPages = await listPages(true);
|
||||
return unfilteredPages
|
||||
.filter((p) => p.name.startsWith("_plug/"))
|
||||
.map((p) => p.name.substring("_plug/".length));
|
||||
}
|
||||
|
||||
export async function listCommand() {
|
||||
console.log(await listPlugs());
|
||||
}
|
||||
|
||||
export async function updatePlugsCommand() {
|
||||
await invokeFunction("server", "updatePlugs");
|
||||
await reloadPlugs();
|
||||
}
|
||||
|
||||
export async function updatePlugs() {
|
||||
let { text: plugPageText } = await readPage("PLUGS");
|
||||
|
||||
let tree = await parseMarkdown(plugPageText);
|
||||
|
||||
let codeTextNode = findNodeOfType(tree, "CodeText");
|
||||
if (!codeTextNode) {
|
||||
console.error("Could not find yaml block in PLUGS");
|
||||
return;
|
||||
}
|
||||
let plugYaml = codeTextNode.children![0].text;
|
||||
let plugList = YAML.parse(plugYaml!);
|
||||
console.log("Plug YAML", plugList);
|
||||
let allPlugNames: string[] = [];
|
||||
for (let plugUri of plugList) {
|
||||
let [protocol, ...rest] = plugUri.split(":");
|
||||
let manifests = await dispatch(`get-plug:${protocol}`, rest.join(":"));
|
||||
if (manifests.length === 0) {
|
||||
console.error("Could not resolve plug", plugUri);
|
||||
}
|
||||
// console.log("Got manifests", plugUri, protocol, manifests);
|
||||
let manifest = manifests[0];
|
||||
allPlugNames.push(manifest.name);
|
||||
// console.log("Writing", `_plug/${manifest.name}`);
|
||||
await writePage(
|
||||
`_plug/${manifest.name}`,
|
||||
JSON.stringify(manifest, null, 2)
|
||||
);
|
||||
}
|
||||
|
||||
// And delete extra ones
|
||||
for (let existingPlug of await listPlugs()) {
|
||||
if (!allPlugNames.includes(existingPlug)) {
|
||||
console.log("Removing plug", existingPlug);
|
||||
await deletePage(`_plug/${existingPlug}`);
|
||||
}
|
||||
}
|
||||
// Important not to await!
|
||||
reloadPlugs();
|
||||
}
|
||||
|
||||
export async function getPlugPlugMd(pageName: string): Promise<Manifest> {
|
||||
let { text } = await readPage(pageName);
|
||||
return compileDefinition(text);
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
name: query
|
||||
functions:
|
||||
updateMaterializedQueriesOnPage:
|
||||
path: ./materialized_queries.ts:updateMaterializedQueriesOnPage
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
name: tasks
|
||||
syntax:
|
||||
DeadlineDate:
|
||||
firstCharacters:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import express, { Express } from "express";
|
||||
import { SilverBulletHooks } from "@silverbulletmd/common/manifest";
|
||||
import { Manifest, SilverBulletHooks } from "@silverbulletmd/common/manifest";
|
||||
import { EndpointHook } from "@plugos/plugos/hooks/endpoint";
|
||||
import { readFile } from "fs/promises";
|
||||
import { System } from "@plugos/plugos/system";
|
||||
|
@ -24,36 +24,40 @@ import buildMarkdown from "@silverbulletmd/web/parser";
|
|||
import { loadMarkdownExtensions } from "@silverbulletmd/web/markdown_ext";
|
||||
import http, { Server } from "http";
|
||||
import { esbuildSyscalls } from "@plugos/plugos/syscalls/esbuild";
|
||||
import { systemSyscalls } from "./syscalls/system";
|
||||
|
||||
export class ExpressServer {
|
||||
app: Express;
|
||||
system: System<SilverBulletHooks>;
|
||||
private rootPath: string;
|
||||
private space: Space;
|
||||
private distDir: string;
|
||||
private eventHook: EventHook;
|
||||
private db: Knex<any, unknown[]>;
|
||||
private port: number;
|
||||
private server?: Server;
|
||||
builtinPlugDir: string;
|
||||
preloadedModules: string[];
|
||||
|
||||
constructor(
|
||||
port: number,
|
||||
rootPath: string,
|
||||
pagesPath: string,
|
||||
distDir: string,
|
||||
builtinPlugDir: string,
|
||||
preloadedModules: string[]
|
||||
) {
|
||||
this.port = port;
|
||||
this.app = express();
|
||||
this.rootPath = rootPath;
|
||||
this.builtinPlugDir = builtinPlugDir;
|
||||
this.distDir = distDir;
|
||||
this.system = new System<SilverBulletHooks>("server");
|
||||
this.preloadedModules = preloadedModules;
|
||||
|
||||
// Setup system
|
||||
this.eventHook = new EventHook();
|
||||
this.system.addHook(this.eventHook);
|
||||
this.space = new Space(
|
||||
new EventedSpacePrimitives(
|
||||
new DiskSpacePrimitives(rootPath),
|
||||
new DiskSpacePrimitives(pagesPath),
|
||||
this.eventHook
|
||||
),
|
||||
true
|
||||
|
@ -61,12 +65,12 @@ export class ExpressServer {
|
|||
this.db = knex({
|
||||
client: "better-sqlite3",
|
||||
connection: {
|
||||
filename: path.join(rootPath, "data.db"),
|
||||
filename: path.join(pagesPath, "data.db"),
|
||||
},
|
||||
useNullAsDefault: true,
|
||||
});
|
||||
|
||||
this.system.registerSyscalls(["shell"], shellSyscalls(rootPath));
|
||||
this.system.registerSyscalls(["shell"], shellSyscalls(pagesPath));
|
||||
this.system.addHook(new NodeCronHook());
|
||||
|
||||
this.system.registerSyscalls([], pageIndexSyscalls(this.db));
|
||||
|
@ -74,6 +78,7 @@ export class ExpressServer {
|
|||
this.system.registerSyscalls([], eventSyscalls(this.eventHook));
|
||||
this.system.registerSyscalls([], markdownSyscalls(buildMarkdown([])));
|
||||
this.system.registerSyscalls([], esbuildSyscalls());
|
||||
this.system.registerSyscalls([], systemSyscalls(this));
|
||||
this.system.registerSyscalls([], jwtSyscalls());
|
||||
this.system.addHook(new EndpointHook(this.app, "/_/"));
|
||||
|
||||
|
@ -81,29 +86,26 @@ export class ExpressServer {
|
|||
this.rebuildMdExtensions();
|
||||
}, 100);
|
||||
|
||||
this.space.on({
|
||||
plugLoaded: (plugName, plug) => {
|
||||
safeRun(async () => {
|
||||
console.log("Plug load", plugName);
|
||||
await this.system.load(plugName, plug, (p) =>
|
||||
createSandbox(p, preloadedModules)
|
||||
this.eventHook.addLocalListener(
|
||||
"get-plug:builtin",
|
||||
async (plugName: string): Promise<Manifest> => {
|
||||
// console.log("Ok, resovling a plugin", plugName);
|
||||
try {
|
||||
let manifestJson = await readFile(
|
||||
path.join(this.builtinPlugDir, `${plugName}.plug.json`),
|
||||
"utf8"
|
||||
);
|
||||
});
|
||||
throttledRebuildMdExtensions();
|
||||
},
|
||||
plugUnloaded: (plugName) => {
|
||||
safeRun(async () => {
|
||||
console.log("Plug unload", plugName);
|
||||
await this.system.unload(plugName);
|
||||
});
|
||||
throttledRebuildMdExtensions();
|
||||
},
|
||||
});
|
||||
return JSON.parse(manifestJson);
|
||||
} catch (e) {
|
||||
throw new Error(`No such builtin: ${plugName}`);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
setInterval(() => {
|
||||
this.space.updatePageListAsync();
|
||||
this.space.updatePageList().catch(console.error);
|
||||
}, 5000);
|
||||
this.space.updatePageListAsync();
|
||||
this.reloadPlugs().catch(console.error);
|
||||
}
|
||||
|
||||
rebuildMdExtensions() {
|
||||
|
@ -113,6 +115,19 @@ export class ExpressServer {
|
|||
);
|
||||
}
|
||||
|
||||
async reloadPlugs() {
|
||||
await this.space.updatePageList();
|
||||
await this.system.unloadAll();
|
||||
console.log("Reloading plugs");
|
||||
for (let pageInfo of this.space.listPlugs()) {
|
||||
let { text } = await this.space.readPage(pageInfo.name);
|
||||
await this.system.load(JSON.parse(text), (p) =>
|
||||
createSandbox(p, this.preloadedModules)
|
||||
);
|
||||
}
|
||||
this.rebuildMdExtensions();
|
||||
}
|
||||
|
||||
async start() {
|
||||
await ensurePageIndexTable(this.db);
|
||||
console.log("Setting up router");
|
||||
|
|
|
@ -28,11 +28,16 @@ const webappDistDir = realpathSync(
|
|||
`${nodeModulesDir}/node_modules/@silverbulletmd/web/dist`
|
||||
);
|
||||
console.log("Webapp dist dir", webappDistDir);
|
||||
const plugDistDir = realpathSync(
|
||||
`${nodeModulesDir}/node_modules/@silverbulletmd/plugs/dist`
|
||||
);
|
||||
console.log("Builtin plug dist dir", plugDistDir);
|
||||
|
||||
const expressServer = new ExpressServer(
|
||||
port,
|
||||
pagesPath,
|
||||
webappDistDir,
|
||||
plugDistDir,
|
||||
preloadModules
|
||||
);
|
||||
expressServer.start().catch((e) => {
|
||||
|
|
|
@ -4,8 +4,8 @@ import { Space } from "@silverbulletmd/common/spaces/space";
|
|||
|
||||
export default (space: Space): SysCallMapping => {
|
||||
return {
|
||||
"space.listPages": async (ctx): Promise<PageMeta[]> => {
|
||||
return [...space.listPages()];
|
||||
"space.listPages": async (ctx, unfiltered = false): Promise<PageMeta[]> => {
|
||||
return [...space.listPages(unfiltered)];
|
||||
},
|
||||
"space.readPage": async (
|
||||
ctx,
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import { SysCallMapping } from "@plugos/plugos/system";
|
||||
import type { ExpressServer } from "../api_server";
|
||||
|
||||
export function systemSyscalls(expressServer: ExpressServer): SysCallMapping {
|
||||
return {
|
||||
"system.invokeFunction": async (
|
||||
ctx,
|
||||
env: string,
|
||||
name: string,
|
||||
...args: any[]
|
||||
) => {
|
||||
if (!ctx.plug) {
|
||||
throw Error("No plug associated with context");
|
||||
}
|
||||
return ctx.plug.invoke(name, args);
|
||||
},
|
||||
"system.reloadPlugs": async () => {
|
||||
return expressServer.reloadPlugs();
|
||||
},
|
||||
};
|
||||
}
|
|
@ -10,7 +10,7 @@ export function StatusBar({ editorView }: { editorView?: EditorView }) {
|
|||
readingTime = util.readingTime(wordCount);
|
||||
}
|
||||
return (
|
||||
<div id="bottom">
|
||||
<div id="status-bar">
|
||||
<div className="inner">
|
||||
{wordCount} words | {readingTime} min
|
||||
</div>
|
||||
|
|
|
@ -58,13 +58,10 @@ import { FilterOption } from "@silverbulletmd/common/types";
|
|||
import { syntaxTree } from "@codemirror/language";
|
||||
|
||||
class PageState {
|
||||
scrollTop: number;
|
||||
selection: EditorSelection;
|
||||
|
||||
constructor(scrollTop: number, selection: EditorSelection) {
|
||||
this.scrollTop = scrollTop;
|
||||
this.selection = selection;
|
||||
}
|
||||
constructor(
|
||||
readonly scrollTop: number,
|
||||
readonly selection: EditorSelection
|
||||
) {}
|
||||
}
|
||||
|
||||
const saveInterval = 1000;
|
||||
|
@ -123,7 +120,7 @@ export class Editor {
|
|||
this.system.registerSyscalls([], editorSyscalls(this));
|
||||
this.system.registerSyscalls([], spaceSyscalls(this));
|
||||
this.system.registerSyscalls([], indexerSyscalls(this.space));
|
||||
this.system.registerSyscalls([], systemSyscalls(this.space));
|
||||
this.system.registerSyscalls([], systemSyscalls(this));
|
||||
this.system.registerSyscalls(
|
||||
[],
|
||||
markdownSyscalls(buildMarkdown(this.mdExtensions))
|
||||
|
@ -153,10 +150,6 @@ export class Editor {
|
|||
}
|
||||
});
|
||||
|
||||
let throttledRebuildEditorState = throttle(() => {
|
||||
this.rebuildEditorState();
|
||||
}, 100);
|
||||
|
||||
this.space.on({
|
||||
pageChanged: (meta) => {
|
||||
if (this.currentPage === meta.name) {
|
||||
|
@ -171,22 +164,10 @@ export class Editor {
|
|||
pages: pages,
|
||||
});
|
||||
},
|
||||
plugLoaded: (plugName, plug) => {
|
||||
safeRun(async () => {
|
||||
console.log("Plug load", plugName);
|
||||
await this.system.load(plugName, plug, createIFrameSandbox);
|
||||
throttledRebuildEditorState();
|
||||
});
|
||||
},
|
||||
plugUnloaded: (plugName) => {
|
||||
safeRun(async () => {
|
||||
console.log("Plug unload", plugName);
|
||||
await this.system.unload(plugName);
|
||||
throttledRebuildEditorState();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
await this.reloadPlugs();
|
||||
|
||||
if (this.pageNavigator.getCurrentPage() === "") {
|
||||
await this.pageNavigator.navigate("start");
|
||||
}
|
||||
|
@ -359,7 +340,7 @@ export class Editor {
|
|||
mac: "Cmd-k",
|
||||
run: (): boolean => {
|
||||
this.viewDispatch({ type: "start-navigate" });
|
||||
this.space.updatePageListAsync();
|
||||
this.space.updatePageList();
|
||||
return true;
|
||||
},
|
||||
},
|
||||
|
@ -427,6 +408,17 @@ export class Editor {
|
|||
});
|
||||
}
|
||||
|
||||
async reloadPlugs() {
|
||||
await this.space.updatePageList();
|
||||
await this.system.unloadAll();
|
||||
console.log("(Re)loading plugs");
|
||||
for (let pageInfo of this.space.listPlugs()) {
|
||||
let { text } = await this.space.readPage(pageInfo.name);
|
||||
await this.system.load(JSON.parse(text), createIFrameSandbox);
|
||||
}
|
||||
this.rebuildEditorState();
|
||||
}
|
||||
|
||||
rebuildEditorState() {
|
||||
const editorView = this.editorView;
|
||||
if (editorView && this.currentPage) {
|
||||
|
@ -438,13 +430,16 @@ export class Editor {
|
|||
markdownSyscalls(buildMarkdown(this.mdExtensions))
|
||||
);
|
||||
|
||||
this.saveState();
|
||||
|
||||
editorView.setState(
|
||||
this.createEditorState(this.currentPage, editorView.state.sliceDoc())
|
||||
);
|
||||
if (editorView.contentDOM) {
|
||||
editorView.contentDOM.spellcheck = true;
|
||||
}
|
||||
editorView.focus();
|
||||
|
||||
this.restoreState(this.currentPage);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -489,12 +484,7 @@ export class Editor {
|
|||
|
||||
// Persist current page state and nicely close page
|
||||
if (this.currentPage) {
|
||||
let pageState = this.openPages.get(this.currentPage);
|
||||
if (pageState) {
|
||||
pageState.selection = this.editorView!.state.selection;
|
||||
pageState.scrollTop = this.editorView!.scrollDOM.scrollTop;
|
||||
// console.log("Saved pageState", this.currentPage, pageState);
|
||||
}
|
||||
this.saveState();
|
||||
this.space.unwatchPage(this.currentPage);
|
||||
await this.save(true);
|
||||
}
|
||||
|
@ -513,26 +503,11 @@ export class Editor {
|
|||
}
|
||||
|
||||
let editorState = this.createEditorState(pageName, doc.text);
|
||||
let pageState = this.openPages.get(pageName);
|
||||
editorView.setState(editorState);
|
||||
if (editorView.contentDOM) {
|
||||
editorView.contentDOM.spellcheck = true;
|
||||
}
|
||||
if (!pageState) {
|
||||
pageState = new PageState(0, editorState.selection);
|
||||
this.openPages.set(pageName, pageState!);
|
||||
editorView.dispatch({
|
||||
selection: { anchor: 0 },
|
||||
});
|
||||
} else {
|
||||
// Restore state
|
||||
// console.log("Restoring selection state", pageState);
|
||||
editorView.dispatch({
|
||||
selection: pageState.selection,
|
||||
});
|
||||
editorView.scrollDOM.scrollTop = pageState!.scrollTop;
|
||||
}
|
||||
|
||||
this.restoreState(pageName);
|
||||
this.space.watchPage(pageName);
|
||||
|
||||
this.viewDispatch({
|
||||
|
@ -543,6 +518,30 @@ export class Editor {
|
|||
await this.eventHook.dispatchEvent("editor:pageSwitched");
|
||||
}
|
||||
|
||||
private restoreState(pageName: string) {
|
||||
let pageState = this.openPages.get(pageName);
|
||||
const editorView = this.editorView!;
|
||||
if (pageState) {
|
||||
// Restore state
|
||||
// console.log("Restoring selection state", pageState);
|
||||
editorView.dispatch({
|
||||
selection: pageState.selection,
|
||||
});
|
||||
editorView.scrollDOM.scrollTop = pageState!.scrollTop;
|
||||
}
|
||||
editorView.focus();
|
||||
}
|
||||
|
||||
private saveState() {
|
||||
this.openPages.set(
|
||||
this.currentPage!,
|
||||
new PageState(
|
||||
this.editorView!.scrollDOM.scrollTop,
|
||||
this.editorView!.state.selection
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
ViewComponent(): React.ReactElement {
|
||||
const [viewState, dispatch] = useReducer(reducer, initialViewState);
|
||||
this.viewState = viewState;
|
||||
|
@ -625,6 +624,11 @@ export class Editor {
|
|||
<Panel html={viewState.rhsHTML} flex={viewState.showRHS} />
|
||||
)}
|
||||
</div>
|
||||
{!!viewState.showBHS && (
|
||||
<div id="bhs">
|
||||
<Panel html={viewState.bhsHTML} flex={1} />
|
||||
</div>
|
||||
)}
|
||||
<StatusBar editorView={editor.editorView} />
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -102,6 +102,18 @@ export default function reducer(
|
|||
showLHS: 0,
|
||||
lhsHTML: "",
|
||||
};
|
||||
case "show-bhs":
|
||||
return {
|
||||
...state,
|
||||
showBHS: action.flex,
|
||||
bhsHTML: action.html,
|
||||
};
|
||||
case "hide-bhs":
|
||||
return {
|
||||
...state,
|
||||
showBHS: 0,
|
||||
bhsHTML: "",
|
||||
};
|
||||
case "show-filterbox":
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -110,13 +110,26 @@ body {
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
#bottom {
|
||||
#bhs {
|
||||
height: 200px;
|
||||
width: 100%;
|
||||
|
||||
.panel {
|
||||
iframe {
|
||||
border: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#status-bar {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
padding: 0 10px;
|
||||
text-align: right;
|
||||
background-color: rgb(213, 213, 213);
|
||||
border-top: rgb(193, 193, 193) 1px solid;
|
||||
.inner {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,6 +92,18 @@ export function editorSyscalls(editor: Editor): SysCallMapping {
|
|||
type: "hide-lhs",
|
||||
});
|
||||
},
|
||||
"editor.showBhs": (ctx, html: string, flex: number) => {
|
||||
editor.viewDispatch({
|
||||
type: "show-bhs",
|
||||
flex,
|
||||
html,
|
||||
});
|
||||
},
|
||||
"editor.hideBhs": (ctx) => {
|
||||
editor.viewDispatch({
|
||||
type: "hide-bhs",
|
||||
});
|
||||
},
|
||||
"editor.insertAtPos": (ctx, text: string, pos: number) => {
|
||||
editor.editorView!.dispatch({
|
||||
changes: {
|
||||
|
|
|
@ -4,8 +4,8 @@ import { PageMeta } from "@silverbulletmd/common/types";
|
|||
|
||||
export function spaceSyscalls(editor: Editor): SysCallMapping {
|
||||
return {
|
||||
"space.listPages": async (): Promise<PageMeta[]> => {
|
||||
return [...(await editor.space.listPages())];
|
||||
"space.listPages": async (ctx, unfiltered = false): Promise<PageMeta[]> => {
|
||||
return [...(await editor.space.listPages(unfiltered))];
|
||||
},
|
||||
"space.readPage": async (
|
||||
ctx,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { SysCallMapping } from "@plugos/plugos/system";
|
||||
import { Space } from "@silverbulletmd/common/spaces/space";
|
||||
import type { Editor } from "../editor";
|
||||
|
||||
export function systemSyscalls(space: Space): SysCallMapping {
|
||||
export function systemSyscalls(editor: Editor): SysCallMapping {
|
||||
return {
|
||||
"system.invokeFunction": async (
|
||||
ctx,
|
||||
|
@ -17,7 +17,10 @@ export function systemSyscalls(space: Space): SysCallMapping {
|
|||
return ctx.plug.invoke(name, args);
|
||||
}
|
||||
|
||||
return space.invokeFunction(ctx.plug, env, name, args);
|
||||
return editor.space.invokeFunction(ctx.plug, env, name, args);
|
||||
},
|
||||
"system.reloadPlugs": async () => {
|
||||
return editor.reloadPlugs();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -16,8 +16,10 @@ export type AppViewState = {
|
|||
unsavedChanges: boolean;
|
||||
showLHS: number; // 0 = hide, > 0 = flex
|
||||
showRHS: number; // 0 = hide, > 0 = flex
|
||||
showBHS: number;
|
||||
rhsHTML: string;
|
||||
lhsHTML: string;
|
||||
bhsHTML: string;
|
||||
allPages: Set<PageMeta>;
|
||||
commands: Map<string, AppCommand>;
|
||||
notifications: Notification[];
|
||||
|
@ -36,8 +38,10 @@ export const initialViewState: AppViewState = {
|
|||
unsavedChanges: false,
|
||||
showLHS: 0,
|
||||
showRHS: 0,
|
||||
showBHS: 0,
|
||||
rhsHTML: "",
|
||||
lhsHTML: "",
|
||||
bhsHTML: "",
|
||||
allPages: new Set(),
|
||||
commands: new Map(),
|
||||
notifications: [],
|
||||
|
@ -65,6 +69,8 @@ export type Action =
|
|||
| { type: "hide-rhs" }
|
||||
| { type: "show-lhs"; html: string; flex: number }
|
||||
| { type: "hide-lhs" }
|
||||
| { type: "show-bhs"; html: string; flex: number }
|
||||
| { type: "hide-bhs" }
|
||||
| {
|
||||
type: "show-filterbox";
|
||||
options: FilterOption[];
|
||||
|
|
Loading…
Reference in New Issue