mirror of
https://github.com/alrayyes/wiki.git
synced 2025-06-01 03:40:59 +00:00
base path refactor to better support subpath hosting
This commit is contained in:
parent
3201f83b70
commit
c874e7e937
29 changed files with 257 additions and 389 deletions
quartz/util
|
@ -1,5 +1,5 @@
|
|||
import { QuartzConfig } from "../cfg"
|
||||
import { ServerSlug } from "./path"
|
||||
import { FullSlug } from "./path"
|
||||
|
||||
export interface Argv {
|
||||
directory: string
|
||||
|
@ -13,5 +13,5 @@ export interface Argv {
|
|||
export interface BuildCtx {
|
||||
argv: Argv
|
||||
cfg: QuartzConfig
|
||||
allSlugs: ServerSlug[]
|
||||
allSlugs: FullSlug[]
|
||||
}
|
||||
|
|
|
@ -1,45 +1,25 @@
|
|||
import test, { describe } from "node:test"
|
||||
import * as path from "./path"
|
||||
import assert from "node:assert"
|
||||
import { CanonicalSlug, ServerSlug, TransformOptions } from "./path"
|
||||
import { FullSlug, TransformOptions } from "./path"
|
||||
|
||||
describe("typeguards", () => {
|
||||
test("isClientSlug", () => {
|
||||
assert(path.isClientSlug("http://example.com"))
|
||||
assert(path.isClientSlug("http://example.com/index"))
|
||||
assert(path.isClientSlug("http://example.com/index.html"))
|
||||
assert(path.isClientSlug("http://example.com/"))
|
||||
assert(path.isClientSlug("https://example.com"))
|
||||
assert(path.isClientSlug("https://example.com/abc/def"))
|
||||
assert(path.isClientSlug("https://example.com/abc/def/"))
|
||||
assert(path.isClientSlug("https://example.com/abc/def#cool"))
|
||||
assert(path.isClientSlug("https://example.com/abc/def?field=1&another=2"))
|
||||
assert(path.isClientSlug("https://example.com/abc/def?field=1&another=2#cool"))
|
||||
assert(path.isClientSlug("https://example.com/abc/def.html?field=1&another=2#cool"))
|
||||
test("isSimpleSlug", () => {
|
||||
assert(path.isSimpleSlug(""))
|
||||
assert(path.isSimpleSlug("abc"))
|
||||
assert(path.isSimpleSlug("abc/"))
|
||||
assert(path.isSimpleSlug("notindex"))
|
||||
assert(path.isSimpleSlug("notindex/def"))
|
||||
|
||||
assert(!path.isClientSlug("./"))
|
||||
assert(!path.isClientSlug(""))
|
||||
assert(!path.isClientSlug("ipfs://example.com"))
|
||||
assert(!path.isClientSlug("http"))
|
||||
assert(!path.isClientSlug("https"))
|
||||
})
|
||||
|
||||
test("isCanonicalSlug", () => {
|
||||
assert(path.isCanonicalSlug(""))
|
||||
assert(path.isCanonicalSlug("abc"))
|
||||
assert(path.isCanonicalSlug("notindex"))
|
||||
assert(path.isCanonicalSlug("notindex/def"))
|
||||
|
||||
assert(!path.isCanonicalSlug("//"))
|
||||
assert(!path.isCanonicalSlug("index"))
|
||||
assert(!path.isCanonicalSlug("https://example.com"))
|
||||
assert(!path.isCanonicalSlug("/abc"))
|
||||
assert(!path.isCanonicalSlug("abc/"))
|
||||
assert(!path.isCanonicalSlug("abc/index"))
|
||||
assert(!path.isCanonicalSlug("abc#anchor"))
|
||||
assert(!path.isCanonicalSlug("abc?query=1"))
|
||||
assert(!path.isCanonicalSlug("index.md"))
|
||||
assert(!path.isCanonicalSlug("index.html"))
|
||||
assert(!path.isSimpleSlug("//"))
|
||||
assert(!path.isSimpleSlug("index"))
|
||||
assert(!path.isSimpleSlug("https://example.com"))
|
||||
assert(!path.isSimpleSlug("/abc"))
|
||||
assert(!path.isSimpleSlug("abc/index"))
|
||||
assert(!path.isSimpleSlug("abc#anchor"))
|
||||
assert(!path.isSimpleSlug("abc?query=1"))
|
||||
assert(!path.isSimpleSlug("index.md"))
|
||||
assert(!path.isSimpleSlug("index.html"))
|
||||
})
|
||||
|
||||
test("isRelativeURL", () => {
|
||||
|
@ -58,18 +38,18 @@ describe("typeguards", () => {
|
|||
assert(!path.isRelativeURL("./abc/def.md"))
|
||||
})
|
||||
|
||||
test("isServerSlug", () => {
|
||||
assert(path.isServerSlug("index"))
|
||||
assert(path.isServerSlug("abc/def"))
|
||||
assert(path.isServerSlug("html.energy"))
|
||||
assert(path.isServerSlug("test.pdf"))
|
||||
test("isFullSlug", () => {
|
||||
assert(path.isFullSlug("index"))
|
||||
assert(path.isFullSlug("abc/def"))
|
||||
assert(path.isFullSlug("html.energy"))
|
||||
assert(path.isFullSlug("test.pdf"))
|
||||
|
||||
assert(!path.isServerSlug("."))
|
||||
assert(!path.isServerSlug("./abc/def"))
|
||||
assert(!path.isServerSlug("../abc/def"))
|
||||
assert(!path.isServerSlug("abc/def#anchor"))
|
||||
assert(!path.isServerSlug("abc/def?query=1"))
|
||||
assert(!path.isServerSlug("note with spaces"))
|
||||
assert(!path.isFullSlug("."))
|
||||
assert(!path.isFullSlug("./abc/def"))
|
||||
assert(!path.isFullSlug("../abc/def"))
|
||||
assert(!path.isFullSlug("abc/def#anchor"))
|
||||
assert(!path.isFullSlug("abc/def?query=1"))
|
||||
assert(!path.isFullSlug("note with spaces"))
|
||||
})
|
||||
|
||||
test("isFilePath", () => {
|
||||
|
@ -100,40 +80,17 @@ describe("transforms", () => {
|
|||
}
|
||||
}
|
||||
|
||||
test("canonicalizeServer", () => {
|
||||
test("simplifySlug", () => {
|
||||
asserts(
|
||||
[
|
||||
["index", ""],
|
||||
["abc/index", "abc"],
|
||||
["abc", "abc"],
|
||||
["abc/index", "abc/"],
|
||||
["abc/def", "abc/def"],
|
||||
],
|
||||
path.canonicalizeServer,
|
||||
path.isServerSlug,
|
||||
path.isCanonicalSlug,
|
||||
)
|
||||
})
|
||||
|
||||
test("canonicalizeClient", () => {
|
||||
asserts(
|
||||
[
|
||||
["http://localhost:3000", ""],
|
||||
["http://localhost:3000/index", ""],
|
||||
["http://localhost:3000/test", "test"],
|
||||
["http://example.com", ""],
|
||||
["http://example.com/index", ""],
|
||||
["http://example.com/index.html", ""],
|
||||
["http://example.com/", ""],
|
||||
["https://example.com", ""],
|
||||
["https://example.com/abc/def", "abc/def"],
|
||||
["https://example.com/abc/def/", "abc/def"],
|
||||
["https://example.com/abc/def#cool", "abc/def"],
|
||||
["https://example.com/abc/def?field=1&another=2", "abc/def"],
|
||||
["https://example.com/abc/def?field=1&another=2#cool", "abc/def"],
|
||||
["https://example.com/abc/def.html?field=1&another=2#cool", "abc/def"],
|
||||
],
|
||||
path.canonicalizeClient,
|
||||
path.isClientSlug,
|
||||
path.isCanonicalSlug,
|
||||
path.simplifySlug,
|
||||
path.isFullSlug,
|
||||
path.isSimpleSlug,
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -151,7 +108,7 @@ describe("transforms", () => {
|
|||
],
|
||||
path.slugifyFilePath,
|
||||
path.isFilePath,
|
||||
path.isServerSlug,
|
||||
path.isFullSlug,
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -186,12 +143,14 @@ describe("transforms", () => {
|
|||
test("pathToRoot", () => {
|
||||
asserts(
|
||||
[
|
||||
["", "."],
|
||||
["abc", ".."],
|
||||
["abc/def", "../.."],
|
||||
["index", "."],
|
||||
["abc", "."],
|
||||
["abc/def", ".."],
|
||||
["abc/def/ghi", "../.."],
|
||||
["abc/def/index", "../.."],
|
||||
],
|
||||
path.pathToRoot,
|
||||
path.isCanonicalSlug,
|
||||
path.isFullSlug,
|
||||
path.isRelativeURL,
|
||||
)
|
||||
})
|
||||
|
@ -206,7 +165,7 @@ describe("link strategies", () => {
|
|||
"e/g/h",
|
||||
"index",
|
||||
"a/test.png",
|
||||
] as ServerSlug[]
|
||||
] as FullSlug[]
|
||||
|
||||
describe("absolute", () => {
|
||||
const opts: TransformOptions = {
|
||||
|
@ -215,28 +174,28 @@ describe("link strategies", () => {
|
|||
}
|
||||
|
||||
test("from a/b/c", () => {
|
||||
const cur = "a/b/c" as CanonicalSlug
|
||||
assert.strictEqual(path.transformLink(cur, "a/b/d", opts), "../../../a/b/d")
|
||||
assert.strictEqual(path.transformLink(cur, "a/b/index", opts), "../../../a/b/")
|
||||
assert.strictEqual(path.transformLink(cur, "e/f", opts), "../../../e/f")
|
||||
assert.strictEqual(path.transformLink(cur, "e/g/h", opts), "../../../e/g/h")
|
||||
assert.strictEqual(path.transformLink(cur, "index", opts), "../../../")
|
||||
assert.strictEqual(path.transformLink(cur, "index.png", opts), "../../../index.png")
|
||||
assert.strictEqual(path.transformLink(cur, "index#abc", opts), "../../../#abc")
|
||||
assert.strictEqual(path.transformLink(cur, "tag/test", opts), "../../../tag/test")
|
||||
assert.strictEqual(path.transformLink(cur, "a/b/c#test", opts), "../../../a/b/c#test")
|
||||
assert.strictEqual(path.transformLink(cur, "a/test.png", opts), "../../../a/test.png")
|
||||
const cur = "a/b/c" as FullSlug
|
||||
assert.strictEqual(path.transformLink(cur, "a/b/d", opts), "../../a/b/d")
|
||||
assert.strictEqual(path.transformLink(cur, "a/b/index", opts), "../../a/b/")
|
||||
assert.strictEqual(path.transformLink(cur, "e/f", opts), "../../e/f")
|
||||
assert.strictEqual(path.transformLink(cur, "e/g/h", opts), "../../e/g/h")
|
||||
assert.strictEqual(path.transformLink(cur, "index", opts), "../../")
|
||||
assert.strictEqual(path.transformLink(cur, "index.png", opts), "../../index.png")
|
||||
assert.strictEqual(path.transformLink(cur, "index#abc", opts), "../../#abc")
|
||||
assert.strictEqual(path.transformLink(cur, "tag/test", opts), "../../tag/test")
|
||||
assert.strictEqual(path.transformLink(cur, "a/b/c#test", opts), "../../a/b/c#test")
|
||||
assert.strictEqual(path.transformLink(cur, "a/test.png", opts), "../../a/test.png")
|
||||
})
|
||||
|
||||
test("from a/b/index", () => {
|
||||
const cur = "a/b" as CanonicalSlug
|
||||
const cur = "a/b/index" as FullSlug
|
||||
assert.strictEqual(path.transformLink(cur, "a/b/d", opts), "../../a/b/d")
|
||||
assert.strictEqual(path.transformLink(cur, "a/b", opts), "../../a/b")
|
||||
assert.strictEqual(path.transformLink(cur, "index", opts), "../../")
|
||||
})
|
||||
|
||||
test("from index", () => {
|
||||
const cur = "" as CanonicalSlug
|
||||
const cur = "index" as FullSlug
|
||||
assert.strictEqual(path.transformLink(cur, "index", opts), "./")
|
||||
assert.strictEqual(path.transformLink(cur, "a/b/c", opts), "./a/b/c")
|
||||
assert.strictEqual(path.transformLink(cur, "a/b/index", opts), "./a/b/")
|
||||
|
@ -250,20 +209,20 @@ describe("link strategies", () => {
|
|||
}
|
||||
|
||||
test("from a/b/c", () => {
|
||||
const cur = "a/b/c" as CanonicalSlug
|
||||
assert.strictEqual(path.transformLink(cur, "d", opts), "../../../a/b/d")
|
||||
assert.strictEqual(path.transformLink(cur, "h", opts), "../../../e/g/h")
|
||||
assert.strictEqual(path.transformLink(cur, "a/b/index", opts), "../../../a/b/")
|
||||
assert.strictEqual(path.transformLink(cur, "a/b/index.png", opts), "../../../a/b/index.png")
|
||||
assert.strictEqual(path.transformLink(cur, "a/b/index#abc", opts), "../../../a/b/#abc")
|
||||
assert.strictEqual(path.transformLink(cur, "index", opts), "../../../")
|
||||
assert.strictEqual(path.transformLink(cur, "index.png", opts), "../../../index.png")
|
||||
assert.strictEqual(path.transformLink(cur, "test.png", opts), "../../../a/test.png")
|
||||
assert.strictEqual(path.transformLink(cur, "index#abc", opts), "../../../#abc")
|
||||
const cur = "a/b/c" as FullSlug
|
||||
assert.strictEqual(path.transformLink(cur, "d", opts), "../../a/b/d")
|
||||
assert.strictEqual(path.transformLink(cur, "h", opts), "../../e/g/h")
|
||||
assert.strictEqual(path.transformLink(cur, "a/b/index", opts), "../../a/b/")
|
||||
assert.strictEqual(path.transformLink(cur, "a/b/index.png", opts), "../../a/b/index.png")
|
||||
assert.strictEqual(path.transformLink(cur, "a/b/index#abc", opts), "../../a/b/#abc")
|
||||
assert.strictEqual(path.transformLink(cur, "index", opts), "../../")
|
||||
assert.strictEqual(path.transformLink(cur, "index.png", opts), "../../index.png")
|
||||
assert.strictEqual(path.transformLink(cur, "test.png", opts), "../../a/test.png")
|
||||
assert.strictEqual(path.transformLink(cur, "index#abc", opts), "../../#abc")
|
||||
})
|
||||
|
||||
test("from a/b/index", () => {
|
||||
const cur = "a/b" as CanonicalSlug
|
||||
const cur = "a/b/index" as FullSlug
|
||||
assert.strictEqual(path.transformLink(cur, "d", opts), "../../a/b/d")
|
||||
assert.strictEqual(path.transformLink(cur, "h", opts), "../../e/g/h")
|
||||
assert.strictEqual(path.transformLink(cur, "a/b/index", opts), "../../a/b/")
|
||||
|
@ -271,7 +230,7 @@ describe("link strategies", () => {
|
|||
})
|
||||
|
||||
test("from index", () => {
|
||||
const cur = "" as CanonicalSlug
|
||||
const cur = "index" as FullSlug
|
||||
assert.strictEqual(path.transformLink(cur, "d", opts), "./a/b/d")
|
||||
assert.strictEqual(path.transformLink(cur, "h", opts), "./e/g/h")
|
||||
assert.strictEqual(path.transformLink(cur, "a/b/index", opts), "./a/b/")
|
||||
|
@ -286,7 +245,7 @@ describe("link strategies", () => {
|
|||
}
|
||||
|
||||
test("from a/b/c", () => {
|
||||
const cur = "a/b/c" as CanonicalSlug
|
||||
const cur = "a/b/c" as FullSlug
|
||||
assert.strictEqual(path.transformLink(cur, "d", opts), "./d")
|
||||
assert.strictEqual(path.transformLink(cur, "index", opts), "./")
|
||||
assert.strictEqual(path.transformLink(cur, "../../../index", opts), "../../../")
|
||||
|
@ -303,7 +262,7 @@ describe("link strategies", () => {
|
|||
})
|
||||
|
||||
test("from a/b/index", () => {
|
||||
const cur = "a/b" as CanonicalSlug
|
||||
const cur = "a/b/index" as FullSlug
|
||||
assert.strictEqual(path.transformLink(cur, "../../index", opts), "../../")
|
||||
assert.strictEqual(path.transformLink(cur, "../../", opts), "../../")
|
||||
assert.strictEqual(path.transformLink(cur, "../../e/g/h", opts), "../../e/g/h")
|
||||
|
@ -311,7 +270,7 @@ describe("link strategies", () => {
|
|||
})
|
||||
|
||||
test("from index", () => {
|
||||
const cur = "" as CanonicalSlug
|
||||
const cur = "index" as FullSlug
|
||||
assert.strictEqual(path.transformLink(cur, "e/g/h", opts), "./e/g/h")
|
||||
assert.strictEqual(path.transformLink(cur, "a/b/index", opts), "./a/b/")
|
||||
})
|
||||
|
|
|
@ -1,73 +1,35 @@
|
|||
import { slug } from "github-slugger"
|
||||
// this file must be isomorphic so it can't use node libs (e.g. path)
|
||||
|
||||
// Quartz Paths
|
||||
// Things in boxes are not actual types but rather sources which these types can be acquired from
|
||||
//
|
||||
// ┌────────────┐
|
||||
// ┌───────────┤ Browser ├────────────┐
|
||||
// │ └────────────┘ │
|
||||
// │ │
|
||||
// ▼ ▼
|
||||
// ┌────────┐ ┌─────────────┐
|
||||
// ┌───────────────────┤ Window │ │ LinkElement │
|
||||
// │ └────┬───┘ └──────┬──────┘
|
||||
// │ │ │
|
||||
// │ getClientSlug() │ .href │
|
||||
// │ ▼ ▼
|
||||
// │
|
||||
// │ Client Slug ┌───► Relative URL
|
||||
// getCanonicalSlug() │ https://test.ca/note/abc#anchor?query=123 │ ../note/def#anchor
|
||||
// │ │
|
||||
// │ canonicalizeClient() │ │ ▲ ▲
|
||||
// │ ▼ │ │ │
|
||||
// │ pathToRoot() │ │ │
|
||||
// └───────────────► Canonical Slug ────────────────┘ │ │
|
||||
// note/abc │ │
|
||||
// ──────────────────────────┘ │
|
||||
// ▲ resolveRelative() │
|
||||
// canonicalizeServer() │ │
|
||||
// │
|
||||
// HTML File Server Slug │
|
||||
// note/abc/index.html ◄───────────── note/abc/index │
|
||||
// │
|
||||
// ▲ ┌────────┴────────┐
|
||||
// slugifyFilePath() │ transformLink() │ │
|
||||
// │ │ │
|
||||
// ┌─────────┴──────────┐ ┌─────┴─────┐ ┌────────┴──────┐
|
||||
// │ File Path │ │ Wikilinks │ │ Markdown Link │
|
||||
// │ note/abc/index.md │ └───────────┘ └───────────────┘
|
||||
// └────────────────────┘ ▲ ▲
|
||||
// ▲ │ │
|
||||
// │ ┌─────────┐ │ │
|
||||
// └────────────┤ MD File ├─────┴─────────────────┘
|
||||
// └─────────┘
|
||||
|
||||
export const QUARTZ = "quartz"
|
||||
|
||||
/// Utility type to simulate nominal types in TypeScript
|
||||
type SlugLike<T> = string & { __brand: T }
|
||||
|
||||
/** Client-side slug, usually obtained through `window.location` */
|
||||
export type ClientSlug = SlugLike<"client">
|
||||
export function isClientSlug(s: string): s is ClientSlug {
|
||||
const res = /^https?:\/\/.+/.test(s)
|
||||
return res
|
||||
/** Cannot be relative and must have a file extension. */
|
||||
export type FilePath = SlugLike<"filepath">
|
||||
export function isFilePath(s: string): s is FilePath {
|
||||
const validStart = !s.startsWith(".")
|
||||
return validStart && _hasFileExtension(s)
|
||||
}
|
||||
|
||||
/** Canonical slug, should be used whenever you need to refer to the location of a file/note.
|
||||
* On the client, this is normally stored in `document.body.dataset.slug`
|
||||
*/
|
||||
export type CanonicalSlug = SlugLike<"canonical">
|
||||
export function isCanonicalSlug(s: string): s is CanonicalSlug {
|
||||
/** Cannot be relative and may not have leading or trailing slashes. It can have `index` as it's last segment. Use this wherever possible is it's the most 'general' interpretation of a slug. */
|
||||
export type FullSlug = SlugLike<"full">
|
||||
export function isFullSlug(s: string): s is FullSlug {
|
||||
const validStart = !(s.startsWith(".") || s.startsWith("/"))
|
||||
const validEnding = !(s.endsWith("/") || s.endsWith("/index") || s === "index")
|
||||
const validEnding = !s.endsWith("/")
|
||||
return validStart && validEnding && !_containsForbiddenCharacters(s)
|
||||
}
|
||||
|
||||
/** 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 validEnding = !(s.endsWith("/index") || s === "index")
|
||||
return validStart && !_containsForbiddenCharacters(s) && validEnding && !_hasFileExtension(s)
|
||||
}
|
||||
|
||||
/** A relative link, can be found on `href`s but can also be constructed for
|
||||
* client-side navigation (e.g. search and graph)
|
||||
*/
|
||||
/** Can be found on `href`s but can also be constructed for client-side navigation (e.g. search and graph) */
|
||||
export type RelativeURL = SlugLike<"relative">
|
||||
export function isRelativeURL(s: string): s is RelativeURL {
|
||||
const validStart = /^\.{1,2}/.test(s)
|
||||
|
@ -75,46 +37,12 @@ export function isRelativeURL(s: string): s is RelativeURL {
|
|||
return validStart && validEnding && ![".md", ".html"].includes(_getFileExtension(s) ?? "")
|
||||
}
|
||||
|
||||
/** A server side slug. This is what Quartz uses to emit files so uses index suffixes */
|
||||
export type ServerSlug = SlugLike<"server">
|
||||
export function isServerSlug(s: string): s is ServerSlug {
|
||||
const validStart = !(s.startsWith(".") || s.startsWith("/"))
|
||||
const validEnding = !s.endsWith("/")
|
||||
return validStart && validEnding && !_containsForbiddenCharacters(s)
|
||||
}
|
||||
|
||||
/** The real file path to a file on disk */
|
||||
export type FilePath = SlugLike<"filepath">
|
||||
export function isFilePath(s: string): s is FilePath {
|
||||
const validStart = !s.startsWith(".")
|
||||
return validStart && _hasFileExtension(s)
|
||||
}
|
||||
|
||||
export function getClientSlug(window: Window): ClientSlug {
|
||||
const res = window.location.href as ClientSlug
|
||||
export function getFullSlug(window: Window): FullSlug {
|
||||
const res = window.document.body.dataset.slug! as FullSlug
|
||||
return res
|
||||
}
|
||||
|
||||
export function getCanonicalSlug(window: Window): CanonicalSlug {
|
||||
const res = window.document.body.dataset.slug! as CanonicalSlug
|
||||
return res
|
||||
}
|
||||
|
||||
export function canonicalizeClient(slug: ClientSlug): CanonicalSlug {
|
||||
const { pathname } = new URL(slug)
|
||||
let fp = pathname.slice(1)
|
||||
fp = fp.replace(new RegExp(_getFileExtension(fp) + "$"), "")
|
||||
const res = _canonicalize(fp) as CanonicalSlug
|
||||
return res
|
||||
}
|
||||
|
||||
export function canonicalizeServer(slug: ServerSlug): CanonicalSlug {
|
||||
let fp = slug as string
|
||||
const res = _canonicalize(fp) as CanonicalSlug
|
||||
return res
|
||||
}
|
||||
|
||||
export function slugifyFilePath(fp: FilePath, excludeExt?: boolean): ServerSlug {
|
||||
export function slugifyFilePath(fp: FilePath, excludeExt?: boolean): FullSlug {
|
||||
fp = _stripSlashes(fp) as FilePath
|
||||
let ext = _getFileExtension(fp)
|
||||
const withoutFileExt = fp.replace(new RegExp(ext + "$"), "")
|
||||
|
@ -133,44 +61,47 @@ export function slugifyFilePath(fp: FilePath, excludeExt?: boolean): ServerSlug
|
|||
slug = slug.replace(/_index$/, "index")
|
||||
}
|
||||
|
||||
return (slug + ext) as ServerSlug
|
||||
return (slug + ext) as FullSlug
|
||||
}
|
||||
|
||||
export function simplifySlug(fp: FullSlug): SimpleSlug {
|
||||
return _stripSlashes(_trimSuffix(fp, "index"), true) as SimpleSlug
|
||||
}
|
||||
|
||||
export function transformInternalLink(link: string): RelativeURL {
|
||||
let [fplike, anchor] = splitAnchor(decodeURI(link))
|
||||
|
||||
const folderPath =
|
||||
fplike.endsWith("index") ||
|
||||
fplike.endsWith("index.md") ||
|
||||
fplike.endsWith("index.html") ||
|
||||
fplike.endsWith("/")
|
||||
|
||||
const folderPath = _isFolderPath(fplike)
|
||||
let segments = fplike.split("/").filter((x) => x.length > 0)
|
||||
let prefix = segments.filter(_isRelativeSegment).join("/")
|
||||
let fp = segments.filter((seg) => !_isRelativeSegment(seg)).join("/")
|
||||
let fp = segments.filter((seg) => !_isRelativeSegment(seg) && seg !== "").join("/")
|
||||
|
||||
// manually add ext here as we want to not strip 'index' if it has an extension
|
||||
fp = canonicalizeServer(slugifyFilePath(fp as FilePath) as ServerSlug)
|
||||
const joined = joinSegments(_stripSlashes(prefix), _stripSlashes(fp))
|
||||
const simpleSlug = simplifySlug(slugifyFilePath(fp as FilePath))
|
||||
const joined = joinSegments(_stripSlashes(prefix), _stripSlashes(simpleSlug))
|
||||
const trail = folderPath ? "/" : ""
|
||||
const res = (_addRelativeToStart(joined) + trail + anchor) as RelativeURL
|
||||
return res
|
||||
}
|
||||
|
||||
// resolve /a/b/c to ../../..
|
||||
export function pathToRoot(slug: CanonicalSlug): RelativeURL {
|
||||
// resolve /a/b/c to ../..
|
||||
export function pathToRoot(slug: FullSlug): RelativeURL {
|
||||
let rootPath = slug
|
||||
.split("/")
|
||||
.filter((x) => x !== "")
|
||||
.slice(0, -1)
|
||||
.map((_) => "..")
|
||||
.join("/")
|
||||
|
||||
const res = _addRelativeToStart(rootPath) as RelativeURL
|
||||
return res
|
||||
if (rootPath.length === 0) {
|
||||
rootPath = "."
|
||||
}
|
||||
|
||||
return rootPath as RelativeURL
|
||||
}
|
||||
|
||||
export function resolveRelative(current: CanonicalSlug, target: CanonicalSlug): RelativeURL {
|
||||
const res = joinSegments(pathToRoot(current), target) as RelativeURL
|
||||
export function resolveRelative(current: FullSlug, target: FullSlug | SimpleSlug): RelativeURL {
|
||||
const res = joinSegments(pathToRoot(current), simplifySlug(target as FullSlug)) as RelativeURL
|
||||
return res
|
||||
}
|
||||
|
||||
|
@ -206,20 +137,16 @@ export function getAllSegmentPrefixes(tags: string): string[] {
|
|||
|
||||
export interface TransformOptions {
|
||||
strategy: "absolute" | "relative" | "shortest"
|
||||
allSlugs: ServerSlug[]
|
||||
allSlugs: FullSlug[]
|
||||
}
|
||||
|
||||
export function transformLink(
|
||||
src: CanonicalSlug,
|
||||
target: string,
|
||||
opts: TransformOptions,
|
||||
): RelativeURL {
|
||||
let targetSlug: string = transformInternalLink(target)
|
||||
export function transformLink(src: FullSlug, target: string, opts: TransformOptions): RelativeURL {
|
||||
let targetSlug = transformInternalLink(target)
|
||||
|
||||
if (opts.strategy === "relative") {
|
||||
return _addRelativeToStart(targetSlug) as RelativeURL
|
||||
return targetSlug as RelativeURL
|
||||
} else {
|
||||
const folderTail = targetSlug.endsWith("/") ? "/" : ""
|
||||
const folderTail = _isFolderPath(targetSlug) ? "/" : ""
|
||||
const canonicalSlug = _stripSlashes(targetSlug.slice(".".length))
|
||||
let [targetCanonical, targetAnchor] = splitAnchor(canonicalSlug)
|
||||
|
||||
|
@ -233,7 +160,7 @@ export function transformLink(
|
|||
|
||||
// only match, just use it
|
||||
if (matchingFileNames.length === 1) {
|
||||
const targetSlug = canonicalizeServer(matchingFileNames[0])
|
||||
const targetSlug = matchingFileNames[0]
|
||||
return (resolveRelative(src, targetSlug) + targetAnchor) as RelativeURL
|
||||
}
|
||||
}
|
||||
|
@ -243,9 +170,13 @@ export function transformLink(
|
|||
}
|
||||
}
|
||||
|
||||
function _canonicalize(fp: string): string {
|
||||
fp = _trimSuffix(fp, "index")
|
||||
return _stripSlashes(fp)
|
||||
function _isFolderPath(fplike: string): boolean {
|
||||
return (
|
||||
fplike.endsWith("/") ||
|
||||
_endsWith(fplike, "index") ||
|
||||
_endsWith(fplike, "index.md") ||
|
||||
_endsWith(fplike, "index.html")
|
||||
)
|
||||
}
|
||||
|
||||
function _endsWith(s: string, suffix: string): boolean {
|
||||
|
@ -275,12 +206,12 @@ function _isRelativeSegment(s: string): boolean {
|
|||
return /^\.{0,2}$/.test(s)
|
||||
}
|
||||
|
||||
export function _stripSlashes(s: string): string {
|
||||
export function _stripSlashes(s: string, onlyStripPrefix?: boolean): string {
|
||||
if (s.startsWith("/")) {
|
||||
s = s.substring(1)
|
||||
}
|
||||
|
||||
if (s.endsWith("/")) {
|
||||
if (!onlyStripPrefix && s.endsWith("/")) {
|
||||
s = s.slice(0, -1)
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue