diff --git a/cmd/publish.ts b/cmd/publish.ts index 685ae50..121d58c 100755 --- a/cmd/publish.ts +++ b/cmd/publish.ts @@ -65,6 +65,7 @@ export async function publishCommand(options: { new PlugSpacePrimitives( new DiskSpacePrimitives(pagesPath), namespaceHook, + "server", ), eventHook, ), diff --git a/plug-api/lib/frontmatter.ts b/plug-api/lib/frontmatter.ts new file mode 100644 index 0000000..3e4517e --- /dev/null +++ b/plug-api/lib/frontmatter.ts @@ -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, +): 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; +} diff --git a/plugs/collab/collab.plug.yaml b/plugs/collab/collab.plug.yaml new file mode 100644 index 0000000..9cd35c7 --- /dev/null +++ b/plugs/collab/collab.plug.yaml @@ -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 diff --git a/plugs/collab/collab.ts b/plugs/collab/collab.ts new file mode 100644 index 0000000..6b9de90 --- /dev/null +++ b/plugs/collab/collab.ts @@ -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 { + 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", + }; +} diff --git a/plugs/core/page.ts b/plugs/core/page.ts index e3c192f..8a74c7e 100644 --- a/plugs/core/page.ts +++ b/plugs/core/page.ts @@ -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 $ diff --git a/plugs/core/template.ts b/plugs/core/template.ts index ed9e28f..8140142 100644 --- a/plugs/core/template.ts +++ b/plugs/core/template.ts @@ -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", ]); diff --git a/plugs/directive/command.ts b/plugs/directive/command.ts index 1074939..dcf7895 100644 --- a/plugs/directive/command.ts +++ b/plugs/directive/command.ts @@ -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; diff --git a/plugs/directive/data.ts b/plugs/directive/data.ts index 457acbb..f9fbd12 100644 --- a/plugs/directive/data.ts +++ b/plugs/directive/data.ts @@ -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 { diff --git a/plugs/directive/directives.ts b/plugs/directive/directives.ts index e6b04aa..2f39b84 100644 --- a/plugs/directive/directives.ts +++ b/plugs/directive/directives.ts @@ -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 { diff --git a/plugs/directive/template_directive.ts b/plugs/directive/template_directive.ts index d29b874..d26378a 100644 --- a/plugs/directive/template_directive.ts +++ b/plugs/directive/template_directive.ts @@ -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), diff --git a/plugs/markdown/markdown.plug.yaml b/plugs/markdown/markdown.plug.yaml index b37b0d5..f33a031 100644 --- a/plugs/markdown/markdown.plug.yaml +++ b/plugs/markdown/markdown.plug.yaml @@ -3,6 +3,8 @@ imports: - https://get.silverbullet.md/global.plug.json assets: - "assets/*" +requiredPermissions: + - fs functions: toggle: path: "./markdown.ts:togglePreview" @@ -22,4 +24,10 @@ functions: path: "./preview.ts:previewClickHandler" env: client events: - - preview:click + - preview:click + + # $share: file:* publisher for markdown files + sharePublisher: + path: ./share.ts:sharePublisher + events: + - share:file \ No newline at end of file diff --git a/plugs/markdown/share.ts b/plugs/markdown/share.ts new file mode 100644 index 0000000..e2d4b3c --- /dev/null +++ b/plugs/markdown/share.ts @@ -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 = + `
${markdownHtml}
`; + await fs.writeFile(path, html, "utf8"); + return true; +} diff --git a/plugs/share/publish.ts b/plugs/share/publish.ts new file mode 100644 index 0000000..5acdea1 --- /dev/null +++ b/plugs/share/publish.ts @@ -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}`); + } + } +} diff --git a/plugs/share/share.plug.yaml b/plugs/share/share.plug.yaml new file mode 100644 index 0000000..4cce13e --- /dev/null +++ b/plugs/share/share.plug.yaml @@ -0,0 +1,9 @@ +name: share +functions: + publishCommand: + path: publish.ts:publishCommand + command: + name: "Share: Publish" + publish: + path: publish.ts:publish + env: server diff --git a/server/hooks/page_namespace.ts b/server/hooks/page_namespace.ts index f426b4c..77db1ba 100644 --- a/server/hooks/page_namespace.ts +++ b/server/hooks/page_namespace.ts @@ -23,6 +23,7 @@ type SpaceFunction = { pattern: RegExp; plug: Plug; name: string; + env?: string; }; export class PageNamespaceHook implements Hook { @@ -42,10 +43,10 @@ export class PageNamespaceHook implements Hook { updateCache(system: System) { 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 { pattern: new RegExp(funcDef.pageNamespace.pattern), plug, name: funcName, + env: funcDef.env, }); } } @@ -63,7 +65,7 @@ export class PageNamespaceHook implements Hook { } validateManifest(manifest: Manifest): string[] { - let errors: string[] = []; + const errors: string[] = []; if (!manifest.functions) { return []; } diff --git a/server/hooks/plug_space_primitives.ts b/server/hooks/plug_space_primitives.ts index 3ad3455..c6f1952 100644 --- a/server/hooks/plug_space_primitives.ts +++ b/server/hooks/plug_space_primitives.ts @@ -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 | 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]); } } diff --git a/server/http_server.ts b/server/http_server.ts index c212d9c..8c35d85 100644 --- a/server/http_server.ts +++ b/server/http_server.ts @@ -102,6 +102,7 @@ export class HttpServer { new PlugSpacePrimitives( new DiskSpacePrimitives(options.pagesPath), namespaceHook, + "server", ), this.eventHook, ), diff --git a/web/boot.ts b/web/boot.ts index b151232..bb59c93 100644 --- a/web/boot.ts +++ b/web/boot.ts @@ -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("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"); -// } diff --git a/web/components/top_bar.tsx b/web/components/top_bar.tsx index 3237bd6..7bd14fb 100644 --- a/web/components/top_bar.tsx +++ b/web/components/top_bar.tsx @@ -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(); diff --git a/web/editor.tsx b/web/editor.tsx index b59a3fe..aa8213d 100644 --- a/web/editor.tsx +++ b/web/editor.tsx @@ -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("client"); + private system: System; private mdExtensions: MDExt[] = []; urlPrefix: string; indexPage: string; @@ -139,11 +139,13 @@ export class Editor { constructor( space: Space, + system: System, 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"); }