Support slashes in file names

pull/3/head
Zef Hemel 2022-02-27 10:17:43 +01:00
parent 158e1cb2ef
commit 89f93963f5
12 changed files with 142 additions and 71 deletions

View File

@ -4,7 +4,7 @@
"invoke": "word_count_command"
},
"Navigate To page": {
"invoke": "link_navigate",
"invoke": "linkNavigate",
"key": "Ctrl-Enter",
"mac": "Cmd-Enter"
},
@ -24,9 +24,13 @@
}
},
"events": {
"page:click": ["taskToggle", "clickNavigate"]
"page:click": ["taskToggle", "clickNavigate"],
"editor:complete": ["pageComplete"]
},
"functions": {
"pageComplete": {
"path": "./navigate.ts:pageComplete"
},
"linkNavigate": {
"path": "./navigate.ts:linkNavigate"
},

View File

@ -1,5 +0,0 @@
import {syscall} from "./syscall.ts";
export async function publish(event: string, data?: object) {
return await syscall("event.publish", event, data);
}

View File

@ -1,16 +1,16 @@
export function syscall(name: string, ...args: Array<any>): any {
let reqId = Math.floor(Math.random() * 1000000);
// console.log("Syscall", name, reqId);
return new Promise((resolve, reject) => {
self.dispatchEvent(
new CustomEvent("syscall", {
detail: {
id: reqId,
name: name,
args: args,
callback: resolve,
},
}),
);
});
export function syscall(name: string, ...args: any[]): any {
let reqId = Math.floor(Math.random() * 1000000);
// console.log("Syscall", name, reqId);
return new Promise((resolve, reject) => {
self.dispatchEvent(
new CustomEvent("syscall", {
detail: {
id: reqId,
name: name,
args: args,
callback: resolve,
},
})
);
});
}

View File

@ -19,3 +19,18 @@ export async function clickNavigate(event: ClickEvent) {
}
}
}
export async function pageComplete() {
let prefix = await syscall("editor.matchBefore", "\\[\\[[\\w\\s]*");
if (!prefix) {
return null;
}
let allPages = await syscall("space.listPages");
return {
from: prefix.from + 2,
options: allPages.map((pageMeta: any) => ({
label: pageMeta.name,
type: "page",
})),
};
}

View File

