1
0

Big page refactor

This commit is contained in:
Zef Hemel 2022-02-26 13:26:31 +01:00
parent 3cf84af894
commit 03e1eb2353
19 changed files with 214 additions and 204 deletions

2
.gitignore vendored
View File

@ -1 +1 @@
nuggets pages

13
plugins/core/click.ts Normal file
View 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);
}
}
}

View File

@ -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"
}, },

View File

@ -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);

View File

@ -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
View 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;
};

View File

@ -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")!
); );

View File

@ -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;
}

View File

@ -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>
); );

View File

@ -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);
}} }}

View File

@ -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" });
}} }}

View File

@ -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) => {

View File

@ -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" />

View File

@ -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 {

View File

@ -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 {

View File

@ -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",
}); });

View File

@ -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;

View File

@ -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),
};
}
},
}); });

View File

@ -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";