File watching and various tweaks
parent
0527430626
commit
2986c2c231
|
@ -13,7 +13,8 @@
|
|||
"requiredContext": {}
|
||||
},
|
||||
"Insert Current Date": {
|
||||
"invoke": "insert_nice_date"
|
||||
"invoke": "insert_nice_date",
|
||||
"slashCommand": "/insert-today"
|
||||
},
|
||||
"Toggle : Heading 1": {
|
||||
"invoke": "toggle_h1",
|
||||
|
|
|
@ -6,24 +6,31 @@ import { oakCors } from "https://deno.land/x/cors@v1.2.0/mod.ts";
|
|||
import { readAll } from "https://deno.land/std@0.126.0/streams/mod.ts";
|
||||
import { exists } from "https://deno.land/std@0.126.0/fs/mod.ts";
|
||||
|
||||
type NuggetMeta = {
|
||||
name: string;
|
||||
lastModified: number;
|
||||
};
|
||||
|
||||
const fsPrefix = "/fs";
|
||||
const nuggetsPath = "../nuggets";
|
||||
|
||||
const fsRouter = new Router();
|
||||
|
||||
fsRouter.use(oakCors());
|
||||
fsRouter.use(oakCors({ methods: ["OPTIONS", "GET", "PUT", "POST"] }));
|
||||
|
||||
fsRouter.get("/", async (context) => {
|
||||
const localPath = nuggetsPath;
|
||||
let fileNames: string[] = [];
|
||||
let fileNames: NuggetMeta[] = [];
|
||||
for await (const dirEntry of Deno.readDir(localPath)) {
|
||||
if (dirEntry.isFile) {
|
||||
fileNames.push(
|
||||
dirEntry.name.substring(
|
||||
const stat = await Deno.stat(`${localPath}/${dirEntry.name}`);
|
||||
fileNames.push({
|
||||
name: dirEntry.name.substring(
|
||||
0,
|
||||
dirEntry.name.length - path.extname(dirEntry.name).length
|
||||
)
|
||||
);
|
||||
),
|
||||
lastModified: stat.mtime?.getTime()!,
|
||||
});
|
||||
}
|
||||
}
|
||||
context.response.body = JSON.stringify(fileNames);
|
||||
|
@ -33,7 +40,9 @@ fsRouter.get("/:nugget", async (context) => {
|
|||
const nuggetName = context.params.nugget;
|
||||
const localPath = `${nuggetsPath}/${nuggetName}.md`;
|
||||
try {
|
||||
const stat = await Deno.stat(localPath);
|
||||
const text = await Deno.readTextFile(localPath);
|
||||
context.response.headers.set("Last-Modified", "" + stat.mtime?.getTime());
|
||||
context.response.body = text;
|
||||
} catch (e) {
|
||||
context.response.status = 404;
|
||||
|
@ -46,7 +55,9 @@ fsRouter.options("/:nugget", async (context) => {
|
|||
try {
|
||||
const stat = await Deno.stat(localPath);
|
||||
context.response.headers.set("Content-length", `${stat.size}`);
|
||||
context.response.headers.set("Last-Modified", "" + stat.mtime?.getTime());
|
||||
} catch (e) {
|
||||
// For CORS
|
||||
context.response.status = 200;
|
||||
context.response.body = "";
|
||||
}
|
||||
|
@ -69,8 +80,10 @@ fsRouter.put("/:nugget", async (context) => {
|
|||
const text = await readAll(result.value);
|
||||
file.write(text);
|
||||
file.close();
|
||||
const stat = await Deno.stat(localPath);
|
||||
console.log("Wrote to", localPath);
|
||||
context.response.status = existingNugget ? 200 : 201;
|
||||
context.response.headers.set("Last-Modified", "" + stat.mtime?.getTime());
|
||||
context.response.body = "OK";
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import { Editor } from "./editor";
|
||||
import { HttpFileSystem } from "./fs";
|
||||
import { safeRun } from "./util";
|
||||
|
||||
let editor = new Editor(
|
||||
new HttpFileSystem(`http://${location.hostname}:2222/fs`),
|
||||
document.getElementById("root")!
|
||||
);
|
||||
|
||||
safeRun(async () => {
|
||||
await editor.init();
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
window.editor = editor;
|
|
@ -1,4 +1,5 @@
|
|||
import { AppCommand } from "../types";
|
||||
import { isMacLike } from "../util";
|
||||
import { FilterList, Option } from "./filter";
|
||||
|
||||
export function CommandPalette({
|
||||
|
@ -9,8 +10,12 @@ export function CommandPalette({
|
|||
onTrigger: (command: AppCommand | undefined) => void;
|
||||
}) {
|
||||
let options: Option[] = [];
|
||||
const isMac = isMacLike();
|
||||
for (let [name, def] of commands.entries()) {
|
||||
options.push({ name: name });
|
||||
options.push({
|
||||
name: name,
|
||||
hint: isMac && def.command.mac ? def.command.mac : def.command.key,
|
||||
});
|
||||
}
|
||||
console.log("Commands", options);
|
||||
return (
|
||||
|
|
|
@ -2,26 +2,28 @@ import React, { useEffect, useRef, useState } from "react";
|
|||
|
||||
export interface Option {
|
||||
name: string;
|
||||
orderId?: number;
|
||||
hint?: string;
|
||||
}
|
||||
|
||||
function magicSorter(a: Option, b: Option): number {
|
||||
if (a.name.toLowerCase() < b.name.toLowerCase()) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
if (a.orderId && b.orderId) {
|
||||
return a.orderId < b.orderId ? -1 : 1;
|
||||
}
|
||||
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
|
||||
}
|
||||
|
||||
export function FilterList({
|
||||
placeholder,
|
||||
options,
|
||||
onSelect,
|
||||
onKeyPress,
|
||||
allowNew = false,
|
||||
newHint,
|
||||
}: {
|
||||
placeholder: string;
|
||||
options: Option[];
|
||||
onKeyPress?: (key: string, currentText: string) => void;
|
||||
onSelect: (option: Option | undefined) => void;
|
||||
allowNew?: boolean;
|
||||
newHint?: string;
|
||||
|
@ -75,7 +77,6 @@ export function FilterList({
|
|||
document.addEventListener("click", closer);
|
||||
|
||||
return () => {
|
||||
console.log("Unsubscribing");
|
||||
document.removeEventListener("click", closer);
|
||||
};
|
||||
}, []);
|
||||
|
@ -90,6 +91,9 @@ export function FilterList({
|
|||
onChange={filter}
|
||||
onKeyDown={(e: React.KeyboardEvent) => {
|
||||
console.log("Key up", e.key);
|
||||
if (onKeyPress) {
|
||||
onKeyPress(e.key, text);
|
||||
}
|
||||
switch (e.key) {
|
||||
case "ArrowUp":
|
||||
setSelectionOption(Math.max(0, selectedOption - 1));
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import { NuggetMeta } from "../types";
|
||||
|
||||
export function NavigationBar({
|
||||
currentNugget,
|
||||
onClick,
|
||||
}: {
|
||||
currentNugget?: string;
|
||||
currentNugget?: NuggetMeta;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div id="top">
|
||||
<div className="current-nugget" onClick={onClick}>
|
||||
» {currentNugget}
|
||||
» {currentNugget?.name}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -11,7 +11,11 @@ export function NuggetNavigator({
|
|||
return (
|
||||
<FilterList
|
||||
placeholder=""
|
||||
options={allNuggets}
|
||||
options={allNuggets.map((meta) => ({
|
||||
...meta,
|
||||
// Order by last modified date in descending order
|
||||
orderId: -meta.lastModified.getTime(),
|
||||
}))}
|
||||
allowNew={true}
|
||||
newHint="Create nugget"
|
||||
onSelect={(opt) => {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
autocompletion,
|
||||
Completion,
|
||||
CompletionContext,
|
||||
completionKeymap,
|
||||
CompletionResult,
|
||||
|
@ -11,12 +12,12 @@ import { indentOnInput, syntaxTree } from "@codemirror/language";
|
|||
import { bracketMatching } from "@codemirror/matchbrackets";
|
||||
import { searchKeymap } from "@codemirror/search";
|
||||
import { EditorState, StateField, Transaction } from "@codemirror/state";
|
||||
import { KeyBinding } from "@codemirror/view";
|
||||
import {
|
||||
drawSelection,
|
||||
dropCursor,
|
||||
EditorView,
|
||||
highlightSpecialChars,
|
||||
KeyBinding,
|
||||
keymap,
|
||||
} from "@codemirror/view";
|
||||
import React, { useEffect, useReducer } from "react";
|
||||
|
@ -28,12 +29,12 @@ import { CommandPalette } from "./components/command_palette";
|
|||
import { NavigationBar } from "./components/navigation_bar";
|
||||
import { NuggetNavigator } from "./components/nugget_navigator";
|
||||
import { StatusBar } from "./components/status_bar";
|
||||
import { FileSystem, HttpFileSystem } from "./fs";
|
||||
import { FileSystem } from "./fs";
|
||||
import { lineWrapper } from "./lineWrapper";
|
||||
import { markdown } from "./markdown";
|
||||
import customMarkDown from "./parser";
|
||||
import { BrowserSystem } from "./plugins/browser_system";
|
||||
import { Manifest } from "./plugins/types";
|
||||
import { Manifest, slashCommandRegexp } from "./plugins/types";
|
||||
import reducer from "./reducer";
|
||||
import customMarkdownStyle from "./style";
|
||||
import dbSyscalls from "./syscalls/db.localstorage";
|
||||
|
@ -44,19 +45,24 @@ import {
|
|||
AppViewState,
|
||||
CommandContext,
|
||||
initialViewState,
|
||||
NuggetMeta,
|
||||
} from "./types";
|
||||
import { safeRun } from "./util";
|
||||
|
||||
class NuggetState {
|
||||
editorState: EditorState;
|
||||
scrollTop: number;
|
||||
meta: NuggetMeta;
|
||||
|
||||
constructor(editorState: EditorState, scrollTop: number) {
|
||||
constructor(editorState: EditorState, scrollTop: number, meta: NuggetMeta) {
|
||||
this.editorState = editorState;
|
||||
this.scrollTop = scrollTop;
|
||||
this.meta = meta;
|
||||
}
|
||||
}
|
||||
|
||||
const watchInterval = 5000;
|
||||
|
||||
export class Editor {
|
||||
editorView?: EditorView;
|
||||
viewState: AppViewState;
|
||||
|
@ -78,6 +84,7 @@ export class Editor {
|
|||
parent: document.getElementById("editor")!,
|
||||
});
|
||||
this.addListeners();
|
||||
this.watch();
|
||||
}
|
||||
|
||||
async init() {
|
||||
|
@ -93,15 +100,15 @@ export class Editor {
|
|||
|
||||
await system.bootServiceWorker();
|
||||
console.log("Now loading core plugin");
|
||||
let mainCartridge = await system.load("core", coreManifest as Manifest);
|
||||
let mainPlugin = await system.load("core", coreManifest as Manifest);
|
||||
this.editorCommands = new Map<string, AppCommand>();
|
||||
const cmds = mainCartridge.manifest!.commands;
|
||||
const cmds = mainPlugin.manifest!.commands;
|
||||
for (let name in cmds) {
|
||||
let cmd = cmds[name];
|
||||
this.editorCommands.set(name, {
|
||||
command: cmd,
|
||||
run: async (arg: CommandContext): Promise<any> => {
|
||||
return await mainCartridge.invoke(cmd.invoke, [arg]);
|
||||
return await mainPlugin.invoke(cmd.invoke, [arg]);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -111,7 +118,7 @@ export class Editor {
|
|||
});
|
||||
}
|
||||
|
||||
get currentNugget(): string | undefined {
|
||||
get currentNugget(): NuggetMeta | undefined {
|
||||
return this.viewState.currentNugget;
|
||||
}
|
||||
|
||||
|
@ -146,7 +153,10 @@ export class Editor {
|
|||
bracketMatching(),
|
||||
closeBrackets(),
|
||||
autocompletion({
|
||||
override: [this.nuggetCompleter.bind(this)],
|
||||
override: [
|
||||
this.nuggetCompleter.bind(this),
|
||||
this.commandCompleter.bind(this),
|
||||
],
|
||||
}),
|
||||
EditorView.lineWrapping,
|
||||
lineWrapper([
|
||||
|
@ -176,15 +186,10 @@ export class Editor {
|
|||
run: commands.insertMarker("_"),
|
||||
},
|
||||
{
|
||||
key: "Ctrl-s",
|
||||
mac: "Cmd-s",
|
||||
run: (target: EditorView): boolean => {
|
||||
Promise.resolve()
|
||||
.then(async () => {
|
||||
console.log("Saving");
|
||||
await this.save();
|
||||
})
|
||||
.catch((e) => console.error(e));
|
||||
key: "Ctrl-e",
|
||||
mac: "Cmd-e",
|
||||
run: (): boolean => {
|
||||
window.open(location.href, "_blank")!.focus();
|
||||
return true;
|
||||
},
|
||||
},
|
||||
|
@ -200,7 +205,9 @@ export class Editor {
|
|||
key: "Ctrl-.",
|
||||
mac: "Cmd-.",
|
||||
run: (target): boolean => {
|
||||
this.viewDispatch({ type: "show-palette" });
|
||||
this.viewDispatch({
|
||||
type: "show-palette",
|
||||
});
|
||||
return true;
|
||||
},
|
||||
},
|
||||
|
@ -220,12 +227,10 @@ export class Editor {
|
|||
}
|
||||
|
||||
nuggetCompleter(ctx: CompletionContext): CompletionResult | null {
|
||||
let prefix = ctx.matchBefore(/\[\[\w*/);
|
||||
let prefix = ctx.matchBefore(/\[\[[\w\s]*/);
|
||||
if (!prefix) {
|
||||
return null;
|
||||
}
|
||||
// TODO: Lots of optimization potential here
|
||||
// TODO: put something in the cm-completionIcon-nugget style
|
||||
return {
|
||||
from: prefix.from + 2,
|
||||
options: this.viewState.allNuggets.map((nuggetMeta) => ({
|
||||
|
@ -235,6 +240,39 @@ export class Editor {
|
|||
};
|
||||
}
|
||||
|
||||
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 () => {
|
||||
def.run(buildContext(def, this));
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
return {
|
||||
from: prefix.from + 1,
|
||||
options: options,
|
||||
};
|
||||
}
|
||||
|
||||
update(value: null, transaction: Transaction): null {
|
||||
if (transaction.docChanged) {
|
||||
this.viewDispatch({
|
||||
|
@ -276,22 +314,26 @@ export class Editor {
|
|||
return;
|
||||
}
|
||||
// Write to file system
|
||||
const created = await this.fs.writeNugget(
|
||||
this.currentNugget,
|
||||
let nuggetMeta = await this.fs.writeNugget(
|
||||
this.currentNugget.name,
|
||||
editorState.sliceDoc()
|
||||
);
|
||||
|
||||
// Update in open nugget cache
|
||||
this.openNuggets.set(
|
||||
this.currentNugget,
|
||||
new NuggetState(editorState, this.editorView!.scrollDOM.scrollTop)
|
||||
this.currentNugget.name,
|
||||
new NuggetState(
|
||||
editorState,
|
||||
this.editorView!.scrollDOM.scrollTop,
|
||||
nuggetMeta
|
||||
)
|
||||
);
|
||||
|
||||
// Dispatch update to view
|
||||
this.viewDispatch({ type: "nugget-saved" });
|
||||
this.viewDispatch({ type: "nugget-saved", meta: nuggetMeta });
|
||||
|
||||
// If a new nugget was created, let's refresh the nugget list
|
||||
if (created) {
|
||||
if (nuggetMeta.created) {
|
||||
await this.loadNuggetList();
|
||||
}
|
||||
}
|
||||
|
@ -304,6 +346,34 @@ export class Editor {
|
|||
});
|
||||
}
|
||||
|
||||
watch() {
|
||||
setInterval(() => {
|
||||
safeRun(async () => {
|
||||
if (!this.currentNugget) {
|
||||
return;
|
||||
}
|
||||
const currentNuggetName = this.currentNugget.name;
|
||||
let newNuggetMeta = await this.fs.getMeta(currentNuggetName);
|
||||
if (
|
||||
this.currentNugget.lastModified.getTime() <
|
||||
newNuggetMeta.lastModified.getTime()
|
||||
) {
|
||||
console.log("File changed on disk, reloading");
|
||||
let nuggetData = await this.fs.readNugget(currentNuggetName);
|
||||
this.openNuggets.set(
|
||||
newNuggetMeta.name,
|
||||
new NuggetState(
|
||||
this.createEditorState(nuggetData.text),
|
||||
0,
|
||||
newNuggetMeta
|
||||
)
|
||||
);
|
||||
await this.loadNugget(currentNuggetName);
|
||||
}
|
||||
});
|
||||
}, watchInterval);
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.editorView!.focus();
|
||||
}
|
||||
|
@ -323,25 +393,33 @@ export class Editor {
|
|||
return;
|
||||
}
|
||||
|
||||
let nuggetState = this.openNuggets.get(nuggetName);
|
||||
if (!nuggetState) {
|
||||
let text = await this.fs.readNugget(nuggetName);
|
||||
nuggetState = new NuggetState(this.createEditorState(text), 0);
|
||||
}
|
||||
this.openNuggets.set(nuggetName, nuggetState!);
|
||||
this.editorView!.setState(nuggetState!.editorState);
|
||||
this.editorView.scrollDOM.scrollTop = nuggetState!.scrollTop;
|
||||
|
||||
this.viewDispatch({
|
||||
type: "nugget-loaded",
|
||||
name: nuggetName,
|
||||
});
|
||||
await this.loadNugget(nuggetName);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
|
||||
async loadNugget(nuggetName: string) {
|
||||
let nuggetState = this.openNuggets.get(nuggetName);
|
||||
if (!nuggetState) {
|
||||
let nuggetData = await this.fs.readNugget(nuggetName);
|
||||
nuggetState = new NuggetState(
|
||||
this.createEditorState(nuggetData.text),
|
||||
0,
|
||||
nuggetData.meta
|
||||
);
|
||||
this.openNuggets.set(nuggetName, nuggetState!);
|
||||
}
|
||||
this.editorView!.setState(nuggetState!.editorState);
|
||||
this.editorView!.scrollDOM.scrollTop = nuggetState!.scrollTop;
|
||||
|
||||
this.viewDispatch({
|
||||
type: "nugget-loaded",
|
||||
meta: nuggetState.meta,
|
||||
});
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
this.$hashChange = this.hashChange.bind(this);
|
||||
window.addEventListener("hashchange", this.$hashChange);
|
||||
|
@ -380,7 +458,7 @@ export class Editor {
|
|||
|
||||
useEffect(() => {
|
||||
if (viewState.currentNugget) {
|
||||
document.title = viewState.currentNugget;
|
||||
document.title = viewState.currentNugget.name;
|
||||
}
|
||||
}, [viewState.currentNugget]);
|
||||
|
||||
|
@ -437,19 +515,3 @@ export class Editor {
|
|||
ReactDOM.render(<ViewComponent />, container);
|
||||
}
|
||||
}
|
||||
|
||||
let ed = new Editor(
|
||||
new HttpFileSystem("http://localhost:2222/fs"),
|
||||
document.getElementById("root")!
|
||||
);
|
||||
|
||||
ed.loadPlugins().catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
|
||||
safeRun(async () => {
|
||||
await ed.init();
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
window.editor = ed;
|
||||
|
|
|
@ -2,9 +2,9 @@ import { NuggetMeta } from "./types";
|
|||
|
||||
export interface FileSystem {
|
||||
listNuggets(): Promise<NuggetMeta[]>;
|
||||
readNugget(name: string): Promise<string>;
|
||||
// @return whether a new nugget was created for this
|
||||
writeNugget(name: string, text: string): Promise<boolean>;
|
||||
readNugget(name: string): Promise<{ text: string; meta: NuggetMeta }>;
|
||||
writeNugget(name: string, text: string): Promise<NuggetMeta>;
|
||||
getMeta(name: string): Promise<NuggetMeta>;
|
||||
}
|
||||
|
||||
export class HttpFileSystem implements FileSystem {
|
||||
|
@ -17,20 +17,43 @@ export class HttpFileSystem implements FileSystem {
|
|||
method: "GET",
|
||||
});
|
||||
|
||||
return (await req.json()).map((name: string) => ({ name }));
|
||||
return (await req.json()).map((meta: any) => ({
|
||||
name: meta.name,
|
||||
lastModified: new Date(meta.lastModified),
|
||||
}));
|
||||
}
|
||||
async readNugget(name: string): Promise<string> {
|
||||
async readNugget(name: string): Promise<{ text: string; meta: NuggetMeta }> {
|
||||
let req = await fetch(`${this.url}/${name}`, {
|
||||
method: "GET",
|
||||
});
|
||||
return await req.text();
|
||||
return {
|
||||
text: await req.text(),
|
||||
meta: {
|
||||
lastModified: new Date(+req.headers.get("Last-Modified")!),
|
||||
name: name,
|
||||
},
|
||||
};
|
||||
}
|
||||
async writeNugget(name: string, text: string): Promise<boolean> {
|
||||
async writeNugget(name: string, text: string): Promise<NuggetMeta> {
|
||||
let req = await fetch(`${this.url}/${name}`, {
|
||||
method: "PUT",
|
||||
body: text,
|
||||
});
|
||||
// 201 (Created) means a new nugget was created
|
||||
return req.status === 201;
|
||||
return {
|
||||
lastModified: new Date(+req.headers.get("Last-Modified")!),
|
||||
name: name,
|
||||
created: req.status === 201,
|
||||
};
|
||||
}
|
||||
|
||||
async getMeta(name: string): Promise<NuggetMeta> {
|
||||
let req = await fetch(`${this.url}/${name}`, {
|
||||
method: "OPTIONS",
|
||||
});
|
||||
return {
|
||||
name: name,
|
||||
lastModified: new Date(+req.headers.get("Last-Modified")!),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="utf-8" />
|
||||
<title>Nugget</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
<script type="module" src="editor.tsx"></script>
|
||||
<script type="module" src="boot.ts"></script>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
</head>
|
||||
|
|
|
@ -66,13 +66,13 @@ self.addEventListener("fetch", (event: any) => {
|
|||
return await handlePut(req, path);
|
||||
}
|
||||
|
||||
let [cartridgeName, resourceType, functionName] = path.split("/");
|
||||
let [pluginName, resourceType, functionName] = path.split("/");
|
||||
|
||||
let manifest = await getManifest(cartridgeName);
|
||||
let manifest = await getManifest(pluginName);
|
||||
|
||||
if (!manifest) {
|
||||
// console.log("Ain't got", cartridgeName);
|
||||
return new Response(`Cartridge not loaded: ${cartridgeName}`, {
|
||||
// console.log("Ain't got", pluginName);
|
||||
return new Response(`Plugin not loaded: ${pluginName}`, {
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { CartridgeLoader, System } from "./runtime";
|
||||
import { PluginLoader, System } from "./runtime";
|
||||
import { Manifest } from "./types";
|
||||
import { sleep } from "../util";
|
||||
|
||||
export class BrowserLoader implements CartridgeLoader {
|
||||
export class BrowserLoader implements PluginLoader {
|
||||
readonly pathPrefix: string;
|
||||
|
||||
constructor(pathPrefix: string) {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { Manifest } from "./types";
|
||||
|
||||
export class SyscallContext {
|
||||
public cartridge: Cartridge;
|
||||
public plugin: Plugin;
|
||||
|
||||
constructor(cartridge: Cartridge) {
|
||||
this.cartridge = cartridge;
|
||||
constructor(Plugin: Plugin) {
|
||||
this.plugin = Plugin;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,9 +19,9 @@ export class FunctionWorker {
|
|||
private initCallback: any;
|
||||
private invokeResolve?: (result?: any) => void;
|
||||
private invokeReject?: (reason?: any) => void;
|
||||
private cartridge: Cartridge;
|
||||
private plugin: Plugin;
|
||||
|
||||
constructor(cartridge: Cartridge, pathPrefix: string, name: string) {
|
||||
constructor(plugin: Plugin, pathPrefix: string, name: string) {
|
||||
// this.worker = new Worker(new URL("function_worker.ts", import.meta.url), {
|
||||
// type: "classic",
|
||||
// });
|
||||
|
@ -40,7 +40,7 @@ export class FunctionWorker {
|
|||
this.inited = new Promise((resolve) => {
|
||||
this.initCallback = resolve;
|
||||
});
|
||||
this.cartridge = cartridge;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
async onmessage(evt: MessageEvent) {
|
||||
|
@ -51,8 +51,8 @@ export class FunctionWorker {
|
|||
this.initCallback();
|
||||
break;
|
||||
case "syscall":
|
||||
const ctx = new SyscallContext(this.cartridge);
|
||||
let result = await this.cartridge.system.syscall(
|
||||
const ctx = new SyscallContext(this.plugin);
|
||||
let result = await this.plugin.system.syscall(
|
||||
ctx,
|
||||
data.name,
|
||||
data.args
|
||||
|
@ -92,11 +92,11 @@ export class FunctionWorker {
|
|||
}
|
||||
}
|
||||
|
||||
export interface CartridgeLoader {
|
||||
export interface PluginLoader {
|
||||
load(name: string, manifest: Manifest): Promise<void>;
|
||||
}
|
||||
|
||||
export class Cartridge {
|
||||
export class Plugin {
|
||||
pathPrefix: string;
|
||||
system: System;
|
||||
private runningFunctions: Map<string, FunctionWorker>;
|
||||
|
@ -112,7 +112,7 @@ export class Cartridge {
|
|||
|
||||
async load(manifest: Manifest) {
|
||||
this.manifest = manifest;
|
||||
await this.system.cartridgeLoader.load(this.name, manifest);
|
||||
await this.system.pluginLoader.load(this.name, manifest);
|
||||
await this.dispatchEvent("load");
|
||||
}
|
||||
|
||||
|
@ -149,15 +149,15 @@ export class Cartridge {
|
|||
}
|
||||
|
||||
export class System {
|
||||
protected cartridges: Map<string, Cartridge>;
|
||||
protected plugins: Map<string, Plugin>;
|
||||
protected pathPrefix: string;
|
||||
registeredSyscalls: SysCallMapping;
|
||||
cartridgeLoader: CartridgeLoader;
|
||||
pluginLoader: PluginLoader;
|
||||
|
||||
constructor(cartridgeLoader: CartridgeLoader, pathPrefix: string) {
|
||||
this.cartridgeLoader = cartridgeLoader;
|
||||
constructor(PluginLoader: PluginLoader, pathPrefix: string) {
|
||||
this.pluginLoader = PluginLoader;
|
||||
this.pathPrefix = pathPrefix;
|
||||
this.cartridges = new Map<string, Cartridge>();
|
||||
this.plugins = new Map<string, Plugin>();
|
||||
this.registeredSyscalls = {};
|
||||
}
|
||||
|
||||
|
@ -184,16 +184,16 @@ export class System {
|
|||
return Promise.resolve(callback(ctx, ...args));
|
||||
}
|
||||
|
||||
async load(name: string, manifest: Manifest): Promise<Cartridge> {
|
||||
const cartridge = new Cartridge(this, this.pathPrefix, name);
|
||||
await cartridge.load(manifest);
|
||||
this.cartridges.set(name, cartridge);
|
||||
return cartridge;
|
||||
async load(name: string, manifest: Manifest): Promise<Plugin> {
|
||||
const plugin = new Plugin(this, this.pathPrefix, name);
|
||||
await plugin.load(manifest);
|
||||
this.plugins.set(name, plugin);
|
||||
return plugin;
|
||||
}
|
||||
|
||||
async stop(): Promise<void[]> {
|
||||
return Promise.all(
|
||||
Array.from(this.cartridges.values()).map((cartridge) => cartridge.stop())
|
||||
Array.from(this.plugins.values()).map((plugin) => plugin.stop())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ export interface Manifest {
|
|||
};
|
||||
}
|
||||
|
||||
export const slashCommandRegexp = /\/[\w\-]*/;
|
||||
|
||||
export interface CommandDef {
|
||||
// Function name to invoke
|
||||
invoke: string;
|
||||
|
@ -15,6 +17,11 @@ export interface CommandDef {
|
|||
// Bind to keyboard shortcut
|
||||
key?: string;
|
||||
mac?: string;
|
||||
|
||||
// If to show in slash invoked menu and if so, with what label
|
||||
// should match slashCommandRegexp
|
||||
slashCommand?: string;
|
||||
|
||||
// Required context to be passed in as function arguments
|
||||
requiredContext?: {
|
||||
text?: boolean;
|
||||
|
|
|
@ -9,12 +9,13 @@ export default function reducer(
|
|||
case "nugget-loaded":
|
||||
return {
|
||||
...state,
|
||||
currentNugget: action.name,
|
||||
currentNugget: action.meta,
|
||||
isSaved: true,
|
||||
};
|
||||
case "nugget-saved":
|
||||
return {
|
||||
...state,
|
||||
currentNugget: action.meta,
|
||||
isSaved: true,
|
||||
};
|
||||
case "nugget-updated":
|
||||
|
|
|
@ -177,8 +177,8 @@ body {
|
|||
border: #333 1px solid;
|
||||
z-index: 1000;
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
top: 8px;
|
||||
left: 25px;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,6 @@ import { SyscallContext } from "../plugins/runtime";
|
|||
|
||||
export default {
|
||||
"event.publish": async (ctx: SyscallContext, name: string, data: any) => {
|
||||
await ctx.cartridge.dispatchEvent(name, data);
|
||||
await ctx.plugin.dispatchEvent(name, data);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ window.addEventListener("message", async (event) => {
|
|||
let data = messageEvent.data;
|
||||
if (data.type === "iframe_event") {
|
||||
// @ts-ignore
|
||||
window.mainCartridge.dispatchEvent(data.data.event, data.data.data);
|
||||
window.mainPlugin.dispatchEvent(data.data.event, data.data.data);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ import { CommandDef } from "./plugins/types";
|
|||
|
||||
export type NuggetMeta = {
|
||||
name: string;
|
||||
lastModified: Date;
|
||||
created?: boolean;
|
||||
};
|
||||
|
||||
export type CommandContext = {
|
||||
|
@ -14,7 +16,7 @@ export type AppCommand = {
|
|||
};
|
||||
|
||||
export type AppViewState = {
|
||||
currentNugget?: string;
|
||||
currentNugget?: NuggetMeta;
|
||||
isSaved: boolean;
|
||||
showNuggetNavigator: boolean;
|
||||
showCommandPalette: boolean;
|
||||
|
@ -31,8 +33,8 @@ export const initialViewState: AppViewState = {
|
|||
};
|
||||
|
||||
export type Action =
|
||||
| { type: "nugget-loaded"; name: string }
|
||||
| { type: "nugget-saved" }
|
||||
| { type: "nugget-loaded"; meta: NuggetMeta }
|
||||
| { type: "nugget-saved"; meta: NuggetMeta }
|
||||
| { type: "nugget-updated" }
|
||||
| { type: "nuggets-listed"; nuggets: NuggetMeta[] }
|
||||
| { type: "start-navigate" }
|
||||
|
|
|
@ -21,3 +21,7 @@ export function sleep(ms: number): Promise<void> {
|
|||
}, ms);
|
||||
});
|
||||
}
|
||||
|
||||
export function isMacLike() {
|
||||
return /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
import { Editor } from "./editor";
|
||||
import { safeRun } from "./util";
|
Loading…
Reference in New Issue