From 27609fb4f3d5b691cdf090c304df526b2ff78464 Mon Sep 17 00:00:00 2001 From: Yuri Karamian Date: Tue, 11 Aug 2026 02:41:39 +0200 Subject: [PATCH] Fix Windows internal note links --- src/lib/components/Editor.svelte | 11 +++++++--- src/lib/utils/paths.ts | 37 ++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/lib/components/Editor.svelte b/src/lib/components/Editor.svelte index abf9d77..62e9026 100644 --- a/src/lib/components/Editor.svelte +++ b/src/lib/components/Editor.svelte @@ -54,7 +54,7 @@ import { convertListNode, type MixedListName } from '$lib/editor/mixedLists'; import { clearFormatting } from '$lib/editor/clearFormatting'; import { serializeInlineMarkdown } from '$lib/editor/markdown'; - import { relativePath } from '$lib/utils/paths'; + import { relativePath, resolvePathFromFile } from '$lib/utils/paths'; import GraphView from './GraphView.svelte'; import TagSuggestInput from './TagSuggestInput.svelte'; import ImageViewer from './ImageViewer.svelte'; @@ -2891,6 +2891,12 @@ $activeNote = { ...content, content: content.content }; $activeNotePath = absPath; } catch (e) { + // Explicit Markdown links do not have a wiki-link title to recreate from. + // Do not turn a failed read into an untitled note. + if (!noteTitle) { + console.error('Failed to navigate to note link:', e); + return; + } // Note at path no longer exists (deleted/moved). Refresh cache and // retry as unresolved so the user can recreate it from the link. await refreshWikiLinkTitles(); @@ -5533,8 +5539,7 @@ if (!decoded.startsWith('/')) { const notePath = $activeNotePath; if (notePath) { - const noteDir = notePath.substring(0, notePath.lastIndexOf('/')); - absPath = normalizePath(`${noteDir}/${decoded}`); + absPath = resolvePathFromFile(notePath, decoded); } else { const vaultRoot = $appConfig?.active_vault; if (vaultRoot) absPath = normalizePath(`${vaultRoot}/${decoded}`); diff --git a/src/lib/utils/paths.ts b/src/lib/utils/paths.ts index f12b7e2..bb9b670 100644 --- a/src/lib/utils/paths.ts +++ b/src/lib/utils/paths.ts @@ -26,3 +26,40 @@ export function relativePath(fromDirectory: string, targetPath: string): string const result = [...Array(from.length - common).fill('..'), ...target.slice(common)]; return result.join('/') || '.'; } + +export function resolvePathFromFile(filePath: string, targetPath: string): string { + const normalizedFile = filePath.replace(/\\/g, '/'); + const normalizedTarget = targetPath.replace(/\\/g, '/'); + const lastSeparator = normalizedFile.lastIndexOf('/'); + const fileDirectory = lastSeparator >= 0 ? normalizedFile.slice(0, lastSeparator) : ''; + const combined = normalizedTarget.startsWith('/') || /^[A-Za-z]:\//.test(normalizedTarget) + ? normalizedTarget + : `${fileDirectory}/${normalizedTarget}`; + const drive = combined.match(/^([A-Za-z]:)\//); + const unc = combined.startsWith('//'); + const absolute = !unc && combined.startsWith('/'); + let prefix = ''; + let remainder = combined; + if (drive) { + prefix = `${drive[1]}/`; + remainder = combined.slice(drive[0].length); + } else if (unc) { + prefix = '//'; + remainder = combined.slice(2); + } else if (absolute) { + prefix = '/'; + remainder = combined.slice(1); + } + const resolved: string[] = []; + + for (const segment of remainder.split('/')) { + if (!segment || segment === '.') continue; + if (segment === '..') { + resolved.pop(); + } else { + resolved.push(segment); + } + } + + return prefix + resolved.join('/'); +}