Support slashes in file names
parent
158e1cb2ef
commit
89f93963f5
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import {syscall} from "./syscall.ts";
|
||||
|
||||
export async function publish(event: string, data?: object) {
|
||||
return await syscall("event.publish", event, data);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
export function syscall(name: string, ...args: Array<any>): any {
|
||||
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) => {
|
||||
|
@ -10,7 +10,7 @@ export function syscall(name: string, ...args: Array<any>): any {
|
|||
args: args,
|
||||
callback: resolve,
|
||||
},
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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}`);
|
||||
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: dirEntry.name.substring(
|
||||
0,
|
||||
dirEntry.name.length - path.extname(dirEntry.name).length
|
||||
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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -134,11 +134,18 @@ 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 {
|
||||
return this.viewState.currentPage;
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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 [];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue