mirror of
https://github.com/quartz-community/utils.git
synced 2026-07-22 02:50:27 +00:00
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.
104 lines
4 KiB
TypeScript
104 lines
4 KiB
TypeScript
import { Element } from "hast";
|
|
import { FullSlug, FilePath } from "@quartz-community/types";
|
|
export { FilePath, FullSlug } from "@quartz-community/types";
|
|
|
|
/** No '/index' ending, no file extension, can have trailing slash for folders. */
|
|
type SimpleSlug = string & {
|
|
_brand: "SimpleSlug";
|
|
};
|
|
/** Starts with './' or '../', used for navigation. */
|
|
type RelativeURL = string & {
|
|
_brand: "RelativeURL";
|
|
};
|
|
interface TransformOptions {
|
|
strategy: "absolute" | "relative" | "shortest";
|
|
allSlugs: FullSlug[];
|
|
}
|
|
declare function isFilePath(s: string): s is FilePath;
|
|
declare function isFullSlug(s: string): s is FullSlug;
|
|
declare function isSimpleSlug(s: string): s is SimpleSlug;
|
|
declare function isRelativeURL(s: string): s is RelativeURL;
|
|
declare function isAbsoluteURL(s: string): boolean;
|
|
declare function simplifySlug(fp: FullSlug | string): SimpleSlug;
|
|
declare function getFullSlug(window: Window): FullSlug;
|
|
declare function getFullSlugFromUrl(): FullSlug;
|
|
declare function slugifyFilePath(fp: FilePath, excludeExt?: boolean): FullSlug;
|
|
declare function joinSegments(...args: string[]): string;
|
|
declare function resolvePath(to: string): string;
|
|
/** Read the base path injected at build time via `data-basepath` on `<body>`.
|
|
* Returns `""` for root deployments, e.g. `"/repository"` for subdirectory. */
|
|
declare function getBasePath(): string;
|
|
/** Resolve a slug to an absolute URL path, prepending the site's base path.
|
|
* e.g. `resolveBasePath("features/Callouts")` → `"/repository/features/Callouts"` */
|
|
declare function resolveBasePath(to: string, basePath?: string): string;
|
|
declare function endsWith(s: string, suffix: string): boolean;
|
|
declare function trimSuffix(s: string, suffix: string): string;
|
|
declare function stripSlashes(s: string, onlyStripPrefix?: boolean): string;
|
|
declare function getFileExtension(s: string): string | undefined;
|
|
declare function isFolderPath(fplike: string): boolean;
|
|
declare function getAllSegmentPrefixes(path: string): string[];
|
|
declare function pathToRoot(slug: FullSlug): RelativeURL;
|
|
declare function resolveRelative(current: FullSlug, target: FullSlug | SimpleSlug): RelativeURL;
|
|
declare function splitAnchor(link: string): [string, string];
|
|
declare function slugTag(tag: string): string;
|
|
declare function transformInternalLink(link: string): RelativeURL;
|
|
declare function transformLink(src: FullSlug, target: string, opts: TransformOptions): RelativeURL;
|
|
/**
|
|
* Slugify a file path for use as an href.
|
|
* Replaces whitespace→hyphens, &→-and-, %→-percent, removes ? and #, and
|
|
* lowercases the result so that link matching is case-insensitive (matching
|
|
* Obsidian's link-resolution semantics). Operates per segment so directory
|
|
* separators are preserved.
|
|
*/
|
|
declare function slugifyPath(s: string): string;
|
|
/**
|
|
* Re-base the relative `href` and `src` attributes of a HAST element so that
|
|
* links inside content originally resolved relative to `newBase` remain valid
|
|
* when the element is embedded at a page with slug `curBase`.
|
|
*
|
|
* Used for transclusions and cross-slug HAST embedding (Quartz core's
|
|
* `renderPage` transclude expansion, canvas-page's embedded file-nodes, and
|
|
* any plugin that takes `vfile.data.htmlAst` from one page and renders it
|
|
* inside another). Absolute URLs pass through unchanged.
|
|
*
|
|
* The element (and its children) are deep-cloned via `structuredClone`, so
|
|
* the original HAST tree is never mutated.
|
|
*/
|
|
declare function normalizeHastElement<T extends Element>(
|
|
rawEl: T,
|
|
curBase: FullSlug,
|
|
newBase: FullSlug,
|
|
): T;
|
|
|
|
export {
|
|
type RelativeURL,
|
|
type SimpleSlug,
|
|
type TransformOptions,
|
|
endsWith,
|
|
getAllSegmentPrefixes,
|
|
getBasePath,
|
|
getFileExtension,
|
|
getFullSlug,
|
|
getFullSlugFromUrl,
|
|
isAbsoluteURL,
|
|
isFilePath,
|
|
isFolderPath,
|
|
isFullSlug,
|
|
isRelativeURL,
|
|
isSimpleSlug,
|
|
joinSegments,
|
|
normalizeHastElement,
|
|
pathToRoot,
|
|
resolveBasePath,
|
|
resolvePath,
|
|
resolveRelative,
|
|
simplifySlug,
|
|
slugTag,
|
|
slugifyFilePath,
|
|
slugifyPath,
|
|
splitAnchor,
|
|
stripSlashes,
|
|
transformInternalLink,
|
|
transformLink,
|
|
trimSuffix,
|
|
};
|