From 8dac58f38a43bd1f978e719d23ef0d67c0953d4b Mon Sep 17 00:00:00 2001 From: Zef Hemel Date: Sun, 8 Jan 2023 15:29:34 +0100 Subject: [PATCH] Handle inline images and attachments via data urls --- common/spaces/space.ts | 6 +++++ mobile/spaces/capacitor_space_primitives.ts | 4 +-- plug-api/silverbullet-syscall/editor.ts | 5 ++++ plug-api/silverbullet-syscall/space.ts | 16 ++++++++++++ plugs/core/navigate.ts | 28 ++++++++++++--------- web/cm_plugins/inline_image.ts | 4 +-- web/syscalls/editor.ts | 6 +++++ 7 files changed, 53 insertions(+), 16 deletions(-) diff --git a/common/spaces/space.ts b/common/spaces/space.ts index e3f542e..7bdbeb1 100644 --- a/common/spaces/space.ts +++ b/common/spaces/space.ts @@ -185,6 +185,12 @@ export class Space extends EventEmitter { ); } + /** + * Reads an attachment + * @param name path of the attachment + * @param encoding how the return value is expected to be encoded + * @returns + */ readAttachment( name: string, encoding: FileEncoding, diff --git a/mobile/spaces/capacitor_space_primitives.ts b/mobile/spaces/capacitor_space_primitives.ts index cb1966c..da20517 100644 --- a/mobile/spaces/capacitor_space_primitives.ts +++ b/mobile/spaces/capacitor_space_primitives.ts @@ -78,10 +78,10 @@ export class CapacitorSpacePrimitives implements SpacePrimitives { } } return { - data, + data: data!, meta: await this.getFileMeta(name), }; - } catch (e: any) { + } catch { throw new Error(`Page not found`); } } diff --git a/plug-api/silverbullet-syscall/editor.ts b/plug-api/silverbullet-syscall/editor.ts index 492cf60..8aa9605 100644 --- a/plug-api/silverbullet-syscall/editor.ts +++ b/plug-api/silverbullet-syscall/editor.ts @@ -46,6 +46,11 @@ export function openUrl(url: string): Promise { return syscall("editor.openUrl", url); } +// Force the client to download the file in dataUrl with filename as file name +export function downloadFile(filename: string, dataUrl: string): Promise { + return syscall("editor.downloadFile", filename, dataUrl); +} + export function flashNotification( message: string, type: "info" | "error" = "info", diff --git a/plug-api/silverbullet-syscall/space.ts b/plug-api/silverbullet-syscall/space.ts index 2c1ddd0..6d42f46 100644 --- a/plug-api/silverbullet-syscall/space.ts +++ b/plug-api/silverbullet-syscall/space.ts @@ -35,12 +35,24 @@ export function getAttachmentMeta(name: string): Promise { return syscall("space.getAttachmentMeta", name); } +/** + * Read an attachment from the space + * @param name path of the attachment to read + * @returns the attachment data encoded as a data URL + */ export function readAttachment( name: string, ): Promise { return syscall("space.readAttachment", name); } +/** + * Writes an attachment to the space + * @param name path of the attachment to write + * @param encoding encoding of the data ("string" or "dataurl) + * @param data data itself + * @returns + */ export function writeAttachment( name: string, encoding: "string" | "dataurl", @@ -49,6 +61,10 @@ export function writeAttachment( return syscall("space.writeAttachment", name, encoding, data); } +/** + * Deletes an attachment from the space + * @param name path of the attachment to delete + */ export function deleteAttachment(name: string): Promise { return syscall("space.deleteAttachment", name); } diff --git a/plugs/core/navigate.ts b/plugs/core/navigate.ts index 641bd29..9d6122d 100644 --- a/plugs/core/navigate.ts +++ b/plugs/core/navigate.ts @@ -1,5 +1,10 @@ import type { ClickEvent } from "$sb/app_event.ts"; -import { editor, markdown, system } from "$sb/silverbullet-syscall/mod.ts"; +import { + editor, + markdown, + space, + system, +} from "$sb/silverbullet-syscall/mod.ts"; import { addParentPointers, findNodeOfType, @@ -8,14 +13,6 @@ import { ParseTree, } from "$sb/lib/tree.ts"; -// Checks if the URL contains a protocol, if so keeps it, otherwise assumes an attachment -function patchUrl(url: string): string { - if (url.indexOf("://") === -1) { - return `fs/${url}`; - } - return url; -} - async function actionClickOrActionEnter( mdTree: ParseTree | null, inNewWindow = false, @@ -71,7 +68,7 @@ async function actionClickOrActionEnter( break; } case "NakedURL": - await editor.openUrl(patchUrl(mdTree.children![0].text!)); + await editor.openUrl(mdTree.children![0].text!); break; case "Image": case "Link": { @@ -79,11 +76,18 @@ async function actionClickOrActionEnter( if (!urlNode) { return; } - const url = patchUrl(urlNode.children![0].text!); + let url = urlNode.children![0].text!; if (url.length <= 1) { return editor.flashNotification("Empty link, ignoring", "error"); } - await editor.openUrl(url); + if (url.indexOf("://") === -1) { + url = decodeURIComponent(url); + // attachment URL, let's fetch as a data url + const dataUrl = await space.readAttachment(url); + return editor.downloadFile(url, dataUrl); + } else { + await editor.openUrl(url); + } break; } case "CommandLink": { diff --git a/web/cm_plugins/inline_image.ts b/web/cm_plugins/inline_image.ts index 0e966e5..32a310a 100644 --- a/web/cm_plugins/inline_image.ts +++ b/web/cm_plugins/inline_image.ts @@ -27,8 +27,8 @@ class InlineImageWidget extends WidgetType { if (this.url.startsWith("http")) { img.src = this.url; } else { - // Specific to mobile - this.space.readAttachment(decodeURI(this.url), "dataurl").then( + // Load the image as a dataURL and inject it into the img's src attribute + this.space.readAttachment(decodeURIComponent(this.url), "dataurl").then( ({ data }) => { img.src = data as string; }, diff --git a/web/syscalls/editor.ts b/web/syscalls/editor.ts index 6b8f154..393d549 100644 --- a/web/syscalls/editor.ts +++ b/web/syscalls/editor.ts @@ -38,6 +38,12 @@ export function editorSyscalls(editor: Editor): SysCallMapping { win.focus(); } }, + "editor.downloadFile": (_ctx, filename: string, dataUrl: string) => { + const link = document.createElement("a"); + link.href = dataUrl; + link.download = filename; + link.click(); + }, "editor.flashNotification": ( _ctx, message: string,