From fe4887dc780d1af7ca33215788971d15c7c9f743 Mon Sep 17 00:00:00 2001 From: Zef Hemel Date: Sat, 29 Jul 2023 23:41:37 +0200 Subject: [PATCH] Federation progress --- common/spaces/fallback_space_primitives.ts | 16 +++--- plug-api/lib/resolve.test.ts | 20 ++++++++ plug-api/lib/resolve.ts | 20 ++++++++ plugs/core/navigate.ts | 4 +- plugs/federation/federation.ts | 59 ++++++++++++---------- plugs/markdown/preview.ts | 4 +- server/http_server.ts | 12 ++++- web/client.ts | 2 +- web/cm_plugins/inline_image.ts | 19 ++++--- web/cm_plugins/table.ts | 3 +- web/cm_plugins/wiki_link.ts | 2 + 11 files changed, 112 insertions(+), 49 deletions(-) create mode 100644 plug-api/lib/resolve.test.ts create mode 100644 plug-api/lib/resolve.ts diff --git a/common/spaces/fallback_space_primitives.ts b/common/spaces/fallback_space_primitives.ts index 1c988869..a7d9e135 100644 --- a/common/spaces/fallback_space_primitives.ts +++ b/common/spaces/fallback_space_primitives.ts @@ -22,10 +22,10 @@ export class FallbackSpacePrimitives implements SpacePrimitives { try { return await this.primary.readFile(name); } catch (e) { - console.info( - `Could not read file ${name} from primary, trying fallback, primary read error:`, - e.message, - ); + // console.info( + // `Could not read file ${name} from primary, trying fallback, primary read error:`, + // e.message, + // ); try { const result = await this.fallback.readFile(name); return { @@ -43,10 +43,10 @@ export class FallbackSpacePrimitives implements SpacePrimitives { try { return await this.primary.getFileMeta(name); } catch (e) { - console.info( - `Could not fetch file ${name} metadata from primary, trying fallback, primary read error`, - e.message, - ); + // console.info( + // `Could not fetch file ${name} metadata from primary, trying fallback, primary read error`, + // e.message, + // ); try { const meta = await this.fallback.getFileMeta(name); return { ...meta, neverSync: true }; diff --git a/plug-api/lib/resolve.test.ts b/plug-api/lib/resolve.test.ts new file mode 100644 index 00000000..d124c82b --- /dev/null +++ b/plug-api/lib/resolve.test.ts @@ -0,0 +1,20 @@ +import { resolvePath } from "$sb/lib/resolve.ts"; +import { assertEquals } from "../../test_deps.ts"; + +Deno.test("Test URL resolver", () => { + assertEquals(resolvePath("test", "some page"), "some page"); + assertEquals( + resolvePath("!silverbullet.md", "some page"), + "!silverbullet.md/some page", + ); + assertEquals( + resolvePath("!silverbullet.md/some/deep/path", "some page"), + "!silverbullet.md/some page", + ); + assertEquals(resolvePath("!bla/bla", "!bla/bla2"), "!bla/bla2"); + + assertEquals( + resolvePath("!silverbullet.md", "test/image.png", true), + "https://silverbullet.md/test/image.png", + ); +}); diff --git a/plug-api/lib/resolve.ts b/plug-api/lib/resolve.ts new file mode 100644 index 00000000..74bab1fe --- /dev/null +++ b/plug-api/lib/resolve.ts @@ -0,0 +1,20 @@ +export function resolvePath( + currentPage: string, + pathToResolve: string, + fullUrl = false, +): string { + if (currentPage.startsWith("!") && !pathToResolve.startsWith("!")) { + let domainPart = currentPage.split("/")[0]; + if (fullUrl) { + domainPart = domainPart.substring(1); + if (domainPart.startsWith("localhost")) { + domainPart = "http://" + domainPart; + } else { + domainPart = "https://" + domainPart; + } + } + return `${domainPart}/${pathToResolve}`; + } else { + return pathToResolve; + } +} diff --git a/plugs/core/navigate.ts b/plugs/core/navigate.ts index 50844e83..53e1a938 100644 --- a/plugs/core/navigate.ts +++ b/plugs/core/navigate.ts @@ -7,6 +7,7 @@ import { nodeAtPos, ParseTree, } from "$sb/lib/tree.ts"; +import { resolvePath } from "$sb/lib/resolve.ts"; async function actionClickOrActionEnter( mdTree: ParseTree | null, @@ -46,6 +47,7 @@ async function actionClickOrActionEnter( pos = +pos; } } + pageLink = resolvePath(currentPage, pageLink); if (!pageLink) { pageLink = currentPage; } @@ -77,7 +79,7 @@ async function actionClickOrActionEnter( return editor.flashNotification("Empty link, ignoring", "error"); } if (url.indexOf("://") === -1 && !url.startsWith("mailto:")) { - return editor.openUrl(decodeURI(url)); + return editor.openUrl(resolvePath(currentPage, decodeURI(url))); } else { await editor.openUrl(url); } diff --git a/plugs/federation/federation.ts b/plugs/federation/federation.ts index 05308e87..632eb4b9 100644 --- a/plugs/federation/federation.ts +++ b/plugs/federation/federation.ts @@ -17,28 +17,28 @@ async function responseToFileMeta( r: Response, name: string, ): Promise { - let perm = r.headers.get("X-Permission") as any || "ro"; - const federationConfigs = await readFederationConfigs(); - const federationConfig = federationConfigs.find((config) => - name.startsWith(config.uri) - ); - if (federationConfig?.perm) { - perm = federationConfig.perm; - } + // const perm = r.headers.get("X-Permission") as any || "ro"; + // const federationConfigs = await readFederationConfigs(); + // const federationConfig = federationConfigs.find((config) => + // name.startsWith(config.uri) + // ); + // if (federationConfig?.perm) { + // perm = federationConfig.perm; + // } return { name: name, size: r.headers.get("Content-length") ? +r.headers.get("Content-length")! : 0, contentType: r.headers.get("Content-type")!, - perm: perm, + perm: "ro", lastModified: +(r.headers.get("X-Last-Modified") || "0"), }; } type FederationConfig = { uri: string; - perm?: "ro" | "rw"; + // perm?: "ro" | "rw"; }; let federationConfigs: FederationConfig[] = []; let lastFederationUrlFetch = 0; @@ -71,7 +71,7 @@ export async function listFiles(): Promise { (await r.json()).filter((meta: FileMeta) => meta.name.startsWith(prefix)) .map((meta: FileMeta) => ({ ...meta, - perm: config.perm || meta.perm, + perm: "ro", //config.perm || meta.perm, name: `${rootUri}/${meta.name}`, })), ); @@ -121,30 +121,33 @@ export async function writeFile( name: string, data: Uint8Array, ): Promise { - const url = resolveFederated(name); - console.log("Writing federation file", url); + throw new Error("Writing federation file, not yet supported"); + // const url = resolveFederated(name); + // console.log("Writing federation file", url); - const r = await nativeFetch(url, { - method: "PUT", - body: data, - }); - const fileMeta = await responseToFileMeta(r, name); - if (!r.ok) { - throw new Error("Could not write file"); - } + // const r = await nativeFetch(url, { + // method: "PUT", + // body: data, + // }); + // const fileMeta = await responseToFileMeta(r, name); + // if (!r.ok) { + // throw new Error("Could not write file"); + // } - return fileMeta; + // return fileMeta; } export async function deleteFile( name: string, ): Promise { - console.log("Deleting federation file", name); - const url = resolveFederated(name); - const r = await nativeFetch(url, { method: "DELETE" }); - if (!r.ok) { - throw Error("Failed to delete file"); - } + throw new Error("Writing federation file, not yet supported"); + + // console.log("Deleting federation file", name); + // const url = resolveFederated(name); + // const r = await nativeFetch(url, { method: "DELETE" }); + // if (!r.ok) { + // throw Error("Failed to delete file"); + // } } export async function getFileMeta(name: string): Promise { diff --git a/plugs/markdown/preview.ts b/plugs/markdown/preview.ts index 22633152..20b67dd8 100644 --- a/plugs/markdown/preview.ts +++ b/plugs/markdown/preview.ts @@ -2,11 +2,13 @@ import { editor, system } from "$sb/silverbullet-syscall/mod.ts"; import { asset, store } from "$sb/plugos-syscall/mod.ts"; import { parseMarkdown } from "$sb/silverbullet-syscall/markdown.ts"; import { renderMarkdownToHtml } from "./markdown_render.ts"; +import { resolvePath } from "$sb/lib/resolve.ts"; export async function updateMarkdownPreview() { if (!(await store.get("enableMarkdownPreview"))) { return; } + const currentPage = await editor.getCurrentPage(); const text = await editor.getText(); const mdTree = await parseMarkdown(text); // const cleanMd = await cleanMarkdown(text); @@ -17,7 +19,7 @@ export async function updateMarkdownPreview() { annotationPositions: true, translateUrls: (url) => { if (!url.includes("://")) { - return decodeURI(url); + url = resolvePath(currentPage, decodeURI(url), true); } return url; }, diff --git a/server/http_server.ts b/server/http_server.ts index bde030b6..0250efba 100644 --- a/server/http_server.ts +++ b/server/http_server.ts @@ -352,7 +352,17 @@ export class HttpServer { } else { url = `https://${url}`; } - response.redirect(url); + try { + const req = await fetch(url); + response.status = req.status; + response.headers = req.headers; + response.body = req.body; + } catch (e: any) { + console.error("Error fetching federated link", e); + response.status = 500; + response.body = e.message; + } + // response.redirect(url); return; } try { diff --git a/web/client.ts b/web/client.ts index 494c0cba..4e3364ad 100644 --- a/web/client.ts +++ b/web/client.ts @@ -159,8 +159,8 @@ export class Client { ); } - this.initNavigator(); await this.loadPlugs(); + this.initNavigator(); this.initSync(); this.loadCustomStyles().catch(console.error); diff --git a/web/cm_plugins/inline_image.ts b/web/cm_plugins/inline_image.ts index 4db2d502..33abe0fe 100644 --- a/web/cm_plugins/inline_image.ts +++ b/web/cm_plugins/inline_image.ts @@ -7,14 +7,14 @@ import { } from "../deps.ts"; import { decoratorStateField } from "./util.ts"; -import type { Space } from "../space.ts"; import type { Client } from "../client.ts"; +import { resolvePath } from "$sb/lib/resolve.ts"; class InlineImageWidget extends WidgetType { constructor( readonly url: string, readonly title: string, - readonly space: Space, + readonly client: Client, ) { super(); // console.log("Creating widget", url); @@ -25,22 +25,25 @@ class InlineImageWidget extends WidgetType { } get estimatedHeight(): number { - const cachedHeight = this.space.getCachedImageHeight(this.url); + const cachedHeight = this.client.space.getCachedImageHeight(this.url); // console.log("Estimated height requested", this.url, cachedHeight); return cachedHeight; } toDOM() { const img = document.createElement("img"); + let url = this.url; + url = resolvePath(this.client.currentPage!, url, true); + console.log("Resolving image to", url); // console.log("Creating DOM", this.url); - const cachedImageHeight = this.space.getCachedImageHeight(this.url); + const cachedImageHeight = this.client.space.getCachedImageHeight(url); img.onload = () => { // console.log("Loaded", this.url, "with height", img.height); if (img.height !== cachedImageHeight) { - this.space.setCachedImageHeight(this.url, img.height); + this.client.space.setCachedImageHeight(url, img.height); } }; - img.src = this.url; + img.src = url; img.alt = this.title; img.title = this.title; img.style.display = "block"; @@ -53,7 +56,7 @@ class InlineImageWidget extends WidgetType { } } -export function inlineImagesPlugin(editor: Client) { +export function inlineImagesPlugin(client: Client) { return decoratorStateField((state: EditorState) => { const widgets: Range[] = []; const imageRegex = /!\[(?[^\]]*)\]\((?<url>.+)\)/; @@ -78,7 +81,7 @@ export function inlineImagesPlugin(editor: Client) { } widgets.push( Decoration.widget({ - widget: new InlineImageWidget(url, title, editor.space), + widget: new InlineImageWidget(url, title, client), block: true, }).range(node.to), ); diff --git a/web/cm_plugins/table.ts b/web/cm_plugins/table.ts index 2f206988..b85c047a 100644 --- a/web/cm_plugins/table.ts +++ b/web/cm_plugins/table.ts @@ -9,6 +9,7 @@ import { renderMarkdownToHtml } from "../../plugs/markdown/markdown_render.ts"; import { ParseTree } from "$sb/lib/tree.ts"; import { lezerToParseTree } from "../../common/markdown_parser/parse_tree.ts"; import type { Client } from "../client.ts"; +import { resolvePath } from "$sb/lib/resolve.ts"; class TableViewWidget extends WidgetType { constructor( @@ -39,7 +40,7 @@ class TableViewWidget extends WidgetType { annotationPositions: true, translateUrls: (url) => { if (!url.includes("://")) { - return `/${url}`; + url = resolvePath(this.editor.currentPage!, decodeURI(url), true); } return url; }, diff --git a/web/cm_plugins/wiki_link.ts b/web/cm_plugins/wiki_link.ts index 6fa5f905..0999870e 100644 --- a/web/cm_plugins/wiki_link.ts +++ b/web/cm_plugins/wiki_link.ts @@ -8,6 +8,7 @@ import { isCursorInRange, LinkWidget, } from "./util.ts"; +import { resolvePath } from "$sb/lib/resolve.ts"; /** * Plugin to hide path prefix when the cursor is not inside. @@ -33,6 +34,7 @@ export function cleanWikiLinkPlugin(editor: Client) { if (page.includes("@")) { cleanPage = page.split("@")[0]; } + cleanPage = resolvePath(editor.currentPage!, cleanPage); // console.log("Resolved page", resolvedPage); for (const pageMeta of allPages) { if (pageMeta.name === cleanPage) {