From 527ce6546e7ec50e7720ff7b9b6ff79a89c3b7fc Mon Sep 17 00:00:00 2001
From: Jacky Zhao <j.zhao2k19@gmail.com>
Date: Tue, 08 Aug 2023 04:41:18 +0000
Subject: [PATCH] various css fixes, fix new image loading bug when previewing, path docs

---
 content/migrating from Quartz 3.md |    1 
 quartz/build.ts                    |   20 +++++----
 quartz/styles/base.scss            |    4 +-
 quartz/plugins/transformers/ofm.ts |   14 ++++--
 content/advanced/paths.md          |   45 ++++++++++++++++++++++
 content/advanced/architecture.md   |    6 +-
 content/images/quartz-layout.png   |    0 
 7 files changed, 71 insertions(+), 19 deletions(-)

diff --git a/content/advanced/architecture.md b/content/advanced/architecture.md
index 31a9d7f..f30331c 100644
--- a/content/advanced/architecture.md
+++ b/content/advanced/architecture.md
@@ -12,17 +12,17 @@
 2. This file has a [shebang](<https://en.wikipedia.org/wiki/Shebang_(Unix)>) line at the top which tells npm to execute it using Node.
 3. `bootstrap-cli.mjs` is responsible for a few things:
    1. Parsing the command-line arguments using [yargs](http://yargs.js.org/).
-   2. Transpiling and bundling the rest of Quartz (which is in Typescript) to regular JavaScript using [esbuild](https://esbuild.github.io/). The `esbuild` configuration here is slightly special as it also handles `.scss` file imports using [esbuild-sass-plugin v2](https://www.npmjs.com/package/esbuild-sass-plugin). Additionally, we bundle 'inline' scripts (any `.inline.ts` file) that components can run client-side using a custom plugin that runs another instance of `esbuild` that bundles for browser instead of `node`. Both of these are imported as plain text.
+   2. Transpiling and bundling the rest of Quartz (which is in Typescript) to regular JavaScript using [esbuild](https://esbuild.github.io/). The `esbuild` configuration here is slightly special as it also handles `.scss` file imports using [esbuild-sass-plugin v2](https://www.npmjs.com/package/esbuild-sass-plugin). Additionally, we bundle 'inline' client-side scripts (any `.inline.ts` file) that components declare usiong a custom `esbuild` plugin that runs another instance of `esbuild` that bundles for the browser instead of `node`. Modules of both types are imported as plain text.
    3. Running the local preview server if `--serve` is set. This starts two servers:
       1. A WebSocket server on port 3001 to handle hot-reload signals. This tracks all inbound connections and sends a 'rebuild' message a server-side change is detected (either content or configuration).
       2. An HTTP file-server on a user defined port (normally 8080) to serve the actual website files.
-   4. Again, if the local preview server is running, it also starts a file watcher to detect source-code changes (e.g. anything that is `.ts`, `.tsx`, `.scss`, or packager files). On a change, we _rebuild_ the module (step 2 above) using esbuild's [rebuild API](https://esbuild.github.io/api/#rebuild) which drastically reduces the build times.
+   4. If the `--serve` flag is set, it also starts a file watcher to detect source-code changes (e.g. anything that is `.ts`, `.tsx`, `.scss`, or packager files). On a change, we rebuild the module (step 2 above) using esbuild's [rebuild API](https://esbuild.github.io/api/#rebuild) which drastically reduces the build times.
    5. After transpiling the main Quartz build module (`quartz/build.ts`), we write it to a cache file `.quartz-cache/transpiled-build.mjs` and then dynamically import this using `await import(cacheFile)`. However, we need to be pretty smart about how to bust Node's [import cache](https://github.com/nodejs/modules/issues/307) so we add a random query string to fake Node into thinking it's a new module. This does, however, cause memory leaks so we just hope that the user doesn't hot-reload their configuration too many times in a single session :)) (it leaks about ~350kB memory on each reload). After importing the module, we then invoke it, passing in the command line arguments we parsed earlier along with a callback function to signal the client to refresh.
 4. In `build.ts`, we start by installing source map support manually to account for the query string cache busting hack we introduced earlier. Then, we start processing content:
    1. Clean the output directory.
    2. Recursively glob all files in the `content` folder, respecting the `.gitignore`.
    3. Parse the Markdown files.
-      1. Quartz detects the number of threads available and chooses to spawn worker threads if there are >128 pieces of content to parse (rough heuristic). If it needs to spawn workers, it will do another esbuild transpile of the worker script `quartz/worker.ts`. Then, a work-stealing [workerpool](https://www.npmjs.com/package/workerpool) is then created and 'chunks' of 128 files are assigned to workers.
+      1. Quartz detects the number of threads available and chooses to spawn worker threads if there are >128 pieces of content to parse (rough heuristic). If it needs to spawn workers, it will invoke esbuild again to transpile the worker script `quartz/worker.ts`. Then, a work-stealing [workerpool](https://www.npmjs.com/package/workerpool) is then created and batches of 128 files are assigned to workers.
       2. Each worker (or just the main thread if there is no concurrency) creates a [unified](https://github.com/unifiedjs/unified) parser based off of the plugins defined in the [[configuration]].
       3. Parsing has three steps:
          1. Read the file into a [vfile](https://github.com/vfile/vfile).
diff --git a/content/advanced/paths.md b/content/advanced/paths.md
index e69de29..2a5e09f 100644
--- a/content/advanced/paths.md
+++ b/content/advanced/paths.md
@@ -0,0 +1,45 @@
+---
+title: Paths in Quartz
+---
+
+Paths are pretty complex to reason about because, especially for a static site generator, they can come from so many places.
+
+The current browser URL? Technically a path. A full file path to a piece of content? Also a path. What about a slug for a piece of content? Yet another path.
+
+It would be silly to type these all as `string` and call it a day as it's pretty common to accidentally mistake one type of path for another. Unfortunately, TypeScript does not have [nominal types](https://en.wikipedia.org/wiki/Nominal_type_system) for type aliases meaning even if you made custom types of a server-side slug or a client-slug slug, you can still accidentally assign one to another and TypeScript wouldn't catch it.
+
+Luckily, we can mimic nominal typing using [brands](https://www.typescriptlang.org/play#example/nominal-typing).
+
+```typescript
+// instead of 
+type ClientSlug = string
+
+// we do
+type ClientSlug = string & { __brand: "client" }
+
+// that way, the following will fail typechecking
+const slug: ClientSlug = "some random slug"
+```
+
+While this prevents most typing mistakes *within* our nominal typing system (e.g. mistaking a server slug for a client slug), it doesn't prevent us from *accidentally* mistaking a string for a client slug when we forcibly cast it.
+
+Thus, we still need to be careful when casting from a string to one of these nominal types in the 'entrypoints', illustrated with hexagon shapes in the diagram below.
+
+The following diagram draws the relationships between all the path sources, nominal path types, and what functions in `quartz/path.ts` convert between them.
+
+```mermaid
+graph LR
+    Browser{{Browser}} --> Window{{Window}} & LinkElement{{Link Element}}
+    Window --"getCanonicalSlug()"--> Canonical[Canonical Slug]
+    Window --"getClientSlug()"--> Client[Client Slug]
+    LinkElement --".href"--> Relative[Relative URL]
+    Client --"canonicalizeClient()"--> Canonical
+    Canonical --"pathToRoot()"--> Relative
+    Canonical --"resolveRelative()" --> Relative
+    MD{{Markdown File}} --> FilePath{{File Path}} & Links[Markdown links]
+    Links --"transformLink()"--> Relative
+    FilePath --"slugifyFilePath()"--> Server[Server Slug]
+    Server --> HTML["HTML File"]
+    Server --"canonicalizeServer()"--> Canonical
+    style Canonical stroke-width:4px
+```
diff --git a/content/images/quartz-layout.png b/content/images/quartz-layout.png
index 4767b54..03435f7 100644
--- a/content/images/quartz-layout.png
+++ b/content/images/quartz-layout.png
Binary files differ
diff --git a/content/migrating from Quartz 3.md b/content/migrating from Quartz 3.md
index 98a5b9a..d3feb3f 100644
--- a/content/migrating from Quartz 3.md
+++ b/content/migrating from Quartz 3.md
@@ -6,6 +6,7 @@
 
 ```bash
 git checkout v4-alpha
+git pull upstream v4-alpha
 npm i
 npx quartz create
 ```
diff --git a/quartz/build.ts b/quartz/build.ts
index ae5fd40..05edf86 100644
--- a/quartz/build.ts
+++ b/quartz/build.ts
@@ -23,7 +23,7 @@
 import { filterContent } from "./processors/filter"
 import { emitContent } from "./processors/emit"
 import cfg from "../quartz.config"
-import { FilePath, joinSegments, slugifyFilePath } from "./path"
+import { FilePath, ServerSlug, joinSegments, slugifyFilePath } from "./path"
 import chokidar from "chokidar"
 import { ProcessedContent } from "./plugins/vfile"
 import { Argv, BuildCtx } from "./ctx"
@@ -91,6 +91,7 @@
     contentMap.set(vfile.data.filePath!, content)
   }
 
+  const initialSlugs = ctx.allSlugs
   let timeoutId: ReturnType<typeof setTimeout> | null = null
   let toRebuild: Set<FilePath> = new Set()
   let toRemove: Set<FilePath> = new Set()
@@ -102,20 +103,19 @@
     }
 
     // dont bother rebuilding for non-content files, just track and refresh
+    fp = toPosixPath(fp)
+    const filePath = joinSegments(argv.directory, fp) as FilePath
     if (path.extname(fp) !== ".md") {
-      fp = toPosixPath(fp)
-      const filePath = joinSegments(argv.directory, fp) as FilePath
       if (action === "add" || action === "change") {
         trackedAssets.add(filePath)
       } else if (action === "delete") {
-        trackedAssets.add(filePath)
+        trackedAssets.delete(filePath)
       }
       clientRefresh()
       return
     }
 
-    fp = toPosixPath(fp)
-    const filePath = joinSegments(argv.directory, fp) as FilePath
+
     if (action === "add" || action === "change") {
       toRebuild.add(filePath)
     } else if (action === "delete") {
@@ -133,10 +133,12 @@
       try {
         const filesToRebuild = [...toRebuild].filter((fp) => !toRemove.has(fp))
 
-        ctx.allSlugs = [...new Set([...contentMap.keys(), ...toRebuild, ...trackedAssets])]
-          .filter((fp) => !toRemove.has(fp))
-          .map((fp) => slugifyFilePath(path.posix.relative(argv.directory, fp) as FilePath))
+        const trackedSlugs =
+          [...new Set([...contentMap.keys(), ...toRebuild, ...trackedAssets])]
+            .filter((fp) => !toRemove.has(fp))
+            .map((fp) => slugifyFilePath(path.posix.relative(argv.directory, fp) as FilePath))
 
+        ctx.allSlugs = [...new Set([...initialSlugs, ...trackedSlugs])]
         const parsedContent = await parseMarkdown(ctx, filesToRebuild)
         for (const content of parsedContent) {
           const [_tree, vfile] = content
diff --git a/quartz/plugins/transformers/ofm.ts b/quartz/plugins/transformers/ofm.ts
index 0904ed1..e7438aa 100644
--- a/quartz/plugins/transformers/ofm.ts
+++ b/quartz/plugins/transformers/ofm.ts
@@ -413,12 +413,16 @@
         js.push({
           script: `
           import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.mjs';
+          const darkMode = document.documentElement.getAttribute('saved-theme') === 'dark'
+          mermaid.initialize({ 
+            startOnLoad: false,
+            securityLevel: 'loose',
+            theme: darkMode ? 'dark' : 'default'
+          });
           document.addEventListener('nav', async () => {
-            const darkMode = document.documentElement.getAttribute('saved-theme') === 'dark'
-            mermaid.initialize({ 
-              securityLevel: 'loose',
-              theme: darkMode ? 'dark' : 'default'
-            });
+            await mermaid.run({
+              querySelector: '.mermaid'
+            })
           });
           `,
           loadTime: "afterDOMReady",
diff --git a/quartz/styles/base.scss b/quartz/styles/base.scss
index e219cc5..185582e 100644
--- a/quartz/styles/base.scss
+++ b/quartz/styles/base.scss
@@ -7,7 +7,7 @@
   scroll-behavior: smooth;
   -webkit-text-size-adjust: none;
   text-size-adjust: none;
-  overflow-x: none;
+  overflow-x: hidden;
   width: 100vw;
 }
 
@@ -311,10 +311,10 @@
   border-radius: 5px;
   overflow-x: auto;
   border: 1px solid var(--lightgray);
+  position: relative;
 
   &:has(> code.mermaid) {
     border: none;
-    position: relative;
   }
 
   & > code {

--
Gitblit v1.10.0