|
|
|
@ -1,4 +1,9 @@
|
|
|
|
|
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"
|
|
|
|
@ -24,7 +29,7 @@ export function isFullSlug(s: string): s is FullSlug {
|
|
|
|
|
/** 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)
|
|
|
|
|
}
|
|
|
|
@ -42,6 +47,14 @@ export function getFullSlug(window: Window): FullSlug {
|
|
|
|
|
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)
|
|
|
|
@ -50,11 +63,7 @@ export function slugifyFilePath(fp: FilePath, excludeExt?: boolean): FullSlug {
|
|
|
|
|
ext = ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let slug = withoutFileExt
|
|
|
|
|
.split("/")
|
|
|
|
|
.map((segment) => segment.replace(/\s/g, "-").replace(/%/g, "-percent").replace(/\?/g, "-q")) // slugify all segments
|
|
|
|
|
.join("/") // always use / as sep
|
|
|
|
|
.replace(/\/$/, "") // remove trailing slash
|
|
|
|
|
let slug = sluggify(withoutFileExt)
|
|
|
|
|
|
|
|
|
|
// treat _index as index
|
|
|
|
|
if (_endsWith(slug, "_index")) {
|
|
|
|
@ -65,7 +74,8 @@ export function slugifyFilePath(fp: FilePath, excludeExt?: boolean): FullSlug {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 {
|
|
|
|
@ -84,6 +94,50 @@ 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
|
|
|
|
@ -111,14 +165,10 @@ export function splitAnchor(link: string): [string, string] {
|
|
|
|
|
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("/")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|