Jacky Zhao
2023-08-17 2f6747b1666316e579c6e7238092ac6a65d00925
fix relative path resolution in router and link crawling
34 files modified
10 files renamed
266 ■■■■■ changed files
content/features/upcoming features.md 2 ●●●●● patch | view | raw | blame | history
content/hosting.md 5 ●●●● patch | view | raw | blame | history
package-lock.json 10 ●●●●● patch | view | raw | blame | history
package.json 3 ●●●● patch | view | raw | blame | history
quartz/bootstrap-cli.mjs 74 ●●●● patch | view | raw | blame | history
quartz/build.ts 12 ●●●● patch | view | raw | blame | history
quartz/components/Backlinks.tsx 2 ●●● patch | view | raw | blame | history
quartz/components/Head.tsx 4 ●●●● patch | view | raw | blame | history
quartz/components/PageList.tsx 2 ●●● patch | view | raw | blame | history
quartz/components/PageTitle.tsx 2 ●●● patch | view | raw | blame | history
quartz/components/TagList.tsx 2 ●●● patch | view | raw | blame | history
quartz/components/pages/FolderContent.tsx 2 ●●● patch | view | raw | blame | history
quartz/components/pages/TagContent.tsx 2 ●●● patch | view | raw | blame | history
quartz/components/renderPage.tsx 4 ●●●● patch | view | raw | blame | history
quartz/components/scripts/graph.inline.ts 2 ●●● patch | view | raw | blame | history
quartz/components/scripts/search.inline.ts 2 ●●● patch | view | raw | blame | history
quartz/components/scripts/spa.inline.ts 2 ●●● patch | view | raw | blame | history
quartz/plugins/emitters/aliases.ts 2 ●●● patch | view | raw | blame | history
quartz/plugins/emitters/assets.ts 4 ●●●● patch | view | raw | blame | history
quartz/plugins/emitters/componentResources.ts 8 ●●●● patch | view | raw | blame | history
quartz/plugins/emitters/contentIndex.ts 8 ●●●● patch | view | raw | blame | history
quartz/plugins/emitters/contentPage.tsx 2 ●●● patch | view | raw | blame | history
quartz/plugins/emitters/folderPage.tsx 8 ●●●● patch | view | raw | blame | history
quartz/plugins/emitters/static.ts 4 ●●●● patch | view | raw | blame | history
quartz/plugins/emitters/tagPage.tsx 4 ●●●● patch | view | raw | blame | history
quartz/plugins/index.ts 6 ●●●● patch | view | raw | blame | history
quartz/plugins/transformers/frontmatter.ts 2 ●●● patch | view | raw | blame | history
quartz/plugins/transformers/links.ts 2 ●●● patch | view | raw | blame | history
quartz/plugins/transformers/ofm.ts 6 ●●●● patch | view | raw | blame | history
quartz/plugins/types.ts 6 ●●●● patch | view | raw | blame | history
quartz/processors/emit.ts 10 ●●●● patch | view | raw | blame | history
quartz/processors/filter.ts 4 ●●●● patch | view | raw | blame | history
quartz/processors/parse.ts 10 ●●●● patch | view | raw | blame | history
quartz/util/ctx.ts 2 ●●● patch | view | raw | blame | history
quartz/util/glob.ts patch | view | raw | blame | history
quartz/util/log.ts patch | view | raw | blame | history
quartz/util/path.test.ts 27 ●●●● patch | view | raw | blame | history
quartz/util/path.ts 17 ●●●●● patch | view | raw | blame | history
quartz/util/perf.ts patch | view | raw | blame | history
quartz/util/resources.tsx patch | view | raw | blame | history
quartz/util/sourcemap.ts patch | view | raw | blame | history
quartz/util/theme.ts patch | view | raw | blame | history
quartz/util/trace.ts patch | view | raw | blame | history
quartz/worker.ts 2 ●●● patch | view | raw | blame | history
content/features/upcoming features.md
@@ -8,8 +8,6 @@
- debounce cfg rebuild on large repos
  - investigate content rebuild triggering multiple times even when debounced, causing an esbuild deadlock
- dereference symlink for npx quartz sync
- test/fix with subpath
- fix docs with deploy from github
## high priority backlog
content/hosting.md
@@ -80,7 +80,10 @@
        uses: actions/deploy-pages@v2
