1
0

Work on $share

This commit is contained in:
Zef Hemel 2022-11-24 12:04:00 +01:00
parent c5c6cd3af0
commit 5b1ec14891
20 changed files with 528 additions and 163 deletions

View File

@ -65,6 +65,7 @@ export async function publishCommand(options: {
new PlugSpacePrimitives(
new DiskSpacePrimitives(pagesPath),
namespaceHook,
"server",
),
eventHook,
),

155
plug-api/lib/frontmatter.ts Normal file
View File

@ -0,0 +1,155 @@
import * as YAML from "yaml";
import {
addParentPointers,
findNodeOfType,
ParseTree,
renderToText,
replaceNodesMatching,
traverseTree,
} from "$sb/lib/tree.ts";
// Extracts front matter (or legacy "meta" code blocks) from a markdown document
// optionally removes certain keys from the front matter
export function extractFrontmatter(
tree: ParseTree,
removeKeys: string[] = [],
): any {
let data: any = {};
addParentPointers(tree);
replaceNodesMatching(tree, (t) => {
// Find top-level hash tags
if (t.type === "Hashtag") {
// Check if if nested directly into a Paragraph
if (t.parent && t.parent.type === "Paragraph") {
const tagname = t.children![0].text!.substring(1);
if (!data.tags) {
data.tags = [];
}
if (!data.tags.includes(tagname)) {
data.tags.push(tagname);
}
}
return;
}
// Find FrontMatter and parse it
if (t.type === "FrontMatter") {
const yamlText = renderToText(t.children![1].children![0]);
try {
const parsedData: any = YAML.parse(yamlText);
const newData = { ...parsedData };
data = { ...data, ...parsedData };
if (removeKeys.length > 0) {
let removedOne = false;
for (const key of removeKeys) {
if (key in newData) {
delete newData[key];
removedOne = true;
}
}
if (removedOne) {
t.children![0].text = YAML.stringify(newData);
}
}
// If nothing is left, let's just delete this whole block
if (Object.keys(newData).length === 0) {
return null;
}
} catch (e: any) {
console.error("Could not parse frontmatter", e);
}
}
// Find a fenced code block with `meta` as the language type
if (t.type !== "FencedCode") {
return;
}
const codeInfoNode = findNodeOfType(t, "CodeInfo");
if (!codeInfoNode) {
return;
}
if (codeInfoNode.children![0].text !== "meta") {
return;
}
const codeTextNode = findNodeOfType(t, "CodeText");
if (!codeTextNode) {
// Honestly, this shouldn't happen
return;
}
const codeText = codeTextNode.children![0].text!;
const parsedData: any = YAML.parse(codeText);
const newData = { ...parsedData };
data = { ...data, ...parsedData };
if (removeKeys.length > 0) {
let removedOne = false;
for (const key of removeKeys) {
if (key in newData) {
delete newData[key];
removedOne = true;
}
}
if (removedOne) {
codeTextNode.children![0].text = YAML.stringify(newData).trim();
}
}
// If nothing is left, let's just delete this whole block
if (Object.keys(newData).length === 0) {
return null;
}
return undefined;
});
if (data.name) {
data.displayName = data.name;
delete data.name;
}
return data;
}
// Updates the front matter of a markdown document and returns the text as a rendered string
export function prepareFrontmatterDispatch(
tree: ParseTree,
data: Record<string, any>,
): any {
let dispatchData: any = null;
traverseTree(tree, (t) => {
// Find FrontMatter and parse it
if (t.type === "FrontMatter") {
const bodyNode = t.children![1].children![0];
const yamlText = renderToText(bodyNode);
try {
const parsedYaml = YAML.parse(yamlText) as any;
const newData = { ...parsedYaml, ...data };
// Patch inline
dispatchData = {
changes: {
from: bodyNode.from,
to: bodyNode.to,
insert: YAML.stringify(newData, { noArrayIndent: true }),
},
};
} catch (e: any) {
console.error("Error parsing YAML", e);
}
return true;
}
return false;
});
if (!dispatchData) {
// If we didn't find frontmatter, let's add it
dispatchData = {
changes: {
from: 0,
to: 0,
insert: "---\n" + YAML.stringify(data, { noArrayIndent: true }) +
"---\n",
},
};
}
return dispatchData;
}

View File

@ -0,0 +1,35 @@
name: collab
imports:
- https://get.silverbullet.md/global.plug.json
functions:
detectCollabPage:
path: "./collab.ts:detectPage"
events:
- editor:pageLoaded
- plugs:loaded
joinCommand:
path: "./collab.ts:joinCommand"
command:
name: "Share: Join Collab"
shareCommand:
path: "./collab.ts:shareCommand"
command:
name: "Share: Collab"
readPageCollab:
path: ./collab.ts:readFileCollab
env: client
pageNamespace:
pattern: "collab:.+"
operation: readFile
writePageCollab:
path: ./collab.ts:writeFileCollab
env: client
pageNamespace:
pattern: "collab:.+"
operation: writeFile
getPageMetaCollab:
path: ./collab.ts:getFileMetaCollab
env: client
pageNamespace:
pattern: "collab:.+"
operation: getFileMeta

169
plugs/collab/collab.ts Normal file
View File

@ -0,0 +1,169 @@
import {
findNodeOfType,
removeParentPointers,
renderToText,
} from "$sb/lib/tree.ts";
import { getText } from "$sb/silverbullet-syscall/editor.ts";
import { parseMarkdown } from "$sb/silverbullet-syscall/markdown.ts";
import {
extractFrontmatter,
prepareFrontmatterDispatch,
} from "$sb/lib/frontmatter.ts";
import * as YAML from "yaml";
import {
clientStore,
collab,
editor,
markdown,
space,
} from "$sb/silverbullet-syscall/mod.ts";
import { nanoid } from "https://esm.sh/nanoid@4.0.0";
import {
FileData,
FileEncoding,
} from "../../common/spaces/space_primitives.ts";
import { FileMeta } from "../../common/types.ts";
import { base64EncodedDataUrl } from "../../plugos/asset_bundle/base64.ts";
const defaultServer = "wss://collab.silverbullet.md";
async function ensureUsername(): Promise<string> {
let username = await clientStore.get("collabUsername");
if (!username) {
username = await editor.prompt(
"Please enter a publicly visible user name (or cancel for 'anonymous'):",
);
if (!username) {
return "anonymous";
} else {
await clientStore.set("collabUsername", username);
}
}
return username;
}
export async function joinCommand() {
let collabUri = await editor.prompt(
"Collab share URI:",
);
if (!collabUri) {
return;
}
if (!collabUri.startsWith("collab:")) {
collabUri = "collab:" + collabUri;
}
await editor.navigate(collabUri);
}
export async function shareCommand() {
const serverUrl = await editor.prompt(
"Please enter the URL of the collab server to use:",
defaultServer,
);
if (!serverUrl) {
return;
}
const roomId = nanoid();
await editor.save();
const text = await editor.getText();
const tree = await markdown.parseMarkdown(text);
let { $share } = extractFrontmatter(tree);
if (!$share) {
$share = [];
}
if (!Array.isArray($share)) {
$share = [$share];
}
removeParentPointers(tree);
const dispatchData = prepareFrontmatterDispatch(tree, {
$share: [...$share, `collab:${serverUrl}/${roomId}`],
});
await editor.dispatch(dispatchData);
collab.start(
serverUrl,
roomId,
await ensureUsername(),
);
}
export async function detectPage() {
const tree = await parseMarkdown(await getText());
const frontMatter = findNodeOfType(tree, "FrontMatter");
if (frontMatter) {
const yamlText = renderToText(frontMatter.children![1].children![0]);
try {
let { $share } = YAML.parse(yamlText) as any;
if (!$share) {
return;
}
if (!Array.isArray($share)) {
$share = [$share];
}
for (const uri of $share) {
if (uri.startsWith("collab:")) {
console.log("Going to enable collab");
const uriPieces = uri.substring("collab:".length).split("/");
await collab.start(
// All parts except the last one
uriPieces.slice(0, uriPieces.length - 1).join("/"),
// because the last one is the room ID
uriPieces[uriPieces.length - 1],
await ensureUsername(),
);
}
}
} catch (e) {
console.error("Error parsing YAML", e);
}
}
}
export async function readFileCollab(
name: string,
encoding: FileEncoding,
): Promise<{ data: FileData; meta: FileMeta }> {
if (!name.endsWith(".md")) {
throw new Error("File not found");
}
const collabUri = name.substring(0, name.length - ".md".length);
const text = `---\n$share: ${collabUri}\n---\n`;
return {
// encoding === "arraybuffer" is not an option, so either it's "string" or "dataurl"
data: encoding === "string" ? text : base64EncodedDataUrl(
"text/markdown",
new TextEncoder().encode(text),
),
meta: {
name,
contentType: "text/markdown",
size: text.length,
lastModified: 0,
perm: "rw",
},
};
}
export function getFileMetaCollab(name: string): FileMeta {
return {
name,
contentType: "text/markdown",
size: -1,
lastModified: 0,
perm: "rw",
};
}
export function writeFileCollab(name: string): FileMeta {
return {
name,
contentType: "text/markdown",
size: -1,
lastModified: 0,
perm: "rw",
};
}

View File

@ -21,7 +21,7 @@ import {
replaceNodesMatching,
} from "$sb/lib/tree.ts";
import { applyQuery } from "$sb/lib/query.ts";
import { extractMeta } from "../directive/data.ts";
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
// Key space:
// pl:toPage:pos => pageName
@ -31,7 +31,7 @@ export async function indexLinks({ name, tree }: IndexTreeEvent) {
const backLinks: { key: string; value: string }[] = [];
// [[Style Links]]
// console.log("Now indexing", name);
const pageMeta = extractMeta(tree);
const pageMeta = extractFrontmatter(tree);
if (Object.keys(pageMeta).length > 0) {
// console.log("Extracted page meta data", pageMeta);
// Don't index meta data starting with $

View File

@ -1,5 +1,5 @@
import { editor, markdown, space } from "$sb/silverbullet-syscall/mod.ts";
import { extractMeta } from "../directive/data.ts";
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
import { renderToText } from "$sb/lib/tree.ts";
import { niceDate } from "$sb/lib/dates.ts";
import { readSettings } from "$sb/lib/settings_page.ts";
@ -31,7 +31,7 @@ export async function instantiateTemplateCommand() {
);
const parseTree = await markdown.parseMarkdown(text);
const additionalPageMeta = extractMeta(parseTree, [
const additionalPageMeta = extractFrontmatter(parseTree, [
"$name",
"$disableDirectives",
]);

View File

@ -2,13 +2,13 @@ import { editor, markdown, system } from "$sb/silverbullet-syscall/mod.ts";
import { nodeAtPos } from "$sb/lib/tree.ts";
import { replaceAsync } from "$sb/lib/util.ts";
import { directiveRegex, renderDirectives } from "./directives.ts";
import { extractMeta } from "./data.ts";
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
export async function updateDirectivesOnPageCommand() {
const pageName = await editor.getCurrentPage();
const text = await editor.getText();
const tree = await markdown.parseMarkdown(text);
const metaData = extractMeta(tree, ["$disableDirectives"]);
const metaData = extractFrontmatter(tree, ["$disableDirectives"]);
if (metaData.$disableDirectives) {
// Not updating, directives disabled
return;

View File

@ -3,14 +3,7 @@
import type { IndexTreeEvent, QueryProviderEvent } from "$sb/app_event.ts";
import { index } from "$sb/silverbullet-syscall/mod.ts";
import {
addParentPointers,
collectNodesOfType,
findNodeOfType,
ParseTree,
renderToText,
replaceNodesMatching,
} from "$sb/lib/tree.ts";
import { collectNodesOfType, findNodeOfType } from "$sb/lib/tree.ts";
import { applyQuery, removeQueries } from "$sb/lib/query.ts";
import * as YAML from "yaml";
@ -56,105 +49,6 @@ export async function indexData({ name, tree }: IndexTreeEvent) {
await index.batchSet(name, dataObjects);
}
export function extractMeta(
parseTree: ParseTree,
removeKeys: string[] = [],
): any {
let data: any = {};
addParentPointers(parseTree);
replaceNodesMatching(parseTree, (t) => {
// Find top-level hash tags
if (t.type === "Hashtag") {
// Check if if nested directly into a Paragraph
if (t.parent && t.parent.type === "Paragraph") {
const tagname = t.children![0].text!.substring(1);
if (!data.tags) {
data.tags = [];
}
if (!data.tags.includes(tagname)) {
data.tags.push(tagname);
}
}
return;
}
// Find FrontMatter and parse it
if (t.type === "FrontMatter") {
const yamlText = renderToText(t.children![1].children![0]);
try {
const parsedData: any = YAML.parse(yamlText);
const newData = { ...parsedData };
data = { ...data, ...parsedData };
if (removeKeys.length > 0) {
let removedOne = false;
for (const key of removeKeys) {
if (key in newData) {
delete newData[key];
removedOne = true;
}
}
if (removedOne) {
t.children![0].text = YAML.stringify(newData);
}
}
// If nothing is left, let's just delete this whole block
if (Object.keys(newData).length === 0) {
return null;
}
} catch (e: any) {
console.error("Could not parse frontmatter", e);
}
}
// Find a fenced code block with `meta` as the language type
if (t.type !== "FencedCode") {
return;
}
const codeInfoNode = findNodeOfType(t, "CodeInfo");
if (!codeInfoNode) {
return;
}
if (codeInfoNode.children![0].text !== "meta") {
return;
}
const codeTextNode = findNodeOfType(t, "CodeText");
if (!codeTextNode) {
// Honestly, this shouldn't happen
return;
}
const codeText = codeTextNode.children![0].text!;
const parsedData: any = YAML.parse(codeText);
const newData = { ...parsedData };
data = { ...data, ...parsedData };
if (removeKeys.length > 0) {
let removedOne = false;
for (const key of removeKeys) {
if (key in newData) {
delete newData[key];
removedOne = true;
}
}
if (removedOne) {
codeTextNode.children![0].text = YAML.stringify(newData).trim();
}
}
// If nothing is left, let's just delete this whole block
if (Object.keys(newData).length === 0) {
return null;
}
return undefined;
});
if (data.name) {
data.displayName = data.name;
delete data.name;
}
return data;
}
export async function queryProvider({
query,
}: QueryProviderEvent): Promise<any[]> {

View File

@ -2,7 +2,6 @@ import { nodeAtPos, ParseTree, renderToText } from "$sb/lib/tree.ts";
import { replaceAsync } from "$sb/lib/util.ts";
import { markdown } from "$sb/silverbullet-syscall/mod.ts";
import { extractMeta } from "./data.ts";
import { evalDirectiveRenderer } from "./eval_directive.ts";
import { queryDirectiveRenderer } from "./query_directive.ts";
import {

View File

@ -5,7 +5,7 @@ import { markdown, space } from "$sb/silverbullet-syscall/mod.ts";
import Handlebars from "handlebars";
import { replaceTemplateVars } from "../core/template.ts";
import { extractMeta } from "./data.ts";
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
import { directiveRegex, renderDirectives } from "./directives.ts";
const templateRegex = /\[\[([^\]]+)\]\]\s*(.*)\s*/;
@ -44,7 +44,7 @@ export async function templateDirectiveRenderer(
// if it's a template injection (not a literal "include")
if (directive === "use" || directive === "use-verbose") {
const tree = await markdown.parseMarkdown(templateText);
extractMeta(tree, ["$disableDirectives"]);
extractFrontmatter(tree, ["$disableDirectives"]);
templateText = renderToText(tree);
const templateFn = Handlebars.compile(
replaceTemplateVars(templateText, pageName),

View File

@ -3,6 +3,8 @@ imports:
- https://get.silverbullet.md/global.plug.json
assets:
- "assets/*"
requiredPermissions:
- fs
functions:
toggle:
path: "./markdown.ts:togglePreview"
@ -23,3 +25,9 @@ functions:
env: client
events:
- preview:click
# $share: file:* publisher for markdown files
sharePublisher:
path: ./share.ts:sharePublisher
events:
- share:file

21
plugs/markdown/share.ts Normal file
View File

@ -0,0 +1,21 @@
import { markdown, space } from "$sb/silverbullet-syscall/mod.ts";
import { fs } from "$sb/plugos-syscall/mod.ts";
import { asset } from "$sb/plugos-syscall/mod.ts";
import type { PublishEvent } from "../share/publish.ts";
import { renderMarkdownToHtml } from "./markdown_render.ts";
export async function sharePublisher(event: PublishEvent) {
const path = event.uri.split(":")[1];
const pageName = event.name;
const text = await space.readPage(pageName);
const tree = await markdown.parseMarkdown(text);
const css = await asset.readAsset("assets/styles.css");
const markdownHtml = renderMarkdownToHtml(tree, {
smartHardBreak: true,
});
const html =
`<html><head><style>${css}</style></head><body><div id="root">${markdownHtml}</div></body></html>`;
await fs.writeFile(path, html, "utf8");
return true;
}

48
plugs/share/publish.ts Normal file
View File

@ -0,0 +1,48 @@
import { events } from "$sb/plugos-syscall/mod.ts";
import { editor, markdown, system } from "$sb/silverbullet-syscall/mod.ts";
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
export type PublishEvent = {
uri: string;
// Page name
name: string;
};
export async function publishCommand() {
await editor.save();
const text = await editor.getText();
const pageName = await editor.getCurrentPage();
const tree = await markdown.parseMarkdown(text);
let { $share } = extractFrontmatter(tree);
if (!$share) {
await editor.flashNotification("No $share directive found", "error");
return;
}
if (!Array.isArray($share)) {
$share = [$share];
}
// Delegate actual publishing to the server
try {
await system.invokeFunction("server", "publish", pageName, $share);
await editor.flashNotification("Done!");
} catch (e: any) {
await editor.flashNotification(e.message, "error");
}
}
// Runs on server side
export async function publish(pageName: string, uris: string[]) {
for (const uri of uris) {
const publisher = uri.split(":")[0];
const results = await events.dispatchEvent(
`share:${publisher}`,
{
uri: uri,
name: pageName,
} as PublishEvent,
);
if (results.length === 0) {
throw new Error(`Unsupported publisher: ${publisher} for URI: ${uri}`);
}
}
}

View File

@ -0,0 +1,9 @@
name: share
functions:
publishCommand:
path: publish.ts:publishCommand
command:
name: "Share: Publish"
publish:
path: publish.ts:publish
env: server

View File

@ -23,6 +23,7 @@ type SpaceFunction = {
pattern: RegExp;
plug: Plug<PageNamespaceHookT>;
name: string;
env?: string;
};
export class PageNamespaceHook implements Hook<PageNamespaceHookT> {
@ -42,10 +43,10 @@ export class PageNamespaceHook implements Hook<PageNamespaceHookT> {
updateCache(system: System<PageNamespaceHookT>) {
this.spaceFunctions = [];
for (let plug of system.loadedPlugs.values()) {
for (const plug of system.loadedPlugs.values()) {
if (plug.manifest?.functions) {
for (
let [funcName, funcDef] of Object.entries(
const [funcName, funcDef] of Object.entries(
plug.manifest.functions,
)
) {
@ -55,6 +56,7 @@ export class PageNamespaceHook implements Hook<PageNamespaceHookT> {
pattern: new RegExp(funcDef.pageNamespace.pattern),
plug,
name: funcName,
env: funcDef.env,
});
}
}
@ -63,7 +65,7 @@ export class PageNamespaceHook implements Hook<PageNamespaceHookT> {
}
validateManifest(manifest: Manifest<PageNamespaceHookT>): string[] {
let errors: string[] = [];
const errors: string[] = [];
if (!manifest.functions) {
return [];
}

View File

@ -12,6 +12,7 @@ export class PlugSpacePrimitives implements SpacePrimitives {
constructor(
private wrapped: SpacePrimitives,
private hook: PageNamespaceHook,
private env: string,
) {}
performOperation(
@ -19,8 +20,13 @@ export class PlugSpacePrimitives implements SpacePrimitives {
pageName: string,
...args: any[]
): Promise<any> | false {
for (const { operation, pattern, plug, name } of this.hook.spaceFunctions) {
if (operation === type && pageName.match(pattern)) {
for (
const { operation, pattern, plug, name, env } of this.hook.spaceFunctions
) {
if (
operation === type && pageName.match(pattern) &&
(env ? env === this.env : true)
) {
return plug.invoke(name, [pageName, ...args]);
}
}

View File

@ -102,6 +102,7 @@ export class HttpServer {
new PlugSpacePrimitives(
new DiskSpacePrimitives(options.pagesPath),
namespaceHook,
"server",
),
this.eventHook,
),

View File

@ -2,6 +2,10 @@ import { Editor } from "./editor.tsx";
import { parseYamlSettings, safeRun } from "../common/util.ts";
import { Space } from "../common/spaces/space.ts";
import { HttpSpacePrimitives } from "../common/spaces/http_space_primitives.ts";
import { PlugSpacePrimitives } from "../server/hooks/plug_space_primitives.ts";
import { PageNamespaceHook } from "../server/hooks/page_namespace.ts";
import { SilverBulletHooks } from "../common/manifest.ts";
import { System } from "../plugos/system.ts";
safeRun(async () => {
let password: string | undefined = localStorage.getItem("password") ||
@ -27,7 +31,21 @@ safeRun(async () => {
}
}
}
const serverSpace = new Space(httpPrimitives);
// Instantiate a PlugOS system for the client
const system = new System<SilverBulletHooks>("client");
// Attach the page namespace hook
const namespaceHook = new PageNamespaceHook();
system.addHook(namespaceHook);
const spacePrimitives = new PlugSpacePrimitives(
httpPrimitives,
namespaceHook,
"client",
);
const serverSpace = new Space(spacePrimitives);
serverSpace.watch();
console.log("Booting...");
@ -36,6 +54,7 @@ safeRun(async () => {
const editor = new Editor(
serverSpace,
system,
document.getElementById("sb-root")!,
"",
settings.indexPage || "index",
@ -46,10 +65,9 @@ safeRun(async () => {
await editor.init();
});
// if (localStorage.getItem("disable_sw") !== "true") {
if (navigator.serviceWorker) {
navigator.serviceWorker
.register(new URL("service_worker.js", location.href), {
.register(new URL("/service_worker.js", location.href), {
type: "module",
})
.then((r) => {
@ -60,6 +78,3 @@ if (navigator.serviceWorker) {
"No launching service worker (not present, maybe because not running on localhost or over SSL)",
);
}
// } else {
// console.log("Service worker disabled via disable_sw");
// }

View File

@ -67,7 +67,6 @@ export function TopBar({
value={pageName}
className="sb-edit-page-name"
onKeyDown={(e) => {
console.log("Key press", e);
e.stopPropagation();
if (e.key === "Enter") {
e.preventDefault();

View File

@ -131,7 +131,7 @@ export class Editor {
.dispatchEvent("editor:updated")
.catch((e) => console.error("Error dispatching editor:updated event", e));
}, 1000);
private system = new System<SilverBulletHooks>("client");
private system: System<SilverBulletHooks>;
private mdExtensions: MDExt[] = [];
urlPrefix: string;
indexPage: string;
@ -139,11 +139,13 @@ export class Editor {
constructor(
space: Space,
system: System<SilverBulletHooks>,
parent: Element,
urlPrefix: string,
indexPage: string,
) {
this.space = space;
this.system = system;
this.urlPrefix = urlPrefix;
this.viewState = initialViewState;
this.viewDispatch = () => {};
@ -223,6 +225,40 @@ export class Editor {
async init() {
this.focus();
const globalModules: any = await (
await fetch(`${this.urlPrefix}/global.plug.json`)
).json();
this.system.on({
sandboxInitialized: async (sandbox) => {
for (
const [modName, code] of Object.entries(
globalModules.dependencies,
)
) {
await sandbox.loadDependency(modName, code as string);
}
},
});
this.space.on({
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,
});
},
});
await this.reloadPlugs();
this.pageNavigator.subscribe(async (pageName, pos: number | string) => {
console.log("Now navigating to", pageName);
@ -266,39 +302,6 @@ export class Editor {
}
});
const globalModules: any = await (
await fetch(`${this.urlPrefix}/global.plug.json`)
).json();
this.system.on({
sandboxInitialized: async (sandbox) => {
for (
const [modName, code] of Object.entries(
globalModules.dependencies,
)
) {
await sandbox.loadDependency(modName, code as string);
}
},
});
this.space.on({
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,
});
},
});
await this.reloadPlugs();
await this.dispatchAppEvent("editor:init");
}