Skip to content

Feat/relative link mode - #1681

Open
bachi76 wants to merge 4 commits into
perber:mainfrom
bachi76:feat/relative-link-mode
Open

bachi76 wants to merge 4 commits into
perber:mainfrom
bachi76:feat/relative-link-mode

Conversation

@bachi76

@bachi76 bachi76 commented Oct 14, 2026

Copy link
Copy Markdown

Related issue

Fixes #1057

What changed

Relative Markdown links currently always resolve with page-as-folder semantics, so wikis authored outside LeafWiki (Obsidian vaults, docs-as-code repos — the README's own "good fit") break on import (#1057).

Adds --relative-link-mode=page-folder|filesystem (default page-folder, fully backward compatible; env LEAFWIKI_RELATIVE_LINK_MODE, surfaced to the frontend via GET /api/config like enableLinkRefactor):

  • filesystem mode: relative targets resolve against the page's parent directory, like files on disk — and against the section itself for index.md section pages (foo/index.md lives in foo/). Section-ness is decided via the tree (kind == section) on the server, via the page kind in the UI.
  • Canonical lookup (both modes): a trailing .md is ignored and X/index(.md) addresses the section X — a folder's index.md is the folder's page, so no page ever lives at .../index. This also makes the README's documented .md-ignoring true for the server-side link index (it previously never stripped it).
  • Applies to the server-side link index and the frontend link rendering alike; switching the mode requires one resync (admin Refresh / SIGUSR1), which recomputes stored link targets.
Link on /docs/guide page-folder (default) filesystem
[x](setup) /docs/guide/setup /docs/setup
[x](../other) /docs/other /other
[x](setup.md) broken /docs/setup (both modes now)

Implementation: internal/links (RelativeLinkMode, section-aware linkBaseDir, canonicalPagePath), flag + validator in cmd/leafwiki, exposure via /api/config, UI counterpart in lib/wikiPath.ts (resolveWikiLinkPath + canonicalWikiPageRoute) consumed by MarkdownLink.tsx from the config store.

Known simplifications (happy to address per maintainer preference):

  • The mode is a set-once-at-startup package setting in internal/links rather than DI through LinkService — kept to avoid threading a param through five call layers; can switch if preferred.
  • The opt-in refactor engine (--enable-link-refactor) keeps a simplified non-section-aware parent-strip in filesystem mode; natural follow-up.

How I tested it

  • Go: new helpers_linkmode_test.go (both modes, section-aware base dir via real tree fixtures, canonical lookup, unknown-mode fallback); full backend suite green (54 packages).
  • Vitest: new cases in wikiPath.test.ts for both modes, section pages and canonicalization; full frontend suite green (725 tests / 99 files).
  • By hand: a live deployment of this build serving ~135 externally-authored pages with filesystem-relative links — broken links went from 94 to 0 wiki-internal after enabling filesystem mode; remaining 14 are genuine references outside the wiki root. Verified link click-through in the browser and correct link indexing straight from a cold start (wiped link store, restart, no manual resync).

Checklist

  • [ x] I discussed the approach on the linked issue before starting (issue filed with the design; happy to adjust to maintainer feedback)
  • This PR is focused on a single change
  • New behaviour plugs into an extension point; no new if feature branches in shared code (the mode is a single seam: base-dir selection + lookup canonicalization in internal/links, mirrored in the UI's resolveWikiLinkPath)
  • I ran npm run format in ui/leafwiki-ui
  • I updated documentation if needed (README: CLI flag, env var, "Relative Markdown Links" section)
  • For UI changes: I tested it manually in the browser (link navigation, both modes)
  • AI tools were used for this PR (Zoo Code agent, GLM). I have reviewed and understand every line

…ode)

Relative Markdown links currently always resolve with page-as-folder
semantics. Wikis authored outside LeafWiki (editors, Git pipelines,
generators) use filesystem-relative links, which then break.

Add --relative-link-mode=page-folder|filesystem (default page-folder,
env LEAFWIKI_RELATIVE_LINK_MODE). filesystem resolves relative targets
against the page's parent directory, like files on disk. Applies to the
server-side link index and the frontend (via /api/config), with tests
on both sides.
A resolved link path like docs/accounts/index.md addressed no page: the
'.md' tail was never stripped for lookup (despite the README) and a
section's index.md routes as the section itself, so '.../index' is
never a real page route. Canonicalize resolved targets on both the
server (canonicalPagePath) and the UI (canonicalWikiPageRoute): strip a
trailing '.md', then map a trailing 'index' segment to the section
route. Makes filesystem-relative wikis (accounts/index.md links) and
plain .md links resolve.
…mselves

A section's index.md lives IN its folder, so relative links from a
section page (e.g. 'usage.md' on wiki/tools/index.md) must resolve
against the section route itself, not its parent. Decide the base
directory via the tree (kind == section) in filesystem mode; the UI
mirrors this with a currentIsSection flag. Refactor engine keeps the
simplified parent-strip (opt-in feature, noted for follow-up).
NewWiki loads the tree and builds the initial link index at construction
time, so the mode has to be set before that point, not right before the
router is built (a restart without a resync briefly indexed links with
the default mode). Also: hoist linkBaseDir out of the per-link loop,
unexport the test-only mode getter, document the tree-less
resolveURLPath variant (refactor engine), and add a section-aware
TestLinkBaseDir.
@bachi76
bachi76 requested a review from perber as a code owner October 14, 2026 23:06
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.

Long-term Link and Storage Strategy

1 participant