Skip to content

Relative links with '..' don't resolve correctly when clicked in preview #1310

@gpolitis

Description

@gpolitis

Description

When clicking a relative markdown link that uses .. to navigate to a parent directory, the link fails to open the file.

For example, given this file structure:

project/
├── docs/
│   └── notes.md       ← editing this file
└── assets/
    └── diagram.pdf

A link in notes.md like [Diagram](../assets/diagram.pdf) should open project/assets/diagram.pdf, but it fails silently.

Links within the same directory (e.g., [Other](other.md)) work correctly.

Root Cause

In EditorViewController+Delegate.swift (lines 16-42), the createWebViewWith delegate handles link clicks from the WKWebView:

  1. The preview WebView has baseURL = http://localhost/
  2. A relative link like ../assets/diagram.pdf is resolved by WKWebView against http://localhost/, producing http://localhost/assets/diagram.pdf (the .. is consumed since you can't go above the URL root)
  3. The string replacement then swaps http://localhost/ with document.baseURL (e.g., file:///project/docs/)
  4. Result: file:///project/docs/assets/diagram.pdf — which is wrong. It should be file:///project/assets/diagram.pdf

The .. component is lost during step 2 because HTTP URL resolution normalizes it away before the string substitution in step 3 can preserve it.

Suggested Fix

Resolve the relative path against document.baseURL directly instead of relying on string replacement of the WKWebView's localhost URL. For example:

func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
    guard let url = navigationAction.request.url else {
        return nil
    }

    let resolvedURL: URL? = {
        // If it already has a file scheme or other real scheme, use directly
        if url.scheme != "http" || url.host != "localhost" {
            return url
        }
        // Resolve the original relative path against the document's base URL
        guard let baseURL = document?.baseURL else { return nil }
        let relativePath = url.path.removingPercentEncoding ?? url.path
        return baseURL.appending(path: relativePath).standardized
    }()

    if let resolvedURL {
        NSWorkspace.shared.openOrReveal(url: resolvedURL)
    }

    return nil
}

The key addition is .standardized which resolves .. components in the path.

Workaround

Create a symlink in the subdirectory pointing to the parent, allowing forward-only relative paths that avoid ...

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions