| | |
| | | import { slug } from "github-slugger" |
| | | import { slug as slugAnchor } from "github-slugger" |
| | | import type { Element as HastElement } from "hast" |
| | | import rfdc from "rfdc" |
| | | |
| | | export const clone = rfdc() |
| | | |
| | | // this file must be isomorphic so it can't use node libs (e.g. path) |
| | | |
| | | export const QUARTZ = "quartz" |
| | |
| | | /** Shouldn't be a relative path and shouldn't have `/index` as an ending or a file extension. It _can_ however have a trailing slash to indicate a folder path. */ |
| | | export type SimpleSlug = SlugLike<"simple"> |
| | | export function isSimpleSlug(s: string): s is SimpleSlug { |
| | | const validStart = !(s.startsWith(".") || s.startsWith("/")) |
| | | const validStart = !(s.startsWith(".") || (s.length > 1 && s.startsWith("/"))) |
| | | const validEnding = !(s.endsWith("/index") || s === "index") |
| | | return validStart && !_containsForbiddenCharacters(s) && validEnding && !_hasFileExtension(s) |
| | | } |
| | |
| | | return res |
| | | } |
| | | |
| | | function sluggify(s: string): string { |
| | | return s |
| | | .split("/") |
| | | .map((segment) => segment.replace(/\s/g, "-").replace(/%/g, "-percent").replace(/\?/g, "-q")) // slugify all segments |
| | | .join("/") // always use / as sep |
| | | .replace(/\/$/, "") |
| | | } |
| | | |
| | | export function slugifyFilePath(fp: FilePath, excludeExt?: boolean): FullSlug { |
| | | fp = _stripSlashes(fp) as FilePath |
| | | let ext = _getFileExtension(fp) |
| | |
| | | ext = "" |
| | | } |
| | | |
| | | let slug = withoutFileExt |
| | | .split("/") |
| | | .map((segment) => segment.replace(/\s/g, "-").replace(/%/g, "-percent")) // slugify all segments |
| | | .join("/") // always use / as sep |
| | | .replace(/\/$/, "") // remove trailing slash |
| | | let slug = sluggify(withoutFileExt) |
| | | |
| | | // treat _index as index |
| | | if (_endsWith(slug, "_index")) { |
| | |
| | | } |
| | | |
| | | export function simplifySlug(fp: FullSlug): SimpleSlug { |
| | | return _stripSlashes(_trimSuffix(fp, "index"), true) as SimpleSlug |
| | | const res = _stripSlashes(_trimSuffix(fp, "index"), true) |
| | | return (res.length === 0 ? "/" : res) as SimpleSlug |
| | | } |
| | | |
| | | export function transformInternalLink(link: string): RelativeURL { |
| | |
| | | return res |
| | | } |
| | | |
| | | // from micromorph/src/utils.ts |
| | | // https://github.com/natemoo-re/micromorph/blob/main/src/utils.ts#L5 |
| | | const _rebaseHtmlElement = (el: Element, attr: string, newBase: string | URL) => { |
| | | const rebased = new URL(el.getAttribute(attr)!, newBase) |
| | | el.setAttribute(attr, rebased.pathname + rebased.hash) |
| | | } |
| | | export function normalizeRelativeURLs(el: Element | Document, destination: string | URL) { |
| | | el.querySelectorAll('[href^="./"], [href^="../"]').forEach((item) => |
| | | _rebaseHtmlElement(item, "href", destination), |
| | | ) |
| | | el.querySelectorAll('[src^="./"], [src^="../"]').forEach((item) => |
| | | _rebaseHtmlElement(item, "src", destination), |
| | | ) |
| | | } |
| | | |
| | | const _rebaseHastElement = ( |
| | | el: HastElement, |
| | | attr: string, |
| | | curBase: FullSlug, |
| | | newBase: FullSlug, |
| | | ) => { |
| | | if (el.properties?.[attr]) { |
| | | if (!isRelativeURL(String(el.properties[attr]))) { |
| | | return |
| | | } |
| | | |
| | | const rel = joinSegments(resolveRelative(curBase, newBase), "..", el.properties[attr] as string) |
| | | el.properties[attr] = rel |
| | | } |
| | | } |
| | | |
| | | export function normalizeHastElement(rawEl: HastElement, curBase: FullSlug, newBase: FullSlug) { |
| | | const el = clone(rawEl) // clone so we dont modify the original page |
| | | _rebaseHastElement(el, "src", curBase, newBase) |
| | | _rebaseHastElement(el, "href", curBase, newBase) |
| | | if (el.children) { |
| | | el.children = el.children.map((child) => |
| | | normalizeHastElement(child as HastElement, curBase, newBase), |
| | | ) |
| | | } |
| | | |
| | | return el |
| | | } |
| | | |
| | | // resolve /a/b/c to ../.. |
| | | export function pathToRoot(slug: FullSlug): RelativeURL { |
| | | let rootPath = slug |
| | |
| | | return [fp, anchor] |
| | | } |
| | | |
| | | export function slugAnchor(anchor: string) { |
| | | return slug(anchor) |
| | | } |
| | | |
| | | export function slugTag(tag: string) { |
| | | return tag |
| | | .split("/") |
| | | .map((tagSegment) => slug(tagSegment)) |
| | | .map((tagSegment) => sluggify(tagSegment)) |
| | | .join("/") |
| | | } |
| | | |