Plugin stuff
This commit is contained in:
+2
-6
@@ -1,11 +1,7 @@
|
||||
export type AppEvent =
|
||||
| "app:ready"
|
||||
| "page:save"
|
||||
| "page:click"
|
||||
| "page:index"
|
||||
| "editor:complete";
|
||||
export type AppEvent = "page:click" | "editor:complete";
|
||||
|
||||
export type ClickEvent = {
|
||||
page: string;
|
||||
pos: number;
|
||||
metaKey: boolean;
|
||||
ctrlKey: boolean;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { useRef } from "react";
|
||||
|
||||
export function Panel({ html }: { html: string }) {
|
||||
const iFrameRef = useRef<HTMLIFrameElement>(null);
|
||||
// @ts-ignore
|
||||
window.iframeRef = iFrameRef;
|
||||
return (
|
||||
<div className="panel">
|
||||
<iframe srcDoc={html} ref={iFrameRef} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+1
-1
@@ -1 +1 @@
|
||||
export const pageLinkRegex = /\[\[([\w\s\/\:,\.\-]+)\]\]/;
|
||||
export const pageLinkRegex = /\[\[([\w\s\/\:,\.@\-]+)\]\]/;
|
||||
|
||||
+25
-17
@@ -31,7 +31,7 @@ import { TopBar } from "./components/top_bar";
|
||||
import { Cursor } from "./cursorEffect";
|
||||
import { lineWrapper } from "./line_wrapper";
|
||||
import { markdown } from "./markdown";
|
||||
import { IPageNavigator, PathPageNavigator } from "./navigator";
|
||||
import { PathPageNavigator } from "./navigator";
|
||||
import customMarkDown from "./parser";
|
||||
import reducer from "./reducer";
|
||||
import { smartQuoteKeymap } from "./smart_quotes";
|
||||
@@ -52,6 +52,7 @@ import { safeRun } from "./util";
|
||||
import { System } from "../plugos/system";
|
||||
import { EventFeature } from "../plugos/feature/event";
|
||||
import { systemSyscalls } from "./syscalls/system";
|
||||
import { Panel } from "./components/panel";
|
||||
|
||||
class PageState {
|
||||
scrollTop: number;
|
||||
@@ -72,7 +73,7 @@ export class Editor implements AppEventDispatcher {
|
||||
viewDispatch: React.Dispatch<Action>;
|
||||
space: Space;
|
||||
navigationResolve?: (val: undefined) => void;
|
||||
pageNavigator: IPageNavigator;
|
||||
pageNavigator: PathPageNavigator;
|
||||
private eventFeature: EventFeature;
|
||||
|
||||
constructor(space: Space, parent: Element) {
|
||||
@@ -102,7 +103,7 @@ export class Editor implements AppEventDispatcher {
|
||||
async init() {
|
||||
this.focus();
|
||||
|
||||
this.pageNavigator.subscribe(async (pageName) => {
|
||||
this.pageNavigator.subscribe(async (pageName, pos) => {
|
||||
console.log("Now navigating to", pageName);
|
||||
|
||||
if (!this.editorView) {
|
||||
@@ -110,6 +111,11 @@ export class Editor implements AppEventDispatcher {
|
||||
}
|
||||
|
||||
await this.loadPage(pageName);
|
||||
if (pos) {
|
||||
this.editorView.dispatch({
|
||||
selection: { anchor: pos },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.space.on({
|
||||
@@ -175,8 +181,8 @@ export class Editor implements AppEventDispatcher {
|
||||
for (let cmd of cmds) {
|
||||
this.editorCommands.set(cmd.name, {
|
||||
command: cmd,
|
||||
run: async (arg): Promise<any> => {
|
||||
return await plug.invoke(name, [arg]);
|
||||
run: () => {
|
||||
return plug.invoke(name, []);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -223,10 +229,11 @@ export class Editor implements AppEventDispatcher {
|
||||
mac: def.command.mac,
|
||||
run: (): boolean => {
|
||||
Promise.resolve()
|
||||
.then(async () => {
|
||||
await def.run(null);
|
||||
})
|
||||
.catch((e) => console.error(e));
|
||||
.then(def.run)
|
||||
.catch((e: any) => {
|
||||
console.error(e);
|
||||
this.flashNotification(`Error running command: ${e.message}`);
|
||||
});
|
||||
return true;
|
||||
},
|
||||
});
|
||||
@@ -317,6 +324,7 @@ export class Editor implements AppEventDispatcher {
|
||||
click: (event: MouseEvent, view: EditorView) => {
|
||||
safeRun(async () => {
|
||||
let clickEvent: ClickEvent = {
|
||||
page: pageName,
|
||||
ctrlKey: event.ctrlKey,
|
||||
metaKey: event.metaKey,
|
||||
altKey: event.altKey,
|
||||
@@ -375,7 +383,7 @@ export class Editor implements AppEventDispatcher {
|
||||
},
|
||||
});
|
||||
safeRun(async () => {
|
||||
await def.run(null);
|
||||
await def.run();
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -390,8 +398,8 @@ export class Editor implements AppEventDispatcher {
|
||||
this.editorView!.focus();
|
||||
}
|
||||
|
||||
async navigate(name: string) {
|
||||
await this.pageNavigator.navigate(name);
|
||||
async navigate(name: string, pos?: number) {
|
||||
await this.pageNavigator.navigate(name, pos);
|
||||
}
|
||||
|
||||
async loadPage(pageName: string) {
|
||||
@@ -451,7 +459,7 @@ export class Editor implements AppEventDispatcher {
|
||||
}, [viewState.currentPage]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={viewState.showRHS ? "rhs-open" : ""}>
|
||||
{viewState.showPageNavigator && (
|
||||
<PageNavigator
|
||||
allPages={viewState.allPages}
|
||||
@@ -473,15 +481,15 @@ export class Editor implements AppEventDispatcher {
|
||||
dispatch({ type: "hide-palette" });
|
||||
editor!.focus();
|
||||
if (cmd) {
|
||||
safeRun(async () => {
|
||||
let result = await cmd.run(null);
|
||||
console.log("Result of command", result);
|
||||
cmd.run().catch((e) => {
|
||||
console.error("Error running command", e);
|
||||
});
|
||||
}
|
||||
}}
|
||||
commands={viewState.commands}
|
||||
/>
|
||||
)}
|
||||
{viewState.showRHS && <Panel html={viewState.rhsHTML} />}
|
||||
<TopBar
|
||||
pageName={viewState.currentPage}
|
||||
notifications={viewState.notifications}
|
||||
@@ -490,7 +498,7 @@ export class Editor implements AppEventDispatcher {
|
||||
}}
|
||||
/>
|
||||
<div id="editor"></div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+21
-41
@@ -1,13 +1,5 @@
|
||||
import { safeRun } from "./util";
|
||||
|
||||
export interface IPageNavigator {
|
||||
subscribe(pageLoadCallback: (pageName: string) => Promise<void>): void;
|
||||
|
||||
navigate(page: string): Promise<void>;
|
||||
|
||||
getCurrentPage(): string;
|
||||
}
|
||||
|
||||
function encodePageUrl(name: string): string {
|
||||
return name.replaceAll(" ", "_");
|
||||
}
|
||||
@@ -16,26 +8,34 @@ function decodePageUrl(url: string): string {
|
||||
return url.replaceAll("_", " ");
|
||||
}
|
||||
|
||||
export class PathPageNavigator implements IPageNavigator {
|
||||
navigationResolve?: (value: undefined) => void;
|
||||
async navigate(page: string) {
|
||||
window.history.pushState({ page: page }, page, `/${encodePageUrl(page)}`);
|
||||
export class PathPageNavigator {
|
||||
navigationResolve?: () => void;
|
||||
|
||||
async navigate(page: string, pos?: number) {
|
||||
window.history.pushState(
|
||||
{ page, pos },
|
||||
page,
|
||||
`/${encodePageUrl(page)}${pos ? "@" + pos : ""}`
|
||||
);
|
||||
window.dispatchEvent(new PopStateEvent("popstate"));
|
||||
await new Promise<undefined>((resolve) => {
|
||||
await new Promise<void>((resolve) => {
|
||||
this.navigationResolve = resolve;
|
||||
});
|
||||
this.navigationResolve = undefined;
|
||||
}
|
||||
subscribe(pageLoadCallback: (pageName: string) => Promise<void>): void {
|
||||
|
||||
subscribe(
|
||||
pageLoadCallback: (pageName: string, pos: number) => Promise<void>
|
||||
): void {
|
||||
const cb = () => {
|
||||
const gotoPage = this.getCurrentPage();
|
||||
if (!gotoPage) {
|
||||
return;
|
||||
}
|
||||
safeRun(async () => {
|
||||
await pageLoadCallback(this.getCurrentPage());
|
||||
await pageLoadCallback(this.getCurrentPage(), this.getCurrentPos());
|
||||
if (this.navigationResolve) {
|
||||
this.navigationResolve(undefined);
|
||||
this.navigationResolve();
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -44,32 +44,12 @@ export class PathPageNavigator implements IPageNavigator {
|
||||
}
|
||||
|
||||
getCurrentPage(): string {
|
||||
return decodePageUrl(location.pathname.substring(1));
|
||||
let [page] = location.pathname.substring(1).split("@");
|
||||
return decodePageUrl(page);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
getCurrentPos(): number {
|
||||
let [, pos] = location.pathname.substring(1).split("@");
|
||||
return +pos || 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,18 @@ export default function reducer(
|
||||
...state,
|
||||
notifications: state.notifications.filter((n) => n.id !== action.id),
|
||||
};
|
||||
case "show-rhs":
|
||||
return {
|
||||
...state,
|
||||
showRHS: true,
|
||||
rhsHTML: action.html,
|
||||
};
|
||||
case "hide-rhs":
|
||||
return {
|
||||
...state,
|
||||
showRHS: false,
|
||||
rhsHTML: "",
|
||||
};
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ export class Space extends EventEmitter<SpaceEvents> {
|
||||
this.reqId++;
|
||||
this.socket!.once(`${eventName}Resp${this.reqId}`, (err, result) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
reject(new Error(err));
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
|
||||
+23
-20
@@ -17,6 +17,24 @@ body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.panel {
|
||||
position: absolute;
|
||||
top: 55px;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 400px;
|
||||
z-index: 20;
|
||||
background: #efefef;
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 10px;
|
||||
scroll: auto;
|
||||
}
|
||||
}
|
||||
|
||||
#top {
|
||||
height: 55px;
|
||||
position: fixed;
|
||||
@@ -40,6 +58,7 @@ body {
|
||||
padding: 3px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.current-page {
|
||||
font-family: var(--ui-font);
|
||||
font-weight: bold;
|
||||
@@ -52,26 +71,6 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
// #bottom {
|
||||
// position: fixed;
|
||||
// bottom: 0;
|
||||
// left: 0;
|
||||
// right: 0;
|
||||
// height: 20px;
|
||||
// background-color: rgb(232, 232, 232);
|
||||
// color: rgb(79, 78, 78);
|
||||
// border-top: rgb(186, 186, 186) 1px solid;
|
||||
// margin: 0;
|
||||
// padding: 5px 10px;
|
||||
// font-family: var(--ui-font);
|
||||
// font-size: 0.9em;
|
||||
// text-align: right;
|
||||
// }
|
||||
|
||||
// body.keyboard #bottom {
|
||||
// bottom: 250px;
|
||||
// }
|
||||
|
||||
#editor {
|
||||
position: absolute;
|
||||
top: 55px;
|
||||
@@ -81,6 +80,10 @@ body {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
div.rhs-open #editor {
|
||||
right: 350px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
.cm-editor .cm-content {
|
||||
margin: 0 10px !important;
|
||||
|
||||
@@ -36,8 +36,8 @@ export default (editor: Editor): SysCallMapping => ({
|
||||
getCursor: (): number => {
|
||||
return editor.editorView!.state.selection.main.from;
|
||||
},
|
||||
navigate: async (ctx, name: string) => {
|
||||
await editor.navigate(name);
|
||||
navigate: async (ctx, name: string, pos: number) => {
|
||||
await editor.navigate(name, pos);
|
||||
},
|
||||
openUrl: async (ctx, url: string) => {
|
||||
window.open(url, "_blank")!.focus();
|
||||
@@ -45,6 +45,12 @@ export default (editor: Editor): SysCallMapping => ({
|
||||
flashNotification: (ctx, message: string) => {
|
||||
editor.flashNotification(message);
|
||||
},
|
||||
showRhs: (ctx, html: string) => {
|
||||
editor.viewDispatch({
|
||||
type: "show-rhs",
|
||||
html: html,
|
||||
});
|
||||
},
|
||||
insertAtPos: (ctx, text: string, pos: number) => {
|
||||
editor.editorView!.dispatch({
|
||||
changes: {
|
||||
@@ -97,6 +103,12 @@ export default (editor: Editor): SysCallMapping => ({
|
||||
}
|
||||
}
|
||||
},
|
||||
getLineUnderCursor: (): string => {
|
||||
const editorState = editor.editorView!.state;
|
||||
let selection = editorState.selection.main;
|
||||
let line = editorState.doc.lineAt(selection.from);
|
||||
return editorState.sliceDoc(line.from, line.to);
|
||||
},
|
||||
matchBefore: (
|
||||
ctx,
|
||||
regexp: string
|
||||
|
||||
@@ -7,7 +7,7 @@ export function systemSyscalls(space: Space): SysCallMapping {
|
||||
if (!ctx.plug) {
|
||||
throw Error("No plug associated with context");
|
||||
}
|
||||
return await space.wsCall("invokeFunction", ctx.plug.name, name, ...args);
|
||||
return space.wsCall("invokeFunction", ctx.plug.name, name, ...args);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
+8
-2
@@ -9,7 +9,7 @@ export type PageMeta = {
|
||||
|
||||
export type AppCommand = {
|
||||
command: CommandDef;
|
||||
run: (arg: any) => Promise<any>;
|
||||
run: () => Promise<void>;
|
||||
};
|
||||
|
||||
export const slashCommandRegexp = /\/[\w\-]*/;
|
||||
@@ -24,6 +24,8 @@ export type AppViewState = {
|
||||
currentPage?: string;
|
||||
showPageNavigator: boolean;
|
||||
showCommandPalette: boolean;
|
||||
showRHS: boolean;
|
||||
rhsHTML: string;
|
||||
allPages: Set<PageMeta>;
|
||||
commands: Map<string, AppCommand>;
|
||||
notifications: Notification[];
|
||||
@@ -32,6 +34,8 @@ export type AppViewState = {
|
||||
export const initialViewState: AppViewState = {
|
||||
showPageNavigator: false,
|
||||
showCommandPalette: false,
|
||||
showRHS: false,
|
||||
rhsHTML: "<h1>Loading...</h1>",
|
||||
allPages: new Set(),
|
||||
commands: new Map(),
|
||||
notifications: [],
|
||||
@@ -46,4 +50,6 @@ export type Action =
|
||||
| { type: "show-palette" }
|
||||
| { type: "hide-palette" }
|
||||
| { type: "show-notification"; notification: Notification }
|
||||
| { type: "dismiss-notification"; id: number };
|
||||
| { type: "dismiss-notification"; id: number }
|
||||
| { type: "show-rhs"; html: string }
|
||||
| { type: "hide-rhs" };
|
||||
|
||||
Reference in New Issue
Block a user