Bunch of stuff
parent
89f93963f5
commit
ad37b9ed10
|
@ -21,13 +21,41 @@
|
||||||
"invoke": "toggle_h2",
|
"invoke": "toggle_h2",
|
||||||
"mac": "Cmd-2",
|
"mac": "Cmd-2",
|
||||||
"key": "Ctrl-2"
|
"key": "Ctrl-2"
|
||||||
|
},
|
||||||
|
"Page: Delete": {
|
||||||
|
"invoke": "deletePage"
|
||||||
|
},
|
||||||
|
"Page: Rename": {
|
||||||
|
"invoke": "renamePage"
|
||||||
|
},
|
||||||
|
"Pages: Reindex": {
|
||||||
|
"invoke": "reindexPages"
|
||||||
|
},
|
||||||
|
"Pages: Back Links": {
|
||||||
|
"invoke": "showBackLinks"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"events": {
|
"events": {
|
||||||
"page:click": ["taskToggle", "clickNavigate"],
|
"page:click": ["taskToggle", "clickNavigate"],
|
||||||
"editor:complete": ["pageComplete"]
|
"editor:complete": ["pageComplete"],
|
||||||
|
"page:index": ["indexLinks"]
|
||||||
},
|
},
|
||||||
"functions": {
|
"functions": {
|
||||||
|
"indexLinks": {
|
||||||
|
"path": "./page.ts:indexLinks"
|
||||||
|
},
|
||||||
|
"deletePage": {
|
||||||
|
"path": "./page.ts:deletePage"
|
||||||
|
},
|
||||||
|
"showBackLinks": {
|
||||||
|
"path": "./page.ts:showBackLinks"
|
||||||
|
},
|
||||||
|
"renamePage": {
|
||||||
|
"path": "./page.ts:renamePage"
|
||||||
|
},
|
||||||
|
"reindexPages": {
|
||||||
|
"path": "./page.ts:reindex"
|
||||||
|
},
|
||||||
"pageComplete": {
|
"pageComplete": {
|
||||||
"path": "./navigate.ts:pageComplete"
|
"path": "./navigate.ts:pageComplete"
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
import { IndexEvent } from "../../webapp/src/app_event.ts";
|
||||||
|
import { pageLinkRegex } from "../../webapp/src/constant.ts";
|
||||||
|
import { syscall } from "./lib/syscall.ts";
|
||||||
|
|
||||||
|
const wikilinkRegex = new RegExp(pageLinkRegex, "g");
|
||||||
|
|
||||||
|
export async function indexLinks({ name, text }: IndexEvent) {
|
||||||
|
console.log("Now indexing", name);
|
||||||
|
let backLinks: { key: string; value: string }[] = [];
|
||||||
|
for (let match of text.matchAll(wikilinkRegex)) {
|
||||||
|
let toPage = match[1];
|
||||||
|
let pos = match.index!;
|
||||||
|
backLinks.push({
|
||||||
|
key: `pl:${toPage}:${pos}`,
|
||||||
|
value: name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log("Found", backLinks.length, "wiki link(s)");
|
||||||
|
await syscall("indexer.batchSet", name, backLinks);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deletePage() {
|
||||||
|
let pageMeta = await syscall("editor.getCurrentPage");
|
||||||
|
console.log("Navigating to start page");
|
||||||
|
await syscall("editor.navigate", "start");
|
||||||
|
console.log("Deleting page from space");
|
||||||
|
await syscall("space.deletePage", pageMeta.name);
|
||||||
|
console.log("Reloading page list");
|
||||||
|
await syscall("space.reloadPageList");
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function renamePage() {
|
||||||
|
const pageMeta = await syscall("editor.getCurrentPage");
|
||||||
|
const oldName = pageMeta.name;
|
||||||
|
const newName = await syscall("editor.prompt", `Rename ${oldName} to:`);
|
||||||
|
if (!newName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("New name", newName);
|
||||||
|
|
||||||
|
let pagesToUpdate = await getBackLinks(oldName);
|
||||||
|
console.log("All pages containing backlinks", pagesToUpdate);
|
||||||
|
|
||||||
|
let text = await syscall("editor.getText");
|
||||||
|
console.log("Writing new page to space");
|
||||||
|
await syscall("space.writePage", newName, text);
|
||||||
|
console.log("Deleting page from space");
|
||||||
|
await syscall("space.deletePage", oldName);
|
||||||
|
console.log("Reloading page list");
|
||||||
|
await syscall("space.reloadPageList");
|
||||||
|
console.log("Navigating to new page");
|
||||||
|
await syscall("editor.navigate", newName);
|
||||||
|
|
||||||
|
let pageToUpdateSet = new Set<string>();
|
||||||
|
for (let pageToUpdate of pagesToUpdate) {
|
||||||
|
pageToUpdateSet.add(pageToUpdate.page);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let pageToUpdate of pageToUpdateSet) {
|
||||||
|
console.log("Now going to update links in", pageToUpdate);
|
||||||
|
let { text } = await syscall("space.readPage", pageToUpdate);
|
||||||
|
if (!text) {
|
||||||
|
// Page likely does not exist, but at least we can skip it
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let newText = text.replaceAll(`[[${oldName}]]`, `[[${newName}]]`);
|
||||||
|
if (text !== newText) {
|
||||||
|
console.log("Changes made, saving...");
|
||||||
|
await syscall("space.writePage", pageToUpdate, newText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type BackLink = {
|
||||||
|
page: string;
|
||||||
|
pos: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function getBackLinks(pageName: string): Promise<BackLink[]> {
|
||||||
|
let allBackLinks = await syscall(
|
||||||
|
"indexer.scanPrefixGlobal",
|
||||||
|
`pl:${pageName}:`
|
||||||
|
);
|
||||||
|
let pagesToUpdate: BackLink[] = [];
|
||||||
|
for (let { key, value } of allBackLinks) {
|
||||||
|
let keyParts = key.split(":");
|
||||||
|
pagesToUpdate.push({
|
||||||
|
page: value,
|
||||||
|
pos: +keyParts[keyParts.length - 1],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return pagesToUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function showBackLinks() {
|
||||||
|
const pageMeta = await syscall("editor.getCurrentPage");
|
||||||
|
let backLinks = await getBackLinks(pageMeta.name);
|
||||||
|
|
||||||
|
console.log("Backlinks", backLinks);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function reindex() {
|
||||||
|
await syscall("space.reindex");
|
||||||
|
}
|
|
@ -18,7 +18,7 @@ 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", "DELETE"] }));
|
||||||
|
|
||||||
fsRouter.get("/", async (context) => {
|
fsRouter.get("/", async (context) => {
|
||||||
const localPath = pagesPath;
|
const localPath = pagesPath;
|
||||||
|
@ -96,6 +96,22 @@ fsRouter.put("/:page(.*)", async (context) => {
|
||||||
context.response.body = "OK";
|
context.response.body = "OK";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fsRouter.delete("/:page(.*)", async (context) => {
|
||||||
|
const pageName = context.params.page;
|
||||||
|
const localPath = `${pagesPath}/${pageName}.md`;
|
||||||
|
try {
|
||||||
|
await Deno.remove(localPath);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error deleting file", localPath, e);
|
||||||
|
context.response.status = 500;
|
||||||
|
context.response.body = e.message;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("Deleted", localPath);
|
||||||
|
|
||||||
|
context.response.body = "OK";
|
||||||
|
});
|
||||||
|
|
||||||
const app = new Application();
|
const app = new Application();
|
||||||
app.use(
|
app.use(
|
||||||
new Router()
|
new Router()
|
||||||
|
@ -109,7 +125,8 @@ app.use(async (context, next) => {
|
||||||
index: "index.html",
|
index: "index.html",
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
next();
|
await context.send({ root: "../webapp/dist", path: "index.html" });
|
||||||
|
// next();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
"@codemirror/state": "^0.19.7",
|
"@codemirror/state": "^0.19.7",
|
||||||
"@codemirror/view": "^0.19.42",
|
"@codemirror/view": "^0.19.42",
|
||||||
"@parcel/service-worker": "^2.3.2",
|
"@parcel/service-worker": "^2.3.2",
|
||||||
|
"dexie": "^3.2.1",
|
||||||
"idb": "^7.0.0",
|
"idb": "^7.0.0",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2"
|
"react-dom": "^17.0.2"
|
||||||
|
|
|
@ -2,6 +2,7 @@ export type AppEvent =
|
||||||
| "app:ready"
|
| "app:ready"
|
||||||
| "page:save"
|
| "page:save"
|
||||||
| "page:click"
|
| "page:click"
|
||||||
|
| "page:index"
|
||||||
| "editor:complete";
|
| "editor:complete";
|
||||||
|
|
||||||
export type ClickEvent = {
|
export type ClickEvent = {
|
||||||
|
@ -10,3 +11,12 @@ export type ClickEvent = {
|
||||||
ctrlKey: boolean;
|
ctrlKey: boolean;
|
||||||
altKey: boolean;
|
altKey: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type IndexEvent = {
|
||||||
|
name: string;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface AppEventDispatcher {
|
||||||
|
dispatchAppEvent(name: AppEvent, data?: any): Promise<any[]>;
|
||||||
|
}
|
||||||
|
|
|
@ -1,21 +1,35 @@
|
||||||
import { PageMeta } from "../types";
|
import { PageMeta } from "../types";
|
||||||
import { FilterList } from "./filter";
|
import { FilterList, Option } from "./filter";
|
||||||
|
|
||||||
export function PageNavigator({
|
export function PageNavigator({
|
||||||
allPages: allPages,
|
allPages,
|
||||||
onNavigate,
|
onNavigate,
|
||||||
|
currentPage,
|
||||||
}: {
|
}: {
|
||||||
allPages: PageMeta[];
|
allPages: PageMeta[];
|
||||||
onNavigate: (page: string | undefined) => void;
|
onNavigate: (page: string | undefined) => void;
|
||||||
|
currentPage?: PageMeta;
|
||||||
}) {
|
}) {
|
||||||
|
let options: Option[] = [];
|
||||||
|
for (let pageMeta of allPages) {
|
||||||
|
if (currentPage && currentPage.name == pageMeta.name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Order by last modified date in descending order
|
||||||
|
let orderId = -pageMeta.lastModified.getTime();
|
||||||
|
// Unless it was opened and is still in memory
|
||||||
|
if (pageMeta.lastOpened) {
|
||||||
|
orderId = -pageMeta.lastOpened.getTime();
|
||||||
|
}
|
||||||
|
options.push({
|
||||||
|
...pageMeta,
|
||||||
|
orderId: orderId,
|
||||||
|
});
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<FilterList
|
<FilterList
|
||||||
placeholder=""
|
placeholder=""
|
||||||
options={allPages.map((meta) => ({
|
options={options}
|
||||||
...meta,
|
|
||||||
// Order by last modified date in descending order
|
|
||||||
orderId: -meta.lastModified.getTime(),
|
|
||||||
}))}
|
|
||||||
allowNew={true}
|
allowNew={true}
|
||||||
newHint="Create page"
|
newHint="Create page"
|
||||||
onSelect={(opt) => {
|
onSelect={(opt) => {
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export const pageLinkRegex = /\[\[([\w\s\/\:,\.\-]+)\]\]/;
|
|
@ -39,6 +39,7 @@ import customMarkdownStyle from "./style";
|
||||||
import dbSyscalls from "./syscalls/db.localstorage";
|
import dbSyscalls from "./syscalls/db.localstorage";
|
||||||
import { Plugin } from "./plugins/runtime";
|
import { Plugin } from "./plugins/runtime";
|
||||||
import editorSyscalls from "./syscalls/editor.browser";
|
import editorSyscalls from "./syscalls/editor.browser";
|
||||||
|
import indexerSyscalls from "./syscalls/indexer.native";
|
||||||
import spaceSyscalls from "./syscalls/space.native";
|
import spaceSyscalls from "./syscalls/space.native";
|
||||||
import {
|
import {
|
||||||
Action,
|
Action,
|
||||||
|
@ -47,8 +48,15 @@ import {
|
||||||
initialViewState,
|
initialViewState,
|
||||||
PageMeta,
|
PageMeta,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { AppEvent, ClickEvent } from "./app_event";
|
import {
|
||||||
|
AppEvent,
|
||||||
|
AppEventDispatcher,
|
||||||
|
ClickEvent,
|
||||||
|
IndexEvent,
|
||||||
|
} from "./app_event";
|
||||||
import { safeRun } from "./util";
|
import { safeRun } from "./util";
|
||||||
|
import { Indexer } from "./indexer";
|
||||||
|
import { IPageNavigator, PathPageNavigator } from "./navigator";
|
||||||
|
|
||||||
class PageState {
|
class PageState {
|
||||||
editorState: EditorState;
|
editorState: EditorState;
|
||||||
|
@ -64,21 +72,23 @@ class PageState {
|
||||||
|
|
||||||
const watchInterval = 5000;
|
const watchInterval = 5000;
|
||||||
|
|
||||||
export class Editor {
|
export class Editor implements AppEventDispatcher {
|
||||||
editorView?: EditorView;
|
editorView?: EditorView;
|
||||||
viewState: AppViewState;
|
viewState: AppViewState;
|
||||||
viewDispatch: React.Dispatch<Action>;
|
viewDispatch: React.Dispatch<Action>;
|
||||||
$hashChange?: () => void;
|
|
||||||
openPages: Map<string, PageState>;
|
openPages: Map<string, PageState>;
|
||||||
fs: Space;
|
space: Space;
|
||||||
editorCommands: Map<string, AppCommand>;
|
editorCommands: Map<string, AppCommand>;
|
||||||
plugins: Plugin[];
|
plugins: Plugin[];
|
||||||
|
indexer: Indexer;
|
||||||
|
navigationResolve?: (val: undefined) => void;
|
||||||
|
pageNavigator: IPageNavigator;
|
||||||
|
|
||||||
constructor(fs: Space, parent: Element) {
|
constructor(space: Space, parent: Element) {
|
||||||
this.editorCommands = new Map();
|
this.editorCommands = new Map();
|
||||||
this.openPages = new Map();
|
this.openPages = new Map();
|
||||||
this.plugins = [];
|
this.plugins = [];
|
||||||
this.fs = fs;
|
this.space = space;
|
||||||
this.viewState = initialViewState;
|
this.viewState = initialViewState;
|
||||||
this.viewDispatch = () => {};
|
this.viewDispatch = () => {};
|
||||||
this.render(parent);
|
this.render(parent);
|
||||||
|
@ -86,16 +96,30 @@ export class Editor {
|
||||||
state: this.createEditorState(""),
|
state: this.createEditorState(""),
|
||||||
parent: document.getElementById("editor")!,
|
parent: document.getElementById("editor")!,
|
||||||
});
|
});
|
||||||
this.addListeners();
|
this.pageNavigator = new PathPageNavigator();
|
||||||
// this.watch();
|
this.indexer = new Indexer("page-index", space);
|
||||||
|
this.watch();
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
await this.loadPageList();
|
await this.loadPageList();
|
||||||
await this.loadPlugins();
|
await this.loadPlugins();
|
||||||
this.$hashChange!();
|
|
||||||
this.focus();
|
this.focus();
|
||||||
await this.dispatchAppEvent("app:ready");
|
|
||||||
|
this.pageNavigator.subscribe(async (pageName) => {
|
||||||
|
await this.save();
|
||||||
|
console.log("Now navigating to", pageName);
|
||||||
|
|
||||||
|
if (!this.editorView) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.loadPage(pageName);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.pageNavigator.getCurrentPage() === "") {
|
||||||
|
this.pageNavigator.navigate("start");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadPlugins() {
|
async loadPlugins() {
|
||||||
|
@ -103,7 +127,8 @@ export class Editor {
|
||||||
system.registerSyscalls(
|
system.registerSyscalls(
|
||||||
dbSyscalls,
|
dbSyscalls,
|
||||||
editorSyscalls(this),
|
editorSyscalls(this),
|
||||||
spaceSyscalls(this)
|
spaceSyscalls(this),
|
||||||
|
indexerSyscalls(this.indexer)
|
||||||
);
|
);
|
||||||
|
|
||||||
await system.bootServiceWorker();
|
await system.bootServiceWorker();
|
||||||
|
@ -332,41 +357,20 @@ export class Editor {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
click(event: MouseEvent, view: EditorView) {
|
|
||||||
// if (event.metaKey || event.ctrlKey) {
|
|
||||||
// let coords = view.posAtCoords(event)!;
|
|
||||||
// let node = syntaxTree(view.state).resolveInner(coords);
|
|
||||||
// if (node && node.name === "WikiLinkPage") {
|
|
||||||
// let pageName = view.state.sliceDoc(node.from, node.to);
|
|
||||||
// this.navigate(pageName);
|
|
||||||
// }
|
|
||||||
// if (node && node.name === "TaskMarker") {
|
|
||||||
// let checkBoxText = view.state.sliceDoc(node.from, node.to);
|
|
||||||
// if (checkBoxText === "[x]" || checkBoxText === "[X]") {
|
|
||||||
// view.dispatch({
|
|
||||||
// changes: { from: node.from, to: node.to, insert: "[ ]" },
|
|
||||||
// });
|
|
||||||
// } else {
|
|
||||||
// view.dispatch({
|
|
||||||
// changes: { from: node.from, to: node.to, insert: "[x]" },
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
async save() {
|
async save() {
|
||||||
const editorState = this.editorView!.state;
|
const editorState = this.editorView!.state;
|
||||||
|
|
||||||
if (!this.currentPage) {
|
if (!this.currentPage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.viewState.isSaved) {
|
||||||
|
console.log("Page not modified, skipping saving");
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Write to file system
|
// Write to file system
|
||||||
let pageMeta = await this.fs.writePage(
|
let text = editorState.sliceDoc();
|
||||||
this.currentPage.name,
|
let pageMeta = await this.space.writePage(this.currentPage.name, text);
|
||||||
editorState.sliceDoc()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update in open page cache
|
// Update in open page cache
|
||||||
this.openPages.set(
|
this.openPages.set(
|
||||||
|
@ -381,10 +385,18 @@ export class Editor {
|
||||||
if (pageMeta.created) {
|
if (pageMeta.created) {
|
||||||
await this.loadPageList();
|
await this.loadPageList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reindex page
|
||||||
|
await this.indexPage(text, pageMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async indexPage(text: string, pageMeta: PageMeta) {
|
||||||
|
console.log("Indexing page", pageMeta.name);
|
||||||
|
this.indexer.indexPage(this, pageMeta, text, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadPageList() {
|
async loadPageList() {
|
||||||
let pagesMeta = await this.fs.listPages();
|
let pagesMeta = await this.space.listPages();
|
||||||
this.viewDispatch({
|
this.viewDispatch({
|
||||||
type: "pages-listed",
|
type: "pages-listed",
|
||||||
pages: pagesMeta,
|
pages: pagesMeta,
|
||||||
|
@ -394,25 +406,27 @@ export class Editor {
|
||||||
watch() {
|
watch() {
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
safeRun(async () => {
|
safeRun(async () => {
|
||||||
if (!this.currentPage) {
|
if (this.currentPage && this.viewState.isSaved) {
|
||||||
return;
|
await this.checkForNewVersion(this.currentPage);
|
||||||
}
|
}
|
||||||
const currentPageName = this.currentPage.name;
|
});
|
||||||
let newPageMeta = await this.fs.getPageMeta(currentPageName);
|
}, watchInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkForNewVersion(cachedMeta: PageMeta) {
|
||||||
|
const currentPageName = cachedMeta.name;
|
||||||
|
let newPageMeta = await this.space.getPageMeta(currentPageName);
|
||||||
if (
|
if (
|
||||||
this.currentPage.lastModified.getTime() <
|
cachedMeta.lastModified.getTime() !== newPageMeta.lastModified.getTime()
|
||||||
newPageMeta.lastModified.getTime()
|
|
||||||
) {
|
) {
|
||||||
console.log("File changed on disk, reloading");
|
console.log("File changed on disk, reloading");
|
||||||
let pageData = await this.fs.readPage(currentPageName);
|
let pageData = await this.space.readPage(currentPageName);
|
||||||
this.openPages.set(
|
this.openPages.set(
|
||||||
newPageMeta.name,
|
newPageMeta.name,
|
||||||
new PageState(this.createEditorState(pageData.text), 0, newPageMeta)
|
new PageState(this.createEditorState(pageData.text), 0, newPageMeta)
|
||||||
);
|
);
|
||||||
await this.loadPage(currentPageName);
|
await this.loadPage(currentPageName);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}, watchInterval);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
focus() {
|
focus() {
|
||||||
|
@ -420,37 +434,24 @@ export class Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
async navigate(name: string) {
|
async navigate(name: string) {
|
||||||
location.hash = encodeURIComponent(name);
|
await this.pageNavigator.navigate(name);
|
||||||
}
|
|
||||||
|
|
||||||
hashChange() {
|
|
||||||
Promise.resolve()
|
|
||||||
.then(async () => {
|
|
||||||
await this.save();
|
|
||||||
const pageName = decodeURIComponent(location.hash.substring(1));
|
|
||||||
console.log("Now navigating to", pageName);
|
|
||||||
|
|
||||||
if (!this.editorView) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.loadPage(pageName);
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.error(e);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadPage(pageName: string) {
|
async loadPage(pageName: string) {
|
||||||
let pageState = this.openPages.get(pageName);
|
let pageState = this.openPages.get(pageName);
|
||||||
if (!pageState) {
|
if (!pageState) {
|
||||||
let pageData = await this.fs.readPage(pageName);
|
let pageData = await this.space.readPage(pageName);
|
||||||
pageState = new PageState(
|
pageState = new PageState(
|
||||||
this.createEditorState(pageData.text),
|
this.createEditorState(pageData.text),
|
||||||
0,
|
0,
|
||||||
pageData.meta
|
pageData.meta
|
||||||
);
|
);
|
||||||
this.openPages.set(pageName, pageState!);
|
this.openPages.set(pageName, pageState!);
|
||||||
|
} else {
|
||||||
|
// Loaded page from in-mory cache, let's async see if this page hasn't been updated
|
||||||
|
this.checkForNewVersion(pageState.meta).catch((e) => {
|
||||||
|
console.error("Failed to check for new version");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.editorView!.setState(pageState!.editorState);
|
this.editorView!.setState(pageState!.editorState);
|
||||||
this.editorView!.scrollDOM.scrollTop = pageState!.scrollTop;
|
this.editorView!.scrollDOM.scrollTop = pageState!.scrollTop;
|
||||||
|
@ -459,16 +460,15 @@ export class Editor {
|
||||||
type: "page-loaded",
|
type: "page-loaded",
|
||||||
meta: pageState.meta,
|
meta: pageState.meta,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
addListeners() {
|
let indexerPageMeta = await this.indexer.getPageIndexPageMeta(pageName);
|
||||||
this.$hashChange = this.hashChange.bind(this);
|
if (
|
||||||
window.addEventListener("hashchange", this.$hashChange);
|
(indexerPageMeta &&
|
||||||
}
|
pageState.meta.lastModified.getTime() !==
|
||||||
|
indexerPageMeta.lastModified.getTime()) ||
|
||||||
dispose() {
|
!indexerPageMeta
|
||||||
if (this.$hashChange) {
|
) {
|
||||||
window.removeEventListener("hashchange", this.$hashChange);
|
await this.indexPage(pageState.editorState.sliceDoc(), pageState.meta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,12 +477,6 @@ export class Editor {
|
||||||
this.viewState = viewState;
|
this.viewState = viewState;
|
||||||
this.viewDispatch = dispatch;
|
this.viewDispatch = dispatch;
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!location.hash) {
|
|
||||||
this.navigate("start");
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Auto save
|
// Auto save
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const id = setTimeout(() => {
|
const id = setTimeout(() => {
|
||||||
|
@ -508,17 +502,13 @@ export class Editor {
|
||||||
{viewState.showPageNavigator && (
|
{viewState.showPageNavigator && (
|
||||||
<PageNavigator
|
<PageNavigator
|
||||||
allPages={viewState.allPages}
|
allPages={viewState.allPages}
|
||||||
|
currentPage={this.currentPage}
|
||||||
onNavigate={(page) => {
|
onNavigate={(page) => {
|
||||||
dispatch({ type: "stop-navigate" });
|
dispatch({ type: "stop-navigate" });
|
||||||
editor!.focus();
|
editor.focus();
|
||||||
if (page) {
|
if (page) {
|
||||||
editor
|
safeRun(async () => {
|
||||||
?.save()
|
editor.navigate(page);
|
||||||
.then(() => {
|
|
||||||
editor!.navigate(page);
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
alert("Could not save page, not switching");
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -0,0 +1,155 @@
|
||||||
|
import { Dexie, Table } from "dexie";
|
||||||
|
import { AppEventDispatcher, IndexEvent } from "./app_event";
|
||||||
|
import { Space } from "./space";
|
||||||
|
import { PageMeta } from "./types";
|
||||||
|
|
||||||
|
function constructKey(pageName: string, key: string): string {
|
||||||
|
return `${pageName}:${key}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanKey(pageName: string, fromKey: string): string {
|
||||||
|
return fromKey.substring(pageName.length + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type KV = {
|
||||||
|
key: string;
|
||||||
|
value: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Indexer {
|
||||||
|
db: Dexie;
|
||||||
|
pageIndex: Table;
|
||||||
|
space: Space;
|
||||||
|
|
||||||
|
constructor(name: string, space: Space) {
|
||||||
|
this.db = new Dexie(name);
|
||||||
|
this.space = space;
|
||||||
|
this.db.version(1).stores({
|
||||||
|
pageIndex: "ck, page, key",
|
||||||
|
});
|
||||||
|
this.pageIndex = this.db.table("pageIndex");
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearPageIndexForPage(pageName: string) {
|
||||||
|
await this.pageIndex.where({ page: pageName }).delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearPageIndex() {
|
||||||
|
await this.pageIndex.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
async setPageIndexPageMeta(pageName: string, meta: PageMeta) {
|
||||||
|
await this.set(pageName, "$meta", {
|
||||||
|
lastModified: meta.lastModified.getTime(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPageIndexPageMeta(pageName: string): Promise<PageMeta | null> {
|
||||||
|
let meta = await this.get(pageName, "$meta");
|
||||||
|
if (meta) {
|
||||||
|
return {
|
||||||
|
name: pageName,
|
||||||
|
lastModified: new Date(meta.lastModified),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async indexPage(
|
||||||
|
appEventDispatcher: AppEventDispatcher,
|
||||||
|
pageMeta: PageMeta,
|
||||||
|
text: string,
|
||||||
|
withFlush: boolean
|
||||||
|
) {
|
||||||
|
if (withFlush) {
|
||||||
|
await this.clearPageIndexForPage(pageMeta.name);
|
||||||
|
}
|
||||||
|
let indexEvent: IndexEvent = {
|
||||||
|
name: pageMeta.name,
|
||||||
|
text,
|
||||||
|
};
|
||||||
|
await appEventDispatcher.dispatchAppEvent("page:index", indexEvent);
|
||||||
|
await this.setPageIndexPageMeta(pageMeta.name, pageMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
async reindexSpace(space: Space, appEventDispatcher: AppEventDispatcher) {
|
||||||
|
await this.clearPageIndex();
|
||||||
|
let allPages = await space.listPages();
|
||||||
|
// TODO: Parallelize?
|
||||||
|
for (let page of allPages) {
|
||||||
|
let pageData = await space.readPage(page.name);
|
||||||
|
await this.indexPage(
|
||||||
|
appEventDispatcher,
|
||||||
|
pageData.meta,
|
||||||
|
pageData.text,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async set(pageName: string, key: string, value: any) {
|
||||||
|
await this.pageIndex.put({
|
||||||
|
ck: constructKey(pageName, key),
|
||||||
|
page: pageName,
|
||||||
|
key: key,
|
||||||
|
value: value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async batchSet(pageName: string, kvs: KV[]) {
|
||||||
|
await this.pageIndex.bulkPut(
|
||||||
|
kvs.map(({ key, value }) => ({
|
||||||
|
ck: constructKey(pageName, key),
|
||||||
|
key: key,
|
||||||
|
page: pageName,
|
||||||
|
value: value,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(pageName: string, key: string): Promise<any | null> {
|
||||||
|
let result = await this.pageIndex.get({
|
||||||
|
ck: constructKey(pageName, key),
|
||||||
|
});
|
||||||
|
return result ? result.value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async scanPrefixForPage(
|
||||||
|
pageName: string,
|
||||||
|
keyPrefix: string
|
||||||
|
): Promise<{ key: string; value: any }[]> {
|
||||||
|
let results = await this.pageIndex
|
||||||
|
.where("ck")
|
||||||
|
.startsWith(constructKey(pageName, keyPrefix))
|
||||||
|
.toArray();
|
||||||
|
return results.map((result) => ({
|
||||||
|
key: cleanKey(pageName, result.key),
|
||||||
|
value: result.value,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async scanPrefixGlobal(
|
||||||
|
keyPrefix: string
|
||||||
|
): Promise<{ key: string; value: any }[]> {
|
||||||
|
let results = await this.pageIndex
|
||||||
|
.where("key")
|
||||||
|
.startsWith(keyPrefix)
|
||||||
|
.toArray();
|
||||||
|
return results.map((result) => ({
|
||||||
|
key: result.key,
|
||||||
|
value: result.value,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async deletePrefixForPage(pageName: string, keyPrefix: string) {
|
||||||
|
await this.pageIndex
|
||||||
|
.where("ck")
|
||||||
|
.startsWith(constructKey(pageName, keyPrefix))
|
||||||
|
.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(pageName: string, key: string) {
|
||||||
|
await this.pageIndex.delete(constructKey(pageName, key));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { safeRun } from "./util";
|
||||||
|
|
||||||
|
export interface IPageNavigator {
|
||||||
|
subscribe(pageLoadCallback: (pageName: string) => Promise<void>): void;
|
||||||
|
navigate(page: string): void;
|
||||||
|
getCurrentPage(): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodePageUrl(name: string): string {
|
||||||
|
return name.replaceAll(" ", "_");
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodePageUrl(url: string): string {
|
||||||
|
return url.replaceAll("_", " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PathPageNavigator implements IPageNavigator {
|
||||||
|
navigationResolve?: (value: undefined) => void;
|
||||||
|
async navigate(page: string) {
|
||||||
|
console.log("Pushing state", page);
|
||||||
|
window.history.pushState({ page: page }, page, `/${encodePageUrl(page)}`);
|
||||||
|
window.dispatchEvent(new PopStateEvent("popstate"));
|
||||||
|
await new Promise<undefined>((resolve) => {
|
||||||
|
this.navigationResolve = resolve;
|
||||||
|
});
|
||||||
|
this.navigationResolve = undefined;
|
||||||
|
}
|
||||||
|
subscribe(pageLoadCallback: (pageName: string) => Promise<void>): void {
|
||||||
|
const cb = () => {
|
||||||
|
console.log("State popped", this.getCurrentPage());
|
||||||
|
safeRun(async () => {
|
||||||
|
await pageLoadCallback(this.getCurrentPage());
|
||||||
|
if (this.navigationResolve) {
|
||||||
|
this.navigationResolve(undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
window.addEventListener("popstate", cb);
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentPage(): string {
|
||||||
|
return decodePageUrl(location.pathname.substring(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HashPageNavigator implements IPageNavigator {
|
||||||
|
navigationResolve?: (value: undefined) => void;
|
||||||
|
async navigate(page: string) {
|
||||||
|
location.hash = encodePageUrl(page);
|
||||||
|
await new Promise<undefined>((resolve) => {
|
||||||
|
this.navigationResolve = resolve;
|
||||||
|
});
|
||||||
|
this.navigationResolve = undefined;
|
||||||
|
}
|
||||||
|
subscribe(pageLoadCallback: (pageName: string) => Promise<void>): void {
|
||||||
|
const cb = () => {
|
||||||
|
safeRun(async () => {
|
||||||
|
await pageLoadCallback(this.getCurrentPage());
|
||||||
|
if (this.navigationResolve) {
|
||||||
|
this.navigationResolve(undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
window.addEventListener("hashchange", cb);
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
getCurrentPage(): string {
|
||||||
|
return decodePageUrl(location.hash.substring(1));
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,11 @@ import { styleTags } from "@codemirror/highlight";
|
||||||
import { MarkdownConfig, TaskList } from "@lezer/markdown";
|
import { MarkdownConfig, TaskList } from "@lezer/markdown";
|
||||||
import { commonmark, mkLang } from "./markdown/markdown";
|
import { commonmark, mkLang } from "./markdown/markdown";
|
||||||
import * as ct from "./customtags";
|
import * as ct from "./customtags";
|
||||||
|
import { pageLinkRegex } from "./constant";
|
||||||
|
|
||||||
|
const pageLinkRegexPrefix = new RegExp(
|
||||||
|
"^" + pageLinkRegex.toString().slice(1, -1)
|
||||||
|
);
|
||||||
|
|
||||||
const WikiLink: MarkdownConfig = {
|
const WikiLink: MarkdownConfig = {
|
||||||
defineNodes: ["WikiLink", "WikiLinkPage"],
|
defineNodes: ["WikiLink", "WikiLinkPage"],
|
||||||
|
@ -12,13 +17,13 @@ const WikiLink: MarkdownConfig = {
|
||||||
let match: RegExpMatchArray | null;
|
let match: RegExpMatchArray | null;
|
||||||
if (
|
if (
|
||||||
next != 91 /* '[' */ ||
|
next != 91 /* '[' */ ||
|
||||||
!(match = /^\[[^\]]+\]\]/.exec(cx.slice(pos + 1, cx.end)))
|
!(match = pageLinkRegexPrefix.exec(cx.slice(pos, cx.end)))
|
||||||
) {
|
) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return cx.addElement(
|
return cx.addElement(
|
||||||
cx.elt("WikiLink", pos, pos + match[0].length + 1, [
|
cx.elt("WikiLink", pos, pos + match[0].length + 1, [
|
||||||
cx.elt("WikiLinkPage", pos + 2, pos + match[0].length - 1),
|
cx.elt("WikiLinkPage", pos + 2, pos + match[0].length - 2),
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,6 +9,11 @@ export default function reducer(
|
||||||
case "page-loaded":
|
case "page-loaded":
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
allPages: state.allPages.map((pageMeta) =>
|
||||||
|
pageMeta.name === action.meta.name
|
||||||
|
? { ...pageMeta, lastOpened: new Date() }
|
||||||
|
: pageMeta
|
||||||
|
),
|
||||||
currentPage: action.meta,
|
currentPage: action.meta,
|
||||||
isSaved: true,
|
isSaved: true,
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,6 +4,7 @@ export interface Space {
|
||||||
listPages(): Promise<PageMeta[]>;
|
listPages(): Promise<PageMeta[]>;
|
||||||
readPage(name: string): Promise<{ text: string; meta: PageMeta }>;
|
readPage(name: string): Promise<{ text: string; meta: PageMeta }>;
|
||||||
writePage(name: string, text: string): Promise<PageMeta>;
|
writePage(name: string, text: string): Promise<PageMeta>;
|
||||||
|
deletePage(name: string): Promise<void>;
|
||||||
getPageMeta(name: string): Promise<PageMeta>;
|
getPageMeta(name: string): Promise<PageMeta>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +13,7 @@ export class HttpRemoteSpace implements Space {
|
||||||
constructor(url: string) {
|
constructor(url: string) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
async listPages(): Promise<PageMeta[]> {
|
async listPages(): Promise<PageMeta[]> {
|
||||||
let req = await fetch(this.url, {
|
let req = await fetch(this.url, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
@ -22,6 +24,7 @@ export class HttpRemoteSpace implements Space {
|
||||||
lastModified: new Date(meta.lastModified),
|
lastModified: new Date(meta.lastModified),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async readPage(name: string): Promise<{ text: string; meta: PageMeta }> {
|
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,6 +37,7 @@ export class HttpRemoteSpace implements Space {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async writePage(name: string, text: string): Promise<PageMeta> {
|
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",
|
||||||
|
@ -47,6 +51,15 @@ export class HttpRemoteSpace implements Space {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deletePage(name: string): Promise<void> {
|
||||||
|
let req = await fetch(`${this.url}/${name}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
if (req.status !== 200) {
|
||||||
|
throw Error(`Failed to delete page: ${req.statusText}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async getPageMeta(name: string): Promise<PageMeta> {
|
async getPageMeta(name: string): Promise<PageMeta> {
|
||||||
let req = await fetch(`${this.url}/${name}`, {
|
let req = await fetch(`${this.url}/${name}`, {
|
||||||
method: "OPTIONS",
|
method: "OPTIONS",
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Editor } from "../editor";
|
import { Editor } from "../editor";
|
||||||
import { syntaxTree } from "@codemirror/language";
|
import { syntaxTree } from "@codemirror/language";
|
||||||
import { Transaction } from "@codemirror/state";
|
import { Transaction } from "@codemirror/state";
|
||||||
|
import { PageMeta } from "../types";
|
||||||
|
|
||||||
type SyntaxNode = {
|
type SyntaxNode = {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -26,6 +27,9 @@ function ensureAnchor(expr: any, start: boolean) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (editor: Editor) => ({
|
export default (editor: Editor) => ({
|
||||||
|
"editor.getCurrentPage": (): PageMeta => {
|
||||||
|
return editor.currentPage!;
|
||||||
|
},
|
||||||
"editor.getText": () => {
|
"editor.getText": () => {
|
||||||
return editor.editorView?.state.sliceDoc();
|
return editor.editorView?.state.sliceDoc();
|
||||||
},
|
},
|
||||||
|
@ -120,4 +124,7 @@ export default (editor: Editor) => ({
|
||||||
"editor.dispatch": (change: Transaction) => {
|
"editor.dispatch": (change: Transaction) => {
|
||||||
editor.editorView!.dispatch(change);
|
editor.editorView!.dispatch(change);
|
||||||
},
|
},
|
||||||
|
"editor.prompt": (message: string): string | null => {
|
||||||
|
return prompt(message);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { Indexer, KV } from "../indexer";
|
||||||
|
|
||||||
|
export default (indexer: Indexer) => ({
|
||||||
|
"indexer.scanPrefixForPage": async (pageName: string, keyPrefix: string) => {
|
||||||
|
return await indexer.scanPrefixForPage(pageName, keyPrefix);
|
||||||
|
},
|
||||||
|
"indexer.scanPrefixGlobal": async (keyPrefix: string) => {
|
||||||
|
return await indexer.scanPrefixGlobal(keyPrefix);
|
||||||
|
},
|
||||||
|
"indexer.get": async (pageName: string, key: string): Promise<any> => {
|
||||||
|
return await indexer.get(pageName, key);
|
||||||
|
},
|
||||||
|
"indexer.set": async (pageName: string, key: string, value: any) => {
|
||||||
|
await indexer.set(pageName, key, value);
|
||||||
|
},
|
||||||
|
"indexer.batchSet": async (pageName: string, kvs: KV[]) => {
|
||||||
|
await indexer.batchSet(pageName, kvs);
|
||||||
|
},
|
||||||
|
"indexer.delete": async (pageName: string, key: string) => {
|
||||||
|
await indexer.delete(pageName, key);
|
||||||
|
},
|
||||||
|
});
|
|
@ -5,12 +5,30 @@ export default (editor: Editor) => ({
|
||||||
"space.listPages": (): PageMeta[] => {
|
"space.listPages": (): PageMeta[] => {
|
||||||
return editor.viewState.allPages;
|
return editor.viewState.allPages;
|
||||||
},
|
},
|
||||||
|
"space.reloadPageList": async () => {
|
||||||
|
await editor.loadPageList();
|
||||||
|
},
|
||||||
|
"space.reindex": async () => {
|
||||||
|
await editor.indexer.reindexSpace(editor.space, editor);
|
||||||
|
},
|
||||||
"space.readPage": async (
|
"space.readPage": async (
|
||||||
name: string
|
name: string
|
||||||
): Promise<{ text: string; meta: PageMeta }> => {
|
): Promise<{ text: string; meta: PageMeta }> => {
|
||||||
return await editor.fs.readPage(name);
|
return await editor.space.readPage(name);
|
||||||
},
|
},
|
||||||
"space.writePage": async (name: string, text: string): Promise<PageMeta> => {
|
"space.writePage": async (name: string, text: string): Promise<PageMeta> => {
|
||||||
return await editor.fs.writePage(name, text);
|
return await editor.space.writePage(name, text);
|
||||||
|
},
|
||||||
|
"space.deletePage": async (name: string) => {
|
||||||
|
console.log("Clearing page index", name);
|
||||||
|
await editor.indexer.clearPageIndexForPage(name);
|
||||||
|
// If we're deleting the current page, navigate to the start page
|
||||||
|
if (editor.currentPage?.name === name) {
|
||||||
|
await editor.navigate("start");
|
||||||
|
}
|
||||||
|
// Remove page from open pages in editor
|
||||||
|
editor.openPages.delete(name);
|
||||||
|
console.log("Deleting page");
|
||||||
|
await editor.space.deletePage(name);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,6 +4,7 @@ export type PageMeta = {
|
||||||
name: string;
|
name: string;
|
||||||
lastModified: Date;
|
lastModified: Date;
|
||||||
created?: boolean;
|
created?: boolean;
|
||||||
|
lastOpened?: Date;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AppCommand = {
|
export type AppCommand = {
|
||||||
|
|
|
@ -1186,6 +1186,11 @@ detect-libc@^1.0.3:
|
||||||
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
|
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
|
||||||
integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
|
integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
|
||||||
|
|
||||||
|
dexie@^3.2.1:
|
||||||
|
version "3.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/dexie/-/dexie-3.2.1.tgz#ef21456d725e700c1ab7ac4307896e4fdabaf753"
|
||||||
|
integrity sha512-Y8oz3t2XC9hvjkP35B5I8rUkKKwM36GGRjWQCMjzIYScg7W+GHKDXobSYswkisW7CxL1/tKQtggMDsiWqDUc1g==
|
||||||
|
|
||||||
dom-serializer@^1.0.1:
|
dom-serializer@^1.0.1:
|
||||||
version "1.3.2"
|
version "1.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91"
|
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91"
|
||||||
|
|
Loading…
Reference in New Issue