Support slashes in file names
parent
158e1cb2ef
commit
89f93963f5
|
@ -4,7 +4,7 @@
|
||||||
"invoke": "word_count_command"
|
"invoke": "word_count_command"
|
||||||
},
|
},
|
||||||
"Navigate To page": {
|
"Navigate To page": {
|
||||||
"invoke": "link_navigate",
|
"invoke": "linkNavigate",
|
||||||
"key": "Ctrl-Enter",
|
"key": "Ctrl-Enter",
|
||||||
"mac": "Cmd-Enter"
|
"mac": "Cmd-Enter"
|
||||||
},
|
},
|
||||||
|
@ -24,9 +24,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"events": {
|
"events": {
|
||||||
"page:click": ["taskToggle", "clickNavigate"]
|
"page:click": ["taskToggle", "clickNavigate"],
|
||||||
|
"editor:complete": ["pageComplete"]
|
||||||
},
|
},
|
||||||
"functions": {
|
"functions": {
|
||||||
|
"pageComplete": {
|
||||||
|
"path": "./navigate.ts:pageComplete"
|
||||||
|
},
|
||||||
"linkNavigate": {
|
"linkNavigate": {
|
||||||
"path": "./navigate.ts: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,16 +1,16 @@
|
||||||
export function syscall(name: string, ...args: Array<any>): any {
|
export function syscall(name: string, ...args: any[]): any {
|
||||||
let reqId = Math.floor(Math.random() * 1000000);
|
let reqId = Math.floor(Math.random() * 1000000);
|
||||||
// console.log("Syscall", name, reqId);
|
// console.log("Syscall", name, reqId);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
self.dispatchEvent(
|
self.dispatchEvent(
|
||||||
new CustomEvent("syscall", {
|
new CustomEvent("syscall", {
|
||||||
detail: {
|
detail: {
|
||||||
id: reqId,
|
id: reqId,
|
||||||
name: name,
|
name: name,
|
||||||
args: args,
|
args: args,
|
||||||
callback: resolve,
|
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 { 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 { 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 = {
|
type PageMeta = {
|
||||||
name: string;
|
name: string;
|
||||||
lastModified: number;
|
lastModified: number;
|
||||||
|
@ -21,22 +23,23 @@ fsRouter.use(oakCors({ methods: ["OPTIONS", "GET", "PUT", "POST"] }));
|
||||||
fsRouter.get("/", async (context) => {
|
fsRouter.get("/", async (context) => {
|
||||||
const localPath = pagesPath;
|
const localPath = pagesPath;
|
||||||
let fileNames: PageMeta[] = [];
|
let fileNames: PageMeta[] = [];
|
||||||
for await (const dirEntry of Deno.readDir(localPath)) {
|
const markdownFiles = (await recursiveReaddir(localPath)).filter(
|
||||||
if (dirEntry.isFile) {
|
(file: string) => path.extname(file) === ".md"
|
||||||
const stat = await Deno.stat(`${localPath}/${dirEntry.name}`);
|
);
|
||||||
fileNames.push({
|
for (const p of markdownFiles) {
|
||||||
name: dirEntry.name.substring(
|
const stat = await Deno.stat(p);
|
||||||
0,
|
fileNames.push({
|
||||||
dirEntry.name.length - path.extname(dirEntry.name).length
|
name: p.substring(
|
||||||
),
|
localPath.length + 1,
|
||||||
lastModified: stat.mtime?.getTime()!,
|
p.length - path.extname(p).length
|
||||||
});
|
),
|
||||||
}
|
lastModified: stat.mtime?.getTime()!,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
context.response.body = JSON.stringify(fileNames);
|
context.response.body = JSON.stringify(fileNames);
|
||||||
});
|
});
|
||||||
|
|
||||||
fsRouter.get("/:page", async (context) => {
|
fsRouter.get("/:page(.*)", async (context) => {
|
||||||
const pageName = context.params.page;
|
const pageName = context.params.page;
|
||||||
const localPath = `${pagesPath}/${pageName}.md`;
|
const localPath = `${pagesPath}/${pageName}.md`;
|
||||||
try {
|
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`;
|
const localPath = `${pagesPath}/${context.params.page}.md`;
|
||||||
try {
|
try {
|
||||||
const stat = await Deno.stat(localPath);
|
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 pageName = context.params.page;
|
||||||
const localPath = `${pagesPath}/${pageName}.md`;
|
const localPath = `${pagesPath}/${pageName}.md`;
|
||||||
const existingPage = await exists(localPath);
|
const existingPage = await exists(localPath);
|
||||||
|
let dirName = path.dirname(localPath);
|
||||||
|
if (!(await exists(dirName))) {
|
||||||
|
await Deno.mkdir(dirName, {
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
let file;
|
let file;
|
||||||
try {
|
try {
|
||||||
file = await Deno.create(localPath);
|
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 = {
|
export type ClickEvent = {
|
||||||
pos: number;
|
pos: number;
|
||||||
|
|
|
@ -134,10 +134,17 @@ export class Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Parallelize?
|
// 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) {
|
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 {
|
get currentPage(): PageMeta | undefined {
|
||||||
|
@ -176,7 +183,7 @@ export class Editor {
|
||||||
closeBrackets(),
|
closeBrackets(),
|
||||||
autocompletion({
|
autocompletion({
|
||||||
override: [
|
override: [
|
||||||
this.pageCompleter.bind(this),
|
this.pluginCompleter.bind(this),
|
||||||
this.commandCompleter.bind(this),
|
this.commandCompleter.bind(this),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
@ -223,6 +230,14 @@ export class Editor {
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "Ctrl-s",
|
||||||
|
mac: "Cmd-s",
|
||||||
|
run: (target): boolean => {
|
||||||
|
this.save();
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "Ctrl-.",
|
key: "Ctrl-.",
|
||||||
mac: "Cmd-.",
|
mac: "Cmd-.",
|
||||||
|
@ -258,18 +273,20 @@ export class Editor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pageCompleter(ctx: CompletionContext): CompletionResult | null {
|
async pluginCompleter(
|
||||||
let prefix = ctx.matchBefore(/\[\[[\w\s]*/);
|
ctx: CompletionContext
|
||||||
if (!prefix) {
|
): Promise<CompletionResult | null> {
|
||||||
return 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 {
|
return null;
|
||||||
from: prefix.from + 2,
|
|
||||||
options: this.viewState.allPages.map((pageMeta) => ({
|
|
||||||
label: pageMeta.name,
|
|
||||||
type: "page",
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
commandCompleter(ctx: CompletionContext): CompletionResult | null {
|
commandCompleter(ctx: CompletionContext): CompletionResult | null {
|
||||||
|
|
|
@ -113,14 +113,17 @@ export class Plugin {
|
||||||
return await this.runningFunctions.get(name)!.invoke(args);
|
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];
|
let functionsToSpawn = this.manifest!.events[name];
|
||||||
if (functionsToSpawn) {
|
if (functionsToSpawn) {
|
||||||
await Promise.all(
|
return await Promise.all(
|
||||||
functionsToSpawn.map(async (functionToSpawn: string) => {
|
functionsToSpawn.map(
|
||||||
await this.invoke(functionToSpawn, [data]);
|
async (functionToSpawn: string) =>
|
||||||
})
|
await this.invoke(functionToSpawn, [data])
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import { SyscallContext } from "../plugins/runtime";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
"db.put": (ctx: SyscallContext, key: string, value: any) => {
|
"db.put": (key: string, value: any) => {
|
||||||
localStorage.setItem(key, value);
|
localStorage.setItem(key, value);
|
||||||
},
|
},
|
||||||
"db.get": (ctx: SyscallContext, key: string) => {
|
"db.get": (key: string) => {
|
||||||
return localStorage.getItem(key);
|
return localStorage.getItem(key);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,6 +9,22 @@ type SyntaxNode = {
|
||||||
to: number;
|
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) => ({
|
export default (editor: Editor) => ({
|
||||||
"editor.getText": () => {
|
"editor.getText": () => {
|
||||||
return editor.editorView?.state.sliceDoc();
|
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 => {
|
"editor.getSyntaxNodeAtPos": (pos: number): SyntaxNode | undefined => {
|
||||||
const editorState = editor.editorView!.state;
|
const editorState = editor.editorView!.state;
|
||||||
let node = syntaxTree(editorState).resolveInner(pos);
|
let node = syntaxTree(editorState).resolveInner(pos);
|
||||||
|
|
|
@ -1,22 +1,16 @@
|
||||||
import { Editor } from "../editor";
|
import { Editor } from "../editor";
|
||||||
import { SyscallContext } from "../plugins/runtime";
|
|
||||||
import { PageMeta } from "../types";
|
import { PageMeta } from "../types";
|
||||||
|
|
||||||
export default (editor: Editor) => ({
|
export default (editor: Editor) => ({
|
||||||
"space.listPages": (ctx: SyscallContext): PageMeta[] => {
|
"space.listPages": (): PageMeta[] => {
|
||||||
return editor.viewState.allPages;
|
return editor.viewState.allPages;
|
||||||
},
|
},
|
||||||
"space.readPage": async (
|
"space.readPage": async (
|
||||||
ctx: SyscallContext,
|
|
||||||
name: string
|
name: string
|
||||||
): Promise<{ text: string; meta: PageMeta }> => {
|
): Promise<{ text: string; meta: PageMeta }> => {
|
||||||
return await editor.fs.readPage(name);
|
return await editor.fs.readPage(name);
|
||||||
},
|
},
|
||||||
"space.writePage": async (
|
"space.writePage": async (name: string, text: string): Promise<PageMeta> => {
|
||||||
ctx: SyscallContext,
|
|
||||||
name: string,
|
|
||||||
text: string
|
|
||||||
): Promise<PageMeta> => {
|
|
||||||
return await editor.fs.writePage(name, text);
|
return await editor.fs.writePage(name, text);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import { SyscallContext } from "../plugins/runtime";
|
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
let frameTest = document.getElementById("main-frame");
|
let frameTest = document.getElementById("main-frame");
|
||||||
|
|
||||||
|
@ -13,7 +11,7 @@ window.addEventListener("message", async (event) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
"ui.update": function (ctx: SyscallContext, doc: any) {
|
"ui.update": function (doc: any) {
|
||||||
// frameTest.contentWindow.postMessage({
|
// frameTest.contentWindow.postMessage({
|
||||||
// type: "loadContent",
|
// type: "loadContent",
|
||||||
// doc: doc,
|
// doc: doc,
|
||||||
|
|
Loading…
Reference in New Issue