Editor refactor: extract system stuff
parent
87b0e7e352
commit
b39a9b8e22
|
@ -8,9 +8,8 @@ import { renderDirectives } from "./directives.ts";
|
||||||
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
|
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
|
||||||
import { PageMeta } from "../../web/types.ts";
|
import { PageMeta } from "../../web/types.ts";
|
||||||
|
|
||||||
export async function updateDirectivesOnPageCommand(arg: any) {
|
export async function updateDirectivesOnPageCommand() {
|
||||||
// If `arg` is a string, it's triggered automatically via an event, not explicitly via a command
|
// If `arg` is a string, it's triggered automatically via an event, not explicitly via a command
|
||||||
const explicitCall = typeof arg !== "string";
|
|
||||||
const pageMeta = await space.getPageMeta(await editor.getCurrentPage());
|
const pageMeta = await space.getPageMeta(await editor.getCurrentPage());
|
||||||
const text = await editor.getText();
|
const text = await editor.getText();
|
||||||
const tree = await markdown.parseMarkdown(text);
|
const tree = await markdown.parseMarkdown(text);
|
||||||
|
|
|
@ -0,0 +1,172 @@
|
||||||
|
import { PageNamespaceHook } from "../common/hooks/page_namespace.ts";
|
||||||
|
import { Manifest, SilverBulletHooks } from "../common/manifest.ts";
|
||||||
|
import buildMarkdown from "../common/markdown_parser/parser.ts";
|
||||||
|
import { CronHook } from "../plugos/hooks/cron.ts";
|
||||||
|
import { EventHook } from "../plugos/hooks/event.ts";
|
||||||
|
import { DexieKVStore } from "../plugos/lib/kv_store.dexie.ts";
|
||||||
|
import { createSandbox } from "../plugos/environments/webworker_sandbox.ts";
|
||||||
|
|
||||||
|
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 { CodeWidgetHook } from "./hooks/code_widget.ts";
|
||||||
|
import { CommandHook } from "./hooks/command.ts";
|
||||||
|
import { SlashCommandHook } from "./hooks/slash_command.ts";
|
||||||
|
import { clientStoreSyscalls } from "./syscalls/clientStore.ts";
|
||||||
|
import { debugSyscalls } from "./syscalls/debug.ts";
|
||||||
|
import { editorSyscalls } from "./syscalls/editor.ts";
|
||||||
|
import { sandboxFetchSyscalls } from "./syscalls/fetch.ts";
|
||||||
|
import { pageIndexSyscalls } from "./syscalls/index.ts";
|
||||||
|
import { markdownSyscalls } from "./syscalls/markdown.ts";
|
||||||
|
import { shellSyscalls } from "./syscalls/shell.ts";
|
||||||
|
import { spaceSyscalls } from "./syscalls/space.ts";
|
||||||
|
import { syncSyscalls } from "./syscalls/sync.ts";
|
||||||
|
import { systemSyscalls } from "./syscalls/system.ts";
|
||||||
|
import { yamlSyscalls } from "./syscalls/yaml.ts";
|
||||||
|
import { Space } from "./space.ts";
|
||||||
|
import {
|
||||||
|
loadMarkdownExtensions,
|
||||||
|
MDExt,
|
||||||
|
} from "../common/markdown_parser/markdown_ext.ts";
|
||||||
|
|
||||||
|
export class ClientSystem {
|
||||||
|
system: System<SilverBulletHooks> = new System("client");
|
||||||
|
commandHook: CommandHook;
|
||||||
|
slashCommandHook: SlashCommandHook;
|
||||||
|
namespaceHook: PageNamespaceHook;
|
||||||
|
indexSyscalls: SysCallMapping;
|
||||||
|
codeWidgetHook: CodeWidgetHook;
|
||||||
|
plugsUpdated = false;
|
||||||
|
mdExtensions: MDExt[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private editor: Editor,
|
||||||
|
private kvStore: DexieKVStore,
|
||||||
|
private dbPrefix: string,
|
||||||
|
private eventHook: EventHook,
|
||||||
|
) {
|
||||||
|
// Attach the page namespace hook
|
||||||
|
const namespaceHook = new PageNamespaceHook();
|
||||||
|
this.system.addHook(namespaceHook);
|
||||||
|
|
||||||
|
this.system.addHook(this.eventHook);
|
||||||
|
|
||||||
|
// Attach the page namespace hook
|
||||||
|
this.namespaceHook = new PageNamespaceHook();
|
||||||
|
this.system.addHook(namespaceHook);
|
||||||
|
|
||||||
|
// Cron hook
|
||||||
|
const cronHook = new CronHook(this.system);
|
||||||
|
this.system.addHook(cronHook);
|
||||||
|
|
||||||
|
this.indexSyscalls = pageIndexSyscalls(
|
||||||
|
`${dbPrefix}_page_index`,
|
||||||
|
globalThis.indexedDB,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Code widget hook
|
||||||
|
this.codeWidgetHook = new CodeWidgetHook();
|
||||||
|
this.system.addHook(this.codeWidgetHook);
|
||||||
|
|
||||||
|
// Command hook
|
||||||
|
this.commandHook = new CommandHook();
|
||||||
|
this.commandHook.on({
|
||||||
|
commandsUpdated: (commandMap) => {
|
||||||
|
this.editor.viewDispatch({
|
||||||
|
type: "update-commands",
|
||||||
|
commands: commandMap,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.system.addHook(this.commandHook);
|
||||||
|
|
||||||
|
// Slash command hook
|
||||||
|
this.slashCommandHook = new SlashCommandHook(this.editor);
|
||||||
|
this.system.addHook(this.slashCommandHook);
|
||||||
|
|
||||||
|
this.eventHook.addLocalListener("plug:changed", async (fileName) => {
|
||||||
|
console.log("Plug updated, reloading:", fileName);
|
||||||
|
this.system.unload(fileName);
|
||||||
|
const plug = await this.system.load(
|
||||||
|
new URL(`/${fileName}`, location.href),
|
||||||
|
createSandbox,
|
||||||
|
);
|
||||||
|
if ((plug.manifest! as Manifest).syntax) {
|
||||||
|
// If there are syntax extensions, rebuild the markdown parser immediately
|
||||||
|
this.updateMarkdownParser();
|
||||||
|
}
|
||||||
|
this.plugsUpdated = true;
|
||||||
|
});
|
||||||
|
this.registerSyscalls();
|
||||||
|
}
|
||||||
|
|
||||||
|
registerSyscalls() {
|
||||||
|
const storeCalls = storeSyscalls(this.kvStore);
|
||||||
|
|
||||||
|
// Slash command hook
|
||||||
|
this.slashCommandHook = new SlashCommandHook(this.editor);
|
||||||
|
this.system.addHook(this.slashCommandHook);
|
||||||
|
|
||||||
|
// Syscalls available to all plugs
|
||||||
|
this.system.registerSyscalls(
|
||||||
|
[],
|
||||||
|
eventSyscalls(this.eventHook),
|
||||||
|
editorSyscalls(this.editor),
|
||||||
|
spaceSyscalls(this.editor),
|
||||||
|
systemSyscalls(this.editor, this.system),
|
||||||
|
markdownSyscalls(buildMarkdown(this.mdExtensions)),
|
||||||
|
assetSyscalls(this.system),
|
||||||
|
yamlSyscalls(),
|
||||||
|
storeCalls,
|
||||||
|
this.indexSyscalls,
|
||||||
|
debugSyscalls(),
|
||||||
|
syncSyscalls(this.editor),
|
||||||
|
// LEGACY
|
||||||
|
clientStoreSyscalls(storeCalls),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Syscalls that require some additional permissions
|
||||||
|
this.system.registerSyscalls(
|
||||||
|
["fetch"],
|
||||||
|
sandboxFetchSyscalls(this.editor.remoteSpacePrimitives),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.system.registerSyscalls(
|
||||||
|
["shell"],
|
||||||
|
shellSyscalls(this.editor.remoteSpacePrimitives),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async reloadPlugsFromSpace(space: Space) {
|
||||||
|
console.log("Loading plugs");
|
||||||
|
await space.updatePageList();
|
||||||
|
await this.system.unloadAll();
|
||||||
|
console.log("(Re)loading plugs");
|
||||||
|
await Promise.all((await space.listPlugs()).map(async (plugName) => {
|
||||||
|
try {
|
||||||
|
await this.system.load(
|
||||||
|
new URL(plugName, location.origin),
|
||||||
|
createSandbox,
|
||||||
|
);
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error("Could not load plug", plugName, "error:", e.message);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMarkdownParser() {
|
||||||
|
// Load all syntax extensions
|
||||||
|
this.mdExtensions = loadMarkdownExtensions(this.system);
|
||||||
|
// And reload the syscalls to use the new syntax extensions
|
||||||
|
this.system.registerSyscalls(
|
||||||
|
[],
|
||||||
|
markdownSyscalls(buildMarkdown(this.mdExtensions)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
localSyscall(name: string, args: any[]) {
|
||||||
|
return this.system.localSyscall("[local]", name, args);
|
||||||
|
}
|
||||||
|
}
|
|
@ -103,7 +103,8 @@ export function fencedCodePlugin(editor: Editor) {
|
||||||
if (isCursorInRange(state, [from, to])) return;
|
if (isCursorInRange(state, [from, to])) return;
|
||||||
const text = state.sliceDoc(from, to);
|
const text = state.sliceDoc(from, to);
|
||||||
const [_, lang] = text.match(/^```(\w+)?/)!;
|
const [_, lang] = text.match(/^```(\w+)?/)!;
|
||||||
const codeWidgetCallback = editor.codeWidgetHook.codeWidgetCallbacks
|
const codeWidgetCallback = editor.system.codeWidgetHook
|
||||||
|
.codeWidgetCallbacks
|
||||||
.get(lang);
|
.get(lang);
|
||||||
if (codeWidgetCallback) {
|
if (codeWidgetCallback) {
|
||||||
// We got a custom renderer!
|
// We got a custom renderer!
|
||||||
|
|
|
@ -137,17 +137,19 @@ export function Panel({
|
||||||
break;
|
break;
|
||||||
case "syscall": {
|
case "syscall": {
|
||||||
const { id, name, args } = data;
|
const { id, name, args } = data;
|
||||||
editor.system.localSyscall("core", name, args).then((result) => {
|
editor.system.localSyscall(name, args).then(
|
||||||
if (!iFrameRef.current?.contentWindow) {
|
(result) => {
|
||||||
// iFrame already went away
|
if (!iFrameRef.current?.contentWindow) {
|
||||||
return;
|
// iFrame already went away
|
||||||
}
|
return;
|
||||||
iFrameRef.current!.contentWindow!.postMessage({
|
}
|
||||||
type: "syscall-response",
|
iFrameRef.current!.contentWindow!.postMessage({
|
||||||
id,
|
type: "syscall-response",
|
||||||
result,
|
id,
|
||||||
});
|
result,
|
||||||
}).catch((e: any) => {
|
});
|
||||||
|
},
|
||||||
|
).catch((e: any) => {
|
||||||
if (!iFrameRef.current?.contentWindow) {
|
if (!iFrameRef.current?.contentWindow) {
|
||||||
// iFrame already went away
|
// iFrame already went away
|
||||||
return;
|
return;
|
||||||
|
|
197
web/editor.tsx
197
web/editor.tsx
|
@ -52,21 +52,11 @@ import {
|
||||||
xmlLanguage,
|
xmlLanguage,
|
||||||
yamlLanguage,
|
yamlLanguage,
|
||||||
} from "../common/deps.ts";
|
} from "../common/deps.ts";
|
||||||
import { Manifest, SilverBulletHooks } from "../common/manifest.ts";
|
|
||||||
import {
|
|
||||||
loadMarkdownExtensions,
|
|
||||||
MDExt,
|
|
||||||
} from "../common/markdown_parser/markdown_ext.ts";
|
|
||||||
import buildMarkdown from "../common/markdown_parser/parser.ts";
|
import buildMarkdown from "../common/markdown_parser/parser.ts";
|
||||||
import { Space } from "./space.ts";
|
import { Space } from "./space.ts";
|
||||||
import { markdownSyscalls } from "./syscalls/markdown.ts";
|
|
||||||
import { FilterOption, PageMeta } from "./types.ts";
|
import { FilterOption, PageMeta } from "./types.ts";
|
||||||
import { isMacLike, parseYamlSettings, safeRun } from "../common/util.ts";
|
import { isMacLike, parseYamlSettings, safeRun } from "../common/util.ts";
|
||||||
import { createSandbox } from "../plugos/environments/webworker_sandbox.ts";
|
|
||||||
import { EventHook } from "../plugos/hooks/event.ts";
|
import { EventHook } from "../plugos/hooks/event.ts";
|
||||||
import assetSyscalls from "../plugos/syscalls/asset.ts";
|
|
||||||
import { eventSyscalls } from "../plugos/syscalls/event.ts";
|
|
||||||
import { System } from "../plugos/system.ts";
|
|
||||||
import { cleanModePlugins } from "./cm_plugins/clean.ts";
|
import { cleanModePlugins } from "./cm_plugins/clean.ts";
|
||||||
import {
|
import {
|
||||||
attachmentExtension,
|
attachmentExtension,
|
||||||
|
@ -91,14 +81,10 @@ import {
|
||||||
useReducer,
|
useReducer,
|
||||||
vim,
|
vim,
|
||||||
} from "./deps.ts";
|
} from "./deps.ts";
|
||||||
import { AppCommand, CommandHook } from "./hooks/command.ts";
|
import { AppCommand } from "./hooks/command.ts";
|
||||||
import { SlashCommandHook } from "./hooks/slash_command.ts";
|
|
||||||
import { PathPageNavigator } from "./navigator.ts";
|
import { PathPageNavigator } from "./navigator.ts";
|
||||||
import reducer from "./reducer.ts";
|
import reducer from "./reducer.ts";
|
||||||
import customMarkdownStyle from "./style.ts";
|
import customMarkdownStyle from "./style.ts";
|
||||||
import { editorSyscalls } from "./syscalls/editor.ts";
|
|
||||||
import { spaceSyscalls } from "./syscalls/space.ts";
|
|
||||||
import { systemSyscalls } from "./syscalls/system.ts";
|
|
||||||
import {
|
import {
|
||||||
Action,
|
Action,
|
||||||
AppViewState,
|
AppViewState,
|
||||||
|
@ -111,33 +97,21 @@ import type {
|
||||||
ClickEvent,
|
ClickEvent,
|
||||||
CompleteEvent,
|
CompleteEvent,
|
||||||
} from "../plug-api/app_event.ts";
|
} from "../plug-api/app_event.ts";
|
||||||
import { CodeWidgetHook } from "./hooks/code_widget.ts";
|
|
||||||
import { throttle } from "../common/async_util.ts";
|
import { throttle } from "../common/async_util.ts";
|
||||||
import { readonlyMode } from "./cm_plugins/readonly.ts";
|
import { readonlyMode } from "./cm_plugins/readonly.ts";
|
||||||
import { PageNamespaceHook } from "../common/hooks/page_namespace.ts";
|
|
||||||
import { CronHook } from "../plugos/hooks/cron.ts";
|
|
||||||
import { pageIndexSyscalls } from "./syscalls/index.ts";
|
|
||||||
import { storeSyscalls } from "../plugos/syscalls/store.dexie_browser.ts";
|
|
||||||
import { PlugSpacePrimitives } from "../common/spaces/plug_space_primitives.ts";
|
import { PlugSpacePrimitives } from "../common/spaces/plug_space_primitives.ts";
|
||||||
import { IndexedDBSpacePrimitives } from "../common/spaces/indexeddb_space_primitives.ts";
|
import { IndexedDBSpacePrimitives } from "../common/spaces/indexeddb_space_primitives.ts";
|
||||||
import { FileMetaSpacePrimitives } from "../common/spaces/file_meta_space_primitives.ts";
|
import { FileMetaSpacePrimitives } from "../common/spaces/file_meta_space_primitives.ts";
|
||||||
import { EventedSpacePrimitives } from "../common/spaces/evented_space_primitives.ts";
|
import { EventedSpacePrimitives } from "../common/spaces/evented_space_primitives.ts";
|
||||||
import { clientStoreSyscalls } from "./syscalls/clientStore.ts";
|
|
||||||
import { sandboxFetchSyscalls } from "./syscalls/fetch.ts";
|
|
||||||
import { shellSyscalls } from "./syscalls/shell.ts";
|
|
||||||
import { SyncService } from "./sync_service.ts";
|
import { SyncService } from "./sync_service.ts";
|
||||||
import { yamlSyscalls } from "./syscalls/yaml.ts";
|
|
||||||
import { simpleHash } from "../common/crypto.ts";
|
import { simpleHash } from "../common/crypto.ts";
|
||||||
import { DexieKVStore } from "../plugos/lib/kv_store.dexie.ts";
|
import { DexieKVStore } from "../plugos/lib/kv_store.dexie.ts";
|
||||||
import { SyncStatus } from "../common/spaces/sync.ts";
|
import { SyncStatus } from "../common/spaces/sync.ts";
|
||||||
import { HttpSpacePrimitives } from "../common/spaces/http_space_primitives.ts";
|
import { HttpSpacePrimitives } from "../common/spaces/http_space_primitives.ts";
|
||||||
import { FallbackSpacePrimitives } from "../common/spaces/fallback_space_primitives.ts";
|
import { FallbackSpacePrimitives } from "../common/spaces/fallback_space_primitives.ts";
|
||||||
import { syncSyscalls } from "./syscalls/sync.ts";
|
|
||||||
import { FilteredSpacePrimitives } from "../common/spaces/filtered_space_primitives.ts";
|
import { FilteredSpacePrimitives } from "../common/spaces/filtered_space_primitives.ts";
|
||||||
import { isValidPageName } from "$sb/lib/page.ts";
|
import { isValidPageName } from "$sb/lib/page.ts";
|
||||||
import { debugSyscalls } from "./syscalls/debug.ts";
|
import { ClientSystem } from "./client_system.ts";
|
||||||
|
|
||||||
const frontMatterRegex = /^---\n(([^\n]|\n)*?)---\n/;
|
|
||||||
|
|
||||||
class PageState {
|
class PageState {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -145,8 +119,9 @@ class PageState {
|
||||||
readonly selection: EditorSelection,
|
readonly selection: EditorSelection,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
const frontMatterRegex = /^---\n(([^\n]|\n)*?)---\n/;
|
||||||
|
|
||||||
const saveInterval = 1000;
|
const autoSaveInterval = 1000;
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
|
@ -160,32 +135,27 @@ declare global {
|
||||||
|
|
||||||
// TODO: Oh my god, need to refactor this
|
// TODO: Oh my god, need to refactor this
|
||||||
export class Editor {
|
export class Editor {
|
||||||
readonly commandHook: CommandHook;
|
|
||||||
readonly slashCommandHook: SlashCommandHook;
|
|
||||||
openPages = new Map<string, PageState>();
|
openPages = new Map<string, PageState>();
|
||||||
editorView?: EditorView;
|
editorView?: EditorView;
|
||||||
viewState: AppViewState = initialViewState;
|
viewState: AppViewState = initialViewState;
|
||||||
viewDispatch: (action: Action) => void = () => {};
|
viewDispatch: (action: Action) => void = () => {};
|
||||||
|
pageNavigator?: PathPageNavigator;
|
||||||
|
|
||||||
space: Space;
|
space: Space;
|
||||||
|
|
||||||
remoteSpacePrimitives: HttpSpacePrimitives;
|
remoteSpacePrimitives: HttpSpacePrimitives;
|
||||||
plugSpaceRemotePrimitives: PlugSpacePrimitives;
|
plugSpaceRemotePrimitives: PlugSpacePrimitives;
|
||||||
|
|
||||||
pageNavigator?: PathPageNavigator;
|
saveTimeout?: number;
|
||||||
eventHook: EventHook;
|
|
||||||
codeWidgetHook: CodeWidgetHook;
|
|
||||||
|
|
||||||
saveTimeout: any;
|
|
||||||
debouncedUpdateEvent = throttle(() => {
|
debouncedUpdateEvent = throttle(() => {
|
||||||
this.eventHook
|
this.eventHook
|
||||||
.dispatchEvent("editor:updated")
|
.dispatchEvent("editor:updated")
|
||||||
.catch((e) => console.error("Error dispatching editor:updated event", e));
|
.catch((e) => console.error("Error dispatching editor:updated event", e));
|
||||||
}, 1000);
|
}, 1000);
|
||||||
system: System<SilverBulletHooks>;
|
system: ClientSystem;
|
||||||
mdExtensions: MDExt[] = [];
|
|
||||||
|
|
||||||
// Track if plugs have been updated since sync cycle
|
// Track if plugs have been updated since sync cycle
|
||||||
private plugsUpdated = false;
|
|
||||||
fullSyncCompleted = false;
|
fullSyncCompleted = false;
|
||||||
|
|
||||||
// Runtime state (that doesn't make sense in viewState)
|
// Runtime state (that doesn't make sense in viewState)
|
||||||
|
@ -193,34 +163,16 @@ export class Editor {
|
||||||
settings?: BuiltinSettings;
|
settings?: BuiltinSettings;
|
||||||
kvStore: DexieKVStore;
|
kvStore: DexieKVStore;
|
||||||
|
|
||||||
|
// Event bus used to communicate between components
|
||||||
|
eventHook: EventHook;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
parent: Element,
|
parent: Element,
|
||||||
) {
|
) {
|
||||||
const runtimeConfig = window.silverBulletConfig;
|
const runtimeConfig = window.silverBulletConfig;
|
||||||
|
|
||||||
// Instantiate a PlugOS system
|
|
||||||
const system = new System<SilverBulletHooks>("client");
|
|
||||||
this.system = system;
|
|
||||||
|
|
||||||
// Generate a semi-unique prefix for the database so not to reuse databases for different space paths
|
// Generate a semi-unique prefix for the database so not to reuse databases for different space paths
|
||||||
const dbPrefix = "" + simpleHash(runtimeConfig.spaceFolderPath);
|
const dbPrefix = "" + simpleHash(window.silverBulletConfig.spaceFolderPath);
|
||||||
|
|
||||||
// Attach the page namespace hook
|
|
||||||
const namespaceHook = new PageNamespaceHook();
|
|
||||||
system.addHook(namespaceHook);
|
|
||||||
|
|
||||||
// Event hook
|
|
||||||
this.eventHook = new EventHook();
|
|
||||||
system.addHook(this.eventHook);
|
|
||||||
|
|
||||||
// Cron hook
|
|
||||||
const cronHook = new CronHook(system);
|
|
||||||
system.addHook(cronHook);
|
|
||||||
|
|
||||||
const indexSyscalls = pageIndexSyscalls(
|
|
||||||
`${dbPrefix}_page_index`,
|
|
||||||
globalThis.indexedDB,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.kvStore = new DexieKVStore(
|
this.kvStore = new DexieKVStore(
|
||||||
`${dbPrefix}_store`,
|
`${dbPrefix}_store`,
|
||||||
|
@ -228,7 +180,16 @@ export class Editor {
|
||||||
globalThis.indexedDB,
|
globalThis.indexedDB,
|
||||||
);
|
);
|
||||||
|
|
||||||
const storeCalls = storeSyscalls(this.kvStore);
|
// Event hook
|
||||||
|
this.eventHook = new EventHook();
|
||||||
|
|
||||||
|
// Instantiate a PlugOS system
|
||||||
|
this.system = new ClientSystem(
|
||||||
|
this,
|
||||||
|
this.kvStore,
|
||||||
|
dbPrefix,
|
||||||
|
this.eventHook,
|
||||||
|
);
|
||||||
|
|
||||||
// Setup space
|
// Setup space
|
||||||
this.remoteSpacePrimitives = new HttpSpacePrimitives(
|
this.remoteSpacePrimitives = new HttpSpacePrimitives(
|
||||||
|
@ -239,10 +200,11 @@ export class Editor {
|
||||||
|
|
||||||
this.plugSpaceRemotePrimitives = new PlugSpacePrimitives(
|
this.plugSpaceRemotePrimitives = new PlugSpacePrimitives(
|
||||||
this.remoteSpacePrimitives,
|
this.remoteSpacePrimitives,
|
||||||
namespaceHook,
|
this.system.namespaceHook,
|
||||||
);
|
);
|
||||||
|
|
||||||
let fileFilterFn: (s: string) => boolean = () => true;
|
let fileFilterFn: (s: string) => boolean = () => true;
|
||||||
|
|
||||||
const localSpacePrimitives = new FilteredSpacePrimitives(
|
const localSpacePrimitives = new FilteredSpacePrimitives(
|
||||||
new FileMetaSpacePrimitives(
|
new FileMetaSpacePrimitives(
|
||||||
new EventedSpacePrimitives(
|
new EventedSpacePrimitives(
|
||||||
|
@ -256,7 +218,7 @@ export class Editor {
|
||||||
),
|
),
|
||||||
this.eventHook,
|
this.eventHook,
|
||||||
),
|
),
|
||||||
indexSyscalls,
|
this.system.indexSyscalls,
|
||||||
),
|
),
|
||||||
(meta) => fileFilterFn(meta.name),
|
(meta) => fileFilterFn(meta.name),
|
||||||
async () => {
|
async () => {
|
||||||
|
@ -287,26 +249,6 @@ export class Editor {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Code widget hook
|
|
||||||
this.codeWidgetHook = new CodeWidgetHook();
|
|
||||||
this.system.addHook(this.codeWidgetHook);
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
this.render(parent);
|
this.render(parent);
|
||||||
|
|
||||||
this.editorView = new EditorView({
|
this.editorView = new EditorView({
|
||||||
|
@ -314,35 +256,6 @@ export class Editor {
|
||||||
parent: document.getElementById("sb-editor")!,
|
parent: document.getElementById("sb-editor")!,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Syscalls available to all plugs
|
|
||||||
this.system.registerSyscalls(
|
|
||||||
[],
|
|
||||||
eventSyscalls(this.eventHook),
|
|
||||||
editorSyscalls(this),
|
|
||||||
spaceSyscalls(this),
|
|
||||||
systemSyscalls(this, this.system),
|
|
||||||
markdownSyscalls(buildMarkdown(this.mdExtensions)),
|
|
||||||
assetSyscalls(this.system),
|
|
||||||
yamlSyscalls(),
|
|
||||||
storeCalls,
|
|
||||||
indexSyscalls,
|
|
||||||
debugSyscalls(),
|
|
||||||
syncSyscalls(this.syncService),
|
|
||||||
// LEGACY
|
|
||||||
clientStoreSyscalls(storeCalls),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Syscalls that require some additional permissions
|
|
||||||
this.system.registerSyscalls(
|
|
||||||
["fetch"],
|
|
||||||
sandboxFetchSyscalls(this.remoteSpacePrimitives),
|
|
||||||
);
|
|
||||||
|
|
||||||
this.system.registerSyscalls(
|
|
||||||
["shell"],
|
|
||||||
shellSyscalls(this.remoteSpacePrimitives),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Make keyboard shortcuts work even when the editor is in read only mode or not focused
|
// Make keyboard shortcuts work even when the editor is in read only mode or not focused
|
||||||
globalThis.addEventListener("keydown", (ev) => {
|
globalThis.addEventListener("keydown", (ev) => {
|
||||||
if (!this.editorView?.hasFocus) {
|
if (!this.editorView?.hasFocus) {
|
||||||
|
@ -370,20 +283,6 @@ export class Editor {
|
||||||
this.viewDispatch({ type: "show-palette", context: this.getContext() });
|
this.viewDispatch({ type: "show-palette", context: this.getContext() });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.eventHook.addLocalListener("plug:changed", async (fileName) => {
|
|
||||||
console.log("Plug updated, reloading:", fileName);
|
|
||||||
system.unload(fileName);
|
|
||||||
const plug = await system.load(
|
|
||||||
new URL(`/${fileName}`, location.href),
|
|
||||||
createSandbox,
|
|
||||||
);
|
|
||||||
if ((plug.manifest! as Manifest).syntax) {
|
|
||||||
// If there are syntax extensions, rebuild the markdown parser immediately
|
|
||||||
this.updateMarkdownParser();
|
|
||||||
}
|
|
||||||
this.plugsUpdated = true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get currentPage(): string | undefined {
|
get currentPage(): string | undefined {
|
||||||
|
@ -431,8 +330,8 @@ export class Editor {
|
||||||
console.log("Navigating to anchor", pos);
|
console.log("Navigating to anchor", pos);
|
||||||
|
|
||||||
// We're going to look up the anchor through a direct page store query...
|
// We're going to look up the anchor through a direct page store query...
|
||||||
|
// TODO: This should be extracted
|
||||||
const posLookup = await this.system.localSyscall(
|
const posLookup = await this.system.localSyscall(
|
||||||
"core",
|
|
||||||
"index.get",
|
"index.get",
|
||||||
[
|
[
|
||||||
pageName,
|
pageName,
|
||||||
|
@ -489,7 +388,7 @@ export class Editor {
|
||||||
// "sync:success" is called with a number of operations only from syncSpace(), not from syncing individual pages
|
// "sync:success" is called with a number of operations only from syncSpace(), not from syncing individual pages
|
||||||
this.fullSyncCompleted = true;
|
this.fullSyncCompleted = true;
|
||||||
}
|
}
|
||||||
if (this.plugsUpdated) {
|
if (this.system.plugsUpdated) {
|
||||||
// To register new commands, update editor state based on new plugs
|
// To register new commands, update editor state based on new plugs
|
||||||
this.rebuildEditorState();
|
this.rebuildEditorState();
|
||||||
this.dispatchAppEvent("editor:pageLoaded", this.currentPage);
|
this.dispatchAppEvent("editor:pageLoaded", this.currentPage);
|
||||||
|
@ -500,7 +399,7 @@ export class Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Reset for next sync cycle
|
// Reset for next sync cycle
|
||||||
this.plugsUpdated = false;
|
this.system.plugsUpdated = false;
|
||||||
|
|
||||||
this.viewDispatch({ type: "sync-change", synced: true });
|
this.viewDispatch({ type: "sync-change", synced: true });
|
||||||
});
|
});
|
||||||
|
@ -582,7 +481,7 @@ export class Editor {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
immediate ? 0 : saveInterval,
|
immediate ? 0 : autoSaveInterval,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -695,7 +594,7 @@ export class Editor {
|
||||||
readOnly: boolean,
|
readOnly: boolean,
|
||||||
): EditorState {
|
): EditorState {
|
||||||
const commandKeyBindings: KeyBinding[] = [];
|
const commandKeyBindings: KeyBinding[] = [];
|
||||||
for (const def of this.commandHook.editorCommands.values()) {
|
for (const def of this.system.commandHook.editorCommands.values()) {
|
||||||
if (def.command.key) {
|
if (def.command.key) {
|
||||||
commandKeyBindings.push({
|
commandKeyBindings.push({
|
||||||
key: def.command.key,
|
key: def.command.key,
|
||||||
|
@ -729,7 +628,7 @@ export class Editor {
|
||||||
const editor = this;
|
const editor = this;
|
||||||
let touchCount = 0;
|
let touchCount = 0;
|
||||||
|
|
||||||
const markdownLanguage = buildMarkdown(this.mdExtensions);
|
const markdownLanguage = buildMarkdown(this.system.mdExtensions);
|
||||||
|
|
||||||
return EditorState.create({
|
return EditorState.create({
|
||||||
doc: text,
|
doc: text,
|
||||||
|
@ -884,12 +783,12 @@ export class Editor {
|
||||||
markdownLanguage.data.of({
|
markdownLanguage.data.of({
|
||||||
closeBrackets: { brackets: ["(", "{", "[", "`"] },
|
closeBrackets: { brackets: ["(", "{", "[", "`"] },
|
||||||
}),
|
}),
|
||||||
syntaxHighlighting(customMarkdownStyle(this.mdExtensions)),
|
syntaxHighlighting(customMarkdownStyle(this.system.mdExtensions)),
|
||||||
autocompletion({
|
autocompletion({
|
||||||
override: [
|
override: [
|
||||||
this.editorComplete.bind(this),
|
this.editorComplete.bind(this),
|
||||||
this.slashCommandHook.slashCommandCompleter.bind(
|
this.system.slashCommandHook.slashCommandCompleter.bind(
|
||||||
this.slashCommandHook,
|
this.system.slashCommandHook,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
@ -1059,38 +958,16 @@ export class Editor {
|
||||||
|
|
||||||
async reloadPlugs() {
|
async reloadPlugs() {
|
||||||
console.log("Loading plugs");
|
console.log("Loading plugs");
|
||||||
await this.space.updatePageList();
|
await this.system.reloadPlugsFromSpace(this.space);
|
||||||
await this.system.unloadAll();
|
|
||||||
console.log("(Re)loading plugs");
|
|
||||||
await Promise.all((await this.space.listPlugs()).map(async (plugName) => {
|
|
||||||
try {
|
|
||||||
await this.system.load(
|
|
||||||
new URL(plugName, location.origin),
|
|
||||||
createSandbox,
|
|
||||||
);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error("Could not load plug", plugName, "error:", e.message);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
this.rebuildEditorState();
|
this.rebuildEditorState();
|
||||||
await this.dispatchAppEvent("plugs:loaded");
|
await this.dispatchAppEvent("plugs:loaded");
|
||||||
}
|
}
|
||||||
|
|
||||||
updateMarkdownParser() {
|
|
||||||
// Load all syntax extensions
|
|
||||||
this.mdExtensions = loadMarkdownExtensions(this.system);
|
|
||||||
// And reload the syscalls to use the new syntax extensions
|
|
||||||
this.system.registerSyscalls(
|
|
||||||
[],
|
|
||||||
markdownSyscalls(buildMarkdown(this.mdExtensions)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
rebuildEditorState() {
|
rebuildEditorState() {
|
||||||
const editorView = this.editorView;
|
const editorView = this.editorView;
|
||||||
console.log("Rebuilding editor state");
|
console.log("Rebuilding editor state");
|
||||||
|
|
||||||
this.updateMarkdownParser();
|
this.system.updateMarkdownParser();
|
||||||
|
|
||||||
if (editorView && this.currentPage) {
|
if (editorView && this.currentPage) {
|
||||||
// And update the editor if a page is loaded
|
// And update the editor if a page is loaded
|
||||||
|
@ -1470,7 +1347,7 @@ export class Editor {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log("Now renaming page to...", newName);
|
console.log("Now renaming page to...", newName);
|
||||||
await editor.system.loadedPlugs.get("core")!.invoke(
|
await editor.system.system.loadedPlugs.get("core")!.invoke(
|
||||||
"renamePage",
|
"renamePage",
|
||||||
[{ page: newName }],
|
[{ page: newName }],
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,26 +3,25 @@ import { SysCallMapping } from "../../plugos/system.ts";
|
||||||
import { AttachmentMeta, PageMeta } from "../types.ts";
|
import { AttachmentMeta, PageMeta } from "../types.ts";
|
||||||
|
|
||||||
export function spaceSyscalls(editor: Editor): SysCallMapping {
|
export function spaceSyscalls(editor: Editor): SysCallMapping {
|
||||||
const space = editor.space;
|
|
||||||
return {
|
return {
|
||||||
"space.listPages": (): Promise<PageMeta[]> => {
|
"space.listPages": (): Promise<PageMeta[]> => {
|
||||||
return space.fetchPageList();
|
return editor.space.fetchPageList();
|
||||||
},
|
},
|
||||||
"space.readPage": async (
|
"space.readPage": async (
|
||||||
_ctx,
|
_ctx,
|
||||||
name: string,
|
name: string,
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
return (await space.readPage(name)).text;
|
return (await editor.space.readPage(name)).text;
|
||||||
},
|
},
|
||||||
"space.getPageMeta": (_ctx, name: string): Promise<PageMeta> => {
|
"space.getPageMeta": (_ctx, name: string): Promise<PageMeta> => {
|
||||||
return space.getPageMeta(name);
|
return editor.space.getPageMeta(name);
|
||||||
},
|
},
|
||||||
"space.writePage": (
|
"space.writePage": (
|
||||||
_ctx,
|
_ctx,
|
||||||
name: string,
|
name: string,
|
||||||
text: string,
|
text: string,
|
||||||
): Promise<PageMeta> => {
|
): Promise<PageMeta> => {
|
||||||
return space.writePage(name, text);
|
return editor.space.writePage(name, text);
|
||||||
},
|
},
|
||||||
"space.deletePage": async (_ctx, name: string) => {
|
"space.deletePage": async (_ctx, name: string) => {
|
||||||
// If we're deleting the current page, navigate to the index page
|
// If we're deleting the current page, navigate to the index page
|
||||||
|
@ -35,32 +34,32 @@ export function spaceSyscalls(editor: Editor): SysCallMapping {
|
||||||
await editor.space.deletePage(name);
|
await editor.space.deletePage(name);
|
||||||
},
|
},
|
||||||
"space.listPlugs": (): Promise<string[]> => {
|
"space.listPlugs": (): Promise<string[]> => {
|
||||||
return space.listPlugs();
|
return editor.space.listPlugs();
|
||||||
},
|
},
|
||||||
"space.listAttachments": async (): Promise<AttachmentMeta[]> => {
|
"space.listAttachments": async (): Promise<AttachmentMeta[]> => {
|
||||||
return await space.fetchAttachmentList();
|
return await editor.space.fetchAttachmentList();
|
||||||
},
|
},
|
||||||
"space.readAttachment": async (
|
"space.readAttachment": async (
|
||||||
_ctx,
|
_ctx,
|
||||||
name: string,
|
name: string,
|
||||||
): Promise<Uint8Array> => {
|
): Promise<Uint8Array> => {
|
||||||
return (await space.readAttachment(name)).data;
|
return (await editor.space.readAttachment(name)).data;
|
||||||
},
|
},
|
||||||
"space.getAttachmentMeta": async (
|
"space.getAttachmentMeta": async (
|
||||||
_ctx,
|
_ctx,
|
||||||
name: string,
|
name: string,
|
||||||
): Promise<AttachmentMeta> => {
|
): Promise<AttachmentMeta> => {
|
||||||
return await space.getAttachmentMeta(name);
|
return await editor.space.getAttachmentMeta(name);
|
||||||
},
|
},
|
||||||
"space.writeAttachment": (
|
"space.writeAttachment": (
|
||||||
_ctx,
|
_ctx,
|
||||||
name: string,
|
name: string,
|
||||||
data: Uint8Array,
|
data: Uint8Array,
|
||||||
): Promise<AttachmentMeta> => {
|
): Promise<AttachmentMeta> => {
|
||||||
return space.writeAttachment(name, data);
|
return editor.space.writeAttachment(name, data);
|
||||||
},
|
},
|
||||||
"space.deleteAttachment": async (_ctx, name: string) => {
|
"space.deleteAttachment": async (_ctx, name: string) => {
|
||||||
await space.deleteAttachment(name);
|
await editor.space.deleteAttachment(name);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { SysCallMapping } from "../../plugos/system.ts";
|
import { SysCallMapping } from "../../plugos/system.ts";
|
||||||
import { SyncService } from "../sync_service.ts";
|
import type { Editor } from "../editor.tsx";
|
||||||
|
|
||||||
export function syncSyscalls(syncService: SyncService): SysCallMapping {
|
export function syncSyscalls(editor: Editor): SysCallMapping {
|
||||||
return {
|
return {
|
||||||
"sync.isSyncing": (): Promise<boolean> => {
|
"sync.isSyncing": (): Promise<boolean> => {
|
||||||
return syncService.isSyncing();
|
return editor.syncService.isSyncing();
|
||||||
},
|
},
|
||||||
"sync.hasInitialSyncCompleted": (): Promise<boolean> => {
|
"sync.hasInitialSyncCompleted": (): Promise<boolean> => {
|
||||||
return syncService.hasInitialSyncCompleted();
|
return editor.syncService.hasInitialSyncCompleted();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ export function systemSyscalls(
|
||||||
},
|
},
|
||||||
"system.listCommands": (): { [key: string]: CommandDef } => {
|
"system.listCommands": (): { [key: string]: CommandDef } => {
|
||||||
const allCommands: { [key: string]: CommandDef } = {};
|
const allCommands: { [key: string]: CommandDef } = {};
|
||||||
for (let [cmd, def] of editor.commandHook.editorCommands) {
|
for (const [cmd, def] of editor.system.commandHook.editorCommands) {
|
||||||
allCommands[cmd] = def.command;
|
allCommands[cmd] = def.command;
|
||||||
}
|
}
|
||||||
return allCommands;
|
return allCommands;
|
||||||
|
|
Loading…
Reference in New Issue