Big page refactor
This commit is contained in:
parent
3cf84af894
commit
03e1eb2353
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1 @@
|
|||||||
nuggets
|
pages
|
||||||
|
13
plugins/core/click.ts
Normal file
13
plugins/core/click.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { ClickEvent } from "../../webapp/src/app_event.ts";
|
||||||
|
import { syscall } from "./lib/syscall.ts";
|
||||||
|
|
||||||
|
export default async function click(event: ClickEvent) {
|
||||||
|
console.log("Event", event);
|
||||||
|
if (event.ctrlKey || event.metaKey) {
|
||||||
|
let syntaxNode = await syscall("editor.getSyntaxNodeAtPos", event.pos);
|
||||||
|
console.log("Here", syntaxNode);
|
||||||
|
if (syntaxNode && syntaxNode.name === "WikiLinkPage") {
|
||||||
|
await syscall("editor.navigate", syntaxNode.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,12 @@
|
|||||||
{
|
{
|
||||||
"commands": {
|
"commands": {
|
||||||
"Count Words": {
|
"Count Words": {
|
||||||
"invoke": "word_count_command",
|
"invoke": "word_count_command"
|
||||||
"requiredContext": {
|
|
||||||
"text": true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"Navigate To page": {
|
"Navigate To page": {
|
||||||
"invoke": "link_navigate",
|
"invoke": "link_navigate",
|
||||||
"key": "Ctrl-Enter",
|
"key": "Ctrl-Enter",
|
||||||
"mac": "Cmd-Enter",
|
"mac": "Cmd-Enter"
|
||||||
"requiredContext": {}
|
|
||||||
},
|
},
|
||||||
"Insert Current Date": {
|
"Insert Current Date": {
|
||||||
"invoke": "insert_nice_date",
|
"invoke": "insert_nice_date",
|
||||||
@ -28,12 +24,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"events": {
|
"events": {
|
||||||
"ready": ["welcome"]
|
"app:ready": ["welcome"],
|
||||||
|
"page:click": ["click"]
|
||||||
},
|
},
|
||||||
"functions": {
|
"functions": {
|
||||||
"welcome": {
|
"welcome": {
|
||||||
"path": "./welcome.ts"
|
"path": "./welcome.ts"
|
||||||
},
|
},
|
||||||
|
"click": {
|
||||||
|
"path": "./click.ts"
|
||||||
|
},
|
||||||
"word_count_command": {
|
"word_count_command": {
|
||||||
"path": "./word_count_command.ts:wordCount"
|
"path": "./word_count_command.ts:wordCount"
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { syscall } from "./lib/syscall.ts";
|
import { syscall } from "./lib/syscall.ts";
|
||||||
|
|
||||||
export async function linkNavigate({ text }: { text: string }) {
|
export async function linkNavigate() {
|
||||||
let syntaxNode = await syscall("editor.getSyntaxNodeUnderCursor");
|
let syntaxNode = await syscall("editor.getSyntaxNodeUnderCursor");
|
||||||
if (syntaxNode && syntaxNode.name === "WikiLinkPage") {
|
if (syntaxNode && syntaxNode.name === "WikiLinkPage") {
|
||||||
await syscall("editor.navigate", syntaxNode.text);
|
await syscall("editor.navigate", syntaxNode.text);
|
||||||
|
@ -6,21 +6,21 @@ 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";
|
||||||
|
|
||||||
type NuggetMeta = {
|
type PageMeta = {
|
||||||
name: string;
|
name: string;
|
||||||
lastModified: number;
|
lastModified: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const fsPrefix = "/fs";
|
const fsPrefix = "/fs";
|
||||||
const nuggetsPath = "../pages";
|
const pagesPath = "../pages";
|
||||||
|
|
||||||
const fsRouter = new Router();
|
const fsRouter = new Router();
|
||||||
|
|
||||||
fsRouter.use(oakCors({ methods: ["OPTIONS", "GET", "PUT", "POST"] }));
|
fsRouter.use(oakCors({ methods: ["OPTIONS", "GET", "PUT", "POST"] }));
|
||||||
|
|
||||||
fsRouter.get("/", async (context) => {
|
fsRouter.get("/", async (context) => {
|
||||||
const localPath = nuggetsPath;
|
const localPath = pagesPath;
|
||||||
let fileNames: NuggetMeta[] = [];
|
let fileNames: PageMeta[] = [];
|
||||||
for await (const dirEntry of Deno.readDir(localPath)) {
|
for await (const dirEntry of Deno.readDir(localPath)) {
|
||||||
if (dirEntry.isFile) {
|
if (dirEntry.isFile) {
|
||||||
const stat = await Deno.stat(`${localPath}/${dirEntry.name}`);
|
const stat = await Deno.stat(`${localPath}/${dirEntry.name}`);
|
||||||
@ -36,9 +36,9 @@ fsRouter.get("/", async (context) => {
|
|||||||
context.response.body = JSON.stringify(fileNames);
|
context.response.body = JSON.stringify(fileNames);
|
||||||
});
|
});
|
||||||
|
|
||||||
fsRouter.get("/:nugget", async (context) => {
|
fsRouter.get("/:page", async (context) => {
|
||||||
const nuggetName = context.params.nugget;
|
const pageName = context.params.page;
|
||||||
const localPath = `${nuggetsPath}/${nuggetName}.md`;
|
const localPath = `${pagesPath}/${pageName}.md`;
|
||||||
try {
|
try {
|
||||||
const stat = await Deno.stat(localPath);
|
const stat = await Deno.stat(localPath);
|
||||||
const text = await Deno.readTextFile(localPath);
|
const text = await Deno.readTextFile(localPath);
|
||||||
@ -50,8 +50,8 @@ fsRouter.get("/:nugget", async (context) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
fsRouter.options("/:nugget", async (context) => {
|
fsRouter.options("/:page", async (context) => {
|
||||||
const localPath = `${nuggetsPath}/${context.params.nugget}.md`;
|
const localPath = `${pagesPath}/${context.params.page}.md`;
|
||||||
try {
|
try {
|
||||||
const stat = await Deno.stat(localPath);
|
const stat = await Deno.stat(localPath);
|
||||||
context.response.headers.set("Content-length", `${stat.size}`);
|
context.response.headers.set("Content-length", `${stat.size}`);
|
||||||
@ -63,10 +63,10 @@ fsRouter.options("/:nugget", async (context) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
fsRouter.put("/:nugget", async (context) => {
|
fsRouter.put("/:page", async (context) => {
|
||||||
const nuggetName = context.params.nugget;
|
const pageName = context.params.page;
|
||||||
const localPath = `${nuggetsPath}/${nuggetName}.md`;
|
const localPath = `${pagesPath}/${pageName}.md`;
|
||||||
const existingNugget = await exists(localPath);
|
const existingPage = await exists(localPath);
|
||||||
let file;
|
let file;
|
||||||
try {
|
try {
|
||||||
file = await Deno.create(localPath);
|
file = await Deno.create(localPath);
|
||||||
@ -82,7 +82,7 @@ fsRouter.put("/:nugget", async (context) => {
|
|||||||
file.close();
|
file.close();
|
||||||
const stat = await Deno.stat(localPath);
|
const stat = await Deno.stat(localPath);
|
||||||
console.log("Wrote to", localPath);
|
console.log("Wrote to", localPath);
|
||||||
context.response.status = existingNugget ? 200 : 201;
|
context.response.status = existingPage ? 200 : 201;
|
||||||
context.response.headers.set("Last-Modified", "" + stat.mtime?.getTime());
|
context.response.headers.set("Last-Modified", "" + stat.mtime?.getTime());
|
||||||
context.response.body = "OK";
|
context.response.body = "OK";
|
||||||
});
|
});
|
||||||
|
8
webapp/src/app_event.ts
Normal file
8
webapp/src/app_event.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export type AppEvent = "app:ready" | "page:save" | "page:load" | "page:click";
|
||||||
|
|
||||||
|
export type ClickEvent = {
|
||||||
|
pos: number;
|
||||||
|
metaKey: boolean;
|
||||||
|
ctrlKey: boolean;
|
||||||
|
altKey: boolean;
|
||||||
|
};
|
@ -1,9 +1,9 @@
|
|||||||
import { Editor } from "./editor";
|
import { Editor } from "./editor";
|
||||||
import { HttpFileSystem } from "./fs";
|
import { HttpRemoteSpace } from "./space";
|
||||||
import { safeRun } from "./util";
|
import { safeRun } from "./util";
|
||||||
|
|
||||||
let editor = new Editor(
|
let editor = new Editor(
|
||||||
new HttpFileSystem(`http://${location.hostname}:2222/fs`),
|
new HttpRemoteSpace(`http://${location.hostname}:2222/fs`),
|
||||||
document.getElementById("root")!
|
document.getElementById("root")!
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
import { Editor } from "./editor";
|
|
||||||
import { AppCommand, CommandContext } from "./types";
|
|
||||||
|
|
||||||
export function buildContext(cmd: AppCommand, editor: Editor) {
|
|
||||||
let ctx: CommandContext = {};
|
|
||||||
if (!cmd.command.requiredContext) {
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
if (cmd.command.requiredContext.text) {
|
|
||||||
ctx.text = editor.editorView?.state.sliceDoc();
|
|
||||||
}
|
|
||||||
return ctx;
|
|
||||||
}
|
|
@ -1,16 +1,16 @@
|
|||||||
import { NuggetMeta } from "../types";
|
import { PageMeta } from "../types";
|
||||||
|
|
||||||
export function NavigationBar({
|
export function NavigationBar({
|
||||||
currentNugget,
|
currentPage,
|
||||||
onClick,
|
onClick,
|
||||||
}: {
|
}: {
|
||||||
currentNugget?: NuggetMeta;
|
currentPage?: PageMeta;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div id="top">
|
<div id="top">
|
||||||
<div className="current-nugget" onClick={onClick}>
|
<div className="current-page" onClick={onClick}>
|
||||||
» {currentNugget?.name}
|
» {currentPage?.name}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
import { NuggetMeta } from "../types";
|
import { PageMeta } from "../types";
|
||||||
import { FilterList } from "./filter";
|
import { FilterList } from "./filter";
|
||||||
|
|
||||||
export function NuggetNavigator({
|
export function PageNavigator({
|
||||||
allNuggets: allNuggets,
|
allPages: allPages,
|
||||||
onNavigate,
|
onNavigate,
|
||||||
}: {
|
}: {
|
||||||
allNuggets: NuggetMeta[];
|
allPages: PageMeta[];
|
||||||
onNavigate: (nugget: string | undefined) => void;
|
onNavigate: (page: string | undefined) => void;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<FilterList
|
<FilterList
|
||||||
placeholder=""
|
placeholder=""
|
||||||
options={allNuggets.map((meta) => ({
|
options={allPages.map((meta) => ({
|
||||||
...meta,
|
...meta,
|
||||||
// Order by last modified date in descending order
|
// Order by last modified date in descending order
|
||||||
orderId: -meta.lastModified.getTime(),
|
orderId: -meta.lastModified.getTime(),
|
||||||
}))}
|
}))}
|
||||||
allowNew={true}
|
allowNew={true}
|
||||||
newHint="Create nugget"
|
newHint="Create page"
|
||||||
onSelect={(opt) => {
|
onSelect={(opt) => {
|
||||||
onNavigate(opt?.name);
|
onNavigate(opt?.name);
|
||||||
}}
|
}}
|
@ -23,13 +23,12 @@ import {
|
|||||||
import React, { useEffect, useReducer } from "react";
|
import React, { useEffect, useReducer } from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import coreManifest from "../../plugins/dist/core.plugin.json";
|
import coreManifest from "../../plugins/dist/core.plugin.json";
|
||||||
import { buildContext } from "./buildContext";
|
|
||||||
import * as commands from "./commands";
|
import * as commands from "./commands";
|
||||||
import { CommandPalette } from "./components/command_palette";
|
import { CommandPalette } from "./components/command_palette";
|
||||||
import { NavigationBar } from "./components/navigation_bar";
|
import { NavigationBar } from "./components/navigation_bar";
|
||||||
import { NuggetNavigator } from "./components/nugget_navigator";
|
import { PageNavigator } from "./components/page_navigator";
|
||||||
import { StatusBar } from "./components/status_bar";
|
import { StatusBar } from "./components/status_bar";
|
||||||
import { FileSystem } from "./fs";
|
import { Space } from "./space";
|
||||||
import { lineWrapper } from "./lineWrapper";
|
import { lineWrapper } from "./lineWrapper";
|
||||||
import { markdown } from "./markdown";
|
import { markdown } from "./markdown";
|
||||||
import customMarkDown from "./parser";
|
import customMarkDown from "./parser";
|
||||||
@ -43,20 +42,19 @@ import editorSyscalls from "./syscalls/editor.browser";
|
|||||||
import {
|
import {
|
||||||
Action,
|
Action,
|
||||||
AppCommand,
|
AppCommand,
|
||||||
AppEvent,
|
|
||||||
AppViewState,
|
AppViewState,
|
||||||
CommandContext,
|
|
||||||
initialViewState,
|
initialViewState,
|
||||||
NuggetMeta,
|
PageMeta,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
import { AppEvent, ClickEvent } from "./app_event";
|
||||||
import { safeRun } from "./util";
|
import { safeRun } from "./util";
|
||||||
|
|
||||||
class NuggetState {
|
class PageState {
|
||||||
editorState: EditorState;
|
editorState: EditorState;
|
||||||
scrollTop: number;
|
scrollTop: number;
|
||||||
meta: NuggetMeta;
|
meta: PageMeta;
|
||||||
|
|
||||||
constructor(editorState: EditorState, scrollTop: number, meta: NuggetMeta) {
|
constructor(editorState: EditorState, scrollTop: number, meta: PageMeta) {
|
||||||
this.editorState = editorState;
|
this.editorState = editorState;
|
||||||
this.scrollTop = scrollTop;
|
this.scrollTop = scrollTop;
|
||||||
this.meta = meta;
|
this.meta = meta;
|
||||||
@ -70,14 +68,14 @@ export class Editor {
|
|||||||
viewState: AppViewState;
|
viewState: AppViewState;
|
||||||
viewDispatch: React.Dispatch<Action>;
|
viewDispatch: React.Dispatch<Action>;
|
||||||
$hashChange?: () => void;
|
$hashChange?: () => void;
|
||||||
openNuggets: Map<string, NuggetState>;
|
openPages: Map<string, PageState>;
|
||||||
fs: FileSystem;
|
fs: Space;
|
||||||
editorCommands: Map<string, AppCommand>;
|
editorCommands: Map<string, AppCommand>;
|
||||||
plugins: Plugin[];
|
plugins: Plugin[];
|
||||||
|
|
||||||
constructor(fs: FileSystem, parent: Element) {
|
constructor(fs: Space, parent: Element) {
|
||||||
this.editorCommands = new Map();
|
this.editorCommands = new Map();
|
||||||
this.openNuggets = new Map();
|
this.openPages = new Map();
|
||||||
this.plugins = [];
|
this.plugins = [];
|
||||||
this.fs = fs;
|
this.fs = fs;
|
||||||
this.viewState = initialViewState;
|
this.viewState = initialViewState;
|
||||||
@ -92,11 +90,11 @@ export class Editor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
await this.loadNuggetList();
|
await this.loadPageList();
|
||||||
await this.loadPlugins();
|
await this.loadPlugins();
|
||||||
this.$hashChange!();
|
this.$hashChange!();
|
||||||
this.focus();
|
this.focus();
|
||||||
await this.dispatchAppEvent("ready");
|
await this.dispatchAppEvent("app:ready");
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadPlugins() {
|
async loadPlugins() {
|
||||||
@ -123,7 +121,7 @@ export class Editor {
|
|||||||
let cmd = cmds[name];
|
let cmd = cmds[name];
|
||||||
this.editorCommands.set(name, {
|
this.editorCommands.set(name, {
|
||||||
command: cmd,
|
command: cmd,
|
||||||
run: async (arg: CommandContext): Promise<any> => {
|
run: async (arg): Promise<any> => {
|
||||||
return await plugin.invoke(cmd.invoke, [arg]);
|
return await plugin.invoke(cmd.invoke, [arg]);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -137,8 +135,8 @@ export class Editor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get currentNugget(): NuggetMeta | undefined {
|
get currentPage(): PageMeta | undefined {
|
||||||
return this.viewState.currentNugget;
|
return this.viewState.currentPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
createEditorState(text: string): EditorState {
|
createEditorState(text: string): EditorState {
|
||||||
@ -152,7 +150,7 @@ export class Editor {
|
|||||||
run: (): boolean => {
|
run: (): boolean => {
|
||||||
Promise.resolve()
|
Promise.resolve()
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
await def.run(buildContext(def, this));
|
await def.run(null);
|
||||||
})
|
})
|
||||||
.catch((e) => console.error(e));
|
.catch((e) => console.error(e));
|
||||||
return true;
|
return true;
|
||||||
@ -173,7 +171,7 @@ export class Editor {
|
|||||||
closeBrackets(),
|
closeBrackets(),
|
||||||
autocompletion({
|
autocompletion({
|
||||||
override: [
|
override: [
|
||||||
this.nuggetCompleter.bind(this),
|
this.pageCompleter.bind(this),
|
||||||
this.commandCompleter.bind(this),
|
this.commandCompleter.bind(this),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
@ -232,7 +230,17 @@ export class Editor {
|
|||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
EditorView.domEventHandlers({
|
EditorView.domEventHandlers({
|
||||||
click: this.click.bind(this),
|
click: (event: MouseEvent, view: EditorView) => {
|
||||||
|
safeRun(async () => {
|
||||||
|
let clickEvent: ClickEvent = {
|
||||||
|
ctrlKey: event.ctrlKey,
|
||||||
|
metaKey: event.metaKey,
|
||||||
|
altKey: event.altKey,
|
||||||
|
pos: view.posAtCoords(event)!,
|
||||||
|
};
|
||||||
|
await this.dispatchAppEvent("page:click", clickEvent);
|
||||||
|
});
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
markdown({
|
markdown({
|
||||||
base: customMarkDown,
|
base: customMarkDown,
|
||||||
@ -245,16 +253,16 @@ export class Editor {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
nuggetCompleter(ctx: CompletionContext): CompletionResult | null {
|
pageCompleter(ctx: CompletionContext): CompletionResult | null {
|
||||||
let prefix = ctx.matchBefore(/\[\[[\w\s]*/);
|
let prefix = ctx.matchBefore(/\[\[[\w\s]*/);
|
||||||
if (!prefix) {
|
if (!prefix) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
from: prefix.from + 2,
|
from: prefix.from + 2,
|
||||||
options: this.viewState.allNuggets.map((nuggetMeta) => ({
|
options: this.viewState.allPages.map((pageMeta) => ({
|
||||||
label: nuggetMeta.name,
|
label: pageMeta.name,
|
||||||
type: "nugget",
|
type: "page",
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -281,7 +289,7 @@ export class Editor {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
safeRun(async () => {
|
safeRun(async () => {
|
||||||
def.run(buildContext(def, this));
|
def.run(null);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -295,7 +303,7 @@ export class Editor {
|
|||||||
update(value: null, transaction: Transaction): null {
|
update(value: null, transaction: Transaction): null {
|
||||||
if (transaction.docChanged) {
|
if (transaction.docChanged) {
|
||||||
this.viewDispatch({
|
this.viewDispatch({
|
||||||
type: "nugget-updated",
|
type: "page-updated",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,91 +311,83 @@ export class Editor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
click(event: MouseEvent, view: EditorView) {
|
click(event: MouseEvent, view: EditorView) {
|
||||||
if (event.metaKey || event.ctrlKey) {
|
// if (event.metaKey || event.ctrlKey) {
|
||||||
let coords = view.posAtCoords(event)!;
|
// let coords = view.posAtCoords(event)!;
|
||||||
let node = syntaxTree(view.state).resolveInner(coords);
|
// let node = syntaxTree(view.state).resolveInner(coords);
|
||||||
if (node && node.name === "WikiLinkPage") {
|
// if (node && node.name === "WikiLinkPage") {
|
||||||
let nuggetName = view.state.sliceDoc(node.from, node.to);
|
// let pageName = view.state.sliceDoc(node.from, node.to);
|
||||||
this.navigate(nuggetName);
|
// this.navigate(pageName);
|
||||||
}
|
// }
|
||||||
if (node && node.name === "TaskMarker") {
|
// if (node && node.name === "TaskMarker") {
|
||||||
let checkBoxText = view.state.sliceDoc(node.from, node.to);
|
// let checkBoxText = view.state.sliceDoc(node.from, node.to);
|
||||||
if (checkBoxText === "[x]" || checkBoxText === "[X]") {
|
// if (checkBoxText === "[x]" || checkBoxText === "[X]") {
|
||||||
view.dispatch({
|
// view.dispatch({
|
||||||
changes: { from: node.from, to: node.to, insert: "[ ]" },
|
// changes: { from: node.from, to: node.to, insert: "[ ]" },
|
||||||
});
|
// });
|
||||||
} else {
|
// } else {
|
||||||
view.dispatch({
|
// view.dispatch({
|
||||||
changes: { from: node.from, to: node.to, insert: "[x]" },
|
// changes: { from: node.from, to: node.to, insert: "[x]" },
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
async save() {
|
async save() {
|
||||||
const editorState = this.editorView!.state;
|
const editorState = this.editorView!.state;
|
||||||
|
|
||||||
if (!this.currentNugget) {
|
if (!this.currentPage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Write to file system
|
// Write to file system
|
||||||
let nuggetMeta = await this.fs.writeNugget(
|
let pageMeta = await this.fs.writePage(
|
||||||
this.currentNugget.name,
|
this.currentPage.name,
|
||||||
editorState.sliceDoc()
|
editorState.sliceDoc()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update in open nugget cache
|
// Update in open page cache
|
||||||
this.openNuggets.set(
|
this.openPages.set(
|
||||||
this.currentNugget.name,
|
this.currentPage.name,
|
||||||
new NuggetState(
|
new PageState(editorState, this.editorView!.scrollDOM.scrollTop, pageMeta)
|
||||||
editorState,
|
|
||||||
this.editorView!.scrollDOM.scrollTop,
|
|
||||||
nuggetMeta
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Dispatch update to view
|
// Dispatch update to view
|
||||||
this.viewDispatch({ type: "nugget-saved", meta: nuggetMeta });
|
this.viewDispatch({ type: "page-saved", meta: pageMeta });
|
||||||
|
|
||||||
// If a new nugget was created, let's refresh the nugget list
|
// If a new page was created, let's refresh the page list
|
||||||
if (nuggetMeta.created) {
|
if (pageMeta.created) {
|
||||||
await this.loadNuggetList();
|
await this.loadPageList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadNuggetList() {
|
async loadPageList() {
|
||||||
let nuggetsMeta = await this.fs.listNuggets();
|
let pagesMeta = await this.fs.listPages();
|
||||||
this.viewDispatch({
|
this.viewDispatch({
|
||||||
type: "nuggets-listed",
|
type: "pages-listed",
|
||||||
nuggets: nuggetsMeta,
|
pages: pagesMeta,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
watch() {
|
watch() {
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
safeRun(async () => {
|
safeRun(async () => {
|
||||||
if (!this.currentNugget) {
|
if (!this.currentPage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const currentNuggetName = this.currentNugget.name;
|
const currentPageName = this.currentPage.name;
|
||||||
let newNuggetMeta = await this.fs.getMeta(currentNuggetName);
|
let newPageMeta = await this.fs.getPageMeta(currentPageName);
|
||||||
if (
|
if (
|
||||||
this.currentNugget.lastModified.getTime() <
|
this.currentPage.lastModified.getTime() <
|
||||||
newNuggetMeta.lastModified.getTime()
|
newPageMeta.lastModified.getTime()
|
||||||
) {
|
) {
|
||||||
console.log("File changed on disk, reloading");
|
console.log("File changed on disk, reloading");
|
||||||
let nuggetData = await this.fs.readNugget(currentNuggetName);
|
let pageData = await this.fs.readPage(currentPageName);
|
||||||
this.openNuggets.set(
|
this.openPages.set(
|
||||||
newNuggetMeta.name,
|
newPageMeta.name,
|
||||||
new NuggetState(
|
new PageState(this.createEditorState(pageData.text), 0, newPageMeta)
|
||||||
this.createEditorState(nuggetData.text),
|
|
||||||
0,
|
|
||||||
newNuggetMeta
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
await this.loadNugget(currentNuggetName);
|
await this.loadPage(currentPageName);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, watchInterval);
|
}, watchInterval);
|
||||||
@ -405,37 +405,37 @@ export class Editor {
|
|||||||
Promise.resolve()
|
Promise.resolve()
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
await this.save();
|
await this.save();
|
||||||
const nuggetName = decodeURIComponent(location.hash.substring(1));
|
const pageName = decodeURIComponent(location.hash.substring(1));
|
||||||
console.log("Now navigating to", nuggetName);
|
console.log("Now navigating to", pageName);
|
||||||
|
|
||||||
if (!this.editorView) {
|
if (!this.editorView) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.loadNugget(nuggetName);
|
await this.loadPage(pageName);
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadNugget(nuggetName: string) {
|
async loadPage(pageName: string) {
|
||||||
let nuggetState = this.openNuggets.get(nuggetName);
|
let pageState = this.openPages.get(pageName);
|
||||||
if (!nuggetState) {
|
if (!pageState) {
|
||||||
let nuggetData = await this.fs.readNugget(nuggetName);
|
let pageData = await this.fs.readPage(pageName);
|
||||||
nuggetState = new NuggetState(
|
pageState = new PageState(
|
||||||
this.createEditorState(nuggetData.text),
|
this.createEditorState(pageData.text),
|
||||||
0,
|
0,
|
||||||
nuggetData.meta
|
pageData.meta
|
||||||
);
|
);
|
||||||
this.openNuggets.set(nuggetName, nuggetState!);
|
this.openPages.set(pageName, pageState!);
|
||||||
}
|
}
|
||||||
this.editorView!.setState(nuggetState!.editorState);
|
this.editorView!.setState(pageState!.editorState);
|
||||||
this.editorView!.scrollDOM.scrollTop = nuggetState!.scrollTop;
|
this.editorView!.scrollDOM.scrollTop = pageState!.scrollTop;
|
||||||
|
|
||||||
this.viewDispatch({
|
this.viewDispatch({
|
||||||
type: "nugget-loaded",
|
type: "page-loaded",
|
||||||
meta: nuggetState.meta,
|
meta: pageState.meta,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -476,27 +476,27 @@ export class Editor {
|
|||||||
let editor = this;
|
let editor = this;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (viewState.currentNugget) {
|
if (viewState.currentPage) {
|
||||||
document.title = viewState.currentNugget.name;
|
document.title = viewState.currentPage.name;
|
||||||
}
|
}
|
||||||
}, [viewState.currentNugget]);
|
}, [viewState.currentPage]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{viewState.showNuggetNavigator && (
|
{viewState.showPageNavigator && (
|
||||||
<NuggetNavigator
|
<PageNavigator
|
||||||
allNuggets={viewState.allNuggets}
|
allPages={viewState.allPages}
|
||||||
onNavigate={(nugget) => {
|
onNavigate={(page) => {
|
||||||
dispatch({ type: "stop-navigate" });
|
dispatch({ type: "stop-navigate" });
|
||||||
editor!.focus();
|
editor!.focus();
|
||||||
if (nugget) {
|
if (page) {
|
||||||
editor
|
editor
|
||||||
?.save()
|
?.save()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
editor!.navigate(nugget);
|
editor!.navigate(page);
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
alert("Could not save nugget, not switching");
|
alert("Could not save page, not switching");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@ -509,7 +509,7 @@ export class Editor {
|
|||||||
editor!.focus();
|
editor!.focus();
|
||||||
if (cmd) {
|
if (cmd) {
|
||||||
safeRun(async () => {
|
safeRun(async () => {
|
||||||
let result = await cmd.run(buildContext(cmd, editor));
|
let result = await cmd.run(null);
|
||||||
console.log("Result of command", result);
|
console.log("Result of command", result);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -518,7 +518,7 @@ export class Editor {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<NavigationBar
|
<NavigationBar
|
||||||
currentNugget={viewState.currentNugget}
|
currentPage={viewState.currentPage}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
dispatch({ type: "start-navigate" });
|
dispatch({ type: "start-navigate" });
|
||||||
}}
|
}}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Nugget: this file is not built by Parcel, it's simply copied to the distribution
|
// Page: this file is not built by Parcel, it's simply copied to the distribution
|
||||||
// The reason is that somehow Parcel cannot accept using importScripts otherwise
|
// The reason is that somehow Parcel cannot accept using importScripts otherwise
|
||||||
function safeRun(fn) {
|
function safeRun(fn) {
|
||||||
fn().catch((e) => {
|
fn().catch((e) => {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>Nugget</title>
|
<title>Page</title>
|
||||||
<link rel="stylesheet" href="styles.css" />
|
<link rel="stylesheet" href="styles.css" />
|
||||||
<script type="module" src="boot.ts"></script>
|
<script type="module" src="boot.ts"></script>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
@ -21,11 +21,6 @@ export interface CommandDef {
|
|||||||
// If to show in slash invoked menu and if so, with what label
|
// If to show in slash invoked menu and if so, with what label
|
||||||
// should match slashCommandRegexp
|
// should match slashCommandRegexp
|
||||||
slashCommand?: string;
|
slashCommand?: string;
|
||||||
|
|
||||||
// Required context to be passed in as function arguments
|
|
||||||
requiredContext?: {
|
|
||||||
text?: boolean;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FunctionDef {
|
export interface FunctionDef {
|
||||||
|
@ -6,19 +6,19 @@ export default function reducer(
|
|||||||
): AppViewState {
|
): AppViewState {
|
||||||
console.log("Got action", action);
|
console.log("Got action", action);
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case "nugget-loaded":
|
case "page-loaded":
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
currentNugget: action.meta,
|
currentPage: action.meta,
|
||||||
isSaved: true,
|
isSaved: true,
|
||||||
};
|
};
|
||||||
case "nugget-saved":
|
case "page-saved":
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
currentNugget: action.meta,
|
currentPage: action.meta,
|
||||||
isSaved: true,
|
isSaved: true,
|
||||||
};
|
};
|
||||||
case "nugget-updated":
|
case "page-updated":
|
||||||
// Minor rerender optimization, this is triggered a lot
|
// Minor rerender optimization, this is triggered a lot
|
||||||
if (!state.isSaved) {
|
if (!state.isSaved) {
|
||||||
return state;
|
return state;
|
||||||
@ -30,17 +30,17 @@ export default function reducer(
|
|||||||
case "start-navigate":
|
case "start-navigate":
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
showNuggetNavigator: true,
|
showPageNavigator: true,
|
||||||
};
|
};
|
||||||
case "stop-navigate":
|
case "stop-navigate":
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
showNuggetNavigator: false,
|
showPageNavigator: false,
|
||||||
};
|
};
|
||||||
case "nuggets-listed":
|
case "pages-listed":
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
allNuggets: action.nuggets,
|
allPages: action.pages,
|
||||||
};
|
};
|
||||||
case "show-palette":
|
case "show-palette":
|
||||||
return {
|
return {
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import { NuggetMeta } from "./types";
|
import { PageMeta } from "./types";
|
||||||
|
|
||||||
export interface FileSystem {
|
export interface Space {
|
||||||
listNuggets(): Promise<NuggetMeta[]>;
|
listPages(): Promise<PageMeta[]>;
|
||||||
readNugget(name: string): Promise<{ text: string; meta: NuggetMeta }>;
|
readPage(name: string): Promise<{ text: string; meta: PageMeta }>;
|
||||||
writeNugget(name: string, text: string): Promise<NuggetMeta>;
|
writePage(name: string, text: string): Promise<PageMeta>;
|
||||||
getMeta(name: string): Promise<NuggetMeta>;
|
getPageMeta(name: string): Promise<PageMeta>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HttpFileSystem implements FileSystem {
|
export class HttpRemoteSpace implements Space {
|
||||||
url: string;
|
url: string;
|
||||||
constructor(url: string) {
|
constructor(url: string) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
}
|
}
|
||||||
async listNuggets(): Promise<NuggetMeta[]> {
|
async listPages(): Promise<PageMeta[]> {
|
||||||
let req = await fetch(this.url, {
|
let req = await fetch(this.url, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
});
|
});
|
||||||
@ -22,7 +22,7 @@ export class HttpFileSystem implements FileSystem {
|
|||||||
lastModified: new Date(meta.lastModified),
|
lastModified: new Date(meta.lastModified),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
async readNugget(name: string): Promise<{ text: string; meta: NuggetMeta }> {
|
async readPage(name: string): Promise<{ text: string; meta: PageMeta }> {
|
||||||
let req = await fetch(`${this.url}/${name}`, {
|
let req = await fetch(`${this.url}/${name}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
});
|
});
|
||||||
@ -34,12 +34,12 @@ export class HttpFileSystem implements FileSystem {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
async writeNugget(name: string, text: string): Promise<NuggetMeta> {
|
async writePage(name: string, text: string): Promise<PageMeta> {
|
||||||
let req = await fetch(`${this.url}/${name}`, {
|
let req = await fetch(`${this.url}/${name}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: text,
|
body: text,
|
||||||
});
|
});
|
||||||
// 201 (Created) means a new nugget was created
|
// 201 (Created) means a new page was created
|
||||||
return {
|
return {
|
||||||
lastModified: new Date(+req.headers.get("Last-Modified")!),
|
lastModified: new Date(+req.headers.get("Last-Modified")!),
|
||||||
name: name,
|
name: name,
|
||||||
@ -47,7 +47,7 @@ export class HttpFileSystem implements FileSystem {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMeta(name: string): Promise<NuggetMeta> {
|
async getPageMeta(name: string): Promise<PageMeta> {
|
||||||
let req = await fetch(`${this.url}/${name}`, {
|
let req = await fetch(`${this.url}/${name}`, {
|
||||||
method: "OPTIONS",
|
method: "OPTIONS",
|
||||||
});
|
});
|
@ -164,7 +164,7 @@ body {
|
|||||||
background-color: #ddd;
|
background-color: #ddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.current-nugget {
|
.current-page {
|
||||||
font-family: var(--editor-font);
|
font-family: var(--editor-font);
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
|
@ -69,4 +69,17 @@ export default (editor: Editor) => ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"editor.getSyntaxNodeAtPos": (
|
||||||
|
ctx: SyscallContext,
|
||||||
|
pos: number
|
||||||
|
): { name: string; text: string } | undefined => {
|
||||||
|
const editorState = editor.editorView!.state;
|
||||||
|
let node = syntaxTree(editorState).resolveInner(pos);
|
||||||
|
if (node) {
|
||||||
|
return {
|
||||||
|
name: node.name,
|
||||||
|
text: editorState.sliceDoc(node.from, node.to),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,46 +1,40 @@
|
|||||||
import { CommandDef } from "./plugins/types";
|
import { CommandDef } from "./plugins/types";
|
||||||
|
|
||||||
export type NuggetMeta = {
|
export type PageMeta = {
|
||||||
name: string;
|
name: string;
|
||||||
lastModified: Date;
|
lastModified: Date;
|
||||||
created?: boolean;
|
created?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CommandContext = {
|
|
||||||
text?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type AppCommand = {
|
export type AppCommand = {
|
||||||
command: CommandDef;
|
command: CommandDef;
|
||||||
run: (ctx: CommandContext) => Promise<any>;
|
run: (arg: any) => Promise<any>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AppViewState = {
|
export type AppViewState = {
|
||||||
currentNugget?: NuggetMeta;
|
currentPage?: PageMeta;
|
||||||
isSaved: boolean;
|
isSaved: boolean;
|
||||||
showNuggetNavigator: boolean;
|
showPageNavigator: boolean;
|
||||||
showCommandPalette: boolean;
|
showCommandPalette: boolean;
|
||||||
allNuggets: NuggetMeta[];
|
allPages: PageMeta[];
|
||||||
commands: Map<string, AppCommand>;
|
commands: Map<string, AppCommand>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const initialViewState: AppViewState = {
|
export const initialViewState: AppViewState = {
|
||||||
isSaved: false,
|
isSaved: false,
|
||||||
showNuggetNavigator: false,
|
showPageNavigator: false,
|
||||||
showCommandPalette: false,
|
showCommandPalette: false,
|
||||||
allNuggets: [],
|
allPages: [],
|
||||||
commands: new Map(),
|
commands: new Map(),
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Action =
|
export type Action =
|
||||||
| { type: "nugget-loaded"; meta: NuggetMeta }
|
| { type: "page-loaded"; meta: PageMeta }
|
||||||
| { type: "nugget-saved"; meta: NuggetMeta }
|
| { type: "page-saved"; meta: PageMeta }
|
||||||
| { type: "nugget-updated" }
|
| { type: "page-updated" }
|
||||||
| { type: "nuggets-listed"; nuggets: NuggetMeta[] }
|
| { type: "pages-listed"; pages: PageMeta[] }
|
||||||
| { type: "start-navigate" }
|
| { type: "start-navigate" }
|
||||||
| { type: "stop-navigate" }
|
| { type: "stop-navigate" }
|
||||||
| { type: "update-commands"; commands: Map<string, AppCommand> }
|
| { type: "update-commands"; commands: Map<string, AppCommand> }
|
||||||
| { type: "show-palette" }
|
| { type: "show-palette" }
|
||||||
| { type: "hide-palette" };
|
| { type: "hide-palette" };
|
||||||
|
|
||||||
export type AppEvent = "ready" | "change" | "switch" | "click";
|
|
||||||
|
Loading…
Reference in New Issue
Block a user