diff --git a/plugins/core/core.plugin.json b/plugins/core/core.plugin.json
index 68bdbfb3..9be62ec0 100644
--- a/plugins/core/core.plugin.json
+++ b/plugins/core/core.plugin.json
@@ -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",
diff --git a/server/server.ts b/server/server.ts
index 95e20f63..a1d24ac6 100644
--- a/server/server.ts
+++ b/server/server.ts
@@ -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";
});
diff --git a/webapp/src/boot.ts b/webapp/src/boot.ts
new file mode 100644
index 00000000..c57217db
--- /dev/null
+++ b/webapp/src/boot.ts
@@ -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;
diff --git a/webapp/src/components/command_palette.tsx b/webapp/src/components/command_palette.tsx
index 33cd7975..f71a418d 100644
--- a/webapp/src/components/command_palette.tsx
+++ b/webapp/src/components/command_palette.tsx
@@ -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 (
diff --git a/webapp/src/components/filter.tsx b/webapp/src/components/filter.tsx
index a1a40c0a..3f159f9f 100644
--- a/webapp/src/components/filter.tsx
+++ b/webapp/src/components/filter.tsx
@@ -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));
diff --git a/webapp/src/components/navigation_bar.tsx b/webapp/src/components/navigation_bar.tsx
index e9252f66..dc94d22c 100644
--- a/webapp/src/components/navigation_bar.tsx
+++ b/webapp/src/components/navigation_bar.tsx
@@ -1,14 +1,16 @@
+import { NuggetMeta } from "../types";
+
export function NavigationBar({
currentNugget,
onClick,
}: {
- currentNugget?: string;
+ currentNugget?: NuggetMeta;
onClick: () => void;
}) {
return (
- » {currentNugget}
+ » {currentNugget?.name}
);
diff --git a/webapp/src/components/nugget_navigator.tsx b/webapp/src/components/nugget_navigator.tsx
index c937221f..14dcac04 100644
--- a/webapp/src/components/nugget_navigator.tsx
+++ b/webapp/src/components/nugget_navigator.tsx
@@ -11,7 +11,11 @@ export function NuggetNavigator({
return (
({
+ ...meta,
+ // Order by last modified date in descending order
+ orderId: -meta.lastModified.getTime(),
+ }))}
allowNew={true}
newHint="Create nugget"
onSelect={(opt) => {
diff --git a/webapp/src/editor.tsx b/webapp/src/editor.tsx
index e7e4f38f..6dde192a 100644
--- a/webapp/src/editor.tsx
+++ b/webapp/src/editor.tsx
@@ -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();
- 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 => {
- 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(, 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;
diff --git a/webapp/src/fs.ts b/webapp/src/fs.ts
index 56f31767..e8aa20e0 100644
--- a/webapp/src/fs.ts
+++ b/webapp/src/fs.ts
@@ -2,9 +2,9 @@ import { NuggetMeta } from "./types";
export interface FileSystem {
listNuggets(): Promise;
- readNugget(name: string): Promise;
- // @return whether a new nugget was created for this
- writeNugget(name: string, text: string): Promise;
+ readNugget(name: string): Promise<{ text: string; meta: NuggetMeta }>;
+ writeNugget(name: string, text: string): Promise;
+ getMeta(name: string): Promise;
}
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 {
+ 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 {
+ async writeNugget(name: string, text: string): Promise {
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 {
+ let req = await fetch(`${this.url}/${name}`, {
+ method: "OPTIONS",
+ });
+ return {
+ name: name,
+ lastModified: new Date(+req.headers.get("Last-Modified")!),
+ };
}
}
diff --git a/webapp/src/index.html b/webapp/src/index.html
index 28f97b51..76cee25c 100644
--- a/webapp/src/index.html
+++ b/webapp/src/index.html
@@ -4,7 +4,7 @@
Nugget
-
+
diff --git a/webapp/src/plugin_sw.ts b/webapp/src/plugin_sw.ts
index 961e3474..6357ef23 100644
--- a/webapp/src/plugin_sw.ts
+++ b/webapp/src/plugin_sw.ts
@@ -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,
});
}
diff --git a/webapp/src/plugins/browser_system.ts b/webapp/src/plugins/browser_system.ts
index a144029a..295ac50f 100644
--- a/webapp/src/plugins/browser_system.ts
+++ b/webapp/src/plugins/browser_system.ts
@@ -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) {
diff --git a/webapp/src/plugins/runtime.ts b/webapp/src/plugins/runtime.ts
index 461e6bdb..5ff904e9 100644
--- a/webapp/src/plugins/runtime.ts
+++ b/webapp/src/plugins/runtime.ts
@@ -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;
}
-export class Cartridge {
+export class Plugin {
pathPrefix: string;
system: System;
private runningFunctions: Map;
@@ -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;
+ protected plugins: Map;
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();
+ this.plugins = new Map();
this.registeredSyscalls = {};
}
@@ -184,16 +184,16 @@ export class System {
return Promise.resolve(callback(ctx, ...args));
}
- async load(name: string, manifest: Manifest): Promise {
- 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 {
+ const plugin = new Plugin(this, this.pathPrefix, name);
+ await plugin.load(manifest);
+ this.plugins.set(name, plugin);
+ return plugin;
}
async stop(): Promise {
return Promise.all(
- Array.from(this.cartridges.values()).map((cartridge) => cartridge.stop())
+ Array.from(this.plugins.values()).map((plugin) => plugin.stop())
);
}
}
diff --git a/webapp/src/plugins/types.ts b/webapp/src/plugins/types.ts
index cfa5f41d..905e4875 100644
--- a/webapp/src/plugins/types.ts
+++ b/webapp/src/plugins/types.ts
@@ -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;
diff --git a/webapp/src/reducer.ts b/webapp/src/reducer.ts
index f562f4d9..02d44447 100644
--- a/webapp/src/reducer.ts
+++ b/webapp/src/reducer.ts
@@ -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":
diff --git a/webapp/src/styles.css b/webapp/src/styles.css
index d7cc1254..02c9a678 100644
--- a/webapp/src/styles.css
+++ b/webapp/src/styles.css
@@ -177,8 +177,8 @@ body {
border: #333 1px solid;
z-index: 1000;
position: absolute;
- left: 8px;
- top: 8px;
+ left: 25px;
+ top: 10px;
right: 10px;
}
diff --git a/webapp/src/syscalls/event.native.ts b/webapp/src/syscalls/event.native.ts
index 20a7b0c0..bc0ad18c 100644
--- a/webapp/src/syscalls/event.native.ts
+++ b/webapp/src/syscalls/event.native.ts
@@ -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);
},
};
diff --git a/webapp/src/syscalls/ui.browser.ts b/webapp/src/syscalls/ui.browser.ts
index caaf2e60..5448ee8d 100644
--- a/webapp/src/syscalls/ui.browser.ts
+++ b/webapp/src/syscalls/ui.browser.ts
@@ -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);
}
});
diff --git a/webapp/src/types.ts b/webapp/src/types.ts
index 3806299f..38c4b364 100644
--- a/webapp/src/types.ts
+++ b/webapp/src/types.ts
@@ -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" }
diff --git a/webapp/src/util.ts b/webapp/src/util.ts
index 16b5ca40..de823705 100644
--- a/webapp/src/util.ts
+++ b/webapp/src/util.ts
@@ -21,3 +21,7 @@ export function sleep(ms: number): Promise {
}, ms);
});
}
+
+export function isMacLike() {
+ return /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
+}
diff --git a/webapp/src/watcher.ts b/webapp/src/watcher.ts
new file mode 100644
index 00000000..35b8f24f
--- /dev/null
+++ b/webapp/src/watcher.ts
@@ -0,0 +1,2 @@
+import { Editor } from "./editor";
+import { safeRun } from "./util";