diff --git a/client/client.ts b/client/client.ts index af37335c..10be6541 100644 --- a/client/client.ts +++ b/client/client.ts @@ -31,6 +31,7 @@ import type { PageMeta, } from "@silverbulletmd/silverbullet/type/index"; import type { StyleObject } from "../plugs/index/space_style.ts"; +import type { ResolveAnchorResult } from "../plugs/index/types.ts"; import { publicVersion } from "../public_version.ts"; import { ClientSystem } from "./client_system.ts"; import { @@ -759,7 +760,8 @@ export class Client { let cursorWasVisible = false; try { const block = editorView.lineBlockAt(previousSelection.main.head); - const scrollBottom = previousScrollTop + editorView.scrollDOM.clientHeight; + const scrollBottom = + previousScrollTop + editorView.scrollDOM.clientHeight; cursorWasVisible = block.bottom > previousScrollTop && block.top < scrollBottom; } catch { @@ -910,6 +912,42 @@ export class Client { async navigate(ref: Ref | null, replaceState = false, newWindow = false) { ref ??= this.getIndexRef(); + // Resolve $-anchor refs into a concrete page + position. The page + // navigator only knows about position/header/linecolumn details, so + // we have to translate here. + if (ref.details?.type === "anchor") { + const anchorName = ref.details.name; + const pageFilter = ref.path + ? ref.path.endsWith(".md") + ? ref.path.slice(0, -3) + : ref.path + : undefined; + const result: ResolveAnchorResult = await this.clientSystem.localSyscall( + "index.resolveAnchor", + [anchorName, pageFilter], + ); + if (!result.ok) { + if (result.reason === "missing") { + this.ui.flashNotification( + `Anchor not found: $${anchorName}`, + "error", + ); + } else { + const pages = result.hits.map((h) => h.page).join(", "); + this.ui.flashNotification( + `Duplicate anchor $${anchorName} on pages: ${pages}`, + "error", + ); + } + return; + } + ref = { + ...ref, + path: `${result.page}.md`, + details: { type: "position", pos: result.range[0] }, + }; + } + if (newWindow) { console.log( "Navigating to new page in new window", diff --git a/client/plugos/syscalls/editor.ts b/client/plugos/syscalls/editor.ts index 626d2b65..49a7bb2d 100644 --- a/client/plugos/syscalls/editor.ts +++ b/client/plugos/syscalls/editor.ts @@ -219,10 +219,12 @@ export function editorSyscalls(client: Client): SysCallMapping { ); } else if ( ref.details && - !["position", "linecolumn", "header"].includes(ref.details.type) + !["position", "linecolumn", "header", "anchor"].includes( + ref.details.type, + ) ) { throw new Error( - "ref.details.type has to be 'position', 'linecolumn' or 'header'", + "ref.details.type has to be 'position', 'linecolumn', 'header' or 'anchor'", ); } @@ -244,6 +246,11 @@ export function editorSyscalls(client: Client): SysCallMapping { throw new Error( "ref.details.line and ref.details.column has to be of type `number`", ); + } else if ( + ref.details?.type === "anchor" && + typeof ref.details.name !== "string" + ) { + throw new Error("ref.details.name has to be of type `string`"); } await client.navigate(ref, replaceState, newWindow); diff --git a/plugs/editor/navigate.ts b/plugs/editor/navigate.ts index 65c89e28..6457dd99 100644 --- a/plugs/editor/navigate.ts +++ b/plugs/editor/navigate.ts @@ -1,7 +1,6 @@ import { config, editor, - index, markdown, space, } from "@silverbulletmd/silverbullet/syscalls"; @@ -59,34 +58,7 @@ async function actionClickOrActionEnter( ); } - // Resolve anchor refs ($name) into a concrete page+position before - // delegating to editor.navigate (which doesn't know about anchors). - if (ref.details?.type === "anchor") { - const anchorName = ref.details.name; - const pageFilter = ref.path - ? ref.path.endsWith(".md") ? ref.path.slice(0, -3) : ref.path - : undefined; - const result = await index.resolveAnchor(anchorName, pageFilter); - if (!result.ok) { - if (result.reason === "missing") { - return editor.flashNotification( - `Anchor not found: $${anchorName}`, - "error", - ); - } - return editor.flashNotification( - `Duplicate anchor $${anchorName} on pages: ${ - result.hits.map((h) => h.page).join(", ") - }`, - "error", - ); - } - ref.path = `${result.page}.md`; - ref.details = { type: "position", pos: result.range[0] }; - return editor.navigate(ref, false, inNewWindow); - } - - if (ref.path === "") { + if (ref.path === "" && ref.details?.type !== "anchor") { ref.path = currentPath; } @@ -133,10 +105,7 @@ async function actionClickOrActionEnter( } case "Hashtag": { const hashtag = extractHashtag(mdTree.children![0].text!); - const tagPage = await config.get( - ["tags", hashtag, "tagPage"], - null, - ); + const tagPage = await config.get(["tags", hashtag, "tagPage"], null); await editor.navigate( tagPage ?? `${tagPrefix}${hashtag}`, false, diff --git a/website/CHANGELOG.md b/website/CHANGELOG.md index feb579e3..5b22d9ee 100644 --- a/website/CHANGELOG.md +++ b/website/CHANGELOG.md @@ -7,6 +7,7 @@ Whenever a commit is pushed to the `main` branch, within ~5 minutes, it will be * **Important**: Run `Space: Reindex` after upgrading (just once) to this version to make linked mentions and other features work again (this should be automatic, but just in case) * Fix: forced space reindex handling * Fix: clicking a wiki link to a page now places the cursor just after the page's frontmatter on first visit (matching fresh-load behavior), instead of at position 0. Pages already visited in the session still restore their previously saved cursor position. +* Fix: `$`-anchor refs now resolve through the index from every navigation path ## 2.8.1 * Fix: cursor and clicks no longer drift by a line or two when working below a tall widget (e.g. arrow-up from a list under a `${query[[…]]}` now advances exactly one line). Some other cursor preservation issues addressed as well.