Editor refactor: extract UI
This commit is contained in:
parent
c5849f881b
commit
e92ed2c5be
@ -1,5 +1,5 @@
|
||||
import { safeRun } from "../common/util.ts";
|
||||
import { Editor } from "./editor.tsx";
|
||||
import { Editor } from "./editor.ts";
|
||||
|
||||
safeRun(async () => {
|
||||
console.log("Booting");
|
||||
|
@ -10,7 +10,7 @@ import assetSyscalls from "../plugos/syscalls/asset.ts";
|
||||
import { eventSyscalls } from "../plugos/syscalls/event.ts";
|
||||
import { storeSyscalls } from "../plugos/syscalls/store.dexie_browser.ts";
|
||||
import { SysCallMapping, System } from "../plugos/system.ts";
|
||||
import type { Editor } from "./editor.tsx";
|
||||
import type { Editor } from "./editor.ts";
|
||||
import { CodeWidgetHook } from "./hooks/code_widget.ts";
|
||||
import { CommandHook } from "./hooks/command.ts";
|
||||
import { SlashCommandHook } from "./hooks/slash_command.ts";
|
||||
@ -74,7 +74,7 @@ export class ClientSystem {
|
||||
this.commandHook = new CommandHook();
|
||||
this.commandHook.on({
|
||||
commandsUpdated: (commandMap) => {
|
||||
this.editor.viewDispatch({
|
||||
this.editor.ui.viewDispatch({
|
||||
type: "update-commands",
|
||||
commands: commandMap,
|
||||
});
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
syntaxTree,
|
||||
WidgetType,
|
||||
} from "../deps.ts";
|
||||
import { Editor } from "../editor.tsx";
|
||||
import { Editor } from "../editor.ts";
|
||||
import { decoratorStateField, isCursorInRange } from "./util.ts";
|
||||
|
||||
type AdmonitionType = "note" | "warning";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { ClickEvent } from "../../plug-api/app_event.ts";
|
||||
import type { Extension } from "../deps.ts";
|
||||
import type { Editor } from "../editor.tsx";
|
||||
import type { Editor } from "../editor.ts";
|
||||
import { blockquotePlugin } from "./block_quote.ts";
|
||||
import { admonitionPlugin } from "./admonition.ts";
|
||||
import { directivePlugin } from "./directive.ts";
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { commandLinkRegex } from "../../common/markdown_parser/parser.ts";
|
||||
import { ClickEvent } from "$sb/app_event.ts";
|
||||
import { Decoration, syntaxTree } from "../deps.ts";
|
||||
import { Editor } from "../editor.tsx";
|
||||
import { Editor } from "../editor.ts";
|
||||
import {
|
||||
ButtonWidget,
|
||||
decoratorStateField,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { EditorView, syntaxTree, ViewPlugin, ViewUpdate } from "../deps.ts";
|
||||
import { maximumAttachmentSize } from "../../common/types.ts";
|
||||
import { Editor } from "../editor.tsx";
|
||||
import { Editor } from "../editor.ts";
|
||||
|
||||
// We use turndown to convert HTML to Markdown
|
||||
import TurndownService from "https://cdn.skypack.dev/turndown@7.1.1";
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { WidgetContent } from "../../plug-api/app_event.ts";
|
||||
import { panelHtml } from "../components/panel.tsx";
|
||||
import { Decoration, EditorState, syntaxTree, WidgetType } from "../deps.ts";
|
||||
import type { Editor } from "../editor.tsx";
|
||||
import type { Editor } from "../editor.ts";
|
||||
import { CodeWidgetCallback } from "../hooks/code_widget.ts";
|
||||
import {
|
||||
decoratorStateField,
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
import { decoratorStateField } from "./util.ts";
|
||||
|
||||
import type { Space } from "../space.ts";
|
||||
import type { Editor } from "../editor.tsx";
|
||||
import type { Editor } from "../editor.ts";
|
||||
|
||||
class InlineImageWidget extends WidgetType {
|
||||
constructor(
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
import { renderMarkdownToHtml } from "../../plugs/markdown/markdown_render.ts";
|
||||
import { ParseTree } from "$sb/lib/tree.ts";
|
||||
import { lezerToParseTree } from "../../common/markdown_parser/parse_tree.ts";
|
||||
import type { Editor } from "../editor.tsx";
|
||||
import type { Editor } from "../editor.ts";
|
||||
|
||||
class TableViewWidget extends WidgetType {
|
||||
constructor(
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { pageLinkRegex } from "../../common/markdown_parser/parser.ts";
|
||||
import { ClickEvent } from "../../plug-api/app_event.ts";
|
||||
import { Decoration, syntaxTree } from "../deps.ts";
|
||||
import { Editor } from "../editor.tsx";
|
||||
import { Editor } from "../editor.ts";
|
||||
import {
|
||||
decoratorStateField,
|
||||
invisibleDecoration,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useEffect, useRef } from "../deps.ts";
|
||||
import { Editor } from "../editor.tsx";
|
||||
import { Editor } from "../editor.ts";
|
||||
import { PanelConfig } from "../types.ts";
|
||||
|
||||
export const panelHtml = `<!DOCTYPE html>
|
||||
|
@ -54,6 +54,7 @@ import { isValidPageName } from "$sb/lib/page.ts";
|
||||
import { ClientSystem } from "./client_system.ts";
|
||||
import { createEditorState } from "./editor_state.ts";
|
||||
import { OpenPages } from "./open_pages.ts";
|
||||
import { MainUI } from "./editor_ui.tsx";
|
||||
const frontMatterRegex = /^---\n(([^\n]|\n)*?)---\n/;
|
||||
|
||||
const autoSaveInterval = 1000;
|
||||
@ -71,8 +72,6 @@ declare global {
|
||||
// TODO: Oh my god, need to refactor this
|
||||
export class Editor {
|
||||
editorView?: EditorView;
|
||||
viewState: AppViewState = initialViewState;
|
||||
viewDispatch: (action: Action) => void = () => {};
|
||||
pageNavigator?: PathPageNavigator;
|
||||
|
||||
space: Space;
|
||||
@ -99,6 +98,8 @@ export class Editor {
|
||||
|
||||
// Event bus used to communicate between components
|
||||
eventHook: EventHook;
|
||||
|
||||
ui: MainUI;
|
||||
openPages: OpenPages;
|
||||
|
||||
constructor(
|
||||
@ -184,7 +185,8 @@ export class Editor {
|
||||
},
|
||||
);
|
||||
|
||||
this.render(parent);
|
||||
this.ui = new MainUI(this);
|
||||
this.ui.render(parent);
|
||||
|
||||
this.editorView = new EditorView({
|
||||
state: createEditorState(this, "", "", false),
|
||||
@ -211,19 +213,22 @@ export class Editor {
|
||||
if (ev.touches.length === 2) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.viewDispatch({ type: "start-navigate" });
|
||||
this.ui.viewDispatch({ type: "start-navigate" });
|
||||
}
|
||||
// Launch the command palette using a three-finger tap
|
||||
if (ev.touches.length === 3) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.viewDispatch({ type: "show-palette", context: this.getContext() });
|
||||
this.ui.viewDispatch({
|
||||
type: "show-palette",
|
||||
context: this.getContext(),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get currentPage(): string | undefined {
|
||||
return this.viewState.currentPage;
|
||||
return this.ui.viewState.currentPage;
|
||||
}
|
||||
|
||||
async init() {
|
||||
@ -239,7 +244,7 @@ export class Editor {
|
||||
}
|
||||
},
|
||||
pageListUpdated: (pages) => {
|
||||
this.viewDispatch({
|
||||
this.ui.viewDispatch({
|
||||
type: "pages-listed",
|
||||
pages: pages,
|
||||
});
|
||||
@ -338,10 +343,10 @@ export class Editor {
|
||||
// Reset for next sync cycle
|
||||
this.system.plugsUpdated = false;
|
||||
|
||||
this.viewDispatch({ type: "sync-change", synced: true });
|
||||
this.ui.viewDispatch({ type: "sync-change", synced: true });
|
||||
});
|
||||
this.eventHook.addLocalListener("sync:error", (name) => {
|
||||
this.viewDispatch({ type: "sync-change", synced: false });
|
||||
this.ui.viewDispatch({ type: "sync-change", synced: false });
|
||||
});
|
||||
this.eventHook.addLocalListener("sync:conflict", (name) => {
|
||||
this.flashNotification(
|
||||
@ -384,8 +389,8 @@ export class Editor {
|
||||
() => {
|
||||
if (this.currentPage) {
|
||||
if (
|
||||
!this.viewState.unsavedChanges ||
|
||||
this.viewState.uiOptions.forcedROMode
|
||||
!this.ui.viewState.unsavedChanges ||
|
||||
this.ui.viewState.uiOptions.forcedROMode
|
||||
) {
|
||||
// No unsaved changes, or read-only mode, not gonna save
|
||||
return resolve();
|
||||
@ -398,7 +403,7 @@ export class Editor {
|
||||
true,
|
||||
)
|
||||
.then(async (meta) => {
|
||||
this.viewDispatch({ type: "page-saved" });
|
||||
this.ui.viewDispatch({ type: "page-saved" });
|
||||
await this.dispatchAppEvent(
|
||||
"editor:pageSaved",
|
||||
this.currentPage,
|
||||
@ -425,7 +430,7 @@ export class Editor {
|
||||
|
||||
flashNotification(message: string, type: "info" | "error" = "info") {
|
||||
const id = Math.floor(Math.random() * 1000000);
|
||||
this.viewDispatch({
|
||||
this.ui.viewDispatch({
|
||||
type: "show-notification",
|
||||
notification: {
|
||||
id,
|
||||
@ -436,7 +441,7 @@ export class Editor {
|
||||
});
|
||||
setTimeout(
|
||||
() => {
|
||||
this.viewDispatch({
|
||||
this.ui.viewDispatch({
|
||||
type: "dismiss-notification",
|
||||
id: id,
|
||||
});
|
||||
@ -448,7 +453,7 @@ export class Editor {
|
||||
progressTimeout?: number;
|
||||
|
||||
showProgress(progressPerc: number) {
|
||||
this.viewDispatch({
|
||||
this.ui.viewDispatch({
|
||||
type: "set-progress",
|
||||
progressPerc,
|
||||
});
|
||||
@ -457,7 +462,7 @@ export class Editor {
|
||||
}
|
||||
this.progressTimeout = setTimeout(
|
||||
() => {
|
||||
this.viewDispatch({
|
||||
this.ui.viewDispatch({
|
||||
type: "set-progress",
|
||||
});
|
||||
},
|
||||
@ -472,14 +477,14 @@ export class Editor {
|
||||
placeHolder = "",
|
||||
): Promise<FilterOption | undefined> {
|
||||
return new Promise((resolve) => {
|
||||
this.viewDispatch({
|
||||
this.ui.viewDispatch({
|
||||
type: "show-filterbox",
|
||||
label,
|
||||
options,
|
||||
placeHolder,
|
||||
helpText,
|
||||
onSelect: (option: any) => {
|
||||
this.viewDispatch({ type: "hide-filterbox" });
|
||||
this.ui.viewDispatch({ type: "hide-filterbox" });
|
||||
this.focus();
|
||||
resolve(option);
|
||||
},
|
||||
@ -492,12 +497,12 @@ export class Editor {
|
||||
defaultValue = "",
|
||||
): Promise<string | undefined> {
|
||||
return new Promise((resolve) => {
|
||||
this.viewDispatch({
|
||||
this.ui.viewDispatch({
|
||||
type: "show-prompt",
|
||||
message,
|
||||
defaultValue,
|
||||
callback: (value: string | undefined) => {
|
||||
this.viewDispatch({ type: "hide-prompt" });
|
||||
this.ui.viewDispatch({ type: "hide-prompt" });
|
||||
this.focus();
|
||||
resolve(value);
|
||||
},
|
||||
@ -509,11 +514,11 @@ export class Editor {
|
||||
message: string,
|
||||
): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
this.viewDispatch({
|
||||
this.ui.viewDispatch({
|
||||
type: "show-confirm",
|
||||
message,
|
||||
callback: (value: boolean) => {
|
||||
this.viewDispatch({ type: "hide-confirm" });
|
||||
this.ui.viewDispatch({ type: "hide-confirm" });
|
||||
this.focus();
|
||||
resolve(value);
|
||||
},
|
||||
@ -547,7 +552,7 @@ export class Editor {
|
||||
this,
|
||||
this.currentPage,
|
||||
editorView.state.sliceDoc(),
|
||||
this.viewState.currentPageMeta?.perm === "ro",
|
||||
this.ui.viewState.currentPageMeta?.perm === "ro",
|
||||
),
|
||||
);
|
||||
if (editorView.contentDOM) {
|
||||
@ -658,7 +663,7 @@ export class Editor {
|
||||
}
|
||||
}
|
||||
|
||||
this.viewDispatch({
|
||||
this.ui.viewDispatch({
|
||||
type: "page-loading",
|
||||
name: pageName,
|
||||
});
|
||||
@ -692,7 +697,7 @@ export class Editor {
|
||||
const stateRestored = this.openPages.restoreState(pageName);
|
||||
this.space.watchPage(pageName);
|
||||
|
||||
this.viewDispatch({
|
||||
this.ui.viewDispatch({
|
||||
type: "page-loaded",
|
||||
meta: doc.meta,
|
||||
});
|
||||
@ -737,220 +742,8 @@ export class Editor {
|
||||
}
|
||||
}
|
||||
|
||||
ViewComponent() {
|
||||
const [viewState, dispatch] = useReducer(reducer, initialViewState);
|
||||
this.viewState = viewState;
|
||||
this.viewDispatch = dispatch;
|
||||
|
||||
// deno-lint-ignore no-this-alias
|
||||
const editor = this;
|
||||
|
||||
useEffect(() => {
|
||||
if (viewState.currentPage) {
|
||||
document.title = viewState.currentPage;
|
||||
}
|
||||
}, [viewState.currentPage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (editor.editorView) {
|
||||
editor.tweakEditorDOM(
|
||||
editor.editorView.contentDOM,
|
||||
);
|
||||
}
|
||||
}, [viewState.uiOptions.forcedROMode]);
|
||||
|
||||
useEffect(() => {
|
||||
this.rebuildEditorState();
|
||||
this.dispatchAppEvent("editor:modeswitch");
|
||||
}, [viewState.uiOptions.vimMode]);
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.dataset.theme = viewState.uiOptions.darkMode
|
||||
? "dark"
|
||||
: "light";
|
||||
}, [viewState.uiOptions.darkMode]);
|
||||
|
||||
useEffect(() => {
|
||||
// Need to dispatch a resize event so that the top_bar can pick it up
|
||||
globalThis.dispatchEvent(new Event("resize"));
|
||||
}, [viewState.panels]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{viewState.showPageNavigator && (
|
||||
<PageNavigator
|
||||
allPages={viewState.allPages}
|
||||
currentPage={this.currentPage}
|
||||
completer={this.miniEditorComplete.bind(this)}
|
||||
vimMode={viewState.uiOptions.vimMode}
|
||||
darkMode={viewState.uiOptions.darkMode}
|
||||
onNavigate={(page) => {
|
||||
dispatch({ type: "stop-navigate" });
|
||||
setTimeout(() => {
|
||||
editor.focus();
|
||||
});
|
||||
if (page) {
|
||||
safeRun(async () => {
|
||||
await editor.navigate(page);
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{viewState.showCommandPalette && (
|
||||
<CommandPalette
|
||||
onTrigger={(cmd) => {
|
||||
dispatch({ type: "hide-palette" });
|
||||
setTimeout(() => {
|
||||
editor.focus();
|
||||
});
|
||||
if (cmd) {
|
||||
dispatch({ type: "command-run", command: cmd.command.name });
|
||||
cmd
|
||||
.run()
|
||||
.catch((e: any) => {
|
||||
console.error("Error running command", e.message);
|
||||
})
|
||||
.then(() => {
|
||||
// Always be focusing the editor after running a command
|
||||
editor.focus();
|
||||
});
|
||||
}
|
||||
}}
|
||||
commands={this.getCommandsByContext(viewState)}
|
||||
vimMode={viewState.uiOptions.vimMode}
|
||||
darkMode={viewState.uiOptions.darkMode}
|
||||
completer={this.miniEditorComplete.bind(this)}
|
||||
recentCommands={viewState.recentCommands}
|
||||
/>
|
||||
)}
|
||||
{viewState.showFilterBox && (
|
||||
<FilterList
|
||||
label={viewState.filterBoxLabel}
|
||||
placeholder={viewState.filterBoxPlaceHolder}
|
||||
options={viewState.filterBoxOptions}
|
||||
vimMode={viewState.uiOptions.vimMode}
|
||||
darkMode={viewState.uiOptions.darkMode}
|
||||
allowNew={false}
|
||||
completer={this.miniEditorComplete.bind(this)}
|
||||
helpText={viewState.filterBoxHelpText}
|
||||
onSelect={viewState.filterBoxOnSelect}
|
||||
/>
|
||||
)}
|
||||
{viewState.showPrompt && (
|
||||
<Prompt
|
||||
message={viewState.promptMessage!}
|
||||
defaultValue={viewState.promptDefaultValue}
|
||||
vimMode={viewState.uiOptions.vimMode}
|
||||
darkMode={viewState.uiOptions.darkMode}
|
||||
completer={this.miniEditorComplete.bind(this)}
|
||||
callback={(value) => {
|
||||
dispatch({ type: "hide-prompt" });
|
||||
viewState.promptCallback!(value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{viewState.showConfirm && (
|
||||
<Confirm
|
||||
message={viewState.confirmMessage!}
|
||||
callback={(value) => {
|
||||
dispatch({ type: "hide-confirm" });
|
||||
viewState.confirmCallback!(value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<TopBar
|
||||
pageName={viewState.currentPage}
|
||||
notifications={viewState.notifications}
|
||||
synced={viewState.synced}
|
||||
unsavedChanges={viewState.unsavedChanges}
|
||||
isLoading={viewState.isLoading}
|
||||
vimMode={viewState.uiOptions.vimMode}
|
||||
darkMode={viewState.uiOptions.darkMode}
|
||||
progressPerc={viewState.progressPerc}
|
||||
completer={editor.miniEditorComplete.bind(editor)}
|
||||
onRename={async (newName) => {
|
||||
if (!newName) {
|
||||
// Always move cursor to the start of the page
|
||||
editor.editorView?.dispatch({
|
||||
selection: { anchor: 0 },
|
||||
});
|
||||
editor.focus();
|
||||
return;
|
||||
}
|
||||
console.log("Now renaming page to...", newName);
|
||||
await editor.system.system.loadedPlugs.get("core")!.invoke(
|
||||
"renamePage",
|
||||
[{ page: newName }],
|
||||
);
|
||||
editor.focus();
|
||||
}}
|
||||
actionButtons={[
|
||||
{
|
||||
icon: HomeIcon,
|
||||
description: `Go home (Alt-h)`,
|
||||
callback: () => {
|
||||
editor.navigate("");
|
||||
},
|
||||
href: "",
|
||||
},
|
||||
{
|
||||
icon: BookIcon,
|
||||
description: `Open page (${isMacLike() ? "Cmd-k" : "Ctrl-k"})`,
|
||||
callback: () => {
|
||||
dispatch({ type: "start-navigate" });
|
||||
this.space.updatePageList();
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: TerminalIcon,
|
||||
description: `Run command (${isMacLike() ? "Cmd-/" : "Ctrl-/"})`,
|
||||
callback: () => {
|
||||
dispatch({ type: "show-palette", context: this.getContext() });
|
||||
},
|
||||
},
|
||||
]}
|
||||
rhs={!!viewState.panels.rhs.mode && (
|
||||
<div
|
||||
className="panel"
|
||||
style={{ flex: viewState.panels.rhs.mode }}
|
||||
/>
|
||||
)}
|
||||
lhs={!!viewState.panels.lhs.mode && (
|
||||
<div
|
||||
className="panel"
|
||||
style={{ flex: viewState.panels.lhs.mode }}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div id="sb-main">
|
||||
{!!viewState.panels.lhs.mode && (
|
||||
<Panel config={viewState.panels.lhs} editor={editor} />
|
||||
)}
|
||||
<div id="sb-editor" />
|
||||
{!!viewState.panels.rhs.mode && (
|
||||
<Panel config={viewState.panels.rhs} editor={editor} />
|
||||
)}
|
||||
</div>
|
||||
{!!viewState.panels.modal.mode && (
|
||||
<div
|
||||
className="sb-modal"
|
||||
style={{ inset: `${viewState.panels.modal.mode}px` }}
|
||||
>
|
||||
<Panel config={viewState.panels.modal} editor={editor} />
|
||||
</div>
|
||||
)}
|
||||
{!!viewState.panels.bhs.mode && (
|
||||
<div className="sb-bhs">
|
||||
<Panel config={viewState.panels.bhs} editor={editor} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
async runCommandByName(name: string, ...args: any[]) {
|
||||
const cmd = this.viewState.commands.get(name);
|
||||
const cmd = this.ui.viewState.commands.get(name);
|
||||
if (cmd) {
|
||||
await cmd.run();
|
||||
} else {
|
||||
@ -958,12 +751,7 @@ export class Editor {
|
||||
}
|
||||
}
|
||||
|
||||
render(container: Element) {
|
||||
const ViewComponent = this.ViewComponent.bind(this);
|
||||
preactRender(<ViewComponent />, container);
|
||||
}
|
||||
|
||||
private getCommandsByContext(
|
||||
getCommandsByContext(
|
||||
state: AppViewState,
|
||||
): Map<string, AppCommand> {
|
||||
const commands = new Map(state.commands);
|
@ -49,7 +49,7 @@ import {
|
||||
xmlLanguage,
|
||||
yamlLanguage,
|
||||
} from "../common/deps.ts";
|
||||
import { Editor } from "./editor.tsx";
|
||||
import { Editor } from "./editor.ts";
|
||||
import { vim } from "./deps.ts";
|
||||
import { inlineImagesPlugin } from "./cm_plugins/inline_image.ts";
|
||||
import { cleanModePlugins } from "./cm_plugins/clean.ts";
|
||||
@ -107,11 +107,15 @@ export function createEditorState(
|
||||
doc: text,
|
||||
extensions: [
|
||||
// Not using CM theming right now, but some extensions depend on the "dark" thing
|
||||
EditorView.theme({}, { dark: editor.viewState.uiOptions.darkMode }),
|
||||
EditorView.theme({}, {
|
||||
dark: editor.ui.viewState.uiOptions.darkMode,
|
||||
}),
|
||||
// Enable vim mode, or not
|
||||
[...editor.viewState.uiOptions.vimMode ? [vim({ status: true })] : []],
|
||||
[
|
||||
...readOnly || editor.viewState.uiOptions.forcedROMode
|
||||
...editor.ui.viewState.uiOptions.vimMode ? [vim({ status: true })] : [],
|
||||
],
|
||||
[
|
||||
...readOnly || editor.ui.viewState.uiOptions.forcedROMode
|
||||
? [readonlyMode()]
|
||||
: [],
|
||||
],
|
||||
@ -309,7 +313,7 @@ export function createEditorState(
|
||||
key: "Ctrl-k",
|
||||
mac: "Cmd-k",
|
||||
run: (): boolean => {
|
||||
editor.viewDispatch({ type: "start-navigate" });
|
||||
editor.ui.viewDispatch({ type: "start-navigate" });
|
||||
editor.space.updatePageList();
|
||||
|
||||
return true;
|
||||
@ -319,7 +323,7 @@ export function createEditorState(
|
||||
key: "Ctrl-/",
|
||||
mac: "Cmd-/",
|
||||
run: (): boolean => {
|
||||
editor.viewDispatch({
|
||||
editor.ui.viewDispatch({
|
||||
type: "show-palette",
|
||||
context: editor.getContext(),
|
||||
});
|
||||
@ -330,7 +334,7 @@ export function createEditorState(
|
||||
key: "Ctrl-.",
|
||||
mac: "Cmd-.",
|
||||
run: (): boolean => {
|
||||
editor.viewDispatch({
|
||||
editor.ui.viewDispatch({
|
||||
type: "show-palette",
|
||||
context: editor.getContext(),
|
||||
});
|
||||
@ -415,7 +419,7 @@ export function createEditorState(
|
||||
class {
|
||||
update(update: ViewUpdate): void {
|
||||
if (update.docChanged) {
|
||||
editor.viewDispatch({ type: "page-changed" });
|
||||
editor.ui.viewDispatch({ type: "page-changed" });
|
||||
editor.debouncedUpdateEvent();
|
||||
editor.save().catch((e) => console.error("Error saving", e));
|
||||
}
|
||||
|
246
web/editor_ui.tsx
Normal file
246
web/editor_ui.tsx
Normal file
@ -0,0 +1,246 @@
|
||||
import { isMacLike, safeRun } from "../common/util.ts";
|
||||
import { Confirm, Prompt } from "./components/basic_modals.tsx";
|
||||
import { CommandPalette } from "./components/command_palette.tsx";
|
||||
import { FilterList } from "./components/filter.tsx";
|
||||
import { PageNavigator } from "./components/page_navigator.tsx";
|
||||
import { TopBar } from "./components/top_bar.tsx";
|
||||
import reducer from "./reducer.ts";
|
||||
import { Action, AppViewState, initialViewState } from "./types.ts";
|
||||
import {
|
||||
BookIcon,
|
||||
HomeIcon,
|
||||
preactRender,
|
||||
TerminalIcon,
|
||||
useEffect,
|
||||
useReducer,
|
||||
} from "./deps.ts";
|
||||
import type { Editor } from "./editor.ts";
|
||||
import { Panel } from "./components/panel.tsx";
|
||||
import { h } from "./deps.ts";
|
||||
|
||||
export class MainUI {
|
||||
viewState: AppViewState = initialViewState;
|
||||
viewDispatch: (action: Action) => void = () => {};
|
||||
|
||||
constructor(private editor: Editor) {
|
||||
}
|
||||
|
||||
ViewComponent() {
|
||||
const [viewState, dispatch] = useReducer(reducer, initialViewState);
|
||||
this.viewState = viewState;
|
||||
this.viewDispatch = dispatch;
|
||||
|
||||
const editor = this.editor;
|
||||
|
||||
useEffect(() => {
|
||||
if (viewState.currentPage) {
|
||||
document.title = viewState.currentPage;
|
||||
}
|
||||
}, [viewState.currentPage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (editor.editorView) {
|
||||
editor.tweakEditorDOM(
|
||||
editor.editorView.contentDOM,
|
||||
);
|
||||
}
|
||||
}, [viewState.uiOptions.forcedROMode]);
|
||||
|
||||
useEffect(() => {
|
||||
this.editor.rebuildEditorState();
|
||||
this.editor.dispatchAppEvent("editor:modeswitch");
|
||||
}, [viewState.uiOptions.vimMode]);
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.dataset.theme = viewState.uiOptions.darkMode
|
||||
? "dark"
|
||||
: "light";
|
||||
}, [viewState.uiOptions.darkMode]);
|
||||
|
||||
useEffect(() => {
|
||||
// Need to dispatch a resize event so that the top_bar can pick it up
|
||||
globalThis.dispatchEvent(new Event("resize"));
|
||||
}, [viewState.panels]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{viewState.showPageNavigator && (
|
||||
<PageNavigator
|
||||
allPages={viewState.allPages}
|
||||
currentPage={editor.currentPage}
|
||||
completer={editor.miniEditorComplete.bind(editor)}
|
||||
vimMode={viewState.uiOptions.vimMode}
|
||||
darkMode={viewState.uiOptions.darkMode}
|
||||
onNavigate={(page) => {
|
||||
dispatch({ type: "stop-navigate" });
|
||||
setTimeout(() => {
|
||||
editor.focus();
|
||||
});
|
||||
if (page) {
|
||||
safeRun(async () => {
|
||||
await editor.navigate(page);
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{viewState.showCommandPalette && (
|
||||
<CommandPalette
|
||||
onTrigger={(cmd) => {
|
||||
dispatch({ type: "hide-palette" });
|
||||
setTimeout(() => {
|
||||
editor.focus();
|
||||
});
|
||||
if (cmd) {
|
||||
dispatch({ type: "command-run", command: cmd.command.name });
|
||||
cmd
|
||||
.run()
|
||||
.catch((e: any) => {
|
||||
console.error("Error running command", e.message);
|
||||
})
|
||||
.then(() => {
|
||||
// Always be focusing the editor after running a command
|
||||
editor.focus();
|
||||
});
|
||||
}
|
||||
}}
|
||||
commands={editor.getCommandsByContext(viewState)}
|
||||
vimMode={viewState.uiOptions.vimMode}
|
||||
darkMode={viewState.uiOptions.darkMode}
|
||||
completer={editor.miniEditorComplete.bind(editor)}
|
||||
recentCommands={viewState.recentCommands}
|
||||
/>
|
||||
)}
|
||||
{viewState.showFilterBox && (
|
||||
<FilterList
|
||||
label={viewState.filterBoxLabel}
|
||||
placeholder={viewState.filterBoxPlaceHolder}
|
||||
options={viewState.filterBoxOptions}
|
||||
vimMode={viewState.uiOptions.vimMode}
|
||||
darkMode={viewState.uiOptions.darkMode}
|
||||
allowNew={false}
|
||||
completer={editor.miniEditorComplete.bind(editor)}
|
||||
helpText={viewState.filterBoxHelpText}
|
||||
onSelect={viewState.filterBoxOnSelect}
|
||||
/>
|
||||
)}
|
||||
{viewState.showPrompt && (
|
||||
<Prompt
|
||||
message={viewState.promptMessage!}
|
||||
defaultValue={viewState.promptDefaultValue}
|
||||
vimMode={viewState.uiOptions.vimMode}
|
||||
darkMode={viewState.uiOptions.darkMode}
|
||||
completer={editor.miniEditorComplete.bind(editor)}
|
||||
callback={(value) => {
|
||||
dispatch({ type: "hide-prompt" });
|
||||
viewState.promptCallback!(value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{viewState.showConfirm && (
|
||||
<Confirm
|
||||
message={viewState.confirmMessage!}
|
||||
callback={(value) => {
|
||||
dispatch({ type: "hide-confirm" });
|
||||
viewState.confirmCallback!(value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<TopBar
|
||||
pageName={viewState.currentPage}
|
||||
notifications={viewState.notifications}
|
||||
synced={viewState.synced}
|
||||
unsavedChanges={viewState.unsavedChanges}
|
||||
isLoading={viewState.isLoading}
|
||||
vimMode={viewState.uiOptions.vimMode}
|
||||
darkMode={viewState.uiOptions.darkMode}
|
||||
progressPerc={viewState.progressPerc}
|
||||
completer={editor.miniEditorComplete.bind(editor)}
|
||||
onRename={async (newName) => {
|
||||
if (!newName) {
|
||||
// Always move cursor to the start of the page
|
||||
editor.editorView?.dispatch({
|
||||
selection: { anchor: 0 },
|
||||
});
|
||||
editor.focus();
|
||||
return;
|
||||
}
|
||||
console.log("Now renaming page to...", newName);
|
||||
await editor.system.system.loadedPlugs.get("core")!.invoke(
|
||||
"renamePage",
|
||||
[{ page: newName }],
|
||||
);
|
||||
editor.focus();
|
||||
}}
|
||||
actionButtons={[
|
||||
{
|
||||
icon: HomeIcon,
|
||||
description: `Go home (Alt-h)`,
|
||||
callback: () => {
|
||||
editor.navigate("");
|
||||
},
|
||||
href: "",
|
||||
},
|
||||
{
|
||||
icon: BookIcon,
|
||||
description: `Open page (${isMacLike() ? "Cmd-k" : "Ctrl-k"})`,
|
||||
callback: () => {
|
||||
dispatch({ type: "start-navigate" });
|
||||
editor.space.updatePageList();
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: TerminalIcon,
|
||||
description: `Run command (${isMacLike() ? "Cmd-/" : "Ctrl-/"})`,
|
||||
callback: () => {
|
||||
dispatch({
|
||||
type: "show-palette",
|
||||
context: editor.getContext(),
|
||||
});
|
||||
},
|
||||
},
|
||||
]}
|
||||
rhs={!!viewState.panels.rhs.mode && (
|
||||
<div
|
||||
className="panel"
|
||||
style={{ flex: viewState.panels.rhs.mode }}
|
||||
/>
|
||||
)}
|
||||
lhs={!!viewState.panels.lhs.mode && (
|
||||
<div
|
||||
className="panel"
|
||||
style={{ flex: viewState.panels.lhs.mode }}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div id="sb-main">
|
||||
{!!viewState.panels.lhs.mode && (
|
||||
<Panel config={viewState.panels.lhs} editor={editor} />
|
||||
)}
|
||||
<div id="sb-editor" />
|
||||
{!!viewState.panels.rhs.mode && (
|
||||
<Panel config={viewState.panels.rhs} editor={editor} />
|
||||
)}
|
||||
</div>
|
||||
{!!viewState.panels.modal.mode && (
|
||||
<div
|
||||
className="sb-modal"
|
||||
style={{ inset: `${viewState.panels.modal.mode}px` }}
|
||||
>
|
||||
<Panel config={viewState.panels.modal} editor={editor} />
|
||||
</div>
|
||||
)}
|
||||
{!!viewState.panels.bhs.mode && (
|
||||
<div className="sb-bhs">
|
||||
<Panel config={viewState.panels.bhs} editor={editor} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
render(container: Element) {
|
||||
// const ViewComponent = this.ui.ViewComponent.bind(this.ui);
|
||||
preactRender(h(this.ViewComponent.bind(this), {}), container);
|
||||
}
|
||||
}
|
@ -53,12 +53,15 @@ export class CommandHook extends EventEmitter<CommandHookEvents>
|
||||
}
|
||||
|
||||
apply(system: System<CommandHookT>): void {
|
||||
this.buildAllCommands(system);
|
||||
system.on({
|
||||
plugLoaded: () => {
|
||||
this.buildAllCommands(system);
|
||||
},
|
||||
});
|
||||
// On next tick
|
||||
setTimeout(() => {
|
||||
this.buildAllCommands(system);
|
||||
});
|
||||
}
|
||||
|
||||
validateManifest(manifest: Manifest<CommandHookT>): string[] {
|
||||
|
@ -2,7 +2,7 @@ import { Hook, Manifest } from "../../plugos/types.ts";
|
||||
import { System } from "../../plugos/system.ts";
|
||||
import { Completion, CompletionContext, CompletionResult } from "../deps.ts";
|
||||
import { safeRun } from "../../common/util.ts";
|
||||
import { Editor } from "../editor.tsx";
|
||||
import { Editor } from "../editor.ts";
|
||||
import { syntaxTree } from "../deps.ts";
|
||||
|
||||
export type SlashCommandDef = {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Editor } from "../editor.tsx";
|
||||
import { Editor } from "../editor.ts";
|
||||
import {
|
||||
EditorView,
|
||||
foldAll,
|
||||
@ -81,14 +81,14 @@ export function editorSyscalls(editor: Editor): SysCallMapping {
|
||||
html: string,
|
||||
script: string,
|
||||
) => {
|
||||
editor.viewDispatch({
|
||||
editor.ui.viewDispatch({
|
||||
type: "show-panel",
|
||||
id: id as any,
|
||||
config: { html, script, mode },
|
||||
});
|
||||
},
|
||||
"editor.hidePanel": (_ctx, id: string) => {
|
||||
editor.viewDispatch({
|
||||
editor.ui.viewDispatch({
|
||||
type: "hide-panel",
|
||||
id: id as any,
|
||||
});
|
||||
@ -169,10 +169,10 @@ export function editorSyscalls(editor: Editor): SysCallMapping {
|
||||
return editor.confirm(message);
|
||||
},
|
||||
"editor.getUiOption": (_ctx, key: string): any => {
|
||||
return (editor.viewState.uiOptions as any)[key];
|
||||
return (editor.ui.viewState.uiOptions as any)[key];
|
||||
},
|
||||
"editor.setUiOption": (_ctx, key: string, value: any) => {
|
||||
editor.viewDispatch({
|
||||
editor.ui.viewDispatch({
|
||||
type: "set-ui-option",
|
||||
key,
|
||||
value,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Editor } from "../editor.tsx";
|
||||
import { Editor } from "../editor.ts";
|
||||
import { SysCallMapping } from "../../plugos/system.ts";
|
||||
import { AttachmentMeta, PageMeta } from "../types.ts";
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { SysCallMapping } from "../../plugos/system.ts";
|
||||
import type { Editor } from "../editor.tsx";
|
||||
import type { Editor } from "../editor.ts";
|
||||
|
||||
export function syncSyscalls(editor: Editor): SysCallMapping {
|
||||
return {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { Plug } from "../../plugos/plug.ts";
|
||||
import { SysCallMapping, System } from "../../plugos/system.ts";
|
||||
import type { Editor } from "../editor.tsx";
|
||||
import type { Editor } from "../editor.ts";
|
||||
import { CommandDef } from "../hooks/command.ts";
|
||||
|
||||
export function systemSyscalls(
|
||||
|
Loading…
Reference in New Issue
Block a user