```
Then, commit these changes by doing `npx quartz sync`. This should deploy your site to `<github-username>.github.io/<repository-name>`.
Then:
1. Head to "Settings" tab of your forked repository and in the sidebar, click "Pages". Under "Source", select "GitHub Actions".
2. Commit these changes by doing `npx quartz sync`. This should deploy your site to `<github-username>.github.io/<repository-name>`.
### Custom Domain
package-lock.json
@@ -72,7 +72,6 @@
        "@types/js-yaml": "^4.0.5",
        "@types/node": "^20.1.2",
        "@types/pretty-time": "^1.1.2",
        "@types/serve-handler": "^6.1.1",
        "@types/source-map-support": "^0.5.6",
        "@types/workerpool": "^6.4.0",
        "@types/ws": "^8.5.5",
@@ -1478,15 +1477,6 @@
      "integrity": "sha512-4i+Y+O5H80Rh01lY/3Z0hB/UWc4R64ReE83joEpVsIG3iQWpYx66k6pQh1amJNZquKtJQyu/RcfkTtvL0KwssA==",
      "dev": true
    },
    "node_modules/@types/serve-handler": {
      "version": "6.1.1",
      "resolved": "https://registry.npmjs.org/@types/serve-handler/-/serve-handler-6.1.1.tgz",
      "integrity": "sha512-bIwSmD+OV8w0t2e7EWsuQYlGoS1o5aEdVktgkXaa43Zm0qVWi21xaSRb3DQA1UXD+DJ5bRq1Rgu14ZczB+CjIQ==",
      "dev": true,
      "dependencies": {
        "@types/node": "*"
      }
    },
    "node_modules/@types/source-map-support": {
      "version": "0.5.6",
      "resolved": "https://registry.npmjs.org/@types/source-map-support/-/source-map-support-0.5.6.tgz",
package.json
@@ -14,7 +14,7 @@
  "scripts": {
    "check": "tsc --noEmit && npx prettier . --check",
    "format": "npx prettier . --write",
    "test": "tsx ./quartz/path.test.ts",
    "test": "tsx ./quartz/util/path.test.ts",
    "profile": "0x -D prof ./quartz/bootstrap-cli.mjs build --concurrency=1"
  },
  "keywords": [
@@ -89,7 +89,6 @@
    "@types/js-yaml": "^4.0.5",
    "@types/node": "^20.1.2",
    "@types/pretty-time": "^1.1.2",
    "@types/serve-handler": "^6.1.1",
    "@types/source-map-support": "^0.5.6",
    "@types/workerpool": "^6.4.0",
    "@types/ws": "^8.5.5",
quartz/bootstrap-cli.mjs
@@ -74,6 +74,10 @@
    default: false,
    describe: "run a local server to live-preview your Quartz",
  },
  baseDir: {
    string: true,
    describe: "base path to serve your local server on",
  },
  port: {
    number: true,
    default: 8080,
@@ -384,19 +388,63 @@
      await build(clientRefresh)
      const server = http.createServer(async (req, res) => {
        await serveHandler(req, res, {
          public: argv.output,
          directoryListing: false,
          trailingSlash: true,
        })
        const status = res.statusCode
        const statusString =
          status >= 200 && status < 300
            ? chalk.green(`[${status}]`)
            : status >= 300 && status < 400
            ? chalk.yellow(`[${status}]`)
            : chalk.red(`[${status}]`)
        console.log(statusString + chalk.grey(` ${req.url}`))
        const serve = async (fp) => {
          await serveHandler(req, res, {
            public: argv.output,
            directoryListing: false,
          })
          const status = res.statusCode
          const statusString =
            status >= 200 && status < 300 ? chalk.green(`[${status}]`) : chalk.red(`[${status}]`)
          console.log(statusString + chalk.grey(` ${req.url}`))
        }
        const redirect = (newFp) => {
          res.writeHead(301, {
            Location: newFp,
          })
          console.log(chalk.yellow("[301]") + chalk.grey(` ${req.url} -> ${newFp}`))
          return res.end()
        }
        let fp = req.url?.split("?")[0] ?? "/"
        // handle redirects
        if (fp.endsWith("/")) {
          // /trailing/
          // does /trailing/index.html exist? if so, serve it
          const indexFp = path.posix.join(fp, "index.html")
          if (fs.existsSync(path.posix.join(argv.output, indexFp))) {
            return serve(indexFp)
          }
          // does /trailing.html exist? if so, redirect to /trailing
          let base = fp.slice(0, -1)
          if (path.extname(base) === "") {
            base += ".html"
          }
          if (fs.existsSync(path.posix.join(argv.output, base))) {
            return redirect(base)
          }
        } else {
          // /regular
          // does /regular.html exist? if so, serve it
          let base = fp
          if (path.extname(base) === "") {
            base += ".html"
          }
          if (fs.existsSync(path.posix.join(argv.output, base))) {
            return serve(base)
          }
          // does /regular/index.html exist? if so, redirect to /regular/
          let indexFp = path.posix.join(fp, "index.html")
          if (fs.existsSync(path.posix.join(argv.output, indexFp))) {
            return redirect(fp + "/")
          }
        }
        return serve(fp)
      })
      server.listen(argv.port)
      console.log(chalk.cyan(`Started a Quartz server listening at http://localhost:${argv.port}`))
