From 579440597427b01860d16c8eaaacc4f815668710 Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Tue, 25 Feb 2025 22:27:10 +0100 Subject: [PATCH 001/134] feat(cli): absolute path support for -d flag (#1756) --- quartz/cli/handlers.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/quartz/cli/handlers.js b/quartz/cli/handlers.js index 6b23d8010..6ef380596 100644 --- a/quartz/cli/handlers.js +++ b/quartz/cli/handlers.js @@ -33,6 +33,15 @@ import { cwd, } from "./constants.js" +/** + * Resolve content directory path + * @param contentPath path to resolve + */ +function resolveContentPath(contentPath) { + if (path.isAbsolute(contentPath)) return path.relative(cwd, contentPath) + return path.join(cwd, contentPath) +} + /** * Handles `npx quartz create` * @param {*} argv arguments for `create` @@ -40,7 +49,7 @@ import { export async function handleCreate(argv) { console.log() intro(chalk.bgGreen.black(` Quartz v${version} `)) - const contentFolder = path.join(cwd, argv.directory) + const contentFolder = resolveContentPath(argv.directory) let setupStrategy = argv.strategy?.toLowerCase() let linkResolutionStrategy = argv.links?.toLowerCase() const sourceDirectory = argv.source @@ -450,7 +459,7 @@ export async function handleBuild(argv) { * @param {*} argv arguments for `update` */ export async function handleUpdate(argv) { - const contentFolder = path.join(cwd, argv.directory) + const contentFolder = resolveContentPath(argv.directory) console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`)) console.log("Backing up your content") execSync( @@ -502,7 +511,7 @@ export async function handleUpdate(argv) { * @param {*} argv arguments for `restore` */ export async function handleRestore(argv) { - const contentFolder = path.join(cwd, argv.directory) + const contentFolder = resolveContentPath(argv.directory) await popContentFolder(contentFolder) } @@ -511,7 +520,7 @@ export async function handleRestore(argv) { * @param {*} argv arguments for `sync` */ export async function handleSync(argv) { - const contentFolder = path.join(cwd, argv.directory) + const contentFolder = resolveContentPath(argv.directory) console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`)) console.log("Backing up your content") From 1a0264246913425808718733798f1f110e929cc6 Mon Sep 17 00:00:00 2001 From: Suamppa <18483942+Suamppa@users.noreply.github.com> Date: Tue, 25 Feb 2025 23:27:32 +0200 Subject: [PATCH 002/134] feat(i18n): add Finnish translations for i18n (#1782) --- quartz/i18n/index.ts | 2 + quartz/i18n/locales/fi-FI.ts | 84 ++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 quartz/i18n/locales/fi-FI.ts diff --git a/quartz/i18n/index.ts b/quartz/i18n/index.ts index 7c8996b50..2f7b9b20f 100644 --- a/quartz/i18n/index.ts +++ b/quartz/i18n/index.ts @@ -24,6 +24,7 @@ import cs from "./locales/cs-CZ" import tr from "./locales/tr-TR" import th from "./locales/th-TH" import lt from "./locales/lt-LT" +import fi from "./locales/fi-FI" export const TRANSLATIONS = { "en-US": enUs, @@ -72,6 +73,7 @@ export const TRANSLATIONS = { "tr-TR": tr, "th-TH": th, "lt-LT": lt, + "fi-FI": fi, } as const export const defaultTranslation = "en-US" diff --git a/quartz/i18n/locales/fi-FI.ts b/quartz/i18n/locales/fi-FI.ts new file mode 100644 index 000000000..f173afae4 --- /dev/null +++ b/quartz/i18n/locales/fi-FI.ts @@ -0,0 +1,84 @@ +import { Translation } from "./definition" + +export default { + propertyDefaults: { + title: "Nimetön", + description: "Ei kuvausta saatavilla", + }, + components: { + callout: { + note: "Merkintä", + abstract: "Tiivistelmä", + info: "Info", + todo: "Tehtävälista", + tip: "Vinkki", + success: "Onnistuminen", + question: "Kysymys", + warning: "Varoitus", + failure: "Epäonnistuminen", + danger: "Vaara", + bug: "Virhe", + example: "Esimerkki", + quote: "Lainaus", + }, + backlinks: { + title: "Takalinkit", + noBacklinksFound: "Takalinkkejä ei löytynyt", + }, + themeToggle: { + lightMode: "Vaalea tila", + darkMode: "Tumma tila", + }, + explorer: { + title: "Selain", + }, + footer: { + createdWith: "Luotu käyttäen", + }, + graph: { + title: "Verkkonäkymä", + }, + recentNotes: { + title: "Viimeisimmät muistiinpanot", + seeRemainingMore: ({ remaining }) => `Näytä ${remaining} lisää →`, + }, + transcludes: { + transcludeOf: ({ targetSlug }) => `Upote kohteesta ${targetSlug}`, + linkToOriginal: "Linkki alkuperäiseen", + }, + search: { + title: "Haku", + searchBarPlaceholder: "Hae jotain", + }, + tableOfContents: { + title: "Sisällysluettelo", + }, + contentMeta: { + readingTime: ({ minutes }) => `${minutes} min lukuaika`, + }, + }, + pages: { + rss: { + recentNotes: "Viimeisimmät muistiinpanot", + lastFewNotes: ({ count }) => `Viimeiset ${count} muistiinpanoa`, + }, + error: { + title: "Ei löytynyt", + notFound: "Tämä sivu on joko yksityinen tai sitä ei ole olemassa.", + home: "Palaa etusivulle", + }, + folderContent: { + folder: "Kansio", + itemsUnderFolder: ({ count }) => + count === 1 ? "1 kohde tässä kansiossa." : `${count} kohdetta tässä kansiossa.`, + }, + tagContent: { + tag: "Tunniste", + tagIndex: "Tunnisteluettelo", + itemsUnderTag: ({ count }) => + count === 1 ? "1 kohde tällä tunnisteella." : `${count} kohdetta tällä tunnisteella.`, + showingFirst: ({ count }) => `Näytetään ensimmäiset ${count} tunnistetta.`, + totalTags: ({ count }) => `Löytyi yhteensä ${count} tunnistetta.`, + }, + }, +} as const satisfies Translation From 29c533a265a380feb2877624cbbb8bff4ad66d34 Mon Sep 17 00:00:00 2001 From: Anton Bulakh Date: Tue, 25 Feb 2025 23:28:16 +0200 Subject: [PATCH 003/134] fix(aliases): wikilink resolution for aliases (#1681) With markdownLinkResolution: "shortest", aka "+/- how Obsidian does it" and given pages A and nested/B which has an alias Z, if you try to link from A using [[Z]] it wouldn't work and get 404. This is caused by alias slugs (nested/Z in this case, emitted by AliasRedirects) not being present in the `allSlugs` list which is used by the link transformer. The fix is to compute the alias slugs in the frontmatter transformer and add them to `allSlugs` there. Also we store them in file data to avoid recomputing them when emitting alias redirect pages. Fixes #904 Note: given how currently the markdown/html transformers are ordered this doesn't really work. Given pages A and nested/B which has an alias Z, here's the order which currently happens: md-transformers(A) => html-transformers(A) => md-transformers(B) => html-transformers(B) Since the nested/Z slug will get added when md-transformers(B) are run, but the slugs are used by html-transformers(A) when resolving it's links - the link [[Z]] in A will still 404 A fix for this is to split the parser into two stages - first apply the md-transformers to all files, and only then apply html-transformers to all files. I did just that in a different commit, which is needed for this one to work correctly. --- quartz/plugins/emitters/aliases.ts | 33 +++------------------- quartz/plugins/transformers/frontmatter.ts | 29 +++++++++++++++++-- 2 files changed, 30 insertions(+), 32 deletions(-) diff --git a/quartz/plugins/emitters/aliases.ts b/quartz/plugins/emitters/aliases.ts index af3578ebe..b5bfff061 100644 --- a/quartz/plugins/emitters/aliases.ts +++ b/quartz/plugins/emitters/aliases.ts @@ -1,8 +1,8 @@ -import { FilePath, FullSlug, joinSegments, resolveRelative, simplifySlug } from "../../util/path" +import { FilePath, joinSegments, resolveRelative, simplifySlug } from "../../util/path" import { QuartzEmitterPlugin } from "../types" -import path from "path" import { write } from "./helpers" import DepGraph from "../../depgraph" +import { getAliasSlugs } from "../transformers/frontmatter" export const AliasRedirects: QuartzEmitterPlugin = () => ({ name: "AliasRedirects", @@ -14,20 +14,7 @@ export const AliasRedirects: QuartzEmitterPlugin = () => ({ const { argv } = ctx for (const [_tree, file] of content) { - const dir = path.posix.relative(argv.directory, path.dirname(file.data.filePath!)) - const aliases = file.data.frontmatter?.aliases ?? [] - const slugs = aliases.map((alias) => path.posix.join(dir, alias) as FullSlug) - const permalink = file.data.frontmatter?.permalink - if (typeof permalink === "string") { - slugs.push(permalink as FullSlug) - } - - for (let slug of slugs) { - // fix any slugs that have trailing slash - if (slug.endsWith("/")) { - slug = joinSegments(slug, "index") as FullSlug - } - + for (const slug of getAliasSlugs(file.data.frontmatter?.aliases ?? [], argv, file)) { graph.addEdge(file.data.filePath!, joinSegments(argv.output, slug + ".html") as FilePath) } } @@ -40,20 +27,8 @@ export const AliasRedirects: QuartzEmitterPlugin = () => ({ for (const [_tree, file] of content) { const ogSlug = simplifySlug(file.data.slug!) - const dir = path.posix.relative(argv.directory, path.dirname(file.data.filePath!)) - const aliases = file.data.frontmatter?.aliases ?? [] - const slugs: FullSlug[] = aliases.map((alias) => path.posix.join(dir, alias) as FullSlug) - const permalink = file.data.frontmatter?.permalink - if (typeof permalink === "string") { - slugs.push(permalink as FullSlug) - } - - for (let slug of slugs) { - // fix any slugs that have trailing slash - if (slug.endsWith("/")) { - slug = joinSegments(slug, "index") as FullSlug - } + for (const slug of file.data.aliases ?? []) { const redirUrl = resolveRelative(slug, file.data.slug!) const fp = await write({ ctx, diff --git a/quartz/plugins/transformers/frontmatter.ts b/quartz/plugins/transformers/frontmatter.ts index e00c700e0..625cf607a 100644 --- a/quartz/plugins/transformers/frontmatter.ts +++ b/quartz/plugins/transformers/frontmatter.ts @@ -3,9 +3,12 @@ import remarkFrontmatter from "remark-frontmatter" import { QuartzTransformerPlugin } from "../types" import yaml from "js-yaml" import toml from "toml" -import { slugTag } from "../../util/path" +import { FilePath, FullSlug, joinSegments, slugifyFilePath, slugTag } from "../../util/path" import { QuartzPluginData } from "../vfile" import { i18n } from "../../i18n" +import { Argv } from "../../util/ctx" +import { VFile } from "vfile" +import path from "path" export interface Options { delimiters: string | [string, string] @@ -40,11 +43,26 @@ function coerceToArray(input: string | string[]): string[] | undefined { .map((tag: string | number) => tag.toString()) } +export function getAliasSlugs(aliases: string[], argv: Argv, file: VFile): FullSlug[] { + const dir = path.posix.relative(argv.directory, path.dirname(file.data.filePath!)) + const slugs: FullSlug[] = aliases.map( + (alias) => path.posix.join(dir, slugifyFilePath(alias as FilePath)) as FullSlug, + ) + const permalink = file.data.frontmatter?.permalink + if (typeof permalink === "string") { + slugs.push(permalink as FullSlug) + } + // fix any slugs that have trailing slash + return slugs.map((slug) => + slug.endsWith("/") ? (joinSegments(slug, "index") as FullSlug) : slug, + ) +} + export const FrontMatter: QuartzTransformerPlugin> = (userOpts) => { const opts = { ...defaultOptions, ...userOpts } return { name: "FrontMatter", - markdownPlugins({ cfg }) { + markdownPlugins({ cfg, allSlugs, argv }) { return [ [remarkFrontmatter, ["yaml", "toml"]], () => { @@ -67,7 +85,11 @@ export const FrontMatter: QuartzTransformerPlugin> = (userOpts) if (tags) data.tags = [...new Set(tags.map((tag: string) => slugTag(tag)))] const aliases = coerceToArray(coalesceAliases(data, ["aliases", "alias"])) - if (aliases) data.aliases = aliases + if (aliases) { + data.aliases = aliases // frontmatter + const slugs = (file.data.aliases = getAliasSlugs(aliases, argv, file)) + allSlugs.push(...slugs) + } const cssclasses = coerceToArray(coalesceAliases(data, ["cssclasses", "cssclass"])) if (cssclasses) data.cssclasses = cssclasses @@ -98,6 +120,7 @@ export const FrontMatter: QuartzTransformerPlugin> = (userOpts) declare module "vfile" { interface DataMap { + aliases: FullSlug[] frontmatter: { [key: string]: unknown } & { title: string } & Partial<{ From 598741a5715834d22bb9621080b4558c1657419a Mon Sep 17 00:00:00 2001 From: Daksh <100519702+dxvsh@users.noreply.github.com> Date: Wed, 26 Feb 2025 03:01:41 +0530 Subject: [PATCH 004/134] fix(fonts): update system-ui trick for improve cross-platform compatibility (#1778) --- quartz/util/theme.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quartz/util/theme.ts b/quartz/util/theme.ts index 9046cec66..0c903066f 100644 --- a/quartz/util/theme.ts +++ b/quartz/util/theme.ts @@ -29,7 +29,7 @@ export interface Theme { export type ThemeKey = keyof Colors const DEFAULT_SANS_SERIF = - '-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif' + 'system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"' const DEFAULT_MONO = "ui-monospace, SFMono-Regular, SF Mono, Menlo, monospace" export function googleFontHref(theme: Theme) { From 9a0d919a3b1730adb1f02e4a26eaad38bf71f408 Mon Sep 17 00:00:00 2001 From: Jonathan Fung <54459292+jonathanmfung@users.noreply.github.com> Date: Tue, 25 Feb 2025 13:34:37 -0800 Subject: [PATCH 005/134] docs: add Citation plugin & feature pages (#1772) * docs: add Citation plugin & feature pages Discussed and extracted from #1557 * Update docs/features/Citations.md * Apply suggestions from code review --------- Co-authored-by: Aaron Pham --- docs/features/Citations.md | 31 +++++++++++++++++++++++++++++++ docs/plugins/Citations.md | 24 ++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 docs/features/Citations.md create mode 100644 docs/plugins/Citations.md diff --git a/docs/features/Citations.md b/docs/features/Citations.md new file mode 100644 index 000000000..741590b0d --- /dev/null +++ b/docs/features/Citations.md @@ -0,0 +1,31 @@ +--- +title: Citations +tags: + - feature/transformer +--- + +Quartz uses [rehype-citation](https://github.com/timlrx/rehype-citation) to support parsing of a BibTex bibliography file. + +Under the default configuration, a citation key `[@templeton2024scaling]` will be exported as `(Templeton et al., 2024)`. + +> [!example]- BibTex file +> +> ```bib title="bibliography.bib" +> @article{templeton2024scaling, +> title={Scaling Monosemanticity: Extracting Interpretable Features from Claude 3 Sonnet}, +> author={Templeton, Adly and Conerly, Tom and Marcus, Jonathan and Lindsey, Jack and Bricken, Trenton and Chen, Brian and Pearce, Adam and Citro, Craig and Ameisen, Emmanuel and Jones, Andy and Cunningham, Hoagy and Turner, Nicholas L and McDougall, Callum and MacDiarmid, Monte and Freeman, C. Daniel and Sumers, Theodore R. and Rees, Edward and Batson, Joshua and Jermyn, Adam and Carter, Shan and Olah, Chris and Henighan, Tom}, +> year={2024}, +> journal={Transformer Circuits Thread}, +> url={https://transformer-circuits.pub/2024/scaling-monosemanticity/index.html} +> } +> ``` + +> [!note] Behaviour of references +> +> By default, the references will be included at the end of the file. To control where the references to be included, uses `[^ref]` +> +> Refer to `rehype-citation` docs for more information. + +## Customization + +Citation parsing is a functionality of the [[plugins/Citations|Citation]] plugin. **This plugin is not enabled by default**. See the plugin page for customization options. diff --git a/docs/plugins/Citations.md b/docs/plugins/Citations.md new file mode 100644 index 000000000..b1a63a037 --- /dev/null +++ b/docs/plugins/Citations.md @@ -0,0 +1,24 @@ +--- +title: "Citations" +tags: + - plugin/transformer +--- + +This plugin adds Citation support to Quartz. + +> [!note] +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. + +This plugin accepts the following configuration options: + +- `bibliographyFile`: the path to the bibliography file. Defaults to `./bibliography.bib`. This is relative to git source of your vault. +- `suppressBibliography`: whether to suppress the bibliography at the end of the document. Defaults to `false`. +- `linkCitations`: whether to link citations to the bibliography. Defaults to `false`. +- `csl`: the citation style to use. Defaults to `apa`. Reference [rehype-citation](https://rehype-citation.netlify.app/custom-csl) for more options. +- `prettyLink`: whether to use pretty links for citations. Defaults to `true`. + +## API + +- Category: Transformer +- Function name: `Plugin.Citations()`. +- Source: [`quartz/plugins/transformers/citations.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/citations.ts). From 28fe9b2095d078fb464e46791265c4bfac7b11c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Feb 2025 16:35:10 -0500 Subject: [PATCH 006/134] chore(deps): bump sigstore/cosign-installer in the ci-dependencies group (#1789) Bumps the ci-dependencies group with 1 update: [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer). Updates `sigstore/cosign-installer` from 3.8.0 to 3.8.1 - [Release notes](https://github.com/sigstore/cosign-installer/releases) - [Commits](https://github.com/sigstore/cosign-installer/compare/v3.8.0...v3.8.1) --- updated-dependencies: - dependency-name: sigstore/cosign-installer dependency-type: direct:production update-type: version-update:semver-patch dependency-group: ci-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker-build-push.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-build-push.yaml b/.github/workflows/docker-build-push.yaml index ca747ac28..5116a73c6 100644 --- a/.github/workflows/docker-build-push.yaml +++ b/.github/workflows/docker-build-push.yaml @@ -37,7 +37,7 @@ jobs: network=host - name: Install cosign if: github.event_name != 'pull_request' - uses: sigstore/cosign-installer@v3.8.0 + uses: sigstore/cosign-installer@v3.8.1 - name: Login to GitHub Container Registry uses: docker/login-action@v3 if: github.event_name != 'pull_request' From 4901472aa88e817d3eb304b7b5e677bba79cd5cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Feb 2025 16:52:52 -0500 Subject: [PATCH 007/134] chore(deps): bump the production-dependencies group with 8 updates (#1788) * chore(deps): bump the production-dependencies group with 8 updates Bumps the production-dependencies group with 8 updates: | Package | From | To | | --- | --- | --- | | [hast-util-to-html](https://github.com/syntax-tree/hast-util-to-html) | `9.0.4` | `9.0.5` | | [hast-util-to-jsx-runtime](https://github.com/syntax-tree/hast-util-to-jsx-runtime) | `2.3.2` | `2.3.4` | | [preact](https://github.com/preactjs/preact) | `10.26.0` | `10.26.2` | | [rehype-mathjax](https://github.com/remarkjs/remark-math) | `7.0.0` | `7.1.0` | | [ws](https://github.com/websockets/ws) | `8.18.0` | `8.18.1` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `22.13.4` | `22.13.5` | | [prettier](https://github.com/prettier/prettier) | `3.5.1` | `3.5.2` | | [tsx](https://github.com/privatenumber/tsx) | `4.19.2` | `4.19.3` | Updates `hast-util-to-html` from 9.0.4 to 9.0.5 - [Release notes](https://github.com/syntax-tree/hast-util-to-html/releases) - [Commits](https://github.com/syntax-tree/hast-util-to-html/compare/9.0.4...9.0.5) Updates `hast-util-to-jsx-runtime` from 2.3.2 to 2.3.4 - [Release notes](https://github.com/syntax-tree/hast-util-to-jsx-runtime/releases) - [Commits](https://github.com/syntax-tree/hast-util-to-jsx-runtime/compare/2.3.2...2.3.4) Updates `preact` from 10.26.0 to 10.26.2 - [Release notes](https://github.com/preactjs/preact/releases) - [Commits](https://github.com/preactjs/preact/compare/10.26.0...10.26.2) Updates `rehype-mathjax` from 7.0.0 to 7.1.0 - [Release notes](https://github.com/remarkjs/remark-math/releases) - [Commits](https://github.com/remarkjs/remark-math/compare/rehype-mathjax@7.0.0...rehype-mathjax@7.1.0) Updates `ws` from 8.18.0 to 8.18.1 - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/8.18.0...8.18.1) Updates `@types/node` from 22.13.4 to 22.13.5 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `prettier` from 3.5.1 to 3.5.2 - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.5.1...3.5.2) Updates `tsx` from 4.19.2 to 4.19.3 - [Release notes](https://github.com/privatenumber/tsx/releases) - [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs) - [Commits](https://github.com/privatenumber/tsx/compare/v4.19.2...v4.19.3) --- updated-dependencies: - dependency-name: hast-util-to-html dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: hast-util-to-jsx-runtime dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: preact dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: rehype-mathjax dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: ws dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: prettier dependency-type: direct:development update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: tsx dependency-type: direct:development update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] * chore: update type error Signed-off-by: Aaron Pham --------- Signed-off-by: dependabot[bot] Signed-off-by: Aaron Pham Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Aaron Pham --- package-lock.json | 520 +++------------------- package.json | 16 +- quartz/components/pages/Content.tsx | 3 +- quartz/components/pages/FolderContent.tsx | 4 +- quartz/components/pages/TagContent.tsx | 4 +- 5 files changed, 77 insertions(+), 470 deletions(-) diff --git a/package-lock.json b/package-lock.json index f1a4a68bf..7f8944cba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,8 +24,8 @@ "github-slugger": "^2.0.0", "globby": "^14.1.0", "gray-matter": "^4.0.3", - "hast-util-to-html": "^9.0.4", - "hast-util-to-jsx-runtime": "^2.3.2", + "hast-util-to-html": "^9.0.5", + "hast-util-to-jsx-runtime": "^2.3.4", "hast-util-to-string": "^3.0.1", "is-absolute-url": "^4.0.1", "js-yaml": "^4.1.0", @@ -35,7 +35,7 @@ "mdast-util-to-string": "^4.0.0", "micromorph": "^0.4.5", "pixi.js": "^8.8.0", - "preact": "^10.26.0", + "preact": "^10.26.2", "preact-render-to-string": "^6.5.13", "pretty-bytes": "^6.1.1", "pretty-time": "^1.1.0", @@ -43,7 +43,7 @@ "rehype-autolink-headings": "^7.1.0", "rehype-citation": "^2.2.2", "rehype-katex": "^7.0.1", - "rehype-mathjax": "^7.0.0", + "rehype-mathjax": "^7.1.0", "rehype-pretty-code": "^0.14.0", "rehype-raw": "^7.0.0", "rehype-slug": "^6.0.0", @@ -68,7 +68,7 @@ "unist-util-visit": "^5.0.0", "vfile": "^6.0.3", "workerpool": "^9.2.0", - "ws": "^8.18.0", + "ws": "^8.18.1", "yargs": "^17.7.2" }, "bin": { @@ -79,14 +79,14 @@ "@types/d3": "^7.4.3", "@types/hast": "^3.0.4", "@types/js-yaml": "^4.0.9", - "@types/node": "^22.13.4", + "@types/node": "^22.13.5", "@types/pretty-time": "^1.1.5", "@types/source-map-support": "^0.5.10", "@types/ws": "^8.5.14", "@types/yargs": "^17.0.33", "esbuild": "^0.25.0", - "prettier": "^3.5.1", - "tsx": "^4.19.2", + "prettier": "^3.5.2", + "tsx": "^4.19.3", "typescript": "^5.7.3" }, "engines": { @@ -510,22 +510,6 @@ "node": ">=18" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz", - "integrity": "sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/openbsd-x64": { "version": "0.25.0", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", @@ -1932,9 +1916,9 @@ } }, "node_modules/@types/node": { - "version": "22.13.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz", - "integrity": "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==", + "version": "22.13.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.5.tgz", + "integrity": "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==", "dev": true, "license": "MIT", "dependencies": { @@ -3519,9 +3503,10 @@ "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" }, "node_modules/hast-util-to-html": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.4.tgz", - "integrity": "sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", @@ -3530,7 +3515,7 @@ "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", - "property-information": "^6.0.0", + "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" @@ -3545,10 +3530,21 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" }, + "node_modules/hast-util-to-html/node_modules/property-information": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.0.0.tgz", + "integrity": "sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/hast-util-to-jsx-runtime": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.2.tgz", - "integrity": "sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.4.tgz", + "integrity": "sha512-2GSifZSlBD35z6/+sp+btB333wHFPck/rrlKZMc9IOUJk6anHuQuqC/oNI80Pj717wo8JCPdXjjasVqQu3UH8Q==", + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", @@ -3560,7 +3556,7 @@ "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", - "property-information": "^6.0.0", + "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-object": "^1.0.0", "unist-util-position": "^5.0.0", @@ -3576,6 +3572,16 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" }, + "node_modules/hast-util-to-jsx-runtime/node_modules/property-information": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.0.0.tgz", + "integrity": "sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/hast-util-to-jsx-runtime/node_modules/unist-util-stringify-position": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", @@ -5485,9 +5491,9 @@ "license": "MIT" }, "node_modules/preact": { - "version": "10.26.0", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.0.tgz", - "integrity": "sha512-6ugi/Mb7lyV5RA6KlnijFyDLMU253i7L0RRiObIzDoqj59KT9iTeNJbA/YGw6M7jP4vxaab0DOA8DgodTOA6EQ==", + "version": "10.26.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.2.tgz", + "integrity": "sha512-0gNmv4qpS9HaN3+40CLBAnKe0ZfyE4ZWo5xKlC1rVrr0ckkEvJvAQqKaHANdFKsGstoxrY4AItZ7kZSGVoVjgg==", "license": "MIT", "funding": { "type": "opencollective", @@ -5503,9 +5509,9 @@ } }, "node_modules/prettier": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.1.tgz", - "integrity": "sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz", + "integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==", "dev": true, "license": "MIT", "bin": { @@ -5670,9 +5676,9 @@ } }, "node_modules/rehype-mathjax": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/rehype-mathjax/-/rehype-mathjax-7.0.0.tgz", - "integrity": "sha512-xdzj8L1y0Zgai/+N3wpi/xIdiHbOJSaz3FFMZLnhnizEMzcl6Zt5LmMZfQ+J5DYo5fyjKGw/pMaJonds1apo/Q==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/rehype-mathjax/-/rehype-mathjax-7.1.0.tgz", + "integrity": "sha512-mJHNpoqCC5UZ24OKx0wNjlzV18qeJz/Q/LtEjxXzt8vqrZ1Z3GxQnVrHcF5/PogcXUK8cWwJ4U/LWOQWEiABHw==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -5681,7 +5687,8 @@ "hastscript": "^9.0.0", "mathjax-full": "^3.0.0", "unified": "^11.0.0", - "unist-util-visit-parents": "^6.0.0" + "unist-util-visit-parents": "^6.0.0", + "vfile": "^6.0.0" }, "funding": { "type": "opencollective", @@ -6941,12 +6948,13 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tsx": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz", - "integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==", + "version": "4.19.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.3.tgz", + "integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==", "dev": true, + "license": "MIT", "dependencies": { - "esbuild": "~0.23.0", + "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" }, "bin": { @@ -6959,413 +6967,6 @@ "fsevents": "~2.3.3" } }, - "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz", - "integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-arm": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.0.tgz", - "integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz", - "integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.0.tgz", - "integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz", - "integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/darwin-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz", - "integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz", - "integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz", - "integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-arm": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz", - "integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz", - "integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-ia32": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz", - "integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-loong64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz", - "integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz", - "integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz", - "integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz", - "integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-s390x": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz", - "integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz", - "integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz", - "integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz", - "integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/sunos-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz", - "integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz", - "integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-ia32": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz", - "integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz", - "integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/esbuild": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz", - "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.23.0", - "@esbuild/android-arm": "0.23.0", - "@esbuild/android-arm64": "0.23.0", - "@esbuild/android-x64": "0.23.0", - "@esbuild/darwin-arm64": "0.23.0", - "@esbuild/darwin-x64": "0.23.0", - "@esbuild/freebsd-arm64": "0.23.0", - "@esbuild/freebsd-x64": "0.23.0", - "@esbuild/linux-arm": "0.23.0", - "@esbuild/linux-arm64": "0.23.0", - "@esbuild/linux-ia32": "0.23.0", - "@esbuild/linux-loong64": "0.23.0", - "@esbuild/linux-mips64el": "0.23.0", - "@esbuild/linux-ppc64": "0.23.0", - "@esbuild/linux-riscv64": "0.23.0", - "@esbuild/linux-s390x": "0.23.0", - "@esbuild/linux-x64": "0.23.0", - "@esbuild/netbsd-x64": "0.23.0", - "@esbuild/openbsd-arm64": "0.23.0", - "@esbuild/openbsd-x64": "0.23.0", - "@esbuild/sunos-x64": "0.23.0", - "@esbuild/win32-arm64": "0.23.0", - "@esbuild/win32-ia32": "0.23.0", - "@esbuild/win32-x64": "0.23.0" - } - }, "node_modules/typescript": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", @@ -7758,9 +7359,10 @@ } }, "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index 956277206..acdbcd896 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,8 @@ "github-slugger": "^2.0.0", "globby": "^14.1.0", "gray-matter": "^4.0.3", - "hast-util-to-html": "^9.0.4", - "hast-util-to-jsx-runtime": "^2.3.2", + "hast-util-to-html": "^9.0.5", + "hast-util-to-jsx-runtime": "^2.3.4", "hast-util-to-string": "^3.0.1", "is-absolute-url": "^4.0.1", "js-yaml": "^4.1.0", @@ -61,7 +61,7 @@ "mdast-util-to-string": "^4.0.0", "micromorph": "^0.4.5", "pixi.js": "^8.8.0", - "preact": "^10.26.0", + "preact": "^10.26.2", "preact-render-to-string": "^6.5.13", "pretty-bytes": "^6.1.1", "pretty-time": "^1.1.0", @@ -69,7 +69,7 @@ "rehype-autolink-headings": "^7.1.0", "rehype-citation": "^2.2.2", "rehype-katex": "^7.0.1", - "rehype-mathjax": "^7.0.0", + "rehype-mathjax": "^7.1.0", "rehype-pretty-code": "^0.14.0", "rehype-raw": "^7.0.0", "rehype-slug": "^6.0.0", @@ -94,7 +94,7 @@ "unist-util-visit": "^5.0.0", "vfile": "^6.0.3", "workerpool": "^9.2.0", - "ws": "^8.18.0", + "ws": "^8.18.1", "yargs": "^17.7.2" }, "devDependencies": { @@ -102,14 +102,14 @@ "@types/d3": "^7.4.3", "@types/hast": "^3.0.4", "@types/js-yaml": "^4.0.9", - "@types/node": "^22.13.4", + "@types/node": "^22.13.5", "@types/pretty-time": "^1.1.5", "@types/source-map-support": "^0.5.10", "@types/ws": "^8.5.14", "@types/yargs": "^17.0.33", "esbuild": "^0.25.0", - "prettier": "^3.5.1", - "tsx": "^4.19.2", + "prettier": "^3.5.2", + "tsx": "^4.19.3", "typescript": "^5.7.3" } } diff --git a/quartz/components/pages/Content.tsx b/quartz/components/pages/Content.tsx index 8222d786e..e21aad7ec 100644 --- a/quartz/components/pages/Content.tsx +++ b/quartz/components/pages/Content.tsx @@ -1,8 +1,9 @@ +import { ComponentChildren } from "preact" import { htmlToJsx } from "../../util/jsx" import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../types" const Content: QuartzComponent = ({ fileData, tree }: QuartzComponentProps) => { - const content = htmlToJsx(fileData.filePath!, tree) + const content = htmlToJsx(fileData.filePath!, tree) as ComponentChildren const classes: string[] = fileData.frontmatter?.cssclasses ?? [] const classString = ["popover-hint", ...classes].join(" ") return
{content}
diff --git a/quartz/components/pages/FolderContent.tsx b/quartz/components/pages/FolderContent.tsx index 593073b96..977da5e9c 100644 --- a/quartz/components/pages/FolderContent.tsx +++ b/quartz/components/pages/FolderContent.tsx @@ -8,6 +8,7 @@ import { Root } from "hast" import { htmlToJsx } from "../../util/jsx" import { i18n } from "../../i18n" import { QuartzPluginData } from "../../plugins/vfile" +import { ComponentChildren } from "preact" interface FolderContentOptions { /** @@ -78,10 +79,11 @@ export default ((opts?: Partial) => { allFiles: allPagesInFolder, } - const content = + const content = ( (tree as Root).children.length === 0 ? fileData.description : htmlToJsx(fileData.filePath!, tree) + ) as ComponentChildren return (
diff --git a/quartz/components/pages/TagContent.tsx b/quartz/components/pages/TagContent.tsx index e56c6d609..087daf18a 100644 --- a/quartz/components/pages/TagContent.tsx +++ b/quartz/components/pages/TagContent.tsx @@ -6,6 +6,7 @@ import { QuartzPluginData } from "../../plugins/vfile" import { Root } from "hast" import { htmlToJsx } from "../../util/jsx" import { i18n } from "../../i18n" +import { ComponentChildren } from "preact" interface TagContentOptions { sort?: SortFn @@ -33,10 +34,11 @@ export default ((opts?: Partial) => { (file.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes).includes(tag), ) - const content = + const content = ( (tree as Root).children.length === 0 ? fileData.description : htmlToJsx(fileData.filePath!, tree) + ) as ComponentChildren const cssClasses: string[] = fileData.frontmatter?.cssclasses ?? [] const classes = cssClasses.join(" ") if (tag === "/") { From 9b47279fe62bfd8921bb4f98c3008feff2d9fe34 Mon Sep 17 00:00:00 2001 From: Jon Erling Hustadnes Date: Thu, 27 Feb 2025 22:33:15 +0100 Subject: [PATCH 008/134] feat(i18n): added nb-NO (Norwegian) (#1795) --- quartz/i18n/index.ts | 2 + quartz/i18n/locales/nb-NO.ts | 84 ++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 quartz/i18n/locales/nb-NO.ts diff --git a/quartz/i18n/index.ts b/quartz/i18n/index.ts index 2f7b9b20f..c7d501810 100644 --- a/quartz/i18n/index.ts +++ b/quartz/i18n/index.ts @@ -25,6 +25,7 @@ import tr from "./locales/tr-TR" import th from "./locales/th-TH" import lt from "./locales/lt-LT" import fi from "./locales/fi-FI" +import no from "./locales/nb-NO" export const TRANSLATIONS = { "en-US": enUs, @@ -74,6 +75,7 @@ export const TRANSLATIONS = { "th-TH": th, "lt-LT": lt, "fi-FI": fi, + "nb-NO": no, } as const export const defaultTranslation = "en-US" diff --git a/quartz/i18n/locales/nb-NO.ts b/quartz/i18n/locales/nb-NO.ts new file mode 100644 index 000000000..5823b19b1 --- /dev/null +++ b/quartz/i18n/locales/nb-NO.ts @@ -0,0 +1,84 @@ +import { Translation } from "./definition" + +export default { + propertyDefaults: { + title: "Uten navn", + description: "Ingen beskrivelse angitt", + }, + components: { + callout: { + note: "Notis", + abstract: "Abstrakt", + info: "Info", + todo: "Husk på", + tip: "Tips", + success: "Suksess", + question: "Spørsmål", + warning: "Advarsel", + failure: "Feil", + danger: "Farlig", + bug: "Bug", + example: "Eksempel", + quote: "Sitat", + }, + backlinks: { + title: "Tilbakekoblinger", + noBacklinksFound: "Ingen tilbakekoblinger funnet", + }, + themeToggle: { + lightMode: "Lys modus", + darkMode: "Mørk modus", + }, + explorer: { + title: "Utforsker", + }, + footer: { + createdWith: "Laget med", + }, + graph: { + title: "Graf-visning", + }, + recentNotes: { + title: "Nylige notater", + seeRemainingMore: ({ remaining }) => `Se ${remaining} til →`, + }, + transcludes: { + transcludeOf: ({ targetSlug }) => `Transkludering of ${targetSlug}`, + linkToOriginal: "Lenke til original", + }, + search: { + title: "Søk", + searchBarPlaceholder: "Søk etter noe", + }, + tableOfContents: { + title: "Oversikt", + }, + contentMeta: { + readingTime: ({ minutes }) => `${minutes} min lesning`, + }, + }, + pages: { + rss: { + recentNotes: "Nylige notat", + lastFewNotes: ({ count }) => `Siste ${count} notat`, + }, + error: { + title: "Ikke funnet", + notFound: "Enten er denne siden privat eller så finnes den ikke.", + home: "Returner til hovedsiden", + }, + folderContent: { + folder: "Mappe", + itemsUnderFolder: ({ count }) => + count === 1 ? "1 gjenstand i denne mappen." : `${count} gjenstander i denne mappen.`, + }, + tagContent: { + tag: "Tagg", + tagIndex: "Tagg Indeks", + itemsUnderTag: ({ count }) => + count === 1 ? "1 gjenstand med denne taggen." : `${count} gjenstander med denne taggen.`, + showingFirst: ({ count }) => `Viser første ${count} tagger.`, + totalTags: ({ count }) => `Fant totalt ${count} tagger.`, + }, + }, +} as const satisfies Translation From 6d350500f1bdadc9604ece5980dafa02150dd849 Mon Sep 17 00:00:00 2001 From: Anton Bulakh Date: Thu, 27 Feb 2025 23:35:19 +0200 Subject: [PATCH 009/134] fix(goatcounter): properly count SPA page hits (#1718) On the surface it seems that only google and plausible scripts handle the SPA correctly - but I don't know if maybe others handle window.history API themselves somehow or something like that. However, I am trying out goatcounter and in it's docs I see that it does no special SPA handling, so this has to be fixed. Just doing the dynamic script thing on every nav seems to do the trick. The script is not "spa-preserve" so they wouldn't accumulate - and when I tried the "spa-preserve" + call goatcounter api route it didn't quite work, they actually did accumulate --- quartz/plugins/emitters/componentResources.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/quartz/plugins/emitters/componentResources.ts b/quartz/plugins/emitters/componentResources.ts index 08278305e..49e281540 100644 --- a/quartz/plugins/emitters/componentResources.ts +++ b/quartz/plugins/emitters/componentResources.ts @@ -122,12 +122,14 @@ function addGlobalPageResources(ctx: BuildCtx, componentResources: ComponentReso `) } else if (cfg.analytics?.provider === "goatcounter") { componentResources.afterDOMLoaded.push(` - const goatcounterScript = document.createElement("script") - goatcounterScript.src = "${cfg.analytics.scriptSrc ?? "https://gc.zgo.at/count.js"}" - goatcounterScript.async = true - goatcounterScript.setAttribute("data-goatcounter", - "https://${cfg.analytics.websiteId}.${cfg.analytics.host ?? "goatcounter.com"}/count") - document.head.appendChild(goatcounterScript) + document.addEventListener("nav", () => { + const goatcounterScript = document.createElement("script") + goatcounterScript.src = "${cfg.analytics.scriptSrc ?? "https://gc.zgo.at/count.js"}" + goatcounterScript.async = true + goatcounterScript.setAttribute("data-goatcounter", + "https://${cfg.analytics.websiteId}.${cfg.analytics.host ?? "goatcounter.com"}/count") + document.head.appendChild(goatcounterScript) + }) `) } else if (cfg.analytics?.provider === "posthog") { componentResources.afterDOMLoaded.push(` From b050162f820c1ef3a75c20b3b1b51e42bb6144aa Mon Sep 17 00:00:00 2001 From: sou7 <17077205+soukouki@users.noreply.github.com> Date: Fri, 28 Feb 2025 06:37:01 +0900 Subject: [PATCH 010/134] docs(frontmatter): mention dealiasing related fields (#1715) * Added to the documentation which values of frontmatter are referenced. * The source code I was looking at seemed to be out of date and the wrong listings were corrected. * The list of frontmatter was moved to Frontmatter.md and a link was added. --- docs/authoring content.md | 2 ++ docs/plugins/CreatedModifiedDate.md | 2 ++ docs/plugins/Frontmatter.md | 48 +++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/docs/authoring content.md b/docs/authoring content.md index 178f44af8..623357fc3 100644 --- a/docs/authoring content.md +++ b/docs/authoring content.md @@ -35,6 +35,8 @@ Some common frontmatter fields that are natively supported by Quartz: - `draft`: Whether to publish the page or not. This is one way to make [[private pages|pages private]] in Quartz. - `date`: A string representing the day the note was published. Normally uses `YYYY-MM-DD` format. +See [[Frontmatter]] for a complete list of frontmatter. + ## Syncing your Content When your Quartz is at a point you're happy with, you can save your changes to GitHub. diff --git a/docs/plugins/CreatedModifiedDate.md b/docs/plugins/CreatedModifiedDate.md index f4134c478..2d1eaeec1 100644 --- a/docs/plugins/CreatedModifiedDate.md +++ b/docs/plugins/CreatedModifiedDate.md @@ -13,6 +13,8 @@ This plugin accepts the following configuration options: - `priority`: The data sources to consult for date information. Highest priority first. Possible values are `"frontmatter"`, `"git"`, and `"filesystem"`. Defaults to `["frontmatter", "git", "filesystem"]`. +When loading the frontmatter, the value of [[Frontmatter#List]] is used. + > [!warning] > If you rely on `git` for dates, make sure `defaultDateType` is set to `modified` in `quartz.config.ts`. > diff --git a/docs/plugins/Frontmatter.md b/docs/plugins/Frontmatter.md index 879d087db..9dfe53378 100644 --- a/docs/plugins/Frontmatter.md +++ b/docs/plugins/Frontmatter.md @@ -17,6 +17,54 @@ This plugin accepts the following configuration options: > [!warning] > This plugin must not be removed, otherwise Quartz will break. +## List + +Quartz supports the following frontmatter: + +- title + - `title` +- description + - `description` +- permalink + - `permalink` +- comments + - `comments` +- lang + - `lang` +- publish + - `publish` +- draft + - `draft` +- enableToc + - `enableToc` +- tags + - `tags` + - `tag` +- aliases + - `aliases` + - `alias` +- cssclasses + - `cssclasses` + - `cssclass` +- socialDescription + - `socialDescription` +- socialImage + - `socialImage` + - `image` + - `cover` +- created + - `created` + - `date` +- modified + - `modified` + - `lastmod` + - `updated` + - `last-modified` +- published + - `published` + - `publishDate` + - `date` + ## API - Category: Transformer From c6f10b44f6878e76a416332f14e3681de8df40db Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 6 Mar 2025 00:54:11 +0100 Subject: [PATCH 011/134] feat(rss): configurable RSS feed URL (#1806) * feat(rss): configurable RSS feed URL * Update docs/features/RSS Feed.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update docs/features/RSS Feed.md --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Jacky Zhao --- docs/features/RSS Feed.md | 5 +++++ docs/plugins/ContentIndex.md | 1 + quartz/plugins/emitters/contentIndex.ts | 4 +++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/features/RSS Feed.md b/docs/features/RSS Feed.md index ed4138dfc..4b1a1bb3e 100644 --- a/docs/features/RSS Feed.md +++ b/docs/features/RSS Feed.md @@ -1,5 +1,10 @@ Quartz emits an RSS feed for all the content on your site by generating an `index.xml` file that RSS readers can subscribe to. Because of the RSS spec, this requires the `baseUrl` property in your [[configuration]] to be set properly for RSS readers to pick it up properly. +> [!info] +> After deploying, the generated RSS link will be available at `https://${baseUrl}/index.xml` by default. +> +> The `index.xml` path can be customized by passing the `rssSlug` option to the [[ContentIndex]] plugin. + ## Configuration This functionality is provided by the [[ContentIndex]] plugin. See the plugin page for customization options. diff --git a/docs/plugins/ContentIndex.md b/docs/plugins/ContentIndex.md index eb7265d47..037f723bf 100644 --- a/docs/plugins/ContentIndex.md +++ b/docs/plugins/ContentIndex.md @@ -17,6 +17,7 @@ This plugin accepts the following configuration options: - `enableRSS`: If `true` (default), produces an RSS feed (`index.xml`) with recent content updates. - `rssLimit`: Defines the maximum number of entries to include in the RSS feed, helping to focus on the most recent or relevant content. Defaults to `10`. - `rssFullHtml`: If `true`, the RSS feed includes full HTML content. Otherwise it includes just summaries. +- `rssSlug`: Slug to the generated RSS feed XML file. Defaults to `"index"`. - `includeEmptyFiles`: If `true` (default), content files with no body text are included in the generated index and resources. ## API diff --git a/quartz/plugins/emitters/contentIndex.ts b/quartz/plugins/emitters/contentIndex.ts index c0fef86d2..f4a1a915b 100644 --- a/quartz/plugins/emitters/contentIndex.ts +++ b/quartz/plugins/emitters/contentIndex.ts @@ -25,6 +25,7 @@ interface Options { enableRSS: boolean rssLimit?: number rssFullHtml: boolean + rssSlug: string includeEmptyFiles: boolean } @@ -33,6 +34,7 @@ const defaultOptions: Options = { enableRSS: true, rssLimit: 10, rssFullHtml: false, + rssSlug: "index", includeEmptyFiles: true, } @@ -151,7 +153,7 @@ export const ContentIndex: QuartzEmitterPlugin> = (opts) => { await write({ ctx, content: generateRSSFeed(cfg, linkIndex, opts.rssLimit), - slug: "index" as FullSlug, + slug: (opts?.rssSlug ?? "index") as FullSlug, ext: ".xml", }), ) From a1162b978aa1dc1003567485d02c2bc001dd79d4 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Wed, 5 Mar 2025 16:27:08 -0800 Subject: [PATCH 012/134] fix(analytics): spa tracking for other providers --- quartz.config.ts | 4 +-- quartz/plugins/emitters/componentResources.ts | 36 ++++++++++++++----- quartz/plugins/emitters/contentIndex.ts | 8 ++--- quartz/plugins/emitters/index.ts | 2 +- 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/quartz.config.ts b/quartz.config.ts index dc339d987..6c8921543 100644 --- a/quartz.config.ts +++ b/quartz.config.ts @@ -2,13 +2,13 @@ import { QuartzConfig } from "./quartz/cfg" import * as Plugin from "./quartz/plugins" /** - * Quartz 4.0 Configuration + * Quartz 4 Configuration * * See https://quartz.jzhao.xyz/configuration for more information. */ const config: QuartzConfig = { configuration: { - pageTitle: "🪴 Quartz 4.0", + pageTitle: "🪴 Quartz 4", pageTitleSuffix: "", enableSPA: true, enablePopovers: true, diff --git a/quartz/plugins/emitters/componentResources.ts b/quartz/plugins/emitters/componentResources.ts index 49e281540..b307aad41 100644 --- a/quartz/plugins/emitters/componentResources.ts +++ b/quartz/plugins/emitters/componentResources.ts @@ -116,35 +116,53 @@ function addGlobalPageResources(ctx: BuildCtx, componentResources: ComponentReso const umamiScript = document.createElement("script") umamiScript.src = "${cfg.analytics.host ?? "https://analytics.umami.is"}/script.js" umamiScript.setAttribute("data-website-id", "${cfg.analytics.websiteId}") + umamiScript.setAttribute("data-auto-track", "false") umamiScript.async = true - document.head.appendChild(umamiScript) + + document.addEventListener("nav", () => { + umami.track(); + }) `) } else if (cfg.analytics?.provider === "goatcounter") { componentResources.afterDOMLoaded.push(` + const goatcounterScript = document.createElement("script") + goatcounterScript.src = "${cfg.analytics.scriptSrc ?? "https://gc.zgo.at/count.js"}" + goatcounterScript.async = true + goatcounterScript.setAttribute("data-goatcounter", + "https://${cfg.analytics.websiteId}.${cfg.analytics.host ?? "goatcounter.com"}/count") + document.head.appendChild(goatcounterScript) + + window.goatcounter = { no_onload: true } document.addEventListener("nav", () => { - const goatcounterScript = document.createElement("script") - goatcounterScript.src = "${cfg.analytics.scriptSrc ?? "https://gc.zgo.at/count.js"}" - goatcounterScript.async = true - goatcounterScript.setAttribute("data-goatcounter", - "https://${cfg.analytics.websiteId}.${cfg.analytics.host ?? "goatcounter.com"}/count") - document.head.appendChild(goatcounterScript) + goatcounter.count({ path: location.pathname }) }) `) } else if (cfg.analytics?.provider === "posthog") { componentResources.afterDOMLoaded.push(` const posthogScript = document.createElement("script") posthogScript.innerHTML= \`!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.async=!0,p.src=s.api_host+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags getFeatureFlag getFeatureFlagPayload reloadFeatureFlags group updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures getActiveMatchingSurveys getSurveys onSessionId".split(" "),n=0;n { + posthog.capture('$pageview', { path: location.pathname }) + }) `) } else if (cfg.analytics?.provider === "tinylytics") { const siteId = cfg.analytics.siteId componentResources.afterDOMLoaded.push(` const tinylyticsScript = document.createElement("script") - tinylyticsScript.src = "https://tinylytics.app/embed/${siteId}.js" + tinylyticsScript.src = "https://tinylytics.app/embed/${siteId}.js?spa" tinylyticsScript.defer = true document.head.appendChild(tinylyticsScript) + + document.addEventListener("nav", () => { + window.tinylytics.triggerUpdate() + }) `) } else if (cfg.analytics?.provider === "cabin") { componentResources.afterDOMLoaded.push(` diff --git a/quartz/plugins/emitters/contentIndex.ts b/quartz/plugins/emitters/contentIndex.ts index f4a1a915b..5d76e087b 100644 --- a/quartz/plugins/emitters/contentIndex.ts +++ b/quartz/plugins/emitters/contentIndex.ts @@ -9,7 +9,7 @@ import { write } from "./helpers" import { i18n } from "../../i18n" import DepGraph from "../../depgraph" -export type ContentIndex = Map +export type ContentIndexMap = Map export type ContentDetails = { title: string links: SimpleSlug[] @@ -38,7 +38,7 @@ const defaultOptions: Options = { includeEmptyFiles: true, } -function generateSiteMap(cfg: GlobalConfiguration, idx: ContentIndex): string { +function generateSiteMap(cfg: GlobalConfiguration, idx: ContentIndexMap): string { const base = cfg.baseUrl ?? "" const createURLEntry = (slug: SimpleSlug, content: ContentDetails): string => ` https://${joinSegments(base, encodeURI(slug))} @@ -50,7 +50,7 @@ function generateSiteMap(cfg: GlobalConfiguration, idx: ContentIndex): string { return `${urls}` } -function generateRSSFeed(cfg: GlobalConfiguration, idx: ContentIndex, limit?: number): string { +function generateRSSFeed(cfg: GlobalConfiguration, idx: ContentIndexMap, limit?: number): string { const base = cfg.baseUrl ?? "" const createURLEntry = (slug: SimpleSlug, content: ContentDetails): string => ` @@ -118,7 +118,7 @@ export const ContentIndex: QuartzEmitterPlugin> = (opts) => { async emit(ctx, content, _resources) { const cfg = ctx.cfg.configuration const emitted: FilePath[] = [] - const linkIndex: ContentIndex = new Map() + const linkIndex: ContentIndexMap = new Map() for (const [tree, file] of content) { const slug = file.data.slug! const date = getDate(ctx.cfg.configuration, file.data) ?? new Date() diff --git a/quartz/plugins/emitters/index.ts b/quartz/plugins/emitters/index.ts index bc378c47b..60f47fe01 100644 --- a/quartz/plugins/emitters/index.ts +++ b/quartz/plugins/emitters/index.ts @@ -1,7 +1,7 @@ export { ContentPage } from "./contentPage" export { TagPage } from "./tagPage" export { FolderPage } from "./folderPage" -export { ContentIndex } from "./contentIndex" +export { ContentIndex as ContentIndex } from "./contentIndex" export { AliasRedirects } from "./aliases" export { Assets } from "./assets" export { Static } from "./static" From a3b62013650f09afd11c4e58675f495bbc085569 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 16:45:02 -0800 Subject: [PATCH 013/134] chore(deps): bump the production-dependencies group with 6 updates (#1804) * chore(deps): bump the production-dependencies group with 6 updates Bumps the production-dependencies group with 6 updates: | Package | From | To | | --- | --- | --- | | [hast-util-to-jsx-runtime](https://github.com/syntax-tree/hast-util-to-jsx-runtime) | `2.3.4` | `2.3.5` | | [pixi.js](https://github.com/pixijs/pixijs) | `8.8.0` | `8.8.1` | | [preact](https://github.com/preactjs/preact) | `10.26.2` | `10.26.4` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `22.13.5` | `22.13.9` | | [prettier](https://github.com/prettier/prettier) | `3.5.2` | `3.5.3` | | [typescript](https://github.com/microsoft/TypeScript) | `5.7.3` | `5.8.2` | Updates `hast-util-to-jsx-runtime` from 2.3.4 to 2.3.5 - [Release notes](https://github.com/syntax-tree/hast-util-to-jsx-runtime/releases) - [Commits](https://github.com/syntax-tree/hast-util-to-jsx-runtime/compare/2.3.4...2.3.5) Updates `pixi.js` from 8.8.0 to 8.8.1 - [Release notes](https://github.com/pixijs/pixijs/releases) - [Commits](https://github.com/pixijs/pixijs/compare/v8.8.0...v8.8.1) Updates `preact` from 10.26.2 to 10.26.4 - [Release notes](https://github.com/preactjs/preact/releases) - [Commits](https://github.com/preactjs/preact/compare/10.26.2...10.26.4) Updates `@types/node` from 22.13.5 to 22.13.9 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `prettier` from 3.5.2 to 3.5.3 - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.5.2...3.5.3) Updates `typescript` from 5.7.3 to 5.8.2 - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml) - [Commits](https://github.com/microsoft/TypeScript/compare/v5.7.3...v5.8.2) --- updated-dependencies: - dependency-name: hast-util-to-jsx-runtime dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: pixi.js dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: preact dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: prettier dependency-type: direct:development update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: typescript dependency-type: direct:development update-type: version-update:semver-minor dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] * type fixes * fix more types --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jacky Zhao --- docs/advanced/making plugins.md | 2 +- package-lock.json | 65 ++++++++++++++-------- package.json | 12 ++-- quartz/plugins/transformers/frontmatter.ts | 3 +- quartz/plugins/transformers/ofm.ts | 20 ++----- quartz/plugins/types.ts | 2 +- 6 files changed, 55 insertions(+), 49 deletions(-) diff --git a/docs/advanced/making plugins.md b/docs/advanced/making plugins.md index 015e1953a..3042737a2 100644 --- a/docs/advanced/making plugins.md +++ b/docs/advanced/making plugins.md @@ -37,7 +37,7 @@ Transformers **map** over content, taking a Markdown file and outputting modifie ```ts export type QuartzTransformerPluginInstance = { name: string - textTransform?: (ctx: BuildCtx, src: string | Buffer) => string | Buffer + textTransform?: (ctx: BuildCtx, src: string) => string markdownPlugins?: (ctx: BuildCtx) => PluggableList htmlPlugins?: (ctx: BuildCtx) => PluggableList externalResources?: (ctx: BuildCtx) => Partial diff --git a/package-lock.json b/package-lock.json index 7f8944cba..1ca894a08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "globby": "^14.1.0", "gray-matter": "^4.0.3", "hast-util-to-html": "^9.0.5", - "hast-util-to-jsx-runtime": "^2.3.4", + "hast-util-to-jsx-runtime": "^2.3.5", "hast-util-to-string": "^3.0.1", "is-absolute-url": "^4.0.1", "js-yaml": "^4.1.0", @@ -34,8 +34,8 @@ "mdast-util-to-hast": "^13.2.0", "mdast-util-to-string": "^4.0.0", "micromorph": "^0.4.5", - "pixi.js": "^8.8.0", - "preact": "^10.26.2", + "pixi.js": "^8.8.1", + "preact": "^10.26.4", "preact-render-to-string": "^6.5.13", "pretty-bytes": "^6.1.1", "pretty-time": "^1.1.0", @@ -79,15 +79,15 @@ "@types/d3": "^7.4.3", "@types/hast": "^3.0.4", "@types/js-yaml": "^4.0.9", - "@types/node": "^22.13.5", + "@types/node": "^22.13.9", "@types/pretty-time": "^1.1.5", "@types/source-map-support": "^0.5.10", "@types/ws": "^8.5.14", "@types/yargs": "^17.0.33", "esbuild": "^0.25.0", - "prettier": "^3.5.2", + "prettier": "^3.5.3", "tsx": "^4.19.3", - "typescript": "^5.7.3" + "typescript": "^5.8.2" }, "engines": { "node": "20 || >=22", @@ -1916,9 +1916,9 @@ } }, "node_modules/@types/node": { - "version": "22.13.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.5.tgz", - "integrity": "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==", + "version": "22.13.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.9.tgz", + "integrity": "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==", "dev": true, "license": "MIT", "dependencies": { @@ -3224,6 +3224,15 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/gifuct-js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/gifuct-js/-/gifuct-js-2.1.2.tgz", + "integrity": "sha512-rI2asw77u0mGgwhV3qA+OEgYqaDn5UNqgs+Bx0FGwSpuqfYn+Ir6RQY5ENNQ8SbIiG/m5gVa7CD5RriO4f4Lsg==", + "license": "MIT", + "dependencies": { + "js-binary-schema-parser": "^2.0.3" + } + }, "node_modules/github-slugger": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", @@ -3541,9 +3550,9 @@ } }, "node_modules/hast-util-to-jsx-runtime": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.4.tgz", - "integrity": "sha512-2GSifZSlBD35z6/+sp+btB333wHFPck/rrlKZMc9IOUJk6anHuQuqC/oNI80Pj717wo8JCPdXjjasVqQu3UH8Q==", + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.5.tgz", + "integrity": "sha512-gHD+HoFxOMmmXLuq9f2dZDMQHVcplCVpMfBNRpJsF03yyLZvJGzsFORe8orVuYDX9k2w0VH0uF8oryFd1whqKQ==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", @@ -3928,6 +3937,12 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/js-binary-schema-parser": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/js-binary-schema-parser/-/js-binary-schema-parser-2.0.3.tgz", + "integrity": "sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg==", + "license": "MIT" + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -5468,9 +5483,9 @@ } }, "node_modules/pixi.js": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.8.0.tgz", - "integrity": "sha512-0xW8tKa+uF28mi1SwvnNscMpYJSQrqLN7jJs6Ore37FZoXmIRzQNrGA6drpHDVTuEmoqJlSiGLCk5cUgz3ODgQ==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.8.1.tgz", + "integrity": "sha512-Zmox3Vy52Kl6X/uxknKlxJWPVEFiP63nsX8soqB4butTkIOK3y7c9C204wcDfAgkwO1OlwYxscWtHv+ef4gqgA==", "license": "MIT", "dependencies": { "@pixi/colord": "^2.9.6", @@ -5480,6 +5495,7 @@ "@xmldom/xmldom": "^0.8.10", "earcut": "^2.2.4", "eventemitter3": "^5.0.1", + "gifuct-js": "^2.1.2", "ismobilejs": "^1.1.1", "parse-svg-path": "^0.1.2" } @@ -5491,9 +5507,9 @@ "license": "MIT" }, "node_modules/preact": { - "version": "10.26.2", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.2.tgz", - "integrity": "sha512-0gNmv4qpS9HaN3+40CLBAnKe0ZfyE4ZWo5xKlC1rVrr0ckkEvJvAQqKaHANdFKsGstoxrY4AItZ7kZSGVoVjgg==", + "version": "10.26.4", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.4.tgz", + "integrity": "sha512-KJhO7LBFTjP71d83trW+Ilnjbo+ySsaAgCfXOXUlmGzJ4ygYPWmysm77yg4emwfmoz3b22yvH5IsVFHbhUaH5w==", "license": "MIT", "funding": { "type": "opencollective", @@ -5509,9 +5525,9 @@ } }, "node_modules/prettier": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz", - "integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true, "license": "MIT", "bin": { @@ -6968,10 +6984,11 @@ } }, "node_modules/typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index acdbcd896..e6c7d4d20 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "globby": "^14.1.0", "gray-matter": "^4.0.3", "hast-util-to-html": "^9.0.5", - "hast-util-to-jsx-runtime": "^2.3.4", + "hast-util-to-jsx-runtime": "^2.3.5", "hast-util-to-string": "^3.0.1", "is-absolute-url": "^4.0.1", "js-yaml": "^4.1.0", @@ -60,8 +60,8 @@ "mdast-util-to-hast": "^13.2.0", "mdast-util-to-string": "^4.0.0", "micromorph": "^0.4.5", - "pixi.js": "^8.8.0", - "preact": "^10.26.2", + "pixi.js": "^8.8.1", + "preact": "^10.26.4", "preact-render-to-string": "^6.5.13", "pretty-bytes": "^6.1.1", "pretty-time": "^1.1.0", @@ -102,14 +102,14 @@ "@types/d3": "^7.4.3", "@types/hast": "^3.0.4", "@types/js-yaml": "^4.0.9", - "@types/node": "^22.13.5", + "@types/node": "^22.13.9", "@types/pretty-time": "^1.1.5", "@types/source-map-support": "^0.5.10", "@types/ws": "^8.5.14", "@types/yargs": "^17.0.33", "esbuild": "^0.25.0", - "prettier": "^3.5.2", + "prettier": "^3.5.3", "tsx": "^4.19.3", - "typescript": "^5.7.3" + "typescript": "^5.8.2" } } diff --git a/quartz/plugins/transformers/frontmatter.ts b/quartz/plugins/transformers/frontmatter.ts index 625cf607a..9679bd1ec 100644 --- a/quartz/plugins/transformers/frontmatter.ts +++ b/quartz/plugins/transformers/frontmatter.ts @@ -67,7 +67,8 @@ export const FrontMatter: QuartzTransformerPlugin> = (userOpts) [remarkFrontmatter, ["yaml", "toml"]], () => { return (_, file) => { - const { data } = matter(Buffer.from(file.value), { + const fileData = Buffer.from(file.value as Uint8Array) + const { data } = matter(fileData, { ...opts, engines: { yaml: (s) => yaml.load(s, { schema: yaml.JSON_SCHEMA }) as object, diff --git a/quartz/plugins/transformers/ofm.ts b/quartz/plugins/transformers/ofm.ts index b0b0a42ef..a39a4db0e 100644 --- a/quartz/plugins/transformers/ofm.ts +++ b/quartz/plugins/transformers/ofm.ts @@ -156,20 +156,12 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin> textTransform(_ctx, src) { // do comments at text level if (opts.comments) { - if (src instanceof Buffer) { - src = src.toString() - } - - src = (src as string).replace(commentRegex, "") + src = src.replace(commentRegex, "") } // pre-transform blockquotes if (opts.callouts) { - if (src instanceof Buffer) { - src = src.toString() - } - - src = (src as string).replace(calloutLineRegex, (value) => { + src = src.replace(calloutLineRegex, (value) => { // force newline after title of callout return value + "\n> " }) @@ -177,12 +169,8 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin> // pre-transform wikilinks (fix anchors to things that may contain illegal syntax e.g. codeblocks, latex) if (opts.wikilinks) { - if (src instanceof Buffer) { - src = src.toString() - } - // replace all wikilinks inside a table first - src = (src as string).replace(tableRegex, (value) => { + src = src.replace(tableRegex, (value) => { // escape all aliases and headers in wikilinks inside a table return value.replace(tableWikilinkRegex, (_value, raw) => { // const [raw]: (string | undefined)[] = capture @@ -196,7 +184,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin> }) // replace all other wikilinks - src = (src as string).replace(wikilinkRegex, (value, ...capture) => { + src = src.replace(wikilinkRegex, (value, ...capture) => { const [rawFp, rawHeader, rawAlias]: (string | undefined)[] = capture const [fp, anchor] = splitAnchor(`${rawFp ?? ""}${rawHeader ?? ""}`) diff --git a/quartz/plugins/types.ts b/quartz/plugins/types.ts index a23f5d6f4..667799f4b 100644 --- a/quartz/plugins/types.ts +++ b/quartz/plugins/types.ts @@ -18,7 +18,7 @@ export type QuartzTransformerPlugin = ( ) => QuartzTransformerPluginInstance export type QuartzTransformerPluginInstance = { name: string - textTransform?: (ctx: BuildCtx, src: string | Buffer) => string | Buffer + textTransform?: (ctx: BuildCtx, src: string) => string markdownPlugins?: (ctx: BuildCtx) => PluggableList htmlPlugins?: (ctx: BuildCtx) => PluggableList externalResources?: (ctx: BuildCtx) => Partial From f6f417a505a813c098e40d1e1d660e19ad37110b Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Wed, 5 Mar 2025 16:30:30 -0800 Subject: [PATCH 014/134] fix: engine reqiurements --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e6c7d4d20..92872d792 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ }, "engines": { "npm": ">=9.3.1", - "node": "20 || >=22" + "node": ">=20" }, "keywords": [ "site generator", From 2acfa0fa238f193332db4605a5f1ce666334717b Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Wed, 5 Mar 2025 17:13:19 -0800 Subject: [PATCH 015/134] fix(og-image): overflow ellipses in title and description --- quartz.config.ts | 2 +- quartz/components/Head.tsx | 2 +- quartz/util/og.tsx | 51 +++++++++++++++++++++++++++----------- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/quartz.config.ts b/quartz.config.ts index 6c8921543..0cd7e946c 100644 --- a/quartz.config.ts +++ b/quartz.config.ts @@ -19,7 +19,7 @@ const config: QuartzConfig = { baseUrl: "quartz.jzhao.xyz", ignorePatterns: ["private", "templates", ".obsidian"], defaultDateType: "created", - generateSocialImages: false, + generateSocialImages: true, theme: { fontOrigin: "googleFonts", cdnCaching: true, diff --git a/quartz/components/Head.tsx b/quartz/components/Head.tsx index 3a4db10de..983dc50a5 100644 --- a/quartz/components/Head.tsx +++ b/quartz/components/Head.tsx @@ -98,7 +98,7 @@ export default (() => { if (fileName) { // Generate social image (happens async) - generateSocialImage( + void generateSocialImage( { title, description, diff --git a/quartz/util/og.tsx b/quartz/util/og.tsx index 42b9b27be..4d675cbd4 100644 --- a/quartz/util/og.tsx +++ b/quartz/util/og.tsx @@ -143,12 +143,10 @@ export const defaultImage: SocialImageOptions["imageStructure"] = ( fonts: SatoriOptions["fonts"], _fileData: QuartzPluginData, ) => { - // How many characters are allowed before switching to smaller font const fontBreakPoint = 22 const useSmallerFont = title.length > fontBreakPoint - - // Setup to access image const iconPath = `https://${cfg.baseUrl}/static/icon.png` + return (
-

- {title} -

+

+ {title} +

+
-

- {description} -

+

+ {description} +

+
) } From c97fd7089ad372537114ab469f1f9d6e95e5237a Mon Sep 17 00:00:00 2001 From: Stephen Tse Date: Thu, 6 Mar 2025 09:14:06 +0800 Subject: [PATCH 016/134] Added emoji support to Satori when generating OG images (#1593) --- quartz/components/Head.tsx | 17 +++++++++- quartz/util/emoji.ts | 66 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 quartz/util/emoji.ts diff --git a/quartz/components/Head.tsx b/quartz/components/Head.tsx index 983dc50a5..1aa8cbe00 100644 --- a/quartz/components/Head.tsx +++ b/quartz/components/Head.tsx @@ -4,6 +4,7 @@ import { CSSResourceToStyleElement, JSResourceToScriptElement } from "../util/re import { googleFontHref } from "../util/theme" import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" import satori, { SatoriOptions } from "satori" +import { loadEmoji, getIconCode } from "../util/emoji" import fs from "fs" import sharp from "sharp" import { ImageOptions, SocialImageOptions, getSatoriFont, defaultImage } from "../util/og" @@ -24,7 +25,21 @@ async function generateSocialImage( // JSX that will be used to generate satori svg const imageComponent = userOpts.imageStructure(cfg, userOpts, title, description, fonts, fileData) - const svg = await satori(imageComponent, { width, height, fonts }) + const svg = await satori(imageComponent, { + width, + height, + fonts, + // `code` will be the detected language code, `emoji` if it's an Emoji, or `unknown` if not able to tell. + // `segment` will be the content to render. + loadAdditionalAsset: async (code: string, segment: string) => { + if (code === "emoji") { + // if segment is an emoji, load the image. + return `data:image/svg+xml;base64,${btoa(await loadEmoji("twemoji", getIconCode(segment)))}` + } + // if segment is normal text + return code + }, + }) // Convert svg directly to webp (with additional compression) const compressed = await sharp(Buffer.from(svg)).webp({ quality: 40 }).toBuffer() diff --git a/quartz/util/emoji.ts b/quartz/util/emoji.ts new file mode 100644 index 000000000..231294348 --- /dev/null +++ b/quartz/util/emoji.ts @@ -0,0 +1,66 @@ +/** + * Modified version of https://unpkg.com/twemoji@13.1.0/dist/twemoji.esm.js. + * Ported from https://github.com/vercel/satori/blob/48aea6f812365959c2888a25261c72ce17992c6d/playground/utils/twemoji.ts. + */ + +/*! Copyright Twitter Inc. and other contributors. Licensed under MIT */ + +const U200D = String.fromCharCode(8205) +const UFE0Fg = /\uFE0F/g + +export function getIconCode(char: string) { + return toCodePoint(char.indexOf(U200D) < 0 ? char.replace(UFE0Fg, "") : char) +} + +function toCodePoint(unicodeSurrogates: string) { + const r = [] + let c = 0, + p = 0, + i = 0 + + while (i < unicodeSurrogates.length) { + c = unicodeSurrogates.charCodeAt(i++) + if (p) { + r.push((65536 + ((p - 55296) << 10) + (c - 56320)).toString(16)) + p = 0 + } else if (55296 <= c && c <= 56319) { + p = c + } else { + r.push(c.toString(16)) + } + } + return r.join("-") +} + +export const apis = { + twemoji: (code: string) => + "https://cdnjs.cloudflare.com/ajax/libs/twemoji/15.1.0/svg/" + code.toLowerCase() + ".svg", + openmoji: "https://cdn.jsdelivr.net/npm/@svgmoji/openmoji@3.2.0/svg/", + blobmoji: "https://cdn.jsdelivr.net/npm/@svgmoji/blob@3.2.0/svg/", + noto: "https://cdn.jsdelivr.net/gh/svgmoji/svgmoji/packages/svgmoji__noto/svg/", + fluent: (code: string) => + "https://cdn.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/" + + code.toLowerCase() + + "_color.svg", + fluentFlat: (code: string) => + "https://cdn.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/" + + code.toLowerCase() + + "_flat.svg", +} + +const emojiCache: Record> = {} + +export function loadEmoji(type: keyof typeof apis, code: string) { + const key = type + ":" + code + if (key in emojiCache) return emojiCache[key] + + if (!type || !apis[type]) { + type = "twemoji" + } + + const api = apis[type] + if (typeof api === "function") { + return (emojiCache[key] = fetch(api(code)).then((r) => r.text())) + } + return (emojiCache[key] = fetch(`${api}${code.toUpperCase()}.svg`).then((r) => r.text())) +} From 3c8ccde62431321c4ad35093a780a1585fd424dc Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Wed, 5 Mar 2025 17:21:19 -0800 Subject: [PATCH 017/134] chore(og-image): force twemoji for emoji util --- quartz/components/Head.tsx | 13 +++++-------- quartz/util/emoji.ts | 38 +++++--------------------------------- 2 files changed, 10 insertions(+), 41 deletions(-) diff --git a/quartz/components/Head.tsx b/quartz/components/Head.tsx index 1aa8cbe00..a1fb0f63c 100644 --- a/quartz/components/Head.tsx +++ b/quartz/components/Head.tsx @@ -29,15 +29,12 @@ async function generateSocialImage( width, height, fonts, - // `code` will be the detected language code, `emoji` if it's an Emoji, or `unknown` if not able to tell. - // `segment` will be the content to render. - loadAdditionalAsset: async (code: string, segment: string) => { - if (code === "emoji") { - // if segment is an emoji, load the image. - return `data:image/svg+xml;base64,${btoa(await loadEmoji("twemoji", getIconCode(segment)))}` + loadAdditionalAsset: async (languageCode: string, segment: string) => { + if (languageCode === "emoji") { + return `data:image/svg+xml;base64,${btoa(await loadEmoji(getIconCode(segment)))}` } - // if segment is normal text - return code + + return languageCode }, }) diff --git a/quartz/util/emoji.ts b/quartz/util/emoji.ts index 231294348..e38618d1d 100644 --- a/quartz/util/emoji.ts +++ b/quartz/util/emoji.ts @@ -1,10 +1,3 @@ -/** - * Modified version of https://unpkg.com/twemoji@13.1.0/dist/twemoji.esm.js. - * Ported from https://github.com/vercel/satori/blob/48aea6f812365959c2888a25261c72ce17992c6d/playground/utils/twemoji.ts. - */ - -/*! Copyright Twitter Inc. and other contributors. Licensed under MIT */ - const U200D = String.fromCharCode(8205) const UFE0Fg = /\uFE0F/g @@ -32,35 +25,14 @@ function toCodePoint(unicodeSurrogates: string) { return r.join("-") } -export const apis = { - twemoji: (code: string) => - "https://cdnjs.cloudflare.com/ajax/libs/twemoji/15.1.0/svg/" + code.toLowerCase() + ".svg", - openmoji: "https://cdn.jsdelivr.net/npm/@svgmoji/openmoji@3.2.0/svg/", - blobmoji: "https://cdn.jsdelivr.net/npm/@svgmoji/blob@3.2.0/svg/", - noto: "https://cdn.jsdelivr.net/gh/svgmoji/svgmoji/packages/svgmoji__noto/svg/", - fluent: (code: string) => - "https://cdn.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/" + - code.toLowerCase() + - "_color.svg", - fluentFlat: (code: string) => - "https://cdn.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/" + - code.toLowerCase() + - "_flat.svg", -} - +const twemoji = (code: string) => + `https://cdnjs.cloudflare.com/ajax/libs/twemoji/15.1.0/svg/${code.toLowerCase()}.svg` const emojiCache: Record> = {} -export function loadEmoji(type: keyof typeof apis, code: string) { +export function loadEmoji(code: string) { + const type = "twemoji" const key = type + ":" + code if (key in emojiCache) return emojiCache[key] - if (!type || !apis[type]) { - type = "twemoji" - } - - const api = apis[type] - if (typeof api === "function") { - return (emojiCache[key] = fetch(api(code)).then((r) => r.text())) - } - return (emojiCache[key] = fetch(`${api}${code.toUpperCase()}.svg`).then((r) => r.text())) + return (emojiCache[key] = fetch(twemoji(code)).then((r) => r.text())) } From 5a39719898fe486994750ec24fa430f332fa67eb Mon Sep 17 00:00:00 2001 From: Aaron Pham Date: Wed, 5 Mar 2025 20:33:16 -0500 Subject: [PATCH 018/134] fix(graph): set container as renderGroup to avoid redrawing multiple times (#1736) Signed-off-by: Aaron Pham --- quartz/components/scripts/graph.inline.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/quartz/components/scripts/graph.inline.ts b/quartz/components/scripts/graph.inline.ts index 16ee33f64..83424607b 100644 --- a/quartz/components/scripts/graph.inline.ts +++ b/quartz/components/scripts/graph.inline.ts @@ -370,9 +370,9 @@ async function renderGraph(container: string, fullSlug: FullSlug) { const stage = app.stage stage.interactive = false - const labelsContainer = new Container({ zIndex: 3 }) - const nodesContainer = new Container({ zIndex: 2 }) - const linkContainer = new Container({ zIndex: 1 }) + const labelsContainer = new Container({ zIndex: 3, isRenderGroup: true }) + const nodesContainer = new Container({ zIndex: 2, isRenderGroup: true }) + const linkContainer = new Container({ zIndex: 1, isRenderGroup: true }) stage.addChild(nodesContainer, labelsContainer, linkContainer) for (const n of graphData.nodes) { From 5b13ff21992a61eb8b03670ae1742a72703c2afe Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Wed, 5 Mar 2025 18:16:17 -0800 Subject: [PATCH 019/134] feat: support emitters defining external resources, emit link from contentindex directly --- docs/advanced/making plugins.md | 2 -- quartz/components/Head.tsx | 11 +++++++++-- quartz/components/renderPage.tsx | 1 + .../emitters/{contentIndex.ts => contentIndex.tsx} | 14 ++++++++++++++ quartz/plugins/index.ts | 6 +++++- quartz/plugins/transformers/latex.ts | 2 -- quartz/plugins/types.ts | 4 +++- quartz/util/resources.tsx | 2 ++ 8 files changed, 34 insertions(+), 8 deletions(-) rename quartz/plugins/emitters/{contentIndex.ts => contentIndex.tsx} (94%) diff --git a/docs/advanced/making plugins.md b/docs/advanced/making plugins.md index 3042737a2..8ed533f88 100644 --- a/docs/advanced/making plugins.md +++ b/docs/advanced/making plugins.md @@ -99,8 +99,6 @@ export const Latex: QuartzTransformerPlugin = (opts?: Options) => { }, ], } - } else { - return {} } }, } diff --git a/quartz/components/Head.tsx b/quartz/components/Head.tsx index a1fb0f63c..09156c9ee 100644 --- a/quartz/components/Head.tsx +++ b/quartz/components/Head.tsx @@ -127,7 +127,7 @@ export default (() => { } } - const { css, js } = externalResources + const { css, js, additionalHead } = externalResources const url = new URL(`https://${cfg.baseUrl ?? "example.com"}`) const path = url.pathname as FullSlug @@ -177,7 +177,7 @@ export default (() => { )} - + {/* OG/Twitter meta tags */} @@ -213,6 +213,13 @@ export default (() => { {js .filter((resource) => resource.loadTime === "beforeDOMReady") .map((res) => JSResourceToScriptElement(res, true))} + {additionalHead.map((resource) => { + if (typeof resource === "function") { + return resource(fileData) + } else { + return resource + } + })} ) } diff --git a/quartz/components/renderPage.tsx b/quartz/components/renderPage.tsx index 3914411ac..9cebaa849 100644 --- a/quartz/components/renderPage.tsx +++ b/quartz/components/renderPage.tsx @@ -54,6 +54,7 @@ export function pageResources( }, ...staticResources.js, ], + additionalHead: staticResources.additionalHead, } if (fileData.hasMermaidDiagram) { diff --git a/quartz/plugins/emitters/contentIndex.ts b/quartz/plugins/emitters/contentIndex.tsx similarity index 94% rename from quartz/plugins/emitters/contentIndex.ts rename to quartz/plugins/emitters/contentIndex.tsx index 5d76e087b..bd609b411 100644 --- a/quartz/plugins/emitters/contentIndex.ts +++ b/quartz/plugins/emitters/contentIndex.tsx @@ -182,6 +182,20 @@ export const ContentIndex: QuartzEmitterPlugin> = (opts) => { return emitted }, + externalResources: (ctx) => { + if (opts?.enableRSS) { + return { + additionalHead: [ + , + ], + } + } + }, getQuartzComponents: () => [], } } diff --git a/quartz/plugins/index.ts b/quartz/plugins/index.ts index df9fd1d24..c41157c2b 100644 --- a/quartz/plugins/index.ts +++ b/quartz/plugins/index.ts @@ -6,9 +6,10 @@ export function getStaticResourcesFromPlugins(ctx: BuildCtx) { const staticResources: StaticResources = { css: [], js: [], + additionalHead: [], } - for (const transformer of ctx.cfg.plugins.transformers) { + for (const transformer of [...ctx.cfg.plugins.transformers, ...ctx.cfg.plugins.emitters]) { const res = transformer.externalResources ? transformer.externalResources(ctx) : {} if (res?.js) { staticResources.js.push(...res.js) @@ -16,6 +17,9 @@ export function getStaticResourcesFromPlugins(ctx: BuildCtx) { if (res?.css) { staticResources.css.push(...res.css) } + if (res?.additionalHead) { + staticResources.additionalHead.push(...res.additionalHead) + } } // if serving locally, listen for rebuilds and reload the page diff --git a/quartz/plugins/transformers/latex.ts b/quartz/plugins/transformers/latex.ts index 26913bac3..40939d5e9 100644 --- a/quartz/plugins/transformers/latex.ts +++ b/quartz/plugins/transformers/latex.ts @@ -59,8 +59,6 @@ export const Latex: QuartzTransformerPlugin> = (opts) => { }, ], } - default: - return { css: [], js: [] } } }, } diff --git a/quartz/plugins/types.ts b/quartz/plugins/types.ts index 667799f4b..283a9999c 100644 --- a/quartz/plugins/types.ts +++ b/quartz/plugins/types.ts @@ -13,6 +13,7 @@ export interface PluginTypes { } type OptionType = object | undefined +type ExternalResourcesFn = (ctx: BuildCtx) => Partial | undefined export type QuartzTransformerPlugin = ( opts?: Options, ) => QuartzTransformerPluginInstance @@ -21,7 +22,7 @@ export type QuartzTransformerPluginInstance = { textTransform?: (ctx: BuildCtx, src: string) => string markdownPlugins?: (ctx: BuildCtx) => PluggableList htmlPlugins?: (ctx: BuildCtx) => PluggableList - externalResources?: (ctx: BuildCtx) => Partial + externalResources?: ExternalResourcesFn } export type QuartzFilterPlugin = ( @@ -44,4 +45,5 @@ export type QuartzEmitterPluginInstance = { content: ProcessedContent[], resources: StaticResources, ): Promise> + externalResources?: ExternalResourcesFn } diff --git a/quartz/util/resources.tsx b/quartz/util/resources.tsx index 72ae9e63e..2ec856191 100644 --- a/quartz/util/resources.tsx +++ b/quartz/util/resources.tsx @@ -1,5 +1,6 @@ import { randomUUID } from "crypto" import { JSX } from "preact/jsx-runtime" +import { QuartzPluginData } from "../plugins/vfile" export type JSResource = { loadTime: "beforeDOMReady" | "afterDOMReady" @@ -62,4 +63,5 @@ export function CSSResourceToStyleElement(resource: CSSResource, preserve?: bool export interface StaticResources { css: CSSResource[] js: JSResource[] + additionalHead: (JSX.Element | ((pageData: QuartzPluginData) => JSX.Element))[] } From 2213424195b6ba761a6bf3343afca43b102d06b3 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Wed, 5 Mar 2025 18:34:02 -0800 Subject: [PATCH 020/134] docs: make role of getQuartzComponents more clear and also make it optional --- quartz/plugins/emitters/aliases.ts | 4 ---- quartz/plugins/emitters/assets.ts | 3 --- quartz/plugins/emitters/cname.ts | 3 --- quartz/plugins/emitters/componentResources.ts | 5 +---- quartz/plugins/emitters/contentIndex.tsx | 1 - quartz/plugins/emitters/static.ts | 3 --- quartz/plugins/types.ts | 7 ++++++- 7 files changed, 7 insertions(+), 19 deletions(-) diff --git a/quartz/plugins/emitters/aliases.ts b/quartz/plugins/emitters/aliases.ts index b5bfff061..9d12a990c 100644 --- a/quartz/plugins/emitters/aliases.ts +++ b/quartz/plugins/emitters/aliases.ts @@ -6,9 +6,6 @@ import { getAliasSlugs } from "../transformers/frontmatter" export const AliasRedirects: QuartzEmitterPlugin = () => ({ name: "AliasRedirects", - getQuartzComponents() { - return [] - }, async getDependencyGraph(ctx, content, _resources) { const graph = new DepGraph() @@ -22,7 +19,6 @@ export const AliasRedirects: QuartzEmitterPlugin = () => ({ return graph }, async emit(ctx, content, _resources): Promise { - const { argv } = ctx const fps: FilePath[] = [] for (const [_tree, file] of content) { diff --git a/quartz/plugins/emitters/assets.ts b/quartz/plugins/emitters/assets.ts index 036b27da4..bb85080c4 100644 --- a/quartz/plugins/emitters/assets.ts +++ b/quartz/plugins/emitters/assets.ts @@ -15,9 +15,6 @@ const filesToCopy = async (argv: Argv, cfg: QuartzConfig) => { export const Assets: QuartzEmitterPlugin = () => { return { name: "Assets", - getQuartzComponents() { - return [] - }, async getDependencyGraph(ctx, _content, _resources) { const { argv, cfg } = ctx const graph = new DepGraph() diff --git a/quartz/plugins/emitters/cname.ts b/quartz/plugins/emitters/cname.ts index cbed2a8b4..380212dd4 100644 --- a/quartz/plugins/emitters/cname.ts +++ b/quartz/plugins/emitters/cname.ts @@ -11,9 +11,6 @@ export function extractDomainFromBaseUrl(baseUrl: string) { export const CNAME: QuartzEmitterPlugin = () => ({ name: "CNAME", - getQuartzComponents() { - return [] - }, async getDependencyGraph(_ctx, _content, _resources) { return new DepGraph() }, diff --git a/quartz/plugins/emitters/componentResources.ts b/quartz/plugins/emitters/componentResources.ts index b307aad41..6c1e3d0b6 100644 --- a/quartz/plugins/emitters/componentResources.ts +++ b/quartz/plugins/emitters/componentResources.ts @@ -24,7 +24,7 @@ type ComponentResources = { function getComponentResources(ctx: BuildCtx): ComponentResources { const allComponents: Set = new Set() for (const emitter of ctx.cfg.plugins.emitters) { - const components = emitter.getQuartzComponents(ctx) + const components = emitter.getQuartzComponents?.(ctx) ?? [] for (const component of components) { allComponents.add(component) } @@ -200,9 +200,6 @@ function addGlobalPageResources(ctx: BuildCtx, componentResources: ComponentReso export const ComponentResources: QuartzEmitterPlugin = () => { return { name: "ComponentResources", - getQuartzComponents() { - return [] - }, async getDependencyGraph(_ctx, _content, _resources) { return new DepGraph() }, diff --git a/quartz/plugins/emitters/contentIndex.tsx b/quartz/plugins/emitters/contentIndex.tsx index bd609b411..2810039fa 100644 --- a/quartz/plugins/emitters/contentIndex.tsx +++ b/quartz/plugins/emitters/contentIndex.tsx @@ -196,6 +196,5 @@ export const ContentIndex: QuartzEmitterPlugin> = (opts) => { } } }, - getQuartzComponents: () => [], } } diff --git a/quartz/plugins/emitters/static.ts b/quartz/plugins/emitters/static.ts index c52c62879..5545d2ccb 100644 --- a/quartz/plugins/emitters/static.ts +++ b/quartz/plugins/emitters/static.ts @@ -6,9 +6,6 @@ import DepGraph from "../../depgraph" export const Static: QuartzEmitterPlugin = () => ({ name: "Static", - getQuartzComponents() { - return [] - }, async getDependencyGraph({ argv, cfg }, _content, _resources) { const graph = new DepGraph() diff --git a/quartz/plugins/types.ts b/quartz/plugins/types.ts index 283a9999c..e7cfb479f 100644 --- a/quartz/plugins/types.ts +++ b/quartz/plugins/types.ts @@ -39,7 +39,12 @@ export type QuartzEmitterPlugin = ( export type QuartzEmitterPluginInstance = { name: string emit(ctx: BuildCtx, content: ProcessedContent[], resources: StaticResources): Promise - getQuartzComponents(ctx: BuildCtx): QuartzComponent[] + /** + * Returns the components (if any) that are used in rendering the page. + * This helps Quartz optimize the page by only including necessary resources + * for components that are actually used. + */ + getQuartzComponents?: (ctx: BuildCtx) => QuartzComponent[] getDependencyGraph?( ctx: BuildCtx, content: ProcessedContent[], From 6d195fd40a48fe275dc910f7a115e5b2f3c1c056 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Thu, 6 Mar 2025 09:21:50 -0800 Subject: [PATCH 021/134] feat: font specification flexibility --- quartz/components/Head.tsx | 6 ++-- quartz/util/theme.ts | 61 +++++++++++++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/quartz/components/Head.tsx b/quartz/components/Head.tsx index 09156c9ee..b6a7e8d07 100644 --- a/quartz/components/Head.tsx +++ b/quartz/components/Head.tsx @@ -1,7 +1,7 @@ import { i18n } from "../i18n" import { FullSlug, joinSegments, pathToRoot } from "../util/path" import { CSSResourceToStyleElement, JSResourceToScriptElement } from "../util/resources" -import { googleFontHref } from "../util/theme" +import { getFontSpecificationName, googleFontHref } from "../util/theme" import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" import satori, { SatoriOptions } from "satori" import { loadEmoji, getIconCode } from "../util/emoji" @@ -77,7 +77,9 @@ export default (() => { // Memoize google fonts if (!fontsPromise && cfg.generateSocialImages) { - fontsPromise = getSatoriFont(cfg.theme.typography.header, cfg.theme.typography.body) + const headerFont = getFontSpecificationName(cfg.theme.typography.header) + const bodyFont = getFontSpecificationName(cfg.theme.typography.body) + fontsPromise = getSatoriFont(headerFont, bodyFont) } const slug = fileData.filePath diff --git a/quartz/util/theme.ts b/quartz/util/theme.ts index 0c903066f..06ddd8c43 100644 --- a/quartz/util/theme.ts +++ b/quartz/util/theme.ts @@ -15,11 +15,19 @@ interface Colors { darkMode: ColorScheme } +type FontSpecification = + | string + | { + name: string + weights?: number[] + includeItalic?: boolean + } + export interface Theme { typography: { - header: string - body: string - code: string + header: FontSpecification + body: FontSpecification + code: FontSpecification } cdnCaching: boolean colors: Colors @@ -32,9 +40,54 @@ const DEFAULT_SANS_SERIF = 'system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"' const DEFAULT_MONO = "ui-monospace, SFMono-Regular, SF Mono, Menlo, monospace" +export function getFontSpecificationName(spec: FontSpecification): string { + if (typeof spec === "string") { + return spec + } + + return spec.name +} + +function formatFontSpecification(type: "header" | "body" | "code", spec: FontSpecification) { + if (typeof spec === "string") { + spec = { name: spec } + } + + const defaultIncludeWeights = type === "header" ? [400, 700] : [400, 600] + const defaultIncludeItalic = type === "body" + const weights = spec.weights ?? defaultIncludeWeights + const italic = spec.includeItalic ?? defaultIncludeItalic + + const features: string[] = [] + if (italic) { + features.push("ital") + } + + if (weights.length > 1) { + const weightSpec = italic + ? weights + .flatMap((w) => [`0,${w}`, `1,${w}`]) + .sort() + .join(";") + : weights.join(";") + + features.push(`wght@${weightSpec}`) + } + + if (features.length > 0) { + return `${spec.name}:${features.join(",")}` + } + + return spec.name +} + export function googleFontHref(theme: Theme) { const { code, header, body } = theme.typography - return `https://fonts.googleapis.com/css2?family=${code}&family=${header}:wght@400;700&family=${body}:ital,wght@0,400;0,600;1,400;1,600&display=swap` + const headerFont = formatFontSpecification("header", header) + const bodyFont = formatFontSpecification("body", body) + const codeFont = formatFontSpecification("code", code) + + return `https://fonts.googleapis.com/css2?family=${bodyFont}&family=${headerFont}&family=${codeFont}&display=swap` } export function joinStyles(theme: Theme, ...stylesheet: string[]) { From cc9704becc78d9ab15abda165799b3c773f2ca2b Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Thu, 6 Mar 2025 09:41:26 -0800 Subject: [PATCH 022/134] chore(deps): bump deps, silence internal punycode deprecation --- package-lock.json | 15 ++++++++------- quartz/bootstrap-cli.mjs | 2 +- quartz/plugins/emitters/contentPage.tsx | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1ca894a08..66d4898a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -90,7 +90,7 @@ "typescript": "^5.8.2" }, "engines": { - "node": "20 || >=22", + "node": ">=20", "npm": ">=9.3.1" } }, @@ -2385,9 +2385,10 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -3955,9 +3956,9 @@ } }, "node_modules/katex": { - "version": "0.16.11", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.11.tgz", - "integrity": "sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ==", + "version": "0.16.21", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.21.tgz", + "integrity": "sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A==", "funding": [ "https://opencollective.com/katex", "https://github.com/sponsors/katex" diff --git a/quartz/bootstrap-cli.mjs b/quartz/bootstrap-cli.mjs index 35d06af77..69b5aa157 100755 --- a/quartz/bootstrap-cli.mjs +++ b/quartz/bootstrap-cli.mjs @@ -1,4 +1,4 @@ -#!/usr/bin/env node +#!/usr/bin/env node --no-deprecation import yargs from "yargs" import { hideBin } from "yargs/helpers" import { diff --git a/quartz/plugins/emitters/contentPage.tsx b/quartz/plugins/emitters/contentPage.tsx index 8788f331d..f59ff6bf5 100644 --- a/quartz/plugins/emitters/contentPage.tsx +++ b/quartz/plugins/emitters/contentPage.tsx @@ -131,7 +131,7 @@ export const ContentPage: QuartzEmitterPlugin> = (userOp if (!containsIndex && !ctx.argv.fastRebuild) { console.log( chalk.yellow( - `\nWarning: you seem to be missing an \`index.md\` home page file at the root of your \`${ctx.argv.directory}\` folder. This may cause errors when deploying.`, + `\nWarning: you seem to be missing an \`index.md\` home page file at the root of your \`${ctx.argv.directory}\` folder (\`${path.join(ctx.argv.directory, "index.md")} does not exist\`). This may cause errors when deploying.`, ), ) } From a201105442c3603a34cb609b70cef71072e71392 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Thu, 6 Mar 2025 10:01:25 -0800 Subject: [PATCH 023/134] fix(docker): instructions + bump deps + bind mount (#1809) * fix docker * test with docs folder --- .github/workflows/ci.yaml | 2 +- Dockerfile | 4 ++-- docs/features/Docker Support.md | 2 +- quartz/bootstrap-cli.mjs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f0fc1fd18..2387e7a5e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -45,7 +45,7 @@ jobs: run: npm test - name: Ensure Quartz builds, check bundle info - run: npx quartz build --bundleInfo + run: npx quartz build --bundleInfo -d docs publish-tag: if: ${{ github.repository == 'jackyzha0/quartz' && github.ref == 'refs/heads/v4' }} diff --git a/Dockerfile b/Dockerfile index 4493853e2..f8a6f2684 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,10 @@ -FROM node:20-slim AS builder +FROM node:22-slim AS builder WORKDIR /usr/src/app COPY package.json . COPY package-lock.json* . RUN npm ci -FROM node:20-slim +FROM node:22-slim WORKDIR /usr/src/app COPY --from=builder /usr/src/app/ /usr/src/app/ COPY . . diff --git a/docs/features/Docker Support.md b/docs/features/Docker Support.md index cf73b7fcc..a31fb5b45 100644 --- a/docs/features/Docker Support.md +++ b/docs/features/Docker Support.md @@ -3,5 +3,5 @@ Quartz comes shipped with a Docker image that will allow you to preview your Qua You can run the below one-liner to run Quartz in Docker. ```sh -docker run --rm -itp 8080:8080 $(docker build -q .) +docker run --rm -itp 8080:8080 -p 3001:3001 -v ./content:/usr/src/app/content $(docker build -q .) ``` diff --git a/quartz/bootstrap-cli.mjs b/quartz/bootstrap-cli.mjs index 69b5aa157..8b0b9268f 100755 --- a/quartz/bootstrap-cli.mjs +++ b/quartz/bootstrap-cli.mjs @@ -1,4 +1,4 @@ -#!/usr/bin/env node --no-deprecation +#!/usr/bin/env -S node --no-deprecation import yargs from "yargs" import { hideBin } from "yargs/helpers" import { From 5480269d38ffaff7ffd6576d9a9407430429fb2d Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Sun, 9 Mar 2025 14:58:26 -0700 Subject: [PATCH 024/134] perf(explorer): client side explorer (#1810) * start work on client side explorer * fix tests * fmt * generic test flag * add prenav hook * add highlight class * make flex more consistent, remove transition * open folders that are prefixes of current path * make mobile look nice * more style fixes --- docs/advanced/creating components.md | 12 + index.d.ts | 1 + package.json | 2 +- quartz.config.ts | 2 +- quartz/components/Backlinks.tsx | 6 +- quartz/components/Explorer.tsx | 152 +++---- quartz/components/ExplorerNode.tsx | 242 ----------- quartz/components/OverflowList.tsx | 39 ++ quartz/components/TableOfContents.tsx | 7 +- quartz/components/renderPage.tsx | 3 +- quartz/components/scripts/explorer.inline.ts | 407 +++++++++++-------- quartz/components/scripts/spa.inline.ts | 6 +- quartz/components/scripts/toc.inline.ts | 2 - quartz/components/scripts/util.ts | 1 + quartz/components/styles/backlinks.scss | 22 - quartz/components/styles/darkmode.scss | 1 + quartz/components/styles/explorer.scss | 186 ++++----- quartz/components/styles/toc.scss | 29 +- quartz/plugins/emitters/contentIndex.tsx | 2 + quartz/styles/base.scss | 23 +- quartz/util/clone.ts | 3 + quartz/util/fileTrie.test.ts | 190 +++++++++ quartz/util/fileTrie.ts | 128 ++++++ quartz/util/path.ts | 5 +- 24 files changed, 797 insertions(+), 674 deletions(-) delete mode 100644 quartz/components/ExplorerNode.tsx create mode 100644 quartz/components/OverflowList.tsx create mode 100644 quartz/util/clone.ts create mode 100644 quartz/util/fileTrie.test.ts create mode 100644 quartz/util/fileTrie.ts diff --git a/docs/advanced/creating components.md b/docs/advanced/creating components.md index 628d5aa29..369405b07 100644 --- a/docs/advanced/creating components.md +++ b/docs/advanced/creating components.md @@ -161,6 +161,18 @@ document.addEventListener("nav", () => { }) ``` +You can also add the equivalent of a `beforeunload` event for [[SPA Routing]] via the `prenav` event. + +```ts +document.addEventListener("prenav", () => { + // executed after an SPA navigation is triggered but + // before the page is replaced + // one usage pattern is to store things in sessionStorage + // in the prenav and then conditionally load then in the consequent + // nav +}) +``` + It is best practice to track any event handlers via `window.addCleanup` to prevent memory leaks. This will get called on page navigation. diff --git a/index.d.ts b/index.d.ts index a6c594fff..8e524af03 100644 --- a/index.d.ts +++ b/index.d.ts @@ -5,6 +5,7 @@ declare module "*.scss" { // dom custom event interface CustomEventMap { + prenav: CustomEvent<{}> nav: CustomEvent<{ url: FullSlug }> themechange: CustomEvent<{ theme: "light" | "dark" }> } diff --git a/package.json b/package.json index 92872d792..81e5dbf10 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "docs": "npx quartz build --serve -d docs", "check": "tsc --noEmit && npx prettier . --check", "format": "npx prettier . --write", - "test": "tsx ./quartz/util/path.test.ts && tsx ./quartz/depgraph.test.ts", + "test": "tsx --test", "profile": "0x -D prof ./quartz/bootstrap-cli.mjs build --concurrency=1" }, "engines": { diff --git a/quartz.config.ts b/quartz.config.ts index 0cd7e946c..51a75515d 100644 --- a/quartz.config.ts +++ b/quartz.config.ts @@ -8,7 +8,7 @@ import * as Plugin from "./quartz/plugins" */ const config: QuartzConfig = { configuration: { - pageTitle: "🪴 Quartz 4", + pageTitle: "Quartz 4", pageTitleSuffix: "", enableSPA: true, enablePopovers: true, diff --git a/quartz/components/Backlinks.tsx b/quartz/components/Backlinks.tsx index e99055e31..735afe727 100644 --- a/quartz/components/Backlinks.tsx +++ b/quartz/components/Backlinks.tsx @@ -3,6 +3,7 @@ import style from "./styles/backlinks.scss" import { resolveRelative, simplifySlug } from "../util/path" import { i18n } from "../i18n" import { classNames } from "../util/lang" +import OverflowList from "./OverflowList" interface BacklinksOptions { hideWhenEmpty: boolean @@ -29,7 +30,7 @@ export default ((opts?: Partial) => { return (

{i18n(cfg.locale).components.backlinks.title}

-
    + {backlinkFiles.length > 0 ? ( backlinkFiles.map((f) => (
  • @@ -41,12 +42,13 @@ export default ((opts?: Partial) => { ) : (
  • {i18n(cfg.locale).components.backlinks.noBacklinksFound}
  • )} -
+
) } Backlinks.css = style + Backlinks.afterDOMLoaded = OverflowList.afterDOMLoaded("backlinks-ul") return Backlinks }) satisfies QuartzComponentConstructor diff --git a/quartz/components/Explorer.tsx b/quartz/components/Explorer.tsx index ac276a8bc..9c1fbdcfe 100644 --- a/quartz/components/Explorer.tsx +++ b/quartz/components/Explorer.tsx @@ -3,22 +3,34 @@ import style from "./styles/explorer.scss" // @ts-ignore import script from "./scripts/explorer.inline" -import { ExplorerNode, FileNode, Options } from "./ExplorerNode" -import { QuartzPluginData } from "../plugins/vfile" import { classNames } from "../util/lang" import { i18n } from "../i18n" +import { FileTrieNode } from "../util/fileTrie" +import OverflowList from "./OverflowList" -// Options interface defined in `ExplorerNode` to avoid circular dependency -const defaultOptions = { - folderClickBehavior: "collapse", +type OrderEntries = "sort" | "filter" | "map" + +export interface Options { + title?: string + folderDefaultState: "collapsed" | "open" + folderClickBehavior: "collapse" | "link" + useSavedState: boolean + sortFn: (a: FileTrieNode, b: FileTrieNode) => number + filterFn: (node: FileTrieNode) => boolean + mapFn: (node: FileTrieNode) => void + order: OrderEntries[] +} + +const defaultOptions: Options = { folderDefaultState: "collapsed", + folderClickBehavior: "collapse", useSavedState: true, mapFn: (node) => { return node }, sortFn: (a, b) => { - // Sort order: folders first, then files. Sort folders and files alphabetically - if ((!a.file && !b.file) || (a.file && b.file)) { + // Sort order: folders first, then files. Sort folders and files alphabeticall + if ((!a.isFolder && !b.isFolder) || (a.isFolder && b.isFolder)) { // numeric: true: Whether numeric collation should be used, such that "1" < "2" < "10" // sensitivity: "base": Only strings that differ in base letters compare as unequal. Examples: a ≠ b, a = á, a = A return a.displayName.localeCompare(b.displayName, undefined, { @@ -27,75 +39,44 @@ const defaultOptions = { }) } - if (a.file && !b.file) { + if (!a.isFolder && b.isFolder) { return 1 } else { return -1 } }, - filterFn: (node) => node.name !== "tags", + filterFn: (node) => node.slugSegment !== "tags", order: ["filter", "map", "sort"], -} satisfies Options +} + +export type FolderState = { + path: string + collapsed: boolean +} export default ((userOpts?: Partial) => { - // Parse config const opts: Options = { ...defaultOptions, ...userOpts } - // memoized - let fileTree: FileNode - let jsonTree: string - let lastBuildId: string = "" - - function constructFileTree(allFiles: QuartzPluginData[]) { - // Construct tree from allFiles - fileTree = new FileNode("") - allFiles.forEach((file) => fileTree.add(file)) - - // Execute all functions (sort, filter, map) that were provided (if none were provided, only default "sort" is applied) - if (opts.order) { - // Order is important, use loop with index instead of order.map() - for (let i = 0; i < opts.order.length; i++) { - const functionName = opts.order[i] - if (functionName === "map") { - fileTree.map(opts.mapFn) - } else if (functionName === "sort") { - fileTree.sort(opts.sortFn) - } else if (functionName === "filter") { - fileTree.filter(opts.filterFn) - } - } - } - - // Get all folders of tree. Initialize with collapsed state - // Stringify to pass json tree as data attribute ([data-tree]) - const folders = fileTree.getFolderPaths(opts.folderDefaultState === "collapsed") - jsonTree = JSON.stringify(folders) - } - - const Explorer: QuartzComponent = ({ - ctx, - cfg, - allFiles, - displayClass, - fileData, - }: QuartzComponentProps) => { - if (ctx.buildId !== lastBuildId) { - lastBuildId = ctx.buildId - constructFileTree(allFiles) - } + const Explorer: QuartzComponent = ({ cfg, displayClass }: QuartzComponentProps) => { return ( -
+
-
-
    - -
  • -
+
+
+ +
) } Explorer.css = style - Explorer.afterDOMLoaded = script + Explorer.afterDOMLoaded = script + OverflowList.afterDOMLoaded("explorer-ul") return Explorer }) satisfies QuartzComponentConstructor diff --git a/quartz/components/ExplorerNode.tsx b/quartz/components/ExplorerNode.tsx deleted file mode 100644 index e57d67715..000000000 --- a/quartz/components/ExplorerNode.tsx +++ /dev/null @@ -1,242 +0,0 @@ -// @ts-ignore -import { QuartzPluginData } from "../plugins/vfile" -import { - joinSegments, - resolveRelative, - clone, - simplifySlug, - SimpleSlug, - FilePath, -} from "../util/path" - -type OrderEntries = "sort" | "filter" | "map" - -export interface Options { - title?: string - folderDefaultState: "collapsed" | "open" - folderClickBehavior: "collapse" | "link" - useSavedState: boolean - sortFn: (a: FileNode, b: FileNode) => number - filterFn: (node: FileNode) => boolean - mapFn: (node: FileNode) => void - order: OrderEntries[] -} - -type DataWrapper = { - file: QuartzPluginData - path: string[] -} - -export type FolderState = { - path: string - collapsed: boolean -} - -function getPathSegment(fp: FilePath | undefined, idx: number): string | undefined { - if (!fp) { - return undefined - } - - return fp.split("/").at(idx) -} - -// Structure to add all files into a tree -export class FileNode { - children: Array - name: string // this is the slug segment - displayName: string - file: QuartzPluginData | null - depth: number - - constructor(slugSegment: string, displayName?: string, file?: QuartzPluginData, depth?: number) { - this.children = [] - this.name = slugSegment - this.displayName = displayName ?? file?.frontmatter?.title ?? slugSegment - this.file = file ? clone(file) : null - this.depth = depth ?? 0 - } - - private insert(fileData: DataWrapper) { - if (fileData.path.length === 0) { - return - } - - const nextSegment = fileData.path[0] - - // base case, insert here - if (fileData.path.length === 1) { - if (nextSegment === "") { - // index case (we are the root and we just found index.md), set our data appropriately - const title = fileData.file.frontmatter?.title - if (title && title !== "index") { - this.displayName = title - } - } else { - // direct child - this.children.push(new FileNode(nextSegment, undefined, fileData.file, this.depth + 1)) - } - - return - } - - // find the right child to insert into - fileData.path = fileData.path.splice(1) - const child = this.children.find((c) => c.name === nextSegment) - if (child) { - child.insert(fileData) - return - } - - const newChild = new FileNode( - nextSegment, - getPathSegment(fileData.file.relativePath, this.depth), - undefined, - this.depth + 1, - ) - newChild.insert(fileData) - this.children.push(newChild) - } - - // Add new file to tree - add(file: QuartzPluginData) { - this.insert({ file: file, path: simplifySlug(file.slug!).split("/") }) - } - - /** - * Filter FileNode tree. Behaves similar to `Array.prototype.filter()`, but modifies tree in place - * @param filterFn function to filter tree with - */ - filter(filterFn: (node: FileNode) => boolean) { - this.children = this.children.filter(filterFn) - this.children.forEach((child) => child.filter(filterFn)) - } - - /** - * Filter FileNode tree. Behaves similar to `Array.prototype.map()`, but modifies tree in place - * @param mapFn function to use for mapping over tree - */ - map(mapFn: (node: FileNode) => void) { - mapFn(this) - this.children.forEach((child) => child.map(mapFn)) - } - - /** - * Get folder representation with state of tree. - * Intended to only be called on root node before changes to the tree are made - * @param collapsed default state of folders (collapsed by default or not) - * @returns array containing folder state for tree - */ - getFolderPaths(collapsed: boolean): FolderState[] { - const folderPaths: FolderState[] = [] - - const traverse = (node: FileNode, currentPath: string) => { - if (!node.file) { - const folderPath = joinSegments(currentPath, node.name) - if (folderPath !== "") { - folderPaths.push({ path: folderPath, collapsed }) - } - - node.children.forEach((child) => traverse(child, folderPath)) - } - } - - traverse(this, "") - return folderPaths - } - - // Sort order: folders first, then files. Sort folders and files alphabetically - /** - * Sorts tree according to sort/compare function - * @param sortFn compare function used for `.sort()`, also used recursively for children - */ - sort(sortFn: (a: FileNode, b: FileNode) => number) { - this.children = this.children.sort(sortFn) - this.children.forEach((e) => e.sort(sortFn)) - } -} - -type ExplorerNodeProps = { - node: FileNode - opts: Options - fileData: QuartzPluginData - fullPath?: string -} - -export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodeProps) { - // Get options - const folderBehavior = opts.folderClickBehavior - const isDefaultOpen = opts.folderDefaultState === "open" - - // Calculate current folderPath - const folderPath = node.name !== "" ? joinSegments(fullPath ?? "", node.name) : "" - const href = resolveRelative(fileData.slug!, folderPath as SimpleSlug) + "/" - - return ( - <> - {node.file ? ( - // Single file node -
  • - - {node.displayName} - -
  • - ) : ( -
  • - {node.name !== "" && ( - // Node with entire folder - // Render svg button + folder name, then children - -
  • - )} - {/* Recursively render children of folder */} -
    -
      - {node.children.map((childNode, i) => ( - - ))} -
    -
    - - )} - - ) -} diff --git a/quartz/components/OverflowList.tsx b/quartz/components/OverflowList.tsx new file mode 100644 index 000000000..d74c5c255 --- /dev/null +++ b/quartz/components/OverflowList.tsx @@ -0,0 +1,39 @@ +import { JSX } from "preact" + +const OverflowList = ({ + children, + ...props +}: JSX.HTMLAttributes & { id: string }) => { + return ( +
      + {children} +
    • +
    + ) +} + +OverflowList.afterDOMLoaded = (id: string) => ` +document.addEventListener("nav", (e) => { + const observer = new IntersectionObserver((entries) => { + for (const entry of entries) { + const parentUl = entry.target.parentElement + if (entry.isIntersecting) { + parentUl.classList.remove("gradient-active") + } else { + parentUl.classList.add("gradient-active") + } + } + }) + + const ul = document.getElementById("${id}") + if (!ul) return + + const end = ul.querySelector(".overflow-end") + if (!end) return + + observer.observe(end) + window.addCleanup(() => observer.disconnect()) +}) +` + +export default OverflowList diff --git a/quartz/components/TableOfContents.tsx b/quartz/components/TableOfContents.tsx index ec457cfe5..485f434a8 100644 --- a/quartz/components/TableOfContents.tsx +++ b/quartz/components/TableOfContents.tsx @@ -6,6 +6,7 @@ import { classNames } from "../util/lang" // @ts-ignore import script from "./scripts/toc.inline" import { i18n } from "../i18n" +import OverflowList from "./OverflowList" interface Options { layout: "modern" | "legacy" @@ -50,7 +51,7 @@ const TableOfContents: QuartzComponent = ({
    - +
    ) } TableOfContents.css = modernStyle -TableOfContents.afterDOMLoaded = script +TableOfContents.afterDOMLoaded = script + OverflowList.afterDOMLoaded("toc-ul") const LegacyTableOfContents: QuartzComponent = ({ fileData, cfg }: QuartzComponentProps) => { if (!fileData.toc) { diff --git a/quartz/components/renderPage.tsx b/quartz/components/renderPage.tsx index 9cebaa849..75ef82b24 100644 --- a/quartz/components/renderPage.tsx +++ b/quartz/components/renderPage.tsx @@ -3,7 +3,8 @@ import { QuartzComponent, QuartzComponentProps } from "./types" import HeaderConstructor from "./Header" import BodyConstructor from "./Body" import { JSResourceToScriptElement, StaticResources } from "../util/resources" -import { clone, FullSlug, RelativeURL, joinSegments, normalizeHastElement } from "../util/path" +import { FullSlug, RelativeURL, joinSegments, normalizeHastElement } from "../util/path" +import { clone } from "../util/clone" import { visit } from "unist-util-visit" import { Root, Element, ElementContent } from "hast" import { GlobalConfiguration } from "../cfg" diff --git a/quartz/components/scripts/explorer.inline.ts b/quartz/components/scripts/explorer.inline.ts index 9c6c0508f..15f3a84dd 100644 --- a/quartz/components/scripts/explorer.inline.ts +++ b/quartz/components/scripts/explorer.inline.ts @@ -1,53 +1,38 @@ -import { FolderState } from "../ExplorerNode" +import { FileTrieNode } from "../../util/fileTrie" +import { FullSlug, resolveRelative, simplifySlug } from "../../util/path" +import { ContentDetails } from "../../plugins/emitters/contentIndex" -// Current state of folders type MaybeHTMLElement = HTMLElement | undefined -let currentExplorerState: FolderState[] -const observer = new IntersectionObserver((entries) => { - // If last element is observed, remove gradient of "overflow" class so element is visible - const explorerUl = document.getElementById("explorer-ul") - if (!explorerUl) return - for (const entry of entries) { - if (entry.isIntersecting) { - explorerUl.classList.add("no-background") - } else { - explorerUl.classList.remove("no-background") - } - } -}) +interface ParsedOptions { + folderClickBehavior: "collapse" | "link" + folderDefaultState: "collapsed" | "open" + useSavedState: boolean + sortFn: (a: FileTrieNode, b: FileTrieNode) => number + filterFn: (node: FileTrieNode) => boolean + mapFn: (node: FileTrieNode) => void + order: "sort" | "filter" | "map"[] +} +type FolderState = { + path: string + collapsed: boolean +} + +let currentExplorerState: Array function toggleExplorer(this: HTMLElement) { - // Toggle collapsed state of entire explorer - this.classList.toggle("collapsed") - - // Toggle collapsed aria state of entire explorer - this.setAttribute( - "aria-expanded", - this.getAttribute("aria-expanded") === "true" ? "false" : "true", - ) - - const content = ( - this.nextElementSibling?.nextElementSibling - ? this.nextElementSibling.nextElementSibling - : this.nextElementSibling - ) as MaybeHTMLElement - if (!content) return - content.classList.toggle("collapsed") - content.classList.toggle("explorer-viewmode") - - // Prevent scroll under - if (document.querySelector("#mobile-explorer")) { - // Disable scrolling on the page when the explorer is opened on mobile - const bodySelector = document.querySelector("#quartz-body") - if (bodySelector) bodySelector.classList.toggle("lock-scroll") + const explorers = document.querySelectorAll(".explorer") + for (const explorer of explorers) { + explorer.classList.toggle("collapsed") + explorer.setAttribute( + "aria-expanded", + explorer.getAttribute("aria-expanded") === "true" ? "false" : "true", + ) } } function toggleFolder(evt: MouseEvent) { evt.stopPropagation() - - // Element that was clicked const target = evt.target as MaybeHTMLElement if (!target) return @@ -55,162 +40,240 @@ function toggleFolder(evt: MouseEvent) { const isSvg = target.nodeName === "svg" // corresponding