diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8c3b526..f0fc1fd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -26,7 +26,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 - name: Cache dependencies uses: actions/cache@v4 @@ -59,7 +59,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 - name: Get package version run: node -p -e '`PACKAGE_VERSION=${require("./package.json").version}`' >> $GITHUB_ENV - name: Create release tag diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..805b5a4 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +v20.9.0 diff --git a/docs/hosting.md b/docs/hosting.md index 8e9cb75..853e30f 100644 --- a/docs/hosting.md +++ b/docs/hosting.md @@ -61,6 +61,8 @@ jobs: with: fetch-depth: 0 # Fetch all history for git info - uses: actions/setup-node@v4 + with: + node-version: 22 - name: Install Dependencies run: npm ci - name: Build Quartz @@ -187,7 +189,7 @@ stages: - build - deploy -image: node:18 +image: node:20 cache: # Cache modules in between jobs key: $CI_COMMIT_REF_SLUG paths: diff --git a/docs/index.md b/docs/index.md index ed1eb28..e41c171 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,7 +6,7 @@ Quartz is a fast, batteries-included static-site generator that transforms Markd ## 🪴 Get Started -Quartz requires **at least [Node](https://nodejs.org/) v18.14** and `npm` v9.3.1 to function correctly. Ensure you have this installed on your machine before continuing. +Quartz requires **at least [Node](https://nodejs.org/) v20** and `npm` v9.3.1 to function correctly. Ensure you have this installed on your machine before continuing. Then, in your terminal of choice, enter the following commands line by line: diff --git a/docs/plugins/Latex.md b/docs/plugins/Latex.md index ac43678..236cbec 100644 --- a/docs/plugins/Latex.md +++ b/docs/plugins/Latex.md @@ -12,6 +12,7 @@ This plugin adds LaTeX support to Quartz. See [[features/Latex|Latex]] for more This plugin accepts the following configuration options: - `renderEngine`: the engine to use to render LaTeX equations. Can be `"katex"` for [KaTeX](https://katex.org/) or `"mathjax"` for [MathJax](https://www.mathjax.org/) [SVG rendering](https://docs.mathjax.org/en/latest/output/svg.html). Defaults to KaTeX. +- `customMacros`: custom macros for all LaTeX blocks. It takes the form of a key-value pair where the key is a new command name and the value is the expansion of the macro. For example: `{"\\R": "\\mathbb{R}"}` ## API diff --git a/docs/showcase.md b/docs/showcase.md index ea7e9d4..5fd7133 100644 --- a/docs/showcase.md +++ b/docs/showcase.md @@ -9,6 +9,7 @@ Want to see what Quartz can do? Here are some cool community gardens: - [Socratica Toolbox](https://toolbox.socratica.info/) - [Morrowind Modding Wiki](https://morrowind-modding.github.io/) - [Aaron Pham's Garden](https://aarnphm.xyz/) +- [Pelayo Arbues' Notes](https://pelayoarbues.com/) - [Stanford CME 302 Numerical Linear Algebra](https://ericdarve.github.io/NLA/) - [A Pattern Language - Christopher Alexander (Architecture)](https://patternlanguage.cc/) - [oldwinter の数字花园](https://garden.oldwinter.top/) @@ -23,5 +24,7 @@ Want to see what Quartz can do? Here are some cool community gardens: - [sspaeti.com's Second Brain](https://brain.sspaeti.com/) - [🪴Aster's notebook](https://notes.asterhu.com) - [Gatekeeper Wiki](https://www.gatekeeper.wiki) +- [Ellie's Notes](https://ellie.wtf) +- [🥷🏻🌳🍃 Computer Science & Thinkering Garden](https://notes.yxy.ninja) If you want to see your own on here, submit a [Pull Request adding yourself to this file](https://github.com/jackyzha0/quartz/blob/v4/docs/showcase.md)! diff --git a/globals.d.ts b/globals.d.ts index ee13005..6cf30f8 100644 --- a/globals.d.ts +++ b/globals.d.ts @@ -4,6 +4,10 @@ export declare global { type: K, listener: (this: Document, ev: CustomEventMap[K]) => void, ): void + removeEventListener( + type: K, + listener: (this: Document, ev: CustomEventMap[K]) => void, + ): void dispatchEvent(ev: CustomEventMap[K] | UIEvent): void } interface Window { diff --git a/package-lock.json b/package-lock.json index 17a6e31..23a553c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@jackyzha0/quartz", - "version": "4.2.4", + "version": "4.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@jackyzha0/quartz", - "version": "4.2.4", + "version": "4.3.0", "license": "MIT", "dependencies": { "@clack/prompts": "^0.7.0", @@ -85,7 +85,7 @@ "typescript": "^5.5.4" }, "engines": { - "node": ">=18.14", + "node": "20 || >=22", "npm": ">=9.3.1" } }, @@ -2434,9 +2434,9 @@ "integrity": "sha512-c5o/+Um8aqCSOXGcZoqZOm+NqtVwNsvVpWv6lfmSclU954O3wvQKxxK8zj74fPaSJbXpSLTs4PRhh+wnoCXnKg==" }, "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -2516,21 +2516,22 @@ "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" }, "node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -3176,14 +3177,14 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", + "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", "dependencies": { "@isaacs/cliui": "^8.0.2" }, "engines": { - "node": ">=14" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -3480,11 +3481,11 @@ } }, "node_modules/lru-cache": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", - "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", + "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==", "engines": { - "node": "14 || >=16.14" + "node": "20 || >=22" } }, "node_modules/markdown-table": { @@ -4532,23 +4533,23 @@ } }, "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "engines": { "node": ">=16 || 14 >=14.17" } @@ -4626,6 +4627,11 @@ "node": ">=0.10.0" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" + }, "node_modules/parse-entities": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", @@ -4702,15 +4708,15 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -5321,7 +5327,8 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.8.tgz", "integrity": "sha512-XSh0V2/yNhDEi8HwdIefD8MLgs4LQXPag/nEJWs3YUc3Upn+UHa1GyIkEg9xSSNt7HnkO5FjTvmcRzgf+8UZuw==", "dependencies": { - "glob": "^10.3.7" + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" }, "bin": { "rimraf": "dist/esm/bin.mjs" diff --git a/package.json b/package.json index a8f45ac..6fe94cc 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@jackyzha0/quartz", "description": "🌱 publish your digital garden and notes as a website", "private": true, - "version": "4.2.4", + "version": "4.3.0", "type": "module", "author": "jackyzha0 ", "license": "MIT", @@ -21,7 +21,7 @@ }, "engines": { "npm": ">=9.3.1", - "node": ">=18.14" + "node": "20 || >=22" }, "keywords": [ "site generator", diff --git a/quartz/build.ts b/quartz/build.ts index 972a7e8..342a27c 100644 --- a/quartz/build.ts +++ b/quartz/build.ts @@ -38,8 +38,13 @@ type BuildData = { type FileEvent = "add" | "change" | "delete" +function newBuildId() { + return new Date().toISOString() +} + async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) { const ctx: BuildCtx = { + buildId: newBuildId(), argv, cfg, allSlugs: [], @@ -167,6 +172,7 @@ async function partialRebuildFromEntrypoint( const perf = new PerfTimer() console.log(chalk.yellow("Detected change, rebuilding...")) + ctx.buildId = newBuildId() // UPDATE DEP GRAPH const fp = joinSegments(argv.directory, toPosixPath(filepath)) as FilePath @@ -363,14 +369,10 @@ async function rebuildFromEntrypoint( const perf = new PerfTimer() console.log(chalk.yellow("Detected change, rebuilding...")) + ctx.buildId = newBuildId() + try { const filesToRebuild = [...toRebuild].filter((fp) => !toRemove.has(fp)) - - 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 @@ -384,6 +386,13 @@ async function rebuildFromEntrypoint( const parsedFiles = [...contentMap.values()] const filteredContent = filterContent(ctx, parsedFiles) + // re-update slugs + 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])] + // TODO: we can probably traverse the link graph to figure out what's safe to delete here // instead of just deleting everything await rimraf(path.join(argv.output, ".*"), { glob: true }) diff --git a/quartz/components/Comments.tsx b/quartz/components/Comments.tsx index ac3813b..8e44940 100644 --- a/quartz/components/Comments.tsx +++ b/quartz/components/Comments.tsx @@ -1,4 +1,7 @@ import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" +import { classNames } from "../util/lang" +// @ts-ignore +import script from "./scripts/comments.inline" type Options = { provider: "giscus" @@ -19,49 +22,23 @@ function boolToStringBool(b: boolean): string { } export default ((opts: Options) => { - const Comments: QuartzComponent = (_props: QuartzComponentProps) =>
+ const Comments: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => { + return ( +
+ ) + } - Comments.afterDOMLoaded = ` - const changeTheme = (e) => { - const theme = e.detail.theme - const iframe = document.querySelector('iframe.giscus-frame') - if (!iframe) { - return - } - - iframe.contentWindow.postMessage({ - giscus: { - setConfig: { - theme: theme - } - } - }, 'https://giscus.app') - } - - document.addEventListener("nav", () => { - const giscusContainer = document.querySelector(".giscus") - const giscusScript = document.createElement("script") - giscusScript.src = "https://giscus.app/client.js" - giscusScript.async = true - giscusScript.crossOrigin = "anonymous" - giscusScript.setAttribute("data-loading", "lazy") - giscusScript.setAttribute("data-emit-metadata", "0") - giscusScript.setAttribute("data-repo", "${opts.options.repo}") - giscusScript.setAttribute("data-repo-id", "${opts.options.repoId}") - giscusScript.setAttribute("data-category", "${opts.options.category}") - giscusScript.setAttribute("data-category-id", "${opts.options.categoryId}") - giscusScript.setAttribute("data-mapping", "${opts.options.mapping ?? "url"}") - giscusScript.setAttribute("data-strict", "${boolToStringBool(opts.options.strict ?? true)}") - giscusScript.setAttribute("data-reactions-enabled", "${boolToStringBool(opts.options.reactionsEnabled ?? true)}") - giscusScript.setAttribute("data-input-position", "${opts.options.inputPosition ?? "bottom"}") - - const theme = document.documentElement.getAttribute("saved-theme") - giscusScript.setAttribute("data-theme", theme) - giscusContainer.appendChild(giscusScript) - - document.addEventListener("themechange", changeTheme) - window.addCleanup(() => document.removeEventListener("themechange", changeTheme)) - })` + Comments.afterDOMLoaded = script return Comments }) satisfies QuartzComponentConstructor diff --git a/quartz/components/Explorer.tsx b/quartz/components/Explorer.tsx index cffc079..ec7c48e 100644 --- a/quartz/components/Explorer.tsx +++ b/quartz/components/Explorer.tsx @@ -44,12 +44,9 @@ export default ((userOpts?: Partial) => { // memoized let fileTree: FileNode let jsonTree: string + let lastBuildId: string = "" function constructFileTree(allFiles: QuartzPluginData[]) { - if (fileTree) { - return - } - // Construct tree from allFiles fileTree = new FileNode("") allFiles.forEach((file) => fileTree.add(file)) @@ -76,12 +73,17 @@ export default ((userOpts?: Partial) => { } const Explorer: QuartzComponent = ({ + ctx, cfg, allFiles, displayClass, fileData, }: QuartzComponentProps) => { - constructFileTree(allFiles) + if (ctx.buildId !== lastBuildId) { + lastBuildId = ctx.buildId + constructFileTree(allFiles) + } + return (
+
-