isFolderPath() was checked on the raw input before slugifyFilePath()
rewrote folder notes (e.g. "My Folder/My Folder" -> "my-folder/index").
Now checks isFolderPath on both the raw input and the slugified result,
so the trailing slash is correctly emitted for folder note links.
Moved from Quartz core (quartz/util/path.ts) into the shared utils
package so that plugins performing cross-slug HAST embedding can
re-use the same rebase helper as Quartz's internal transclude
expansion, rather than reimplementing or skipping the rebase.
The primary consumer outside of Quartz core is canvas-page, whose
buildEmbeddedContent() pulls vfile.data.htmlAst from arbitrary target
pages and serializes it verbatim into canvas pages - producing broken
relative hrefs that survive accidentally on root deployments but fail
under non-trivial basePath configurations. Exposing the helper here
lets canvas-page (and any future embedding plugin) rebase correctly
without duplicating the logic.
Implementation details:
- Deep-clones the input via structuredClone (Node >=22 builtin) so
callers don't need to bring their own clone function
- Absolute URLs (http://, https://, /root-relative) pass through
untouched; only relative URLs are rebased
- Recurses into children so nested anchors inside a rebased subtree
are also normalized
Backed by 10 new tests covering same-slug, cross-slug (both directions),
nested depth, anchor preservation, absolute URL passthrough, root-relative
passthrough, src-attribute rebasing, elements without href/src, nested
children recursion, and input-immutability.
slugifyPath now lowercases its output, which cascades through
slugifyFilePath, slugTag, and transformInternalLink (all of which
funnel through _sluggify).
This matches Obsidian's case-insensitive link and tag matching:
[[MyNote]], [[mynote]], and [[MYNOTE]] all resolve to the slug
'my-note', and the tags #MyTag, #mytag, and #MYTAG collapse into a
single tag page.
BREAKING CHANGE: all generated URLs are now lowercase. Users
upgrading with mixed-case source filenames will see their URLs
change (e.g. /MyNote -> /my-note). Also eliminates silent data
loss on case-insensitive filesystems (macOS APFS, Windows NTFS)
where Apple.md and apple.md previously produced conflicting HTML
outputs that overwrote each other without warning.
The 'shortest' strategy in transformLink() only matched by filename (last
segment), causing wiki-links like [[Species/Elf/Wood]] to fail resolution
when the target lived under a deeper path (e.g. Compendium/Species/Elf/Wood).
Add suffix-based matching for multi-segment partial paths and handle the
/index variant for folder targets whose suffix gets stripped by
transformInternalLink().
When src is a folder page (e.g. 'Compendium/Spells' backed by index.md),
pathToRoot miscalculated the depth because the browser serves folder pages
at '/Compendium/Spells/' (trailing slash). Now detects folder pages via
allSlugs and uses the index slug for correct relative URL generation.
Add utility functions to resolve links correctly when Quartz is deployed
to a subdirectory (e.g., user.github.io/repository). getBasePath() reads
the base path from a data attribute on <body>, and resolveBasePath()
prepends it to slugs for client-side navigation.