diff --git a/plugs/markdown/markdown_render.ts b/plugs/markdown/markdown_render.ts index a7ecfb2..0e8d574 100644 --- a/plugs/markdown/markdown_render.ts +++ b/plugs/markdown/markdown_render.ts @@ -9,6 +9,7 @@ import { } from "$sb/lib/tree.ts"; import { parsePageRef } from "$sb/lib/page.ts"; import { Fragment, renderHtml, Tag } from "./html_render.ts"; +import { encodePageRef } from "$sb/lib/page.ts"; export type MarkdownRenderOptions = { failOnUnknown?: true; @@ -246,11 +247,11 @@ function render( if (aliasNode) { linkText = aliasNode.children![0].text!; } - const { page: pageName, anchor } = parsePageRef(ref); + const pageRef = parsePageRef(ref); return { name: "a", attrs: { - href: `/${pageName}${anchor ? "#" + anchor : ""}`, + href: `/${encodePageRef(pageRef)}`, class: "wiki-link", "data-ref": ref, }, diff --git a/web/client.ts b/web/client.ts index 9f7587b..0ff8c82 100644 --- a/web/client.ts +++ b/web/client.ts @@ -13,7 +13,11 @@ import { FilterOption } from "./types.ts"; import { ensureSettingsAndIndex } from "../common/util.ts"; import { EventHook } from "../plugos/hooks/event.ts"; import { AppCommand } from "./hooks/command.ts"; -import { PageState, PathPageNavigator } from "./navigator.ts"; +import { + PageState, + parsePageRefFromURI, + PathPageNavigator, +} from "./navigator.ts"; import { AppViewState, BuiltinSettings } from "./types.ts"; @@ -110,6 +114,7 @@ export class Client { // Used by the "wiki link" highlighter to check if a page exists public allKnownPages = new Set(); + onLoadPageRef: PageRef; constructor( private parent: Element, @@ -120,6 +125,7 @@ export class Client { } // Generate a semi-unique prefix for the database so not to reuse databases for different space paths this.dbPrefix = "" + simpleHash(window.silverBulletConfig.spaceFolderPath); + this.onLoadPageRef = parsePageRefFromURI(); } /** @@ -224,7 +230,7 @@ export class Client { setInterval(() => { try { - this.syncService.syncFile(`${this.currentPage!}.md`).catch((e: any) => { + this.syncService.syncFile(`${this.currentPage}.md`).catch((e: any) => { console.error("Interval sync error", e); }); } catch (e: any) { @@ -305,7 +311,11 @@ export class Client { let adjustedPosition = false; // Was a particular scroll position persisted? - if (pageState.scrollTop !== undefined) { + if ( + pageState.scrollTop !== undefined && + !(pageState.scrollTop === 0 && + (pageState.pos !== undefined || pageState.anchor !== undefined)) + ) { setTimeout(() => { console.log("Kicking off scroll to", pageState.scrollTop); this.editorView.scrollDOM.scrollTop = pageState.scrollTop!; @@ -592,8 +602,10 @@ export class Client { return localSpacePrimitives; } - get currentPage(): string | undefined { - return this.ui.viewState.currentPage; + get currentPage(): string { + return this.ui.viewState.currentPage !== undefined + ? this.ui.viewState.currentPage + : this.onLoadPageRef.page; // best effort } dispatchAppEvent(name: AppEvent, ...args: any[]): Promise { @@ -814,7 +826,7 @@ export class Client { } const results = await this.dispatchAppEvent(eventName, { - pageName: this.currentPage!, + pageName: this.currentPage, linePrefix, pos: selection.from, parentNodes, @@ -851,7 +863,7 @@ export class Client { async reloadPage() { console.log("Reloading page"); clearTimeout(this.saveTimeout); - await this.loadPage(this.currentPage!); + await this.loadPage(this.currentPage); } focus() { @@ -889,6 +901,10 @@ export class Client { } if (newWindow) { + console.log( + "Navigating to new page in new window", + `${location.origin}/${encodePageRef(pageRef)}`, + ); const win = window.open( `${location.origin}/${encodePageRef(pageRef)}`, "_blank", diff --git a/web/cm_plugins/clean.ts b/web/cm_plugins/clean.ts index 7ebae8f..6f86e3d 100644 --- a/web/cm_plugins/clean.ts +++ b/web/cm_plugins/clean.ts @@ -28,7 +28,7 @@ export function cleanModePlugins(client: Client) { // TODO: Move this logic elsewhere? onCheckboxClick: (pos) => { const clickEvent: ClickEvent = { - page: client.currentPage!, + page: client.currentPage, altKey: false, ctrlKey: false, metaKey: false, diff --git a/web/cm_plugins/command_link.ts b/web/cm_plugins/command_link.ts index 097e081..d09cef9 100644 --- a/web/cm_plugins/command_link.ts +++ b/web/cm_plugins/command_link.ts @@ -55,7 +55,7 @@ export function cleanCommandLinkPlugin(editor: Client) { } // Dispatch click event to navigate there without moving the cursor const clickEvent: ClickEvent = { - page: editor.currentPage!, + page: editor.currentPage, ctrlKey: e.ctrlKey, metaKey: e.metaKey, altKey: e.altKey, diff --git a/web/cm_plugins/editor_paste.ts b/web/cm_plugins/editor_paste.ts index 2cc0e0b..4bae6b0 100644 --- a/web/cm_plugins/editor_paste.ts +++ b/web/cm_plugins/editor_paste.ts @@ -215,7 +215,7 @@ export function attachmentExtension(editor: Client) { return; } await editor.space.writeAttachment( - resolve(folderName(editor.currentPage!), finalFileName), + resolve(folderName(editor.currentPage), finalFileName), new Uint8Array(data), ); let attachmentMarkdown = `[${finalFileName}](${encodeURI(finalFileName)})`; diff --git a/web/cm_plugins/iframe_widget.ts b/web/cm_plugins/iframe_widget.ts index 7dd4ccb..86eae3e 100644 --- a/web/cm_plugins/iframe_widget.ts +++ b/web/cm_plugins/iframe_widget.ts @@ -22,7 +22,7 @@ export class IFrameWidget extends WidgetType { const iframe = createWidgetSandboxIFrame( this.client, this.bodyText, - this.codeWidgetCallback(this.bodyText, this.client.currentPage!), + this.codeWidgetCallback(this.bodyText, this.client.currentPage), (message) => { switch (message.type) { case "blur": @@ -33,7 +33,7 @@ export class IFrameWidget extends WidgetType { break; case "reload": - this.codeWidgetCallback(this.bodyText, this.client.currentPage!) + this.codeWidgetCallback(this.bodyText, this.client.currentPage) .then( (widgetContent: WidgetContent | null) => { if (widgetContent === null) { diff --git a/web/cm_plugins/inline_image.ts b/web/cm_plugins/inline_image.ts index 583cdb0..9a90d23 100644 --- a/web/cm_plugins/inline_image.ts +++ b/web/cm_plugins/inline_image.ts @@ -33,7 +33,7 @@ class InlineImageWidget extends WidgetType { toDOM() { const img = document.createElement("img"); let url = this.url; - url = resolvePath(this.client.currentPage!, url, true); + url = resolvePath(this.client.currentPage, url, true); // console.log("Creating DOM", this.url); const cachedImageHeight = this.client.getCachedWidgetHeight( `image:${this.url}`, @@ -79,7 +79,7 @@ export function inlineImagesPlugin(client: Client) { const title = imageRexexResult.groups.title; if (url.indexOf("://") === -1 && !url.startsWith("/")) { - url = resolveAttachmentPath(client.currentPage!, 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 a7a917d..9dd2d9f 100644 --- a/web/cm_plugins/link.ts +++ b/web/cm_plugins/link.ts @@ -37,7 +37,7 @@ export function linkPlugin(client: Client) { if (!cleanLink.includes("://")) { cleanLink = resolveAttachmentPath( - client.currentPage!, + client.currentPage, decodeURI(cleanLink), ); } diff --git a/web/cm_plugins/lint.ts b/web/cm_plugins/lint.ts index 6de2adb..616be65 100644 --- a/web/cm_plugins/lint.ts +++ b/web/cm_plugins/lint.ts @@ -11,7 +11,7 @@ export function plugLinter(client: Client) { view.state.sliceDoc(), ); const results = (await client.dispatchAppEvent("editor:lint", { - name: client.currentPage!, + name: client.currentPage, tree: tree, } as LintEvent)).flat(); return results; diff --git a/web/cm_plugins/markdown_widget.ts b/web/cm_plugins/markdown_widget.ts index d7fbf55..40992e8 100644 --- a/web/cm_plugins/markdown_widget.ts +++ b/web/cm_plugins/markdown_widget.ts @@ -48,7 +48,7 @@ export class MarkdownWidget extends WidgetType { ) { const widgetContent = await this.codeWidgetCallback( this.bodyText, - this.client.currentPage!, + this.client.currentPage, ); activeWidgets.add(this); if (!widgetContent) { @@ -79,7 +79,7 @@ export class MarkdownWidget extends WidgetType { translateUrls: (url) => { if (!url.includes("://")) { url = resolveAttachmentPath( - this.client.currentPage!, + this.client.currentPage, decodeURI(url), ); } diff --git a/web/cm_plugins/table.ts b/web/cm_plugins/table.ts index 0337313..63580d5 100644 --- a/web/cm_plugins/table.ts +++ b/web/cm_plugins/table.ts @@ -42,7 +42,7 @@ class TableViewWidget extends WidgetType { annotationPositions: true, translateUrls: (url) => { if (!url.includes("://")) { - url = resolveAttachmentPath(this.client.currentPage!, decodeURI(url)); + url = resolveAttachmentPath(this.client.currentPage, decodeURI(url)); } return url; diff --git a/web/cm_plugins/wiki_link.ts b/web/cm_plugins/wiki_link.ts index 305df23..fb00ad1 100644 --- a/web/cm_plugins/wiki_link.ts +++ b/web/cm_plugins/wiki_link.ts @@ -9,7 +9,7 @@ import { LinkWidget, } from "./util.ts"; import { resolvePath } from "$sb/lib/resolve.ts"; -import { parsePageRef } from "$sb/lib/page.ts"; +import { encodePageRef, parsePageRef } from "$sb/lib/page.ts"; /** * Plugin to hide path prefix when the cursor is not inside. @@ -30,10 +30,9 @@ export function cleanWikiLinkPlugin(client: Client) { const [_fullMatch, page, pipePart, alias] = match; let pageExists = !client.fullSyncCompleted; - let cleanPage = page; - cleanPage = parsePageRef(page).page; - cleanPage = resolvePath(client.currentPage!, cleanPage); - const lowerCasePageName = cleanPage.toLowerCase(); + const pageRef = parsePageRef(page); + pageRef.page = resolvePath(client.currentPage, pageRef.page); + const lowerCasePageName = pageRef.page.toLowerCase(); for (const pageName of client.allKnownPages) { if (pageName.toLowerCase() === lowerCasePageName) { pageExists = true; @@ -41,8 +40,8 @@ export function cleanWikiLinkPlugin(client: Client) { } } if ( - cleanPage === "" || - client.plugSpaceRemotePrimitives.isLikelyHandled(cleanPage) + pageRef.page === "" || + client.plugSpaceRemotePrimitives.isLikelyHandled(pageRef.page) ) { // Empty page name with local @anchor use or a link to a page that dynamically generated by a plug pageExists = true; @@ -81,9 +80,9 @@ export function cleanWikiLinkPlugin(client: Client) { { text: linkText, title: pageExists - ? `Navigate to ${cleanPage}` - : `Create ${cleanPage}`, - href: `/${cleanPage}`, + ? `Navigate to ${encodePageRef(pageRef)}` + : `Create ${pageRef.page}`, + href: `/${encodePageRef(pageRef)}`, cssClass: pageExists ? "sb-wiki-link-page" : "sb-wiki-link-page-missing", @@ -96,7 +95,7 @@ export function cleanWikiLinkPlugin(client: Client) { } // Dispatch click event to navigate there without moving the cursor const clickEvent: ClickEvent = { - page: client.currentPage!, + page: client.currentPage, ctrlKey: e.ctrlKey, metaKey: e.metaKey, altKey: e.altKey, diff --git a/web/navigator.ts b/web/navigator.ts index 9ac0e07..87740b0 100644 --- a/web/navigator.ts +++ b/web/navigator.ts @@ -14,7 +14,6 @@ export type PageState = PageRef & { export class PathPageNavigator { navigationResolve?: () => void; - root: string; indexPage: string; openPages = new Map(); @@ -22,7 +21,6 @@ export class PathPageNavigator { constructor( private client: Client, ) { - this.root = ""; this.indexPage = cleanPageRef( renderHandlebarsTemplate(client.settings.indexPage, {}, {}), ); @@ -52,20 +50,20 @@ export class PathPageNavigator { window.history.replaceState( cleanState, "", - `${this.root}/${currentState.page}`, + `/${currentState.page}`, ); console.log("Pushing new state", pageRef); window.history.pushState( pageRef, "", - `${this.root}/${pageRef.page}`, + `/${pageRef.page}`, ); } else { // console.log("Replacing state", pageRef); window.history.replaceState( pageRef, "", - `${this.root}/${pageRef.page}`, + `/${pageRef.page}`, ); } // console.log("Explicitly dispatching the popstate", pageRef); @@ -81,7 +79,7 @@ export class PathPageNavigator { } buildCurrentPageState(): PageState { - const pageState: PageState = this.parseURI(); + const pageState: PageState = parsePageRefFromURI(); const mainSelection = this.client.editorView.state.selection.main; pageState.scrollTop = this.client.editorView.scrollDOM.scrollTop; pageState.selection = { @@ -122,7 +120,7 @@ export class PathPageNavigator { } else { // This occurs when the page is loaded completely fresh with no browser history around it // console.log("Got null state so using", this.parseURI()); - const pageRef = this.parseURI(); + const pageRef = parsePageRefFromURI(); if (!pageRef.page) { pageRef.page = this.indexPage; } @@ -141,16 +139,12 @@ export class PathPageNavigator { }), ); } - - parseURI(): PageRef { - const pageRef = parsePageRef(decodeURI( - location.pathname.substring(this.root.length + 1), - )); - - // if (!pageRef.page) { - // pageRef.page = this.indexPage; - // } - - return pageRef; - } +} + +export function parsePageRefFromURI(): PageRef { + const pageRef = parsePageRef(decodeURI( + location.pathname.substring(1), + )); + + return pageRef; } diff --git a/web/syscalls/editor.ts b/web/syscalls/editor.ts index c4896a1..f6c78fa 100644 --- a/web/syscalls/editor.ts +++ b/web/syscalls/editor.ts @@ -18,7 +18,7 @@ import { PageRef } from "$sb/lib/page.ts"; export function editorSyscalls(client: Client): SysCallMapping { const syscalls: SysCallMapping = { "editor.getCurrentPage": (): string => { - return client.currentPage!; + return client.currentPage; }, "editor.getText": () => { return client.editorView.state.sliceDoc(); diff --git a/website/CHANGELOG.md b/website/CHANGELOG.md index 7c87d93..7584df9 100644 --- a/website/CHANGELOG.md +++ b/website/CHANGELOG.md @@ -6,6 +6,9 @@ release. ## Edge _The changes below are not yet released “properly”. To them out early, check out [the docs on edge](https://community.silverbullet.md/t/living-on-the-edge-builds/27)._ +* Bug fixes: + * Improved Ctrl/Cmd-click (to open links in a new window) behavior: now actually follow `@pos` and `$anchor` links. + * Right-clicking links now opens browser native context menu again * Internal changes: * Big refactor: of navigation and browser history, fixed some {[Page: Rename]} bugs along the way * Plugs now can no longer define their own markdown syntax, migrated all plug-specific syntax into the main parser. This should remove a bunch of editor “flashing” especially during sync. @@ -118,8 +121,8 @@ _The changes below are not yet released “properly”. To them out early, check --- ## 0.5.3 * Changes to [[Objects]]: - * Paragraphs are now indexed, see [[Objects@paragraph]] (thanks to [Ian Shehadeh](https://github.com/silverbulletmd/silverbullet/pull/528)) - * For consistency, list items are now always indexed as well (whether they contain a [[Tags|tag]] or not) see [[Objects@item]]. + * Paragraphs are now indexed, see [[Objects$paragraph]] (thanks to [Ian Shehadeh](https://github.com/silverbulletmd/silverbullet/pull/528)) + * For consistency, list items are now always indexed as well (whether they contain a [[Tags|tag]] or not) see [[Objects$item]]. * The {[Directive: Convert to Live Query/Template]} now also converts `#use` and `#include` directives * Styling improvements for Linked Mentions * SilverBullet now fully works when added as PWA on Safari 17 (via the “Add to Dock” option). diff --git a/website/SETTINGS.md b/website/SETTINGS.md index 51ec7d8..96c2444 100644 --- a/website/SETTINGS.md +++ b/website/SETTINGS.md @@ -16,6 +16,8 @@ shortcuts: key: "Alt-x" - command: "{[Upload: File]}" priority: 1 # Make sure this appears at the top of the list in the command palette +- command: "{[Open Command Palette]}" + key: "Ctrl-." # Defines files to ignore in a format compatible with .gitignore spaceIgnore: |