diff --git a/plug-api/lib/resolve.test.ts b/plug-api/lib/resolve.test.ts index fc81dbe..f79cb61 100644 --- a/plug-api/lib/resolve.test.ts +++ b/plug-api/lib/resolve.test.ts @@ -1,6 +1,7 @@ import { cleanPageRef, federatedPathToUrl, + resolveAttachmentPath, resolvePath, rewritePageRefs, } from "$sb/lib/resolve.ts"; @@ -90,4 +91,22 @@ This is a [[!silverbullet.md/local link]] and [[!silverbullet.md/local link|with rewrittenText, `This is a [[local link]] and [[local link|with alias]].`, ); + + assertEquals("test.jpg", resolveAttachmentPath("test", "test.jpg")); + assertEquals( + "folder/test.jpg", + resolveAttachmentPath("folder/test", "test.jpg"), + ); + assertEquals( + "test.jpg", + resolveAttachmentPath("folder/test", "/test.jpg"), + ); + assertEquals( + "https://silverbullet.md/something/test.jpg", + resolveAttachmentPath("!silverbullet.md/something/bla", "test.jpg"), + ); + assertEquals( + "https://silverbullet.md/test.jpg", + resolveAttachmentPath("!silverbullet.md/something/bla", "/test.jpg"), + ); }); diff --git a/plug-api/lib/resolve.ts b/plug-api/lib/resolve.ts index 08acdce..84c2ff1 100644 --- a/plug-api/lib/resolve.ts +++ b/plug-api/lib/resolve.ts @@ -16,7 +16,24 @@ export function resolvePath( } } +export function resolveAttachmentPath( + currentPage: string, + pathToResolve: string, +): string { + const folder = folderName(currentPage); + if (folder && !pathToResolve.startsWith("/")) { + pathToResolve = folder + "/" + pathToResolve; + } + if (pathToResolve.startsWith("/")) { + pathToResolve = pathToResolve.slice(1); + } + return federatedPathToUrl(resolvePath(currentPage, pathToResolve)); +} + export function federatedPathToUrl(path: string): string { + if (!path.startsWith("!")) { + return path; + } path = path.substring(1); if (path.startsWith("localhost")) { path = "http://" + path; @@ -101,3 +118,7 @@ export function cleanPageRef(pageRef: string) { return pageRef; } } + +export function folderName(path: string) { + return path.split("/").slice(0, -1).join("/"); +} diff --git a/plugs/editor/navigate.ts b/plugs/editor/navigate.ts index b0828af..a26f3b6 100644 --- a/plugs/editor/navigate.ts +++ b/plugs/editor/navigate.ts @@ -7,7 +7,7 @@ import { nodeAtPos, ParseTree, } from "$sb/lib/tree.ts"; -import { resolvePath } from "$sb/lib/resolve.ts"; +import { resolveAttachmentPath, resolvePath } from "$sb/lib/resolve.ts"; async function actionClickOrActionEnter( mdTree: ParseTree | null, @@ -79,7 +79,9 @@ async function actionClickOrActionEnter( return editor.flashNotification("Empty link, ignoring", "error"); } if (url.indexOf("://") === -1 && !url.startsWith("mailto:")) { - return editor.openUrl(resolvePath(currentPage, decodeURI(url))); + return editor.openUrl( + resolveAttachmentPath(currentPage, decodeURI(url)), + ); } else { await editor.openUrl(url); } @@ -93,8 +95,11 @@ async function actionClickOrActionEnter( try { const args = argsText ? JSON.parse(`[${argsText}]`) : []; await system.invokeCommand(commandName, args); - } catch(e: any) { - await editor.flashNotification(`Error parsing command link arguments: ${e.message}`, "error"); + } catch (e: any) { + await editor.flashNotification( + `Error parsing command link arguments: ${e.message}`, + "error", + ); } break; } diff --git a/plugs/markdown/preview.ts b/plugs/markdown/preview.ts index 1020750..556b6a9 100644 --- a/plugs/markdown/preview.ts +++ b/plugs/markdown/preview.ts @@ -1,8 +1,7 @@ import { asset, clientStore, editor, markdown, system } from "$sb/syscalls.ts"; import { renderMarkdownToHtml } from "./markdown_render.ts"; -import { resolvePath } from "$sb/lib/resolve.ts"; +import { resolveAttachmentPath } from "$sb/lib/resolve.ts"; import { expandCodeWidgets } from "./api.ts"; -import { folderName, resolve } from "../../common/path.ts"; export async function updateMarkdownPreview() { if (!(await clientStore.get("enableMarkdownPreview"))) { @@ -19,14 +18,9 @@ export async function updateMarkdownPreview() { const html = renderMarkdownToHtml(mdTree, { smartHardBreak: true, annotationPositions: true, - translateUrls: (url, type) => { + translateUrls: (url) => { if (!url.includes("://")) { - if (type === "image" && !url.startsWith("/")) { - // Make relative to current folder - url = resolve(folderName(currentPage), decodeURI(url)); - } else if (type === "link") { // link - url = resolvePath(currentPage, decodeURI(url), true); - } + url = resolveAttachmentPath(currentPage, decodeURI(url)); } return url; }, diff --git a/web/cm_plugins/clean.ts b/web/cm_plugins/clean.ts index 65f2656..4ca2f6c 100644 --- a/web/cm_plugins/clean.ts +++ b/web/cm_plugins/clean.ts @@ -16,7 +16,7 @@ import { fencedCodePlugin } from "./fenced_code.ts"; export function cleanModePlugins(editor: Client) { return [ - linkPlugin(), + linkPlugin(editor), directivePlugin(), blockquotePlugin(), admonitionPlugin(editor), diff --git a/web/cm_plugins/inline_image.ts b/web/cm_plugins/inline_image.ts index d87791e..566bcda 100644 --- a/web/cm_plugins/inline_image.ts +++ b/web/cm_plugins/inline_image.ts @@ -8,8 +8,7 @@ import { import { decoratorStateField } from "./util.ts"; import type { Client } from "../client.ts"; -import { resolvePath } from "$sb/lib/resolve.ts"; -import { folderName, resolve } from "../../common/path.ts"; +import { resolveAttachmentPath, resolvePath } from "$sb/lib/resolve.ts"; class InlineImageWidget extends WidgetType { constructor( @@ -77,11 +76,8 @@ export function inlineImagesPlugin(client: Client) { let url = imageRexexResult.groups.url; const title = imageRexexResult.groups.title; - const currentPage = client.currentPage!; - const currentFolder = folderName(currentPage); - if (url.indexOf("://") === -1 && !url.startsWith("/")) { - url = resolve(currentFolder, decodeURI(url)); + url = resolveAttachmentPath(client.currentPage!, decodeURI(url)); } widgets.push( Decoration.widget({ diff --git a/web/cm_plugins/link.ts b/web/cm_plugins/link.ts index 3cf0f56..a7a917d 100644 --- a/web/cm_plugins/link.ts +++ b/web/cm_plugins/link.ts @@ -1,3 +1,5 @@ +import { resolveAttachmentPath } from "$sb/lib/resolve.ts"; +import { Client } from "../client.ts"; import { Decoration, syntaxTree } from "../deps.ts"; import { decoratorStateField, @@ -5,7 +7,7 @@ import { isCursorInRange, } from "./util.ts"; -export function linkPlugin() { +export function linkPlugin(client: Client) { return decoratorStateField((state) => { const widgets: any[] = []; @@ -31,7 +33,14 @@ export function linkPlugin() { return; } const cleanAnchor = anchorPart.substring(1); // cut off the initial [ - const cleanLink = linkPart.substring(0, linkPart.length - 1); // cut off the final ) + let cleanLink = linkPart.substring(0, linkPart.length - 1); // cut off the final ) + + if (!cleanLink.includes("://")) { + cleanLink = resolveAttachmentPath( + client.currentPage!, + decodeURI(cleanLink), + ); + } // Hide the start [ widgets.push( diff --git a/web/cm_plugins/table.ts b/web/cm_plugins/table.ts index 492049e..88e9e9f 100644 --- a/web/cm_plugins/table.ts +++ b/web/cm_plugins/table.ts @@ -9,8 +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"; -import { folderName, resolve } from "../../common/path.ts"; +import { resolveAttachmentPath } from "$sb/lib/resolve.ts"; class TableViewWidget extends WidgetType { constructor( @@ -39,14 +38,9 @@ class TableViewWidget extends WidgetType { // Annotate every element with its position so we can use it to put // the cursor there when the user clicks on the table. annotationPositions: true, - translateUrls: (url, type) => { + translateUrls: (url) => { if (!url.includes("://")) { - if (type === "image" && !url.startsWith("/")) { - // Make relative to current folder - url = resolve(folderName(this.editor.currentPage!), decodeURI(url)); - } else if (type === "link") { // link - url = resolvePath(this.editor.currentPage!, decodeURI(url), true); - } + url = resolveAttachmentPath(this.editor.currentPage!, decodeURI(url)); } return url; diff --git a/website/CHANGELOG.md b/website/CHANGELOG.md index 11f94ca..b40677c 100644 --- a/website/CHANGELOG.md +++ b/website/CHANGELOG.md @@ -11,6 +11,7 @@ release. * A new `silverbullet sync` command to [[Sync]] spaces (early days, use with caution) * Technical refactoring in preparation for multi-tenant deployment support (allowing you to run a single SB instance and serve multiple spaces and users at the same time) * Lazy everything: plugs are now lazily loaded (after a first load, manifests are cached). On the server side, a whole lot of infrastructure is now only booted once the first HTTP request comes in +* Non-external URLs used in links (`[page](url)` syntax and `![alt](url)` image syntax) are now relative to the page's folder, unless their URL starts with a `/` then they're relative to the space root. ---