@ -6,6 +6,8 @@ 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";
import { recursiveReaddir } from "https://deno.land/x/recursive_readdir@v2.0.0/mod.ts";
type PageMeta = {
name: string;
lastModified: number;
@ -21,22 +23,23 @@ fsRouter.use(oakCors({ methods: ["OPTIONS", "GET", "PUT", "POST"] }));
fsRouter.get("/", async (context) => {
const localPath = pagesPath;
let fileNames: PageMeta[] = [];
for await (const dirEntry of Deno.readDir(localPath)) {
if (dirEntry.isFile) {
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()!,
});
}
const markdownFiles = (await recursiveReaddir(localPath)).filter(
(file: string) => path.extname(file) === ".md"
);
for (const p of markdownFiles) {
const stat = await Deno.stat(p);
fileNames.push({
name: p.substring(
localPath.length + 1,
p.length - path.extname(p).length
),
lastModified: stat.mtime?.getTime()!,
});
}
context.response.body = JSON.stringify(fileNames);
});
fsRouter.get("/:page", async (context) => {
fsRouter.get("/:page(.*)", async (context) => {
const pageName = context.params.page;
const localPath = `${pagesPath}/${pageName}.md`;
try {
@ -50,7 +53,7 @@ fsRouter.get("/:page", async (context) => {
}
});
fsRouter.options("/:page", async (context) => {
fsRouter.options("/:page(.*)", async (context) => {
const localPath = `${pagesPath}/${context.params.page}.md`;
try {
const stat = await Deno.stat(localPath);
@ -63,10 +66,16 @@ fsRouter.options("/:page", async (context) => {
}
});
fsRouter.put("/:page", async (context) => {
fsRouter.put("/:page(.*)", async (context) => {
const pageName = context.params.page;
const localPath = `${pagesPath}/${pageName}.md`;
const existingPage = await exists(localPath);
let dirName = path.dirname(localPath);
if (!(await exists(dirName))) {
await Deno.mkdir(dirName, {
recursive: true,
});
}
let file;
try {
file = await Deno.create(localPath);

View File

@ -1,4 +1,8 @@
export type AppEvent = "app:ready" | "page:save" | "page:load" | "page:click";
export type AppEvent =
| "app:ready"
| "page:save"
| "page:click"
| "editor:complete";
export type ClickEvent = {
pos: number;

View File

@ -134,10 +134,17 @@ export class Editor {
}
// TODO: Parallelize?
async dispatchAppEvent(name: AppEvent, data?: any) {
async dispatchAppEvent(name: AppEvent, data?: any): Promise<any[]> {
let results: any[] = [];
for (let plugin of this.plugins) {
await plugin.dispatchEvent(name, data);
let pluginResults = await plugin.dispatchEvent(name, data);
if (pluginResults) {
for (let result of pluginResults) {
results.push(result);
}
}
}
return results;
}
get currentPage(): PageMeta | undefined {
@ -176,7 +183,7 @@ export class Editor {
closeBrackets(),
autocompletion({
override: [
this.pageCompleter.bind(this),
this.pluginCompleter.bind(this),
this.commandCompleter.bind(this),
],
}),
@ -223,6 +230,14 @@ export class Editor {
return true;
},
},
{
key: "Ctrl-s",
mac: "Cmd-s",
run: (target): boolean => {
this.save();
return true;
},
},
{
key: "Ctrl-.",
mac: "Cmd-.",
@ -258,18 +273,20 @@ export class Editor {
});
}
pageCompleter(ctx: CompletionContext): CompletionResult | null {
let prefix = ctx.matchBefore(/\[\[[\w\s]*/);
if (!prefix) {
return null;
async pluginCompleter(
ctx: CompletionContext
): Promise<CompletionResult | null> {
let allCompletionResults = await this.dispatchAppEvent("editor:complete");
// console.log("All results", allCompletionResults);
if (allCompletionResults.length === 1) {
return allCompletionResults[0];
} else if (allCompletionResults.length > 1) {
console.error(
"Got completion results from multiple sources, cannot deal with that",
allCompletionResults
);
}
return {
from: prefix.from + 2,
options: this.viewState.allPages.map((pageMeta) => ({
label: pageMeta.name,
type: "page",
})),
};
return null;
}
commandCompleter(ctx: CompletionContext): CompletionResult | null {

View File

@ -113,14 +113,17 @@ export class Plugin {
return await this.runningFunctions.get(name)!.invoke(args);
}
async dispatchEvent(name: string, data?: any) {
async dispatchEvent(name: string, data?: any): Promise<any[]> {
let functionsToSpawn = this.manifest!.events[name];
if (functionsToSpawn) {
await Promise.all(
functionsToSpawn.map(async (functionToSpawn: string) => {
await this.invoke(functionToSpawn, [data]);
})
return await Promise.all(
functionsToSpawn.map(
async (functionToSpawn: string) =>
await this.invoke(functionToSpawn, [data])
)
);
} else {
return [];
}
}

View File

@ -1,10 +1,8 @@
import { SyscallContext } from "../plugins/runtime";
export default {
"db.put": (ctx: SyscallContext, key: string, value: any) => {
"db.put": (key: string, value: any) => {
localStorage.setItem(key, value);
},
"db.get": (ctx: SyscallContext, key: string) => {
"db.get": (key: string) => {
return localStorage.getItem(key);
},
};

View File

@ -9,6 +9,22 @@ type SyntaxNode = {
to: number;
};
function ensureAnchor(expr: any, start: boolean) {
var _a;
let { source } = expr;
let addStart = start && source[0] != "^",
addEnd = source[source.length - 1] != "$";
if (!addStart && !addEnd) return expr;
return new RegExp(
`${addStart ? "^" : ""}(?:${source})${addEnd ? "$" : ""}`,
(_a = expr.flags) !== null && _a !== void 0
? _a
: expr.ignoreCase
? "i"
: ""
);
}
export default (editor: Editor) => ({
"editor.getText": () => {
return editor.editorView?.state.sliceDoc();
@ -71,6 +87,24 @@ export default (editor: Editor) => ({
}
}
},
"editor.matchBefore": (
regexp: string
): { from: number; to: number; text: string } | null => {
const editorState = editor.editorView!.state;
let selection = editorState.selection.main;
let from = selection.from;
if (selection.empty) {
let line = editorState.doc.lineAt(from);
let start = Math.max(line.from, from - 250);
let str = line.text.slice(start - line.from, from - line.from);
let found = str.search(ensureAnchor(new RegExp(regexp), false));
// console.log("Line", line, start, str, new RegExp(regexp), found);
return found < 0
? null
: { from: start + found, to: from, text: str.slice(found) };
}
return null;
},
"editor.getSyntaxNodeAtPos": (pos: number): SyntaxNode | undefined => {
const editorState = editor.editorView!.state;
let node = syntaxTree(editorState).resolveInner(pos);

View File

@ -1,22 +1,16 @@
import { Editor } from "../editor";
import { SyscallContext } from "../plugins/runtime";
import { PageMeta } from "../types";
export default (editor: Editor) => ({
"space.listPages": (ctx: SyscallContext): PageMeta[] => {
"space.listPages": (): PageMeta[] => {
return editor.viewState.allPages;
},
"space.readPage": async (
ctx: SyscallContext,
name: string
): Promise<{ text: string; meta: PageMeta }> => {
return await editor.fs.readPage(name);
},
"space.writePage": async (
ctx: SyscallContext,
name: string,
text: string
): Promise<PageMeta> => {
"space.writePage": async (name: string, text: string): Promise<PageMeta> => {
return await editor.fs.writePage(name, text);
},
});

View File

@ -1,5 +1,3 @@
import { SyscallContext } from "../plugins/runtime";
// @ts-ignore
let frameTest = document.getElementById("main-frame");
@ -13,7 +11,7 @@ window.addEventListener("message", async (event) => {
});
export default {
"ui.update": function (ctx: SyscallContext, doc: any) {
"ui.update": function (doc: any) {
// frameTest.contentWindow.postMessage({
// type: "loadContent",
// doc: doc,