Skip to content

fix: resolve markdown-style relative .md links to permalinks - #398

Merged
oleeskild merged 1 commit into
mainfrom
fix/resolve-relative-md-links
Aug 10, 2026
Merged

fix: resolve markdown-style relative .md links to permalinks#398
oleeskild merged 1 commit into
mainfrom
fix/resolve-relative-md-links

Conversation

@oleeskild

@oleeskild oleeskild commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Problem

Gardens whose notes use plain markdown links ([X](../concepts/x.md)) instead of wikilinks end up with broken links and an empty graph:

  • The link pipeline (plugin convertLinksToFullPath, the link filter, link_open rule) only handles [[wikilinks]], so relative .md hrefs reach the published HTML untouched.
  • Pages are served at trailing-slash URLs, so the browser resolves ../concepts/x.md one directory too deep: /wiki/concepts/concepts/x.md → 404 (reported by a user with exactly this duplicated segment).
  • extractLinks only matches wikilinks and root-absolute hrefs, so the graph shows isolated dots with no edges and backlinks are empty.

Fix

  • src/helpers/linkUtils.js
    • resolveVaultPath(target, sourceDir): resolves relative/root-anchored link targets against the note's vault directory; rejects external schemes and paths escaping the vault.
    • convertMdHrefs(html, sourceDir, resolveAnchor): rewrites .md hrefs in rendered HTML, preserving fragments.
    • extractLinks(content, sourcePath): now also extracts markdown-style .md links (image embeds excluded) resolved to vault stems, so the graph and backlinks pick them up.
  • .eleventy.js — new resolveMdLinks filter that derives the note's vault directory from its inputPath and reuses getAnchorAttributes, so unresolved targets get the same /404 handling as dead wikilinks. Accepts an explicit input path for looped contexts.
  • note.njk / index.njk / feed.njk: apply the filter; the feed passes note.inputPath explicitly since this.page there is the feed template, not the note.

Notes written with plain markdown links (e.g. [X](../concepts/x.md))
instead of wikilinks reached the rendered HTML untouched. Because pages
are served at trailing-slash URLs, the browser resolved those hrefs one
directory too deep (/wiki/concepts/concepts/x.md -> 404), and the graph
showed no edges since link extraction only matched wikilinks and
root-absolute hrefs.

- linkUtils: resolveVaultPath() resolves relative/root link targets
  against the note's vault directory; convertMdHrefs() rewrites .md
  hrefs in rendered HTML; extractLinks() now also extracts
  markdown-style links so the graph and backlinks see them
- .eleventy.js: new resolveMdLinks filter reusing getAnchorAttributes,
  so dead links get the same /404 handling as dead wikilinks
- note/index layouts and the RSS feed apply the filter (the feed passes
  note.inputPath explicitly since this.page there is the feed template)

Verified against a real affected garden: 0 unresolved internal .md
hrefs in built output, graph edges went from 0 to 1196 across 167
notes, and all 166 feed entries carry resolved absolute permalinks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HWqT1iTxSSr3rRhDp4Uqhm
@oleeskild
oleeskild merged commit 4c0c195 into main Aug 10, 2026
1 check passed
oleeskild added a commit that referenced this pull request Aug 11, 2026
The resolveMdLinks filter added in 1.83.0 (#398) sent every
dataview-generated link to /404, two defects compounding:

- convertMdHrefs' anchor regex matched the tail of data-href=", so
  dataviewjs anchors (<a data-href="Folder/Note.md" href=...>) had
  their data-href clobbered to /404 before the dataview-js-links
  transform could resolve the anchor from it. The regex now requires
  href to be a standalone attribute.
- resolveVaultPath treated vault-root targets (Folder/Note.md, as
  dataview and Obsidian's "absolute path in vault" setting emit) as
  note-relative, so links from nested notes resolved to paths that
  don't exist. Targets now yield ordered candidate interpretations,
  note-relative first, vault-root as fallback, and the filter picks
  the first that resolves to a real note. Dead links keep the /404 +
  is-unresolved handling.

Verified with unit tests (TDD, 6 new failing tests before the fix)
and an end-to-end build of the test garden covering dataviewjs
anchors, vault-absolute and relative .md links.

Fixes #399

Co-authored-by: Ole Eskild Steensen <6201338+oleeskild@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant