2022-04-05 15:02:17 +00:00
|
|
|
import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
|
|
|
|
import { closeBrackets, closeBracketsKeymap } from "@codemirror/closebrackets";
|
|
|
|
import { indentWithTab, standardKeymap } from "@codemirror/commands";
|
|
|
|
import { history, historyKeymap } from "@codemirror/history";
|
|
|
|
import { bracketMatching } from "@codemirror/matchbrackets";
|
|
|
|
import { searchKeymap } from "@codemirror/search";
|
|
|
|
import { EditorSelection, EditorState } from "@codemirror/state";
|
2022-03-20 08:56:28 +00:00
|
|
|
import {
|
2022-04-04 13:25:07 +00:00
|
|
|
drawSelection,
|
|
|
|
dropCursor,
|
|
|
|
EditorView,
|
|
|
|
highlightSpecialChars,
|
|
|
|
KeyBinding,
|
|
|
|
keymap,
|
|
|
|
ViewPlugin,
|
2022-04-05 15:02:17 +00:00
|
|
|
ViewUpdate
|
2022-03-20 08:56:28 +00:00
|
|
|
} from "@codemirror/view";
|
2022-04-05 15:02:17 +00:00
|
|
|
import React, { useEffect, useReducer } from "react";
|
2022-03-20 08:56:28 +00:00
|
|
|
import ReactDOM from "react-dom";
|
2022-04-05 15:02:17 +00:00
|
|
|
import { createSandbox as createIFrameSandbox } from "../plugos/environments/webworker_sandbox";
|
|
|
|
import { AppEvent, AppEventDispatcher, ClickEvent } from "./app_event";
|
2022-03-20 08:56:28 +00:00
|
|
|
import * as commands from "./commands";
|
2022-04-05 15:02:17 +00:00
|
|
|
import { CommandPalette } from "./components/command_palette";
|
|
|
|
import { PageNavigator } from "./components/page_navigator";
|
|
|
|
import { TopBar } from "./components/top_bar";
|
|
|
|
import { lineWrapper } from "./line_wrapper";
|
|
|
|
import { markdown } from "./markdown";
|
|
|
|
import { PathPageNavigator } from "./navigator";
|
2022-03-20 08:56:28 +00:00
|
|
|
import customMarkDown from "./parser";
|
|
|
|
import reducer from "./reducer";
|
2022-04-05 15:02:17 +00:00
|
|
|
import { smartQuoteKeymap } from "./smart_quotes";
|
2022-04-08 15:46:09 +00:00
|
|
|
import { Space } from "../common/spaces/space";
|
2022-03-20 08:56:28 +00:00
|
|
|
import customMarkdownStyle from "./style";
|
2022-04-05 15:02:17 +00:00
|
|
|
import { editorSyscalls } from "./syscalls/editor";
|
2022-04-07 12:04:50 +00:00
|
|
|
import { indexerSyscalls } from "./syscalls";
|
2022-04-05 15:02:17 +00:00
|
|
|
import { spaceSyscalls } from "./syscalls/space";
|
|
|
|
import { Action, AppViewState, initialViewState } from "./types";
|
|
|
|
import { SilverBulletHooks } from "../common/manifest";
|
|
|
|
import { safeRun, throttle } from "./util";
|
|
|
|
import { System } from "../plugos/system";
|
|
|
|
import { EventHook } from "../plugos/hooks/event";
|
|
|
|
import { systemSyscalls } from "./syscalls/system";
|
|
|
|
import { Panel } from "./components/panel";
|
|
|
|
import { CommandHook } from "./hooks/command";
|
|
|
|
import { SlashCommandHook } from "./hooks/slash_command";
|
|
|
|
import { CompleterHook } from "./hooks/completer";
|
|
|
|
import { pasteLinkExtension } from "./editor_paste";
|
|
|
|
import { markdownSyscalls } from "../common/syscalls/markdown";
|
|
|
|
import { clientStoreSyscalls } from "./syscalls/clientStore";
|
|
|
|
import { StatusBar } from "./components/status_bar";
|
2022-04-03 16:12:16 +00:00
|
|
|
|
2022-03-20 08:56:28 +00:00
|
|
|
class PageState {
|
|
|
|
scrollTop: number;
|
|
|
|
selection: EditorSelection;
|
|
|
|
|
|
|
|
constructor(scrollTop: number, selection: EditorSelection) {
|
|
|
|
this.scrollTop = scrollTop;
|
|
|
|
this.selection = selection;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-07 13:21:30 +00:00
|
|
|
const saveInterval = 1000;
|
2022-03-31 12:28:07 +00:00
|
|
|
|
2022-03-20 08:56:28 +00:00
|
|
|
export class Editor implements AppEventDispatcher {
|
2022-03-29 10:13:46 +00:00
|
|
|
readonly commandHook: CommandHook;
|
|
|
|
readonly slashCommandHook: SlashCommandHook;
|
|
|
|
readonly completerHook: CompleterHook;
|
2022-03-23 14:41:12 +00:00
|
|
|
openPages = new Map<string, PageState>();
|
2022-03-20 08:56:28 +00:00
|
|
|
editorView?: EditorView;
|
|
|
|
viewState: AppViewState;
|
|
|
|
viewDispatch: React.Dispatch<Action>;
|
2022-04-07 13:21:30 +00:00
|
|
|
space: Space;
|
2022-03-28 13:25:05 +00:00
|
|
|
pageNavigator: PathPageNavigator;
|
2022-03-29 09:21:32 +00:00
|
|
|
eventHook: EventHook;
|
2022-04-03 16:42:12 +00:00
|
|
|
saveTimeout: any;
|
2022-04-04 13:25:07 +00:00
|
|
|
debouncedUpdateEvent = throttle(() => {
|
|
|
|
this.eventHook.dispatchEvent("editor:updated");
|
|
|
|
}, 1000);
|
2022-04-03 16:42:12 +00:00
|
|
|
private system = new System<SilverBulletHooks>("client");
|
2022-03-20 08:56:28 +00:00
|
|
|
|
2022-04-07 13:21:30 +00:00
|
|
|
constructor(space: Space, parent: Element) {
|
2022-03-20 08:56:28 +00:00
|
|
|
this.space = space;
|
|
|
|
this.viewState = initialViewState;
|
|
|
|
this.viewDispatch = () => {};
|
2022-03-25 11:03:06 +00:00
|
|
|
|
2022-03-29 09:21:32 +00:00
|
|
|
// Event hook
|
|
|
|
this.eventHook = new EventHook();
|
|
|
|
this.system.addHook(this.eventHook);
|
|
|
|
|
|
|
|
// Command hook
|
|
|
|
this.commandHook = new CommandHook();
|
|
|
|
this.commandHook.on({
|
|
|
|
commandsUpdated: (commandMap) => {
|
|
|
|
this.viewDispatch({
|
|
|
|
type: "update-commands",
|
|
|
|
commands: commandMap,
|
|
|
|
});
|
|
|
|
},
|
|
|
|
});
|
|
|
|
this.system.addHook(this.commandHook);
|
|
|
|
|
|
|
|
// Slash command hook
|
|
|
|
this.slashCommandHook = new SlashCommandHook(this);
|
|
|
|
this.system.addHook(this.slashCommandHook);
|
2022-03-25 11:03:06 +00:00
|
|
|
|
2022-03-29 10:13:46 +00:00
|
|
|
// Completer hook
|
|
|
|
this.completerHook = new CompleterHook();
|
|
|
|
this.system.addHook(this.completerHook);
|
|
|
|
|
2022-03-20 08:56:28 +00:00
|
|
|
this.render(parent);
|
|
|
|
this.editorView = new EditorView({
|
2022-03-31 12:28:07 +00:00
|
|
|
state: this.createEditorState("", ""),
|
2022-03-20 08:56:28 +00:00
|
|
|
parent: document.getElementById("editor")!,
|
|
|
|
});
|
|
|
|
this.pageNavigator = new PathPageNavigator();
|
2022-03-21 14:21:34 +00:00
|
|
|
|
2022-04-03 16:42:12 +00:00
|
|
|
this.system.registerSyscalls([], editorSyscalls(this));
|
|
|
|
this.system.registerSyscalls([], spaceSyscalls(this));
|
|
|
|
this.system.registerSyscalls([], indexerSyscalls(this.space));
|
|
|
|
this.system.registerSyscalls([], systemSyscalls(this.space));
|
|
|
|
this.system.registerSyscalls([], markdownSyscalls());
|
2022-04-04 13:25:07 +00:00
|
|
|
this.system.registerSyscalls([], clientStoreSyscalls());
|
2022-04-03 16:42:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
get currentPage(): string | undefined {
|
|
|
|
return this.viewState.currentPage;
|
2022-03-20 08:56:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async init() {
|
|
|
|
this.focus();
|
|
|
|
|
2022-03-28 13:25:05 +00:00
|
|
|
this.pageNavigator.subscribe(async (pageName, pos) => {
|
2022-03-20 08:56:28 +00:00
|
|
|
console.log("Now navigating to", pageName);
|
|
|
|
|
|
|
|
if (!this.editorView) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
await this.loadPage(pageName);
|
2022-03-28 13:25:05 +00:00
|
|
|
if (pos) {
|
|
|
|
this.editorView.dispatch({
|
|
|
|
selection: { anchor: pos },
|
|
|
|
});
|
|
|
|
}
|
2022-03-20 08:56:28 +00:00
|
|
|
});
|
|
|
|
|
2022-03-31 15:25:34 +00:00
|
|
|
let throttledRebuildEditorState = throttle(() => {
|
|
|
|
this.rebuildEditorState();
|
|
|
|
}, 100);
|
|
|
|
|
2022-03-20 08:56:28 +00:00
|
|
|
this.space.on({
|
2022-03-31 12:28:07 +00:00
|
|
|
pageCreated: (meta) => {
|
|
|
|
console.log("Page created", meta);
|
|
|
|
},
|
|
|
|
pageDeleted: (meta) => {
|
|
|
|
console.log("Page delete", meta);
|
2022-03-20 08:56:28 +00:00
|
|
|
},
|
|
|
|
pageChanged: (meta) => {
|
|
|
|
if (this.currentPage === meta.name) {
|
|
|
|
console.log("Page changed on disk, reloading");
|
|
|
|
this.flashNotification("Page changed on disk, reloading");
|
|
|
|
this.reloadPage();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
pageListUpdated: (pages) => {
|
|
|
|
this.viewDispatch({
|
|
|
|
type: "pages-listed",
|
|
|
|
pages: pages,
|
|
|
|
});
|
|
|
|
},
|
2022-03-23 14:41:12 +00:00
|
|
|
plugLoaded: (plugName, plug) => {
|
2022-03-21 14:21:34 +00:00
|
|
|
safeRun(async () => {
|
2022-03-23 14:41:12 +00:00
|
|
|
console.log("Plug load", plugName);
|
2022-03-25 11:03:06 +00:00
|
|
|
await this.system.load(plugName, plug, createIFrameSandbox);
|
2022-03-31 15:25:34 +00:00
|
|
|
throttledRebuildEditorState();
|
2022-03-21 14:21:34 +00:00
|
|
|
});
|
|
|
|
},
|
2022-03-23 14:41:12 +00:00
|
|
|
plugUnloaded: (plugName) => {
|
2022-03-21 14:21:34 +00:00
|
|
|
safeRun(async () => {
|
2022-03-23 14:41:12 +00:00
|
|
|
console.log("Plug unload", plugName);
|
2022-03-21 14:21:34 +00:00
|
|
|
await this.system.unload(plugName);
|
2022-03-31 15:25:34 +00:00
|
|
|
throttledRebuildEditorState();
|
2022-03-21 14:21:34 +00:00
|
|
|
});
|
|
|
|
},
|
2022-03-20 08:56:28 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
if (this.pageNavigator.getCurrentPage() === "") {
|
2022-03-29 09:21:32 +00:00
|
|
|
await this.pageNavigator.navigate("start");
|
2022-03-20 08:56:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-31 12:28:07 +00:00
|
|
|
async save(immediate: boolean = false): Promise<void> {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
if (!this.viewState.unsavedChanges) {
|
|
|
|
return resolve();
|
|
|
|
}
|
|
|
|
if (this.saveTimeout) {
|
|
|
|
clearTimeout(this.saveTimeout);
|
|
|
|
}
|
|
|
|
this.saveTimeout = setTimeout(
|
|
|
|
() => {
|
|
|
|
if (this.currentPage) {
|
|
|
|
console.log("Saving page", this.currentPage);
|
|
|
|
this.space
|
|
|
|
.writePage(
|
|
|
|
this.currentPage,
|
|
|
|
this.editorView!.state.sliceDoc(0),
|
|
|
|
true
|
|
|
|
)
|
|
|
|
.then(() => {
|
|
|
|
this.viewDispatch({ type: "page-saved" });
|
|
|
|
resolve();
|
|
|
|
})
|
|
|
|
.catch(reject);
|
|
|
|
} else {
|
|
|
|
resolve();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
immediate ? 0 : saveInterval
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-03-20 08:56:28 +00:00
|
|
|
flashNotification(message: string) {
|
|
|
|
let id = Math.floor(Math.random() * 1000000);
|
|
|
|
this.viewDispatch({
|
|
|
|
type: "show-notification",
|
|
|
|
notification: {
|
|
|
|
id: id,
|
|
|
|
message: message,
|
|
|
|
date: new Date(),
|
|
|
|
},
|
|
|
|
});
|
|
|
|
setTimeout(() => {
|
|
|
|
this.viewDispatch({
|
|
|
|
type: "dismiss-notification",
|
|
|
|
id: id,
|
|
|
|
});
|
|
|
|
}, 2000);
|
|
|
|
}
|
|
|
|
|
2022-03-29 10:13:46 +00:00
|
|
|
async dispatchAppEvent(name: AppEvent, data?: any): Promise<void> {
|
2022-03-29 09:21:32 +00:00
|
|
|
return this.eventHook.dispatchEvent(name, data);
|
2022-03-20 08:56:28 +00:00
|
|
|
}
|
|
|
|
|
2022-03-31 12:28:07 +00:00
|
|
|
createEditorState(pageName: string, text: string): EditorState {
|
2022-03-20 08:56:28 +00:00
|
|
|
let commandKeyBindings: KeyBinding[] = [];
|
2022-03-29 09:21:32 +00:00
|
|
|
for (let def of this.commandHook.editorCommands.values()) {
|
2022-03-20 08:56:28 +00:00
|
|
|
if (def.command.key) {
|
|
|
|
commandKeyBindings.push({
|
|
|
|
key: def.command.key,
|
|
|
|
mac: def.command.mac,
|
|
|
|
run: (): boolean => {
|
|
|
|
Promise.resolve()
|
2022-03-28 13:25:05 +00:00
|
|
|
.then(def.run)
|
|
|
|
.catch((e: any) => {
|
|
|
|
console.error(e);
|
|
|
|
this.flashNotification(`Error running command: ${e.message}`);
|
|
|
|
});
|
2022-03-20 08:56:28 +00:00
|
|
|
return true;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2022-03-31 12:28:07 +00:00
|
|
|
const editor = this;
|
2022-03-20 08:56:28 +00:00
|
|
|
return EditorState.create({
|
2022-03-31 12:28:07 +00:00
|
|
|
doc: text,
|
2022-03-20 08:56:28 +00:00
|
|
|
extensions: [
|
|
|
|
highlightSpecialChars(),
|
|
|
|
history(),
|
|
|
|
drawSelection(),
|
|
|
|
dropCursor(),
|
|
|
|
customMarkdownStyle,
|
|
|
|
bracketMatching(),
|
|
|
|
closeBrackets(),
|
|
|
|
autocompletion({
|
|
|
|
override: [
|
2022-03-29 10:13:46 +00:00
|
|
|
this.completerHook.plugCompleter.bind(this.completerHook),
|
2022-03-29 09:21:32 +00:00
|
|
|
this.slashCommandHook.slashCommandCompleter.bind(
|
|
|
|
this.slashCommandHook
|
|
|
|
),
|
2022-03-20 08:56:28 +00:00
|
|
|
],
|
|
|
|
}),
|
|
|
|
EditorView.lineWrapping,
|
|
|
|
lineWrapper([
|
|
|
|
{ selector: "ATXHeading1", class: "line-h1" },
|
|
|
|
{ selector: "ATXHeading2", class: "line-h2" },
|
|
|
|
{ selector: "ATXHeading3", class: "line-h3" },
|
|
|
|
{ selector: "ListItem", class: "line-li", nesting: true },
|
|
|
|
{ selector: "Blockquote", class: "line-blockquote" },
|
|
|
|
{ selector: "Task", class: "line-task" },
|
|
|
|
{ selector: "CodeBlock", class: "line-code" },
|
|
|
|
{ selector: "FencedCode", class: "line-fenced-code" },
|
|
|
|
{ selector: "Comment", class: "line-comment" },
|
|
|
|
{ selector: "BulletList", class: "line-ul" },
|
|
|
|
{ selector: "OrderedList", class: "line-ol" },
|
|
|
|
]),
|
|
|
|
keymap.of([
|
|
|
|
...smartQuoteKeymap,
|
|
|
|
...closeBracketsKeymap,
|
|
|
|
...standardKeymap,
|
|
|
|
...searchKeymap,
|
|
|
|
...historyKeymap,
|
|
|
|
...completionKeymap,
|
|
|
|
indentWithTab,
|
|
|
|
...commandKeyBindings,
|
|
|
|
{
|
|
|
|
key: "Ctrl-b",
|
|
|
|
mac: "Cmd-b",
|
|
|
|
run: commands.insertMarker("**"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
key: "Ctrl-i",
|
|
|
|
mac: "Cmd-i",
|
|
|
|
run: commands.insertMarker("_"),
|
|
|
|
},
|
2022-04-04 16:33:13 +00:00
|
|
|
// {
|
|
|
|
// key: "Ctrl-p",
|
|
|
|
// mac: "Cmd-p",
|
|
|
|
// run: (): boolean => {
|
|
|
|
// window.open(location.href, "_blank")!.focus();
|
|
|
|
// return true;
|
|
|
|
// },
|
|
|
|
// },
|
2022-03-20 08:56:28 +00:00
|
|
|
{
|
|
|
|
key: "Ctrl-k",
|
|
|
|
mac: "Cmd-k",
|
|
|
|
run: (): boolean => {
|
|
|
|
this.viewDispatch({ type: "start-navigate" });
|
2022-03-31 12:28:07 +00:00
|
|
|
this.space.updatePageListAsync();
|
2022-03-20 08:56:28 +00:00
|
|
|
return true;
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
key: "Ctrl-.",
|
|
|
|
mac: "Cmd-.",
|
|
|
|
run: (): boolean => {
|
|
|
|
this.viewDispatch({
|
|
|
|
type: "show-palette",
|
|
|
|
});
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
},
|
2022-04-05 15:02:17 +00:00
|
|
|
{
|
|
|
|
key: "Ctrl-l",
|
|
|
|
mac: "Cmd-l",
|
|
|
|
run: (): boolean => {
|
|
|
|
this.editorView?.dispatch({
|
|
|
|
effects: [
|
|
|
|
EditorView.scrollIntoView(
|
|
|
|
this.editorView.state.selection.main.anchor,
|
|
|
|
{
|
|
|
|
y: "center",
|
|
|
|
}
|
|
|
|
),
|
|
|
|
],
|
|
|
|
});
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
},
|
2022-03-20 08:56:28 +00:00
|
|
|
]),
|
2022-03-30 13:16:22 +00:00
|
|
|
|
2022-03-20 08:56:28 +00:00
|
|
|
EditorView.domEventHandlers({
|
|
|
|
click: (event: MouseEvent, view: EditorView) => {
|
|
|
|
safeRun(async () => {
|
|
|
|
let clickEvent: ClickEvent = {
|
2022-03-28 13:25:05 +00:00
|
|
|
page: pageName,
|
2022-03-20 08:56:28 +00:00
|
|
|
ctrlKey: event.ctrlKey,
|
|
|
|
metaKey: event.metaKey,
|
|
|
|
altKey: event.altKey,
|
|
|
|
pos: view.posAtCoords(event)!,
|
|
|
|
};
|
|
|
|
await this.dispatchAppEvent("page:click", clickEvent);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
}),
|
2022-03-31 12:28:07 +00:00
|
|
|
ViewPlugin.fromClass(
|
|
|
|
class {
|
|
|
|
update(update: ViewUpdate): void {
|
|
|
|
if (update.docChanged) {
|
|
|
|
editor.viewDispatch({ type: "page-changed" });
|
2022-04-04 13:25:07 +00:00
|
|
|
editor.debouncedUpdateEvent();
|
2022-03-31 12:28:07 +00:00
|
|
|
editor.save();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
),
|
2022-03-30 13:16:22 +00:00
|
|
|
pasteLinkExtension,
|
2022-03-20 08:56:28 +00:00
|
|
|
markdown({
|
|
|
|
base: customMarkDown,
|
|
|
|
}),
|
|
|
|
],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-03-31 15:25:34 +00:00
|
|
|
rebuildEditorState() {
|
|
|
|
const editorView = this.editorView;
|
|
|
|
if (editorView && this.currentPage) {
|
|
|
|
editorView.setState(
|
|
|
|
this.createEditorState(this.currentPage, editorView.state.sliceDoc())
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-20 08:56:28 +00:00
|
|
|
reloadPage() {
|
|
|
|
console.log("Reloading page");
|
|
|
|
safeRun(async () => {
|
2022-03-31 12:28:07 +00:00
|
|
|
clearTimeout(this.saveTimeout);
|
2022-03-20 08:56:28 +00:00
|
|
|
await this.loadPage(this.currentPage!);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
focus() {
|
|
|
|
this.editorView!.focus();
|
|
|
|
}
|
|
|
|
|
2022-03-28 13:25:05 +00:00
|
|
|
async navigate(name: string, pos?: number) {
|
|
|
|
await this.pageNavigator.navigate(name, pos);
|
2022-03-20 08:56:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async loadPage(pageName: string) {
|
|
|
|
const editorView = this.editorView;
|
|
|
|
if (!editorView) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Persist current page state and nicely close page
|
|
|
|
if (this.currentPage) {
|
2022-04-05 15:02:17 +00:00
|
|
|
let pageState = this.openPages.get(this.currentPage);
|
2022-03-20 08:56:28 +00:00
|
|
|
if (pageState) {
|
|
|
|
pageState.selection = this.editorView!.state.selection;
|
2022-04-08 15:46:09 +00:00
|
|
|
pageState.scrollTop = this.editorView!.scrollDOM.scrollTop;
|
2022-04-05 15:02:17 +00:00
|
|
|
// console.log("Saved pageState", this.currentPage, pageState);
|
2022-03-20 08:56:28 +00:00
|
|
|
}
|
2022-03-31 12:28:07 +00:00
|
|
|
this.space.unwatchPage(this.currentPage);
|
|
|
|
await this.save(true);
|
2022-03-20 08:56:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch next page to open
|
2022-04-06 13:39:20 +00:00
|
|
|
let doc;
|
|
|
|
try {
|
|
|
|
doc = await this.space.readPage(pageName);
|
|
|
|
} catch (e: any) {
|
|
|
|
// Not found, new page
|
|
|
|
console.log("Creating new page", pageName);
|
|
|
|
doc = {
|
|
|
|
text: "",
|
|
|
|
meta: { name: pageName, lastModified: 0 },
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-03-31 12:28:07 +00:00
|
|
|
let editorState = this.createEditorState(pageName, doc.text);
|
2022-03-20 08:56:28 +00:00
|
|
|
let pageState = this.openPages.get(pageName);
|
|
|
|
editorView.setState(editorState);
|
|
|
|
if (!pageState) {
|
|
|
|
pageState = new PageState(0, editorState.selection);
|
|
|
|
this.openPages.set(pageName, pageState!);
|
|
|
|
editorView.dispatch({
|
|
|
|
selection: { anchor: 0 },
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// Restore state
|
2022-04-05 15:02:17 +00:00
|
|
|
// console.log("Restoring selection state", pageState);
|
2022-03-20 08:56:28 +00:00
|
|
|
editorView.dispatch({
|
|
|
|
selection: pageState.selection,
|
|
|
|
});
|
2022-04-08 15:46:09 +00:00
|
|
|
editorView.scrollDOM.scrollTop = pageState!.scrollTop;
|
2022-03-20 08:56:28 +00:00
|
|
|
}
|
|
|
|
|
2022-03-31 12:28:07 +00:00
|
|
|
this.space.watchPage(pageName);
|
|
|
|
|
2022-03-20 08:56:28 +00:00
|
|
|
this.viewDispatch({
|
|
|
|
type: "page-loaded",
|
|
|
|
name: pageName,
|
|
|
|
});
|
2022-04-04 13:25:07 +00:00
|
|
|
|
|
|
|
await this.eventHook.dispatchEvent("editor:pageSwitched");
|
2022-03-20 08:56:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ViewComponent(): React.ReactElement {
|
|
|
|
const [viewState, dispatch] = useReducer(reducer, initialViewState);
|
|
|
|
this.viewState = viewState;
|
|
|
|
this.viewDispatch = dispatch;
|
|
|
|
|
|
|
|
let editor = this;
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (viewState.currentPage) {
|
|
|
|
document.title = viewState.currentPage;
|
|
|
|
}
|
|
|
|
}, [viewState.currentPage]);
|
|
|
|
|
|
|
|
return (
|
2022-04-04 16:33:13 +00:00
|
|
|
<>
|
2022-03-20 08:56:28 +00:00
|
|
|
{viewState.showPageNavigator && (
|
|
|
|
<PageNavigator
|
|
|
|
allPages={viewState.allPages}
|
|
|
|
currentPage={this.currentPage}
|
|
|
|
onNavigate={(page) => {
|
|
|
|
dispatch({ type: "stop-navigate" });
|
|
|
|
editor.focus();
|
|
|
|
if (page) {
|
|
|
|
safeRun(async () => {
|
2022-03-29 09:21:32 +00:00
|
|
|
await editor.navigate(page);
|
2022-03-20 08:56:28 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
{viewState.showCommandPalette && (
|
|
|
|
<CommandPalette
|
|
|
|
onTrigger={(cmd) => {
|
|
|
|
dispatch({ type: "hide-palette" });
|
|
|
|
editor!.focus();
|
|
|
|
if (cmd) {
|
2022-03-28 13:25:05 +00:00
|
|
|
cmd.run().catch((e) => {
|
|
|
|
console.error("Error running command", e);
|
2022-03-20 08:56:28 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
commands={viewState.commands}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
<TopBar
|
|
|
|
pageName={viewState.currentPage}
|
|
|
|
notifications={viewState.notifications}
|
2022-03-31 12:28:07 +00:00
|
|
|
unsavedChanges={viewState.unsavedChanges}
|
2022-03-20 08:56:28 +00:00
|
|
|
onClick={() => {
|
|
|
|
dispatch({ type: "start-navigate" });
|
|
|
|
}}
|
2022-04-04 16:33:13 +00:00
|
|
|
rhs={
|
|
|
|
!!viewState.showRHS && (
|
|
|
|
<div className="panel" style={{ flex: viewState.showRHS }} />
|
|
|
|
)
|
|
|
|
}
|
|
|
|
lhs={
|
|
|
|
!!viewState.showLHS && (
|
|
|
|
<div className="panel" style={{ flex: viewState.showLHS }} />
|
|
|
|
)
|
|
|
|
}
|
2022-03-20 08:56:28 +00:00
|
|
|
/>
|
2022-04-04 16:33:13 +00:00
|
|
|
<div id="main">
|
|
|
|
{!!viewState.showLHS && (
|
|
|
|
<Panel html={viewState.lhsHTML} flex={viewState.showLHS} />
|
|
|
|
)}
|
|
|
|
<div id="editor" />
|
|
|
|
{!!viewState.showRHS && (
|
|
|
|
<Panel html={viewState.rhsHTML} flex={viewState.showRHS} />
|
|
|
|
)}
|
|
|
|
</div>
|
2022-04-04 13:25:07 +00:00
|
|
|
<StatusBar editorView={editor.editorView} />
|
2022-04-04 16:33:13 +00:00
|
|
|
</>
|
2022-03-20 08:56:28 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
render(container: ReactDOM.Container) {
|
|
|
|
const ViewComponent = this.ViewComponent.bind(this);
|
|
|
|
ReactDOM.render(<ViewComponent />, container);
|
|
|
|
}
|
|
|
|
}
|