quartz/build.ts
@@ -1,7 +1,7 @@
import sourceMapSupport from "source-map-support"
sourceMapSupport.install(options)
import path from "path"
import { PerfTimer } from "./perf"
import { PerfTimer } from "./util/perf"
import { rimraf } from "rimraf"
import { isGitIgnored } from "globby"
import chalk from "chalk"
@@ -9,13 +9,13 @@
import { filterContent } from "./processors/filter"
import { emitContent } from "./processors/emit"
import cfg from "../quartz.config"
import { FilePath, joinSegments, slugifyFilePath } from "./path"
import { FilePath, joinSegments, slugifyFilePath } from "./util/path"
import chokidar from "chokidar"
import { ProcessedContent } from "./plugins/vfile"
import { Argv, BuildCtx } from "./ctx"
import { glob, toPosixPath } from "./glob"
import { trace } from "./trace"
import { options } from "./sourcemap"
import { Argv, BuildCtx } from "./util/ctx"
import { glob, toPosixPath } from "./util/glob"
import { trace } from "./util/trace"
import { options } from "./util/sourcemap"
async function buildQuartz(argv: Argv, clientRefresh: () => void) {
  const ctx: BuildCtx = {
quartz/components/Backlinks.tsx
@@ -1,6 +1,6 @@
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import style from "./styles/backlinks.scss"
import { canonicalizeServer, resolveRelative } from "../path"
import { canonicalizeServer, resolveRelative } from "../util/path"
function Backlinks({ fileData, allFiles }: QuartzComponentProps) {
  const slug = canonicalizeServer(fileData.slug!)
quartz/components/Head.tsx
@@ -1,5 +1,5 @@
import { canonicalizeServer, pathToRoot } from "../path"
import { JSResourceToScriptElement } from "../resources"
import { canonicalizeServer, pathToRoot } from "../util/path"
import { JSResourceToScriptElement } from "../util/resources"
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
export default (() => {
quartz/components/PageList.tsx
@@ -1,4 +1,4 @@
import { CanonicalSlug, canonicalizeServer, resolveRelative } from "../path"
import { CanonicalSlug, canonicalizeServer, resolveRelative } from "../util/path"
import { QuartzPluginData } from "../plugins/vfile"
import { Date } from "./Date"
import { QuartzComponentProps } from "./types"
quartz/components/PageTitle.tsx
@@ -1,4 +1,4 @@
import { canonicalizeServer, pathToRoot } from "../path"
import { canonicalizeServer, pathToRoot } from "../util/path"
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
function PageTitle({ fileData, cfg }: QuartzComponentProps) {
quartz/components/TagList.tsx
@@ -1,4 +1,4 @@
import { canonicalizeServer, pathToRoot, slugTag } from "../path"
import { canonicalizeServer, pathToRoot, slugTag } from "../util/path"
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
function TagList({ fileData }: QuartzComponentProps) {
quartz/components/pages/FolderContent.tsx
@@ -5,7 +5,7 @@
import style from "../styles/listPage.scss"
import { PageList } from "../PageList"
import { canonicalizeServer } from "../../path"
import { canonicalizeServer } from "../../util/path"
function FolderContent(props: QuartzComponentProps) {
  const { tree, fileData, allFiles } = props
quartz/components/pages/TagContent.tsx
@@ -3,7 +3,7 @@
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
import style from "../styles/listPage.scss"
import { PageList } from "../PageList"
import { ServerSlug, canonicalizeServer, getAllSegmentPrefixes } from "../../path"
import { ServerSlug, canonicalizeServer, getAllSegmentPrefixes } from "../../util/path"
import { QuartzPluginData } from "../../plugins/vfile"
const numPages = 10
quartz/components/renderPage.tsx
@@ -2,8 +2,8 @@
import { QuartzComponent, QuartzComponentProps } from "./types"
import HeaderConstructor from "./Header"
import BodyConstructor from "./Body"
import { JSResourceToScriptElement, StaticResources } from "../resources"
import { CanonicalSlug, pathToRoot } from "../path"
import { JSResourceToScriptElement, StaticResources } from "../util/resources"
import { CanonicalSlug, pathToRoot } from "../util/path"
interface RenderComponents {
  head: QuartzComponent
quartz/components/scripts/graph.inline.ts
@@ -1,7 +1,7 @@
import type { ContentDetails } from "../../plugins/emitters/contentIndex"
import * as d3 from "d3"
import { registerEscapeHandler, removeAllChildren } from "./util"
import { CanonicalSlug, getCanonicalSlug, getClientSlug, resolveRelative } from "../../path"
import { CanonicalSlug, getCanonicalSlug, getClientSlug, resolveRelative } from "../../util/path"
type NodeData = {
  id: CanonicalSlug
quartz/components/scripts/search.inline.ts
@@ -1,7 +1,7 @@
import { Document } from "flexsearch"
import { ContentDetails } from "../../plugins/emitters/contentIndex"
import { registerEscapeHandler, removeAllChildren } from "./util"
import { CanonicalSlug, getClientSlug, resolveRelative } from "../../path"
import { CanonicalSlug, getClientSlug, resolveRelative } from "../../util/path"
interface Item {
  id: number
quartz/components/scripts/spa.inline.ts
@@ -1,5 +1,5 @@
import micromorph from "micromorph"
import { CanonicalSlug, RelativeURL, getCanonicalSlug } from "../../path"
import { CanonicalSlug, RelativeURL, getCanonicalSlug } from "../../util/path"
// adapted from `micromorph`
// https://github.com/natemoo-re/micromorph
quartz/plugins/emitters/aliases.ts
@@ -4,7 +4,7 @@
  ServerSlug,
  canonicalizeServer,
  resolveRelative,
} from "../../path"
} from "../../util/path"
import { QuartzEmitterPlugin } from "../types"
import path from "path"
quartz/plugins/emitters/assets.ts
@@ -1,8 +1,8 @@
import { FilePath, joinSegments, slugifyFilePath } from "../../path"
import { FilePath, joinSegments, slugifyFilePath } from "../../util/path"
import { QuartzEmitterPlugin } from "../types"
import path from "path"
import fs from "fs"
import { glob } from "../../glob"
import { glob } from "../../util/glob"
export const Assets: QuartzEmitterPlugin = () => {
  return {
quartz/plugins/emitters/componentResources.ts
@@ -1,4 +1,4 @@
import { FilePath, ServerSlug } from "../../path"
import { FilePath, ServerSlug } from "../../util/path"
import { QuartzEmitterPlugin } from "../types"
// @ts-ignore
@@ -9,10 +9,10 @@
import popoverScript from "../../components/scripts/popover.inline"
import styles from "../../styles/base.scss"
import popoverStyle from "../../components/styles/popover.scss"
import { BuildCtx } from "../../ctx"
import { StaticResources } from "../../resources"
import { BuildCtx } from "../../util/ctx"
import { StaticResources } from "../../util/resources"
import { QuartzComponent } from "../../components/types"
import { googleFontHref, joinStyles } from "../../theme"
import { googleFontHref, joinStyles } from "../../util/theme"
import { Features, transform } from "lightningcss"
type ComponentResources = {
quartz/plugins/emitters/contentIndex.ts
@@ -1,5 +1,11 @@
import { GlobalConfiguration } from "../../cfg"
import { CanonicalSlug, ClientSlug, FilePath, ServerSlug, canonicalizeServer } from "../../path"
import {
  CanonicalSlug,
  ClientSlug,
  FilePath,
  ServerSlug,
  canonicalizeServer,
} from "../../util/path"
import { QuartzEmitterPlugin } from "../types"
import path from "path"
quartz/plugins/emitters/contentPage.tsx
@@ -4,7 +4,7 @@
import BodyConstructor from "../../components/Body"
import { pageResources, renderPage } from "../../components/renderPage"
import { FullPageLayout } from "../../cfg"
import { FilePath, canonicalizeServer } from "../../path"
import { FilePath, canonicalizeServer } from "../../util/path"
import { defaultContentPageLayout, sharedPageComponents } from "../../../quartz.layout"
import { Content } from "../../components"
quartz/plugins/emitters/folderPage.tsx
@@ -6,7 +6,13 @@
import { ProcessedContent, defaultProcessedContent } from "../vfile"
import { FullPageLayout } from "../../cfg"
import path from "path"
import { CanonicalSlug, FilePath, ServerSlug, canonicalizeServer, joinSegments } from "../../path"
import {
  CanonicalSlug,
  FilePath,
  ServerSlug,
  canonicalizeServer,
  joinSegments,
} from "../../util/path"
import { defaultListPageLayout, sharedPageComponents } from "../../../quartz.layout"
import { FolderContent } from "../../components"
quartz/plugins/emitters/static.ts
@@ -1,7 +1,7 @@
import { FilePath, QUARTZ, joinSegments } from "../../path"
import { FilePath, QUARTZ, joinSegments } from "../../util/path"
import { QuartzEmitterPlugin } from "../types"
import fs from "fs"
import { glob } from "../../glob"
import { glob } from "../../util/glob"
export const Static: QuartzEmitterPlugin = () => ({
  name: "Static",
quartz/plugins/emitters/tagPage.tsx
@@ -11,7 +11,7 @@
  ServerSlug,
  getAllSegmentPrefixes,
  joinSegments,
} from "../../path"
} from "../../util/path"
import { defaultListPageLayout, sharedPageComponents } from "../../../quartz.layout"
import { TagContent } from "../../components"
@@ -41,7 +41,7 @@
        allFiles.flatMap((data) => data.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes),
      )
      // add base tag
      tags.add("")
      tags.add("index")
      const tagDescriptions: Record<string, ProcessedContent> = Object.fromEntries(
        [...tags].map((tag) => {
quartz/plugins/index.ts
@@ -1,6 +1,6 @@
import { StaticResources } from "../resources"
import { FilePath, ServerSlug } from "../path"
import { BuildCtx } from "../ctx"
import { StaticResources } from "../util/resources"
import { FilePath, ServerSlug } from "../util/path"
import { BuildCtx } from "../util/ctx"
export function getStaticResourcesFromPlugins(ctx: BuildCtx) {
  const staticResources: StaticResources = {
quartz/plugins/transformers/frontmatter.ts
@@ -2,7 +2,7 @@
import remarkFrontmatter from "remark-frontmatter"
import { QuartzTransformerPlugin } from "../types"
import yaml from "js-yaml"
import { slugTag } from "../../path"
import { slugTag } from "../../util/path"
export interface Options {
  delims: string | string[]
quartz/plugins/transformers/links.ts
@@ -8,7 +8,7 @@
  joinSegments,
  splitAnchor,
  transformLink,
} from "../../path"
} from "../../util/path"
import path from "path"
import { visit } from "unist-util-visit"
import isAbsoluteUrl from "is-absolute-url"
quartz/plugins/transformers/ofm.ts
@@ -6,10 +6,10 @@
import rehypeRaw from "rehype-raw"
import { visit } from "unist-util-visit"
import path from "path"
import { JSResource } from "../../resources"
import { JSResource } from "../../util/resources"
// @ts-ignore
import calloutScript from "../../components/scripts/callout.inline.ts"
import { FilePath, canonicalizeServer, pathToRoot, slugTag, slugifyFilePath } from "../../path"
import { FilePath, canonicalizeServer, pathToRoot, slugTag, slugifyFilePath } from "../../util/path"
import { toHast } from "mdast-util-to-hast"
import { toHtml } from "hast-util-to-html"
import { PhrasingContent } from "mdast-util-find-and-replace/lib"
@@ -294,7 +294,7 @@
              }
              const text = firstChild.children[0].value
              const restChildren = firstChild.children.splice(1)
              const restChildren = firstChild.children.slice(1)
              const [firstLine, ...remainingLines] = text.split("\n")
              const remainingText = remainingLines.join("\n")
quartz/plugins/types.ts
@@ -1,9 +1,9 @@
import { PluggableList } from "unified"
import { StaticResources } from "../resources"
import { StaticResources } from "../util/resources"
import { ProcessedContent } from "./vfile"
import { QuartzComponent } from "../components/types"
import { FilePath, ServerSlug } from "../path"
import { BuildCtx } from "../ctx"
import { FilePath, ServerSlug } from "../util/path"
import { BuildCtx } from "../util/ctx"
export interface PluginTypes {
  transformers: QuartzTransformerPluginInstance[]
quartz/processors/emit.ts
@@ -1,13 +1,13 @@
import path from "path"
import fs from "fs"
import { PerfTimer } from "../perf"
import { PerfTimer } from "../util/perf"
import { getStaticResourcesFromPlugins } from "../plugins"
import { EmitCallback } from "../plugins/types"
import { ProcessedContent } from "../plugins/vfile"
import { FilePath, joinSegments } from "../path"
import { QuartzLogger } from "../log"
import { trace } from "../trace"
import { BuildCtx } from "../ctx"
import { FilePath, joinSegments } from "../util/path"
import { QuartzLogger } from "../util/log"
import { trace } from "../util/trace"
import { BuildCtx } from "../util/ctx"
export async function emitContent(ctx: BuildCtx, content: ProcessedContent[]) {
  const { argv, cfg } = ctx
quartz/processors/filter.ts
@@ -1,5 +1,5 @@
import { BuildCtx } from "../ctx"
import { PerfTimer } from "../perf"
import { BuildCtx } from "../util/ctx"
import { PerfTimer } from "../util/perf"
import { ProcessedContent } from "../plugins/vfile"
export function filterContent(ctx: BuildCtx, content: ProcessedContent[]): ProcessedContent[] {
quartz/processors/parse.ts
@@ -5,14 +5,14 @@
import { Root as MDRoot } from "remark-parse/lib"
import { Root as HTMLRoot } from "hast"
import { ProcessedContent } from "../plugins/vfile"
import { PerfTimer } from "../perf"
import { PerfTimer } from "../util/perf"
import { read } from "to-vfile"
import { FilePath, QUARTZ, slugifyFilePath } from "../path"
import { FilePath, QUARTZ, slugifyFilePath } from "../util/path"
import path from "path"
import workerpool, { Promise as WorkerPromise } from "workerpool"
import { QuartzLogger } from "../log"
import { trace } from "../trace"
import { BuildCtx } from "../ctx"
import { QuartzLogger } from "../util/log"
import { trace } from "../util/trace"
import { BuildCtx } from "../util/ctx"
export type QuartzProcessor = Processor<MDRoot, HTMLRoot, void>
export function createProcessor(ctx: BuildCtx): QuartzProcessor {
quartz/util/ctx.ts
File was renamed from quartz/ctx.ts
@@ -1,4 +1,4 @@
import { QuartzConfig } from "./cfg"
import { QuartzConfig } from "../cfg"
import { ServerSlug } from "./path"
export interface Argv {
quartz/util/glob.ts
quartz/util/log.ts
quartz/util/path.test.ts
File was renamed from quartz/path.test.ts
@@ -53,8 +53,6 @@
    assert(!path.isRelativeURL("abc"))
    assert(!path.isRelativeURL("/abc/def"))
    assert(!path.isRelativeURL(""))
    assert(!path.isRelativeURL("../"))
    assert(!path.isRelativeURL("./"))
    assert(!path.isRelativeURL("./abc/def.html"))
    assert(!path.isRelativeURL("./abc/def.md"))
  })
@@ -160,17 +158,18 @@
      [
        ["", "."],
        [".", "."],
        ["./", "."],
        ["./index", "."],
        ["./index.html", "."],
        ["./index.md", "."],
        ["./", "./"],
        ["./index", "./"],
        ["./index.html", "./"],
        ["./index.md", "./"],
        ["content", "./content"],
        ["content/test.md", "./content/test"],
        ["./content/test.md", "./content/test"],
        ["../content/test.md", "../content/test"],
        ["tags/", "./tags"],
        ["/tags/", "./tags"],
        ["tags/", "./tags/"],
        ["/tags/", "./tags/"],
        ["content/with spaces", "./content/with-spaces"],
        ["content/with spaces/index", "./content/with-spaces/"],
        ["content/with spaces#and Anchor!", "./content/with-spaces#and-anchor"],
      ],
      path.transformInternalLink,
@@ -269,16 +268,16 @@
    test("from a/b/c", () => {
      const cur = "a/b/c" as CanonicalSlug
      assert.strictEqual(path.transformLink(cur, "d", opts), "./d")
      assert.strictEqual(path.transformLink(cur, "index", opts), ".")
      assert.strictEqual(path.transformLink(cur, "../../index", opts), "../..")
      assert.strictEqual(path.transformLink(cur, "../../", opts), "../..")
      assert.strictEqual(path.transformLink(cur, "index", opts), "./")
      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")
    })
    test("from a/b/index", () => {
      const cur = "a/b" as CanonicalSlug
      assert.strictEqual(path.transformLink(cur, "../../index", opts), "../..")
      assert.strictEqual(path.transformLink(cur, "../../", opts), "../..")
      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")
      assert.strictEqual(path.transformLink(cur, "c", opts), "./c")
    })
@@ -286,7 +285,7 @@
    test("from index", () => {
      const cur = "" as CanonicalSlug
      assert.strictEqual(path.transformLink(cur, "e/g/h", opts), "./e/g/h")
      assert.strictEqual(path.transformLink(cur, "a/b/index", opts), "./a/b")
      assert.strictEqual(path.transformLink(cur, "a/b/index", opts), "./a/b/")
    })
  })
})
quartz/util/path.ts
File was renamed from quartz/path.ts
@@ -71,7 +71,7 @@
export type RelativeURL = SlugLike<"relative">
export function isRelativeURL(s: string): s is RelativeURL {
  const validStart = /^\.{1,2}/.test(s)
  const validEnding = !(s.endsWith("/") || s.endsWith("/index") || s === "index")
  const validEnding = !(s.endsWith("/index") || s === "index")
  return validStart && validEnding && !_hasFileExtension(s)
}
@@ -133,6 +133,12 @@
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("/")
  let segments = fplike.split("/").filter((x) => x.length > 0)
  let prefix = segments.filter(_isRelativeSegment).join("/")
  let fp = segments.filter((seg) => !_isRelativeSegment(seg)).join("/")
@@ -143,14 +149,13 @@
  }
  fp = canonicalizeServer(slugifyFilePath(fp as FilePath))
  fp = _trimSuffix(fp, "index")
  let joined = joinSegments(_stripSlashes(prefix), _stripSlashes(fp))
  const res = (_addRelativeToStart(joined) + anchor) as RelativeURL
  const joined = joinSegments(_stripSlashes(prefix), _stripSlashes(fp))
  const trail = folderPath ? "/" : ""
  const res = (_addRelativeToStart(joined) + anchor + trail) as RelativeURL
  return res
}
// resolve /a/b/c to ../../
// resolve /a/b/c to ../../..
export function pathToRoot(slug: CanonicalSlug): RelativeURL {
  let rootPath = slug
    .split("/")
quartz/util/perf.ts
quartz/util/resources.tsx
quartz/util/sourcemap.ts
quartz/util/theme.ts
quartz/util/trace.ts
quartz/worker.ts
@@ -1,7 +1,7 @@
import sourceMapSupport from "source-map-support"
sourceMapSupport.install(options)
import cfg from "../quartz.config"
import { Argv, BuildCtx } from "./ctx"
import { Argv, BuildCtx } from "./util/ctx"
import { FilePath, ServerSlug } from "./path"
import { createFileParser, createProcessor } from "./processors/parse"
import { options } from "./sourcemap"