From fdb9d001474013b02987f54000ce66aec3c06449 Mon Sep 17 00:00:00 2001 From: Heli-o Date: Fri, 5 Apr 2024 02:03:11 +0200 Subject: [PATCH] upload of all files --- .gitattributes | 1 + .github/FUNDING.yml | 1 + .github/ISSUE_TEMPLATE/bug_report.md | 40 + .github/ISSUE_TEMPLATE/feature_request.md | 19 + .github/dependabot.yml | 11 + .github/workflows/ci.yaml | 71 + .npmrc | 1 + .prettierignore | 3 + .prettierrc | 7 + .vault-stats | 1 + CODE_OF_CONDUCT.md | 90 + Dockerfile | 11 + LICENSE.txt | 21 + content/.gitkeep | 0 content/.trash/[[Links]].md | 0 content/Characters.md | 27 + content/Links.md | 23 + content/index.md | 19 + docker-compose.yml | 7 + docs/advanced/architecture.md | 52 + docs/advanced/creating components.md | 234 + docs/advanced/index.md | 3 + docs/advanced/making plugins.md | 303 + docs/advanced/paths.md | 51 + docs/authoring content.md | 44 + docs/build.md | 23 + docs/configuration.md | 104 + docs/features/Docker Support.md | 7 + docs/features/Latex.md | 82 + docs/features/Mermaid diagrams.md | 34 + docs/features/Obsidian compatibility.md | 17 + docs/features/OxHugo compatibility.md | 29 + docs/features/RSS Feed.md | 5 + docs/features/SPA Routing.md | 7 + docs/features/backlinks.md | 14 + docs/features/breadcrumbs.md | 36 + docs/features/callouts.md | 96 + docs/features/darkmode.md | 23 + docs/features/explorer.md | 319 + docs/features/folder and tag listings.md | 33 + docs/features/full-text search.md | 30 + docs/features/graph view.md | 63 + docs/features/i18n.md | 18 + docs/features/index.md | 3 + docs/features/popover previews.md | 17 + docs/features/private pages.md | 35 + docs/features/recent notes.md | 16 + docs/features/syntax highlighting.md | 133 + docs/features/table of contents.md | 18 + docs/features/upcoming features.md | 23 + docs/features/wikilinks.md | 24 + docs/hosting.md | 270 + docs/images/dns records.png | Bin 0 -> 77842 bytes docs/images/github-init-repo-options.png | Bin 0 -> 91774 bytes docs/images/github-quick-setup.png | Bin 0 -> 153358 bytes docs/images/quartz layout.png | Bin 0 -> 56729 bytes docs/images/quartz transform pipeline.png | Bin 0 -> 73574 bytes docs/index.md | 46 + docs/layout.md | 42 + docs/migrating from Quartz 3.md | 41 + docs/philosophy.md | 47 + docs/plugins/AliasRedirects.md | 37 + docs/plugins/Assets.md | 20 + docs/plugins/CNAME.md | 22 + docs/plugins/ComponentResources.md | 18 + docs/plugins/ContentIndex.md | 26 + docs/plugins/ContentPage.md | 18 + docs/plugins/CrawlLinks.md | 30 + docs/plugins/CreatedModifiedDate.md | 25 + docs/plugins/Description.md | 23 + docs/plugins/ExplicitPublish.md | 18 + docs/plugins/FolderPage.md | 22 + docs/plugins/Frontmatter.md | 24 + docs/plugins/GitHubFlavoredMarkdown.md | 23 + docs/plugins/HardLineBreaks.md | 18 + docs/plugins/Latex.md | 20 + docs/plugins/NotFoundPage.md | 18 + docs/plugins/ObsidianFlavoredMarkdown.md | 34 + docs/plugins/OxHugoFlavoredMarkdown.md | 29 + docs/plugins/RemoveDrafts.md | 18 + docs/plugins/Static.md | 21 + docs/plugins/SyntaxHighlighting.md | 23 + docs/plugins/TableOfContents.md | 26 + docs/plugins/TagPage.md | 20 + docs/plugins/index.md | 3 + docs/setting up your GitHub repository.md | 48 + docs/showcase.md | 31 + docs/tags/component.md | 5 + docs/tags/plugin.md | 3 + docs/upgrading.md | 19 + globals.d.ts | 13 + index.d.ts | 12 + package-lock.json | 6534 +++++++++++++++++ package.json | 109 + quartz.config.ts | 91 + quartz.layout.ts | 48 + quartz/bootstrap-cli.mjs | 41 + quartz/bootstrap-worker.mjs | 7 + quartz/build.ts | 411 ++ quartz/cfg.ts | 73 + quartz/cli/args.js | 108 + quartz/cli/constants.js | 15 + quartz/cli/handlers.js | 544 ++ quartz/cli/helpers.js | 54 + quartz/components/ArticleTitle.tsx | 19 + quartz/components/Backlinks.tsx | 36 + quartz/components/Body.tsx | 13 + quartz/components/Breadcrumbs.tsx | 139 + quartz/components/ContentMeta.tsx | 60 + quartz/components/Darkmode.tsx | 53 + quartz/components/Date.tsx | 31 + quartz/components/DesktopOnly.tsx | 18 + quartz/components/Explorer.tsx | 124 + quartz/components/ExplorerNode.tsx | 248 + quartz/components/Footer.tsx | 30 + quartz/components/Graph.tsx | 105 + quartz/components/Head.tsx | 52 + quartz/components/Header.tsx | 22 + quartz/components/MobileOnly.tsx | 18 + quartz/components/PageList.tsx | 87 + quartz/components/PageTitle.tsx | 22 + quartz/components/RecentNotes.tsx | 89 + quartz/components/Search.tsx | 61 + quartz/components/Spacer.tsx | 8 + quartz/components/TableOfContents.tsx | 89 + quartz/components/TagList.tsx | 58 + quartz/components/index.ts | 45 + quartz/components/pages/404.tsx | 13 + quartz/components/pages/Content.tsx | 11 + quartz/components/pages/FolderContent.tsx | 69 + quartz/components/pages/TagContent.tsx | 113 + quartz/components/renderPage.tsx | 248 + quartz/components/scripts/callout.inline.ts | 44 + quartz/components/scripts/checkbox.inline.ts | 23 + quartz/components/scripts/clipboard.inline.ts | 35 + quartz/components/scripts/darkmode.inline.ts | 40 + quartz/components/scripts/explorer.inline.ts | 132 + quartz/components/scripts/graph.inline.ts | 345 + quartz/components/scripts/popover.inline.ts | 108 + quartz/components/scripts/search.inline.ts | 491 ++ quartz/components/scripts/spa.inline.ts | 187 + quartz/components/scripts/toc.inline.ts | 45 + quartz/components/scripts/util.ts | 25 + quartz/components/styles/backlinks.scss | 20 + quartz/components/styles/breadcrumbs.scss | 22 + quartz/components/styles/clipboard.scss | 36 + quartz/components/styles/contentMeta.scss | 14 + quartz/components/styles/darkmode.scss | 48 + quartz/components/styles/explorer.scss | 148 + quartz/components/styles/footer.scss | 15 + quartz/components/styles/graph.scss | 70 + quartz/components/styles/legacyToc.scss | 27 + quartz/components/styles/listPage.scss | 40 + quartz/components/styles/popover.scss | 83 + quartz/components/styles/recentNotes.scss | 24 + quartz/components/styles/search.scss | 228 + quartz/components/styles/toc.scss | 60 + quartz/components/types.ts | 29 + quartz/depgraph.test.ts | 118 + quartz/depgraph.ts | 228 + quartz/i18n/index.ts | 62 + quartz/i18n/locales/ar-SA.ts | 88 + quartz/i18n/locales/de-DE.ts | 83 + quartz/i18n/locales/definition.ts | 83 + quartz/i18n/locales/en-US.ts | 83 + quartz/i18n/locales/es-ES.ts | 83 + quartz/i18n/locales/fr-FR.ts | 83 + quartz/i18n/locales/hu-HU.ts | 81 + quartz/i18n/locales/it-IT.ts | 83 + quartz/i18n/locales/ja-JP.ts | 81 + quartz/i18n/locales/ko-KR.ts | 81 + quartz/i18n/locales/nl-NL.ts | 85 + quartz/i18n/locales/pt-BR.ts | 83 + quartz/i18n/locales/ro-RO.ts | 84 + quartz/i18n/locales/ru-RU.ts | 95 + quartz/i18n/locales/uk-UA.ts | 83 + quartz/i18n/locales/vi-VN.ts | 83 + quartz/i18n/locales/zh-CN.ts | 81 + quartz/plugins/emitters/404.tsx | 68 + quartz/plugins/emitters/aliases.ts | 81 + quartz/plugins/emitters/assets.ts | 58 + quartz/plugins/emitters/cname.ts | 33 + quartz/plugins/emitters/componentResources.ts | 258 + quartz/plugins/emitters/contentIndex.ts | 185 + quartz/plugins/emitters/contentPage.tsx | 131 + quartz/plugins/emitters/folderPage.tsx | 120 + quartz/plugins/emitters/helpers.ts | 19 + quartz/plugins/emitters/index.ts | 10 + quartz/plugins/emitters/static.ts | 35 + quartz/plugins/emitters/tagPage.tsx | 124 + quartz/plugins/filters/draft.ts | 9 + quartz/plugins/filters/explicit.ts | 8 + quartz/plugins/filters/index.ts | 2 + quartz/plugins/index.ts | 52 + quartz/plugins/transformers/citations.ts | 52 + quartz/plugins/transformers/description.ts | 82 + quartz/plugins/transformers/frontmatter.ts | 98 + quartz/plugins/transformers/gfm.ts | 80 + quartz/plugins/transformers/index.ts | 12 + quartz/plugins/transformers/lastmod.ts | 99 + quartz/plugins/transformers/latex.ts | 45 + quartz/plugins/transformers/linebreaks.ts | 11 + quartz/plugins/transformers/links.ts | 171 + quartz/plugins/transformers/ofm.ts | 712 ++ quartz/plugins/transformers/oxhugofm.ts | 108 + quartz/plugins/transformers/syntax.ts | 33 + quartz/plugins/transformers/toc.ts | 75 + quartz/plugins/types.ts | 47 + quartz/plugins/vfile.ts | 12 + quartz/processors/emit.ts | 33 + quartz/processors/filter.ts | 24 + quartz/processors/parse.ts | 160 + quartz/static/icon.png | Bin 0 -> 17368 bytes quartz/static/og-image.png | Bin 0 -> 39281 bytes quartz/styles/base.scss | 520 ++ quartz/styles/callouts.scss | 162 + quartz/styles/custom.scss | 3 + quartz/styles/syntax.scss | 17 + quartz/styles/variables.scss | 9 + quartz/util/ctx.ts | 20 + quartz/util/escape.ts | 8 + quartz/util/glob.ts | 22 + quartz/util/jsx.tsx | 27 + quartz/util/lang.ts | 13 + quartz/util/log.ts | 28 + quartz/util/path.test.ts | 282 + quartz/util/path.ts | 295 + quartz/util/perf.ts | 19 + quartz/util/resources.tsx | 42 + quartz/util/sourcemap.ts | 18 + quartz/util/theme.ts | 69 + quartz/util/trace.ts | 43 + quartz/worker.ts | 19 + tsconfig.json | 20 + 234 files changed, 22131 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yaml create mode 100644 .npmrc create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 .vault-stats create mode 100644 CODE_OF_CONDUCT.md create mode 100644 Dockerfile create mode 100644 LICENSE.txt create mode 100644 content/.gitkeep create mode 100644 content/.trash/[[Links]].md create mode 100644 content/Characters.md create mode 100644 content/Links.md create mode 100644 content/index.md create mode 100644 docker-compose.yml create mode 100644 docs/advanced/architecture.md create mode 100644 docs/advanced/creating components.md create mode 100644 docs/advanced/index.md create mode 100644 docs/advanced/making plugins.md create mode 100644 docs/advanced/paths.md create mode 100644 docs/authoring content.md create mode 100644 docs/build.md create mode 100644 docs/configuration.md create mode 100644 docs/features/Docker Support.md create mode 100644 docs/features/Latex.md create mode 100644 docs/features/Mermaid diagrams.md create mode 100644 docs/features/Obsidian compatibility.md create mode 100644 docs/features/OxHugo compatibility.md create mode 100644 docs/features/RSS Feed.md create mode 100644 docs/features/SPA Routing.md create mode 100644 docs/features/backlinks.md create mode 100644 docs/features/breadcrumbs.md create mode 100644 docs/features/callouts.md create mode 100644 docs/features/darkmode.md create mode 100644 docs/features/explorer.md create mode 100644 docs/features/folder and tag listings.md create mode 100644 docs/features/full-text search.md create mode 100644 docs/features/graph view.md create mode 100644 docs/features/i18n.md create mode 100644 docs/features/index.md create mode 100644 docs/features/popover previews.md create mode 100644 docs/features/private pages.md create mode 100644 docs/features/recent notes.md create mode 100644 docs/features/syntax highlighting.md create mode 100644 docs/features/table of contents.md create mode 100644 docs/features/upcoming features.md create mode 100644 docs/features/wikilinks.md create mode 100644 docs/hosting.md create mode 100644 docs/images/dns records.png create mode 100644 docs/images/github-init-repo-options.png create mode 100644 docs/images/github-quick-setup.png create mode 100644 docs/images/quartz layout.png create mode 100644 docs/images/quartz transform pipeline.png create mode 100644 docs/index.md create mode 100644 docs/layout.md create mode 100644 docs/migrating from Quartz 3.md create mode 100644 docs/philosophy.md create mode 100644 docs/plugins/AliasRedirects.md create mode 100644 docs/plugins/Assets.md create mode 100644 docs/plugins/CNAME.md create mode 100644 docs/plugins/ComponentResources.md create mode 100644 docs/plugins/ContentIndex.md create mode 100644 docs/plugins/ContentPage.md create mode 100644 docs/plugins/CrawlLinks.md create mode 100644 docs/plugins/CreatedModifiedDate.md create mode 100644 docs/plugins/Description.md create mode 100644 docs/plugins/ExplicitPublish.md create mode 100644 docs/plugins/FolderPage.md create mode 100644 docs/plugins/Frontmatter.md create mode 100644 docs/plugins/GitHubFlavoredMarkdown.md create mode 100644 docs/plugins/HardLineBreaks.md create mode 100644 docs/plugins/Latex.md create mode 100644 docs/plugins/NotFoundPage.md create mode 100644 docs/plugins/ObsidianFlavoredMarkdown.md create mode 100644 docs/plugins/OxHugoFlavoredMarkdown.md create mode 100644 docs/plugins/RemoveDrafts.md create mode 100644 docs/plugins/Static.md create mode 100644 docs/plugins/SyntaxHighlighting.md create mode 100644 docs/plugins/TableOfContents.md create mode 100644 docs/plugins/TagPage.md create mode 100644 docs/plugins/index.md create mode 100644 docs/setting up your GitHub repository.md create mode 100644 docs/showcase.md create mode 100644 docs/tags/component.md create mode 100644 docs/tags/plugin.md create mode 100644 docs/upgrading.md create mode 100644 globals.d.ts create mode 100644 index.d.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 quartz.config.ts create mode 100644 quartz.layout.ts create mode 100644 quartz/bootstrap-cli.mjs create mode 100644 quartz/bootstrap-worker.mjs create mode 100644 quartz/build.ts create mode 100644 quartz/cfg.ts create mode 100644 quartz/cli/args.js create mode 100644 quartz/cli/constants.js create mode 100644 quartz/cli/handlers.js create mode 100644 quartz/cli/helpers.js create mode 100644 quartz/components/ArticleTitle.tsx create mode 100644 quartz/components/Backlinks.tsx create mode 100644 quartz/components/Body.tsx create mode 100644 quartz/components/Breadcrumbs.tsx create mode 100644 quartz/components/ContentMeta.tsx create mode 100644 quartz/components/Darkmode.tsx create mode 100644 quartz/components/Date.tsx create mode 100644 quartz/components/DesktopOnly.tsx create mode 100644 quartz/components/Explorer.tsx create mode 100644 quartz/components/ExplorerNode.tsx create mode 100644 quartz/components/Footer.tsx create mode 100644 quartz/components/Graph.tsx create mode 100644 quartz/components/Head.tsx create mode 100644 quartz/components/Header.tsx create mode 100644 quartz/components/MobileOnly.tsx create mode 100644 quartz/components/PageList.tsx create mode 100644 quartz/components/PageTitle.tsx create mode 100644 quartz/components/RecentNotes.tsx create mode 100644 quartz/components/Search.tsx create mode 100644 quartz/components/Spacer.tsx create mode 100644 quartz/components/TableOfContents.tsx create mode 100644 quartz/components/TagList.tsx create mode 100644 quartz/components/index.ts create mode 100644 quartz/components/pages/404.tsx create mode 100644 quartz/components/pages/Content.tsx create mode 100644 quartz/components/pages/FolderContent.tsx create mode 100644 quartz/components/pages/TagContent.tsx create mode 100644 quartz/components/renderPage.tsx create mode 100644 quartz/components/scripts/callout.inline.ts create mode 100644 quartz/components/scripts/checkbox.inline.ts create mode 100644 quartz/components/scripts/clipboard.inline.ts create mode 100644 quartz/components/scripts/darkmode.inline.ts create mode 100644 quartz/components/scripts/explorer.inline.ts create mode 100644 quartz/components/scripts/graph.inline.ts create mode 100644 quartz/components/scripts/popover.inline.ts create mode 100644 quartz/components/scripts/search.inline.ts create mode 100644 quartz/components/scripts/spa.inline.ts create mode 100644 quartz/components/scripts/toc.inline.ts create mode 100644 quartz/components/scripts/util.ts create mode 100644 quartz/components/styles/backlinks.scss create mode 100644 quartz/components/styles/breadcrumbs.scss create mode 100644 quartz/components/styles/clipboard.scss create mode 100644 quartz/components/styles/contentMeta.scss create mode 100644 quartz/components/styles/darkmode.scss create mode 100644 quartz/components/styles/explorer.scss create mode 100644 quartz/components/styles/footer.scss create mode 100644 quartz/components/styles/graph.scss create mode 100644 quartz/components/styles/legacyToc.scss create mode 100644 quartz/components/styles/listPage.scss create mode 100644 quartz/components/styles/popover.scss create mode 100644 quartz/components/styles/recentNotes.scss create mode 100644 quartz/components/styles/search.scss create mode 100644 quartz/components/styles/toc.scss create mode 100644 quartz/components/types.ts create mode 100644 quartz/depgraph.test.ts create mode 100644 quartz/depgraph.ts create mode 100644 quartz/i18n/index.ts create mode 100644 quartz/i18n/locales/ar-SA.ts create mode 100644 quartz/i18n/locales/de-DE.ts create mode 100644 quartz/i18n/locales/definition.ts create mode 100644 quartz/i18n/locales/en-US.ts create mode 100644 quartz/i18n/locales/es-ES.ts create mode 100644 quartz/i18n/locales/fr-FR.ts create mode 100644 quartz/i18n/locales/hu-HU.ts create mode 100644 quartz/i18n/locales/it-IT.ts create mode 100644 quartz/i18n/locales/ja-JP.ts create mode 100644 quartz/i18n/locales/ko-KR.ts create mode 100644 quartz/i18n/locales/nl-NL.ts create mode 100644 quartz/i18n/locales/pt-BR.ts create mode 100644 quartz/i18n/locales/ro-RO.ts create mode 100644 quartz/i18n/locales/ru-RU.ts create mode 100644 quartz/i18n/locales/uk-UA.ts create mode 100644 quartz/i18n/locales/vi-VN.ts create mode 100644 quartz/i18n/locales/zh-CN.ts create mode 100644 quartz/plugins/emitters/404.tsx create mode 100644 quartz/plugins/emitters/aliases.ts create mode 100644 quartz/plugins/emitters/assets.ts create mode 100644 quartz/plugins/emitters/cname.ts create mode 100644 quartz/plugins/emitters/componentResources.ts create mode 100644 quartz/plugins/emitters/contentIndex.ts create mode 100644 quartz/plugins/emitters/contentPage.tsx create mode 100644 quartz/plugins/emitters/folderPage.tsx create mode 100644 quartz/plugins/emitters/helpers.ts create mode 100644 quartz/plugins/emitters/index.ts create mode 100644 quartz/plugins/emitters/static.ts create mode 100644 quartz/plugins/emitters/tagPage.tsx create mode 100644 quartz/plugins/filters/draft.ts create mode 100644 quartz/plugins/filters/explicit.ts create mode 100644 quartz/plugins/filters/index.ts create mode 100644 quartz/plugins/index.ts create mode 100644 quartz/plugins/transformers/citations.ts create mode 100644 quartz/plugins/transformers/description.ts create mode 100644 quartz/plugins/transformers/frontmatter.ts create mode 100644 quartz/plugins/transformers/gfm.ts create mode 100644 quartz/plugins/transformers/index.ts create mode 100644 quartz/plugins/transformers/lastmod.ts create mode 100644 quartz/plugins/transformers/latex.ts create mode 100644 quartz/plugins/transformers/linebreaks.ts create mode 100644 quartz/plugins/transformers/links.ts create mode 100644 quartz/plugins/transformers/ofm.ts create mode 100644 quartz/plugins/transformers/oxhugofm.ts create mode 100644 quartz/plugins/transformers/syntax.ts create mode 100644 quartz/plugins/transformers/toc.ts create mode 100644 quartz/plugins/types.ts create mode 100644 quartz/plugins/vfile.ts create mode 100644 quartz/processors/emit.ts create mode 100644 quartz/processors/filter.ts create mode 100644 quartz/processors/parse.ts create mode 100644 quartz/static/icon.png create mode 100644 quartz/static/og-image.png create mode 100644 quartz/styles/base.scss create mode 100644 quartz/styles/callouts.scss create mode 100644 quartz/styles/custom.scss create mode 100644 quartz/styles/syntax.scss create mode 100644 quartz/styles/variables.scss create mode 100644 quartz/util/ctx.ts create mode 100644 quartz/util/escape.ts create mode 100644 quartz/util/glob.ts create mode 100644 quartz/util/jsx.tsx create mode 100644 quartz/util/lang.ts create mode 100644 quartz/util/log.ts create mode 100644 quartz/util/path.test.ts create mode 100644 quartz/util/path.ts create mode 100644 quartz/util/perf.ts create mode 100644 quartz/util/resources.tsx create mode 100644 quartz/util/sourcemap.ts create mode 100644 quartz/util/theme.ts create mode 100644 quartz/util/trace.ts create mode 100644 quartz/worker.ts create mode 100644 tsconfig.json diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..40b2d4a --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [jackyzha0] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..9ac527d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,40 @@ +--- +name: Bug report +about: Something about Quartz isn't working the way you expect +title: "" +labels: bug +assignees: "" +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: + +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots and Source** +If applicable, add screenshots to help explain your problem. + +You can help speed up fixing the problem by either + +1. providing a simple reproduction +2. linking to your Quartz repository where the problem can be observed + +**Desktop (please complete the following information):** + +- Quartz Version: [e.g. v4.1.2] +- `node` Version: [e.g. v18.16] +- `npm` version: [e.g. v10.1.0] +- OS: [e.g. iOS] +- Browser [e.g. chrome, safari] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..e766b49 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,19 @@ +--- +name: Feature request +about: Suggest an idea or improvement for Quartz +title: "" +labels: enhancement +assignees: "" +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..42adb44 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..56107cf --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,71 @@ +name: Build and Test + +on: + pull_request: + branches: + - v4 + push: + branches: + - v4 + +jobs: + build-and-test: + if: ${{ github.repository == 'jackyzha0/quartz' }} + strategy: + matrix: + os: [windows-latest, macos-latest, ubuntu-latest] + runs-on: ${{ matrix.os }} + permissions: + contents: write + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: Cache dependencies + uses: actions/cache@v3 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + + - run: npm ci + + - name: Check types and style + run: npm run check + + - name: Test + run: npm test + + - name: Ensure Quartz builds, check bundle info + run: npx quartz build --bundleInfo + + publish-tag: + if: ${{ github.repository == 'jackyzha0/quartz' }} + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Get package version + run: node -p -e '`PACKAGE_VERSION=${require("./package.json").version}`' >> $GITHUB_ENV + - name: Create release tag + uses: pkgdeps/git-tag-action@v2 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + github_repo: ${{ github.repository }} + version: ${{ env.PACKAGE_VERSION }} + git_commit_sha: ${{ github.sha }} + git_tag_prefix: "v" diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..3c0687a --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +public +node_modules +.quartz-cache diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..5788b66 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "printWidth": 100, + "quoteProps": "as-needed", + "trailingComma": "all", + "tabWidth": 2, + "semi": false +} diff --git a/.vault-stats b/.vault-stats new file mode 100644 index 0000000..9367074 --- /dev/null +++ b/.vault-stats @@ -0,0 +1 @@ +{"2024-04-05":{"files":1,"modifiedFiles":{},"words":0,"characters":0,"sentences":0,"totalWords":36,"totalCharacters":203,"totalSentences":3}} \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..887a2c4 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,90 @@ +# Citizen Code of Conduct + +## 1. Purpose + +A primary goal of the Quartz community is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof). + +This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior. + +We invite all those who participate in the Quartz community to help us create safe and positive experiences for everyone. + +## 2. Open [Source/Culture/Tech] Citizenship + +A supplemental goal of this Code of Conduct is to increase open [source/culture/tech] citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community. + +Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society. + +If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, we want to know. + +## 3. Expected Behavior + +The following behaviors are expected and requested of all community members: + +- Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community. +- Exercise consideration and respect in your speech and actions. +- Attempt collaboration before conflict. +- Refrain from demeaning, discriminatory, or harassing behavior and speech. +- Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential. +- Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations. + +## 4. Unacceptable Behavior + +The following behaviors are considered harassment and are unacceptable within our community: + +- Violence, threats of violence or violent language directed against another person. +- Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language. +- Posting or displaying sexually explicit or violent material. +- Posting or threatening to post other people's personally identifying information ("doxing"). +- Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability. +- Inappropriate photography or recording. +- Inappropriate physical contact. You should have someone's consent before touching them. +- Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances. +- Deliberate intimidation, stalking or following (online or in person). +- Advocating for, or encouraging, any of the above behavior. +- Sustained disruption of community events, including talks and presentations. + +## 5. Weapons Policy + +No weapons will be allowed at Quartz community events, community spaces, or in other spaces covered by the scope of this Code of Conduct. Weapons include but are not limited to guns, explosives (including fireworks), and large knives such as those used for hunting or display, as well as any other item used for the purpose of causing injury or harm to others. Anyone seen in possession of one of these items will be asked to leave immediately, and will only be allowed to return without the weapon. Community members are further expected to comply with all state and local laws on this matter. + +## 6. Consequences of Unacceptable Behavior + +Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated. + +Anyone asked to stop unacceptable behavior is expected to comply immediately. + +If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event). + +## 7. Reporting Guidelines + +If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. j.zhao2k19@gmail.com. + +Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress. + +## 8. Addressing Grievances + +If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify @jackyzha0 with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies. + +## 9. Scope + +We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues--online and in-person--as well as in all one-on-one communications pertaining to community business. + +This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of community activities when such behavior has the potential to adversely affect the safety and well-being of community members. + +## 10. Contact info + +j.zhao2k19@gmail.com + +## 11. License and attribution + +The Citizen Code of Conduct is distributed by [Stumptown Syndicate](http://stumptownsyndicate.org) under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/). + +Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy). + +_Revision 2.3. Posted 6 March 2017._ + +_Revision 2.2. Posted 4 February 2016._ + +_Revision 2.1. Posted 23 June 2014._ + +_Revision 2.0, adopted by the [Stumptown Syndicate](http://stumptownsyndicate.org) board on 10 January 2013. Posted 17 March 2013._ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1d9e591 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM node:20-slim as builder +WORKDIR /usr/src/app +COPY package.json . +COPY package-lock.json* . +RUN npm ci + +FROM node:20-slim +WORKDIR /usr/src/app +COPY --from=builder /usr/src/app/ /usr/src/app/ +COPY . . +CMD ["npx", "quartz", "build", "--serve"] diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..147e2ca --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 jackyzha0 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/content/.gitkeep b/content/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/content/.trash/[[Links]].md b/content/.trash/[[Links]].md new file mode 100644 index 0000000..e69de29 diff --git a/content/Characters.md b/content/Characters.md new file mode 100644 index 0000000..5c63a12 --- /dev/null +++ b/content/Characters.md @@ -0,0 +1,27 @@ +Here are my characters + +### Current order of use: +1. Ulysia +2. Helio +3. Scarlet +4. Flow + +# Size Chart for the dragons +![[https://f003.backblazeb2.com/file/Solweaver-Site/Helio+and+Uly+size+ref+ver5.png]] + +# Ulysia +Ulysia is a female silkwing + +### Headshot +![[https://f003.backblazeb2.com/file/Solweaver-Site/Ulysia.png]] +- Artist is [Amoadragon](https://twitter.com/aomadragon) on Twitter +### Ref Sheet +![[https://f003.backblazeb2.com/file/Solweaver-Site/Ulysia_RefSheet_Original.png]] +- Artist of the bases used is [K9Pestilence](https://sta.sh/2rpe0f28aat) + +# Helio +Helio is a western dragon with plasma running inside of him + +### Ref Sheet +![[https://f003.backblazeb2.com/file/Solweaver-Site/Helio_RefSheet_original.jpg]] +- Artist is [@starlyscies](https://www.deviantart.com/starlyscies) \ No newline at end of file diff --git a/content/Links.md b/content/Links.md new file mode 100644 index 0000000..9178e6e --- /dev/null +++ b/content/Links.md @@ -0,0 +1,23 @@ + +## Social +- [Fediverse - Derg.social](https://derg.social/@ulysia) +- [Bluesky](https://bsky.app/profile/nexie.net) +- [Tumblr is tumbling](https://ulysium.tumblr.com) +- [T~~w~~itter](https://twitter.com/nexie_net) +- [VrChat](https://vrchat.com/home/user/usr_b250054c-8dd4-4f55-a2e3-23e9597629ce) +## Messaging + +- [Matrix](https://matrix.to/#/@ulysia:derg.social) +- [Discord](https://discord.com/users/367055831465852931/) +- [Telegram](https://t.me/ulysium_nexie) + +## Others +### Code +- [Github](https://github.com/ulyxie) +- [Scratch cause why not](https://scratch.mit.edu/users/H3li0s/) + +### Misc +- [Spotify Stats (stats.fm)](https://stats.fm/ulysia) +- [Inara aka Elite Dangerous profile](https://inara.cz/elite/cmdr/240202/) +- [Flickr is flickring](https://www.flickr.com/people/195925494@N05/) +- [ToyHouse](https://toyhou.se/Ulysia) diff --git a/content/index.md b/content/index.md new file mode 100644 index 0000000..31381fe --- /dev/null +++ b/content/index.md @@ -0,0 +1,19 @@ +Heyo ya'll,  Ulysia/Helio  here. I am just an IT student that is learning a few things at once. I am jack of all trades when it comes to computers. I can do a little bit of everything. +Terrible graphic design, video editing, programing, etc. +But **I refuse** to do **`websites`** and **`IT Support`**. Fuck those. not literally for voids sake + +Also I am a furry. I am not sorry if it inconveniences you. + +Feel free to DM me on any of the socials listed down below or all in [[Links]]. +I never start conversation so it's up to you to start it. + +- [Fediverse - Derg.social](https://derg.social/@ulysia) +- [Matrix](https://matrix.to/#/@ulysia:derg.social) +- [Bluesky](https://bsky.app/profile/nexie.net) +- [Tumblr is tumbling](https://ulysium.tumblr.com) +- [T~~w~~itter](https://twitter.com/nexie_net) +- [Discord](https://discord.com/users/367055831465852931/) +- [Telegram](https://t.me/ulysium_nexie) +- [Github](https://github.com/ulyxie) + +### Characters can be found [here](Characters) \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f374777 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,7 @@ +version: '3' # Specify a Docker Compose file version + +services: + site: # Name of your service + build: . # Path to your Dockerfile (assumes it's in the same directory) + ports: + - "3000:8080" # Map external port 3000 to internal port 8080 diff --git a/docs/advanced/architecture.md b/docs/advanced/architecture.md new file mode 100644 index 0000000..33da89d --- /dev/null +++ b/docs/advanced/architecture.md @@ -0,0 +1,52 @@ +--- +title: Architecture +--- + +Quartz is a static site generator. How does it work? + +This question is best answered by tracing what happens when a user (you!) runs `npx quartz build` in the command line: + +## On the server + +1. After running `npx quartz build`, npm will look at `package.json` to find the `bin` entry for `quartz` which points at `./quartz/bootstrap-cli.mjs`. +2. This file has a [shebang]() line at the top which tells npm to execute it using Node. +3. `bootstrap-cli.mjs` is responsible for a few things: + 1. Parsing the command-line arguments using [yargs](http://yargs.js.org/). + 2. Transpiling and bundling the rest of Quartz (which is in Typescript) to regular JavaScript using [esbuild](https://esbuild.github.io/). The `esbuild` configuration here is slightly special as it also handles `.scss` file imports using [esbuild-sass-plugin v2](https://www.npmjs.com/package/esbuild-sass-plugin). Additionally, we bundle 'inline' client-side scripts (any `.inline.ts` file) that components declare using a custom `esbuild` plugin that runs another instance of `esbuild` which bundles for the browser instead of `node`. Modules of both types are imported as plain text. + 3. Running the local preview server if `--serve` is set. This starts two servers: + 1. A WebSocket server on port 3001 to handle hot-reload signals. This tracks all inbound connections and sends a 'rebuild' message a server-side change is detected (either content or configuration). + 2. An HTTP file-server on a user defined port (normally 8080) to serve the actual website files. + 4. If the `--serve` flag is set, it also starts a file watcher to detect source-code changes (e.g. anything that is `.ts`, `.tsx`, `.scss`, or packager files). On a change, we rebuild the module (step 2 above) using esbuild's [rebuild API](https://esbuild.github.io/api/#rebuild) which drastically reduces the build times. + 5. After transpiling the main Quartz build module (`quartz/build.ts`), we write it to a cache file `.quartz-cache/transpiled-build.mjs` and then dynamically import this using `await import(cacheFile)`. However, we need to be pretty smart about how to bust Node's [import cache](https://github.com/nodejs/modules/issues/307) so we add a random query string to fake Node into thinking it's a new module. This does, however, cause memory leaks so we just hope that the user doesn't hot-reload their configuration too many times in a single session :)) (it leaks about ~350kB memory on each reload). After importing the module, we then invoke it, passing in the command line arguments we parsed earlier along with a callback function to signal the client to refresh. +4. In `build.ts`, we start by installing source map support manually to account for the query string cache busting hack we introduced earlier. Then, we start processing content: + 1. Clean the output directory. + 2. Recursively glob all files in the `content` folder, respecting the `.gitignore`. + 3. Parse the Markdown files. + 1. Quartz detects the number of threads available and chooses to spawn worker threads if there are >128 pieces of content to parse (rough heuristic). If it needs to spawn workers, it will invoke esbuild again to transpile the worker script `quartz/worker.ts`. Then, a work-stealing [workerpool](https://www.npmjs.com/package/workerpool) is then created and batches of 128 files are assigned to workers. + 2. Each worker (or just the main thread if there is no concurrency) creates a [unified](https://github.com/unifiedjs/unified) parser based off of the plugins defined in the [[configuration]]. + 3. Parsing has three steps: + 1. Read the file into a [vfile](https://github.com/vfile/vfile). + 2. Applied plugin-defined text transformations over the content. + 3. Slugify the file path and store it in the data for the file. See the page on [[paths]] for more details about how path logic works in Quartz (spoiler: its complicated). + 4. Markdown parsing using [remark-parse](https://www.npmjs.com/package/remark-parse) (text to [mdast](https://github.com/syntax-tree/mdast)). + 5. Apply plugin-defined Markdown-to-Markdown transformations. + 6. Convert Markdown into HTML using [remark-rehype](https://github.com/remarkjs/remark-rehype) ([mdast](https://github.com/syntax-tree/mdast) to [hast](https://github.com/syntax-tree/hast)). + 7. Apply plugin-defined HTML-to-HTML transformations. + 4. Filter out unwanted content using plugins. + 5. Emit files using plugins. + 1. Gather all the static resources (e.g. external CSS, JS modules, etc.) each emitter plugin declares. + 2. Emitters that emit HTML files do a bit of extra work here as they need to transform the [hast](https://github.com/syntax-tree/hast) produced in the parse step to JSX. This is done using [hast-util-to-jsx-runtime](https://github.com/syntax-tree/hast-util-to-jsx-runtime) with the [Preact](https://preactjs.com/) runtime. Finally, the JSX is rendered to HTML using [preact-render-to-string](https://github.com/preactjs/preact-render-to-string) which statically renders the JSX to HTML (i.e. doesn't care about `useState`, `useEffect`, or any other React/Preact interactive bits). Here, we also do a bunch of fun stuff like assemble the page [[layout]] from `quartz.layout.ts`, assemble all the inline scripts that actually get shipped to the client, and all the transpiled styles. The bulk of this logic can be found in `quartz/components/renderPage.tsx`. Other fun things of note: + 1. CSS is minified and transformed using [Lightning CSS](https://github.com/parcel-bundler/lightningcss) to add vendor prefixes and do syntax lowering. + 2. Scripts are split into `beforeDOMLoaded` and `afterDOMLoaded` and are inserted in the `` and `` respectively. + 3. Finally, each emitter plugin is responsible for emitting and writing it's own emitted files to disk. + 6. If the `--serve` flag was detected, we also set up another file watcher to detect content changes (only `.md` files). We keep a content map that tracks the parsed AST and plugin data for each slug and update this on file changes. Newly added or modified paths are rebuilt and added to the content map. Then, all the filters and emitters are run over the resulting content map. This file watcher is debounced with a threshold of 250ms. On success, we send a client refresh signal using the passed in callback function. + +## On the client + +1. The browser opens a Quartz page and loads the HTML. The `` also links to page styles (emitted to `public/index.css`) and page-critical JS (emitted to `public/prescript.js`) +2. Then, once the body is loaded, the browser loads the non-critical JS (emitted to `public/postscript.js`) +3. Once the page is done loading, the page will then dispatch a custom synthetic browser event `"nav"`. This is used so client-side scripts declared by components can 'setup' anything that requires access to the page DOM. + 1. If the [[SPA Routing|enableSPA option]] is enabled in the [[configuration]], this `"nav"` event is also fired on any client-navigation to allow for components to unregister and reregister any event handlers and state. + 2. If it's not, we wire up the `"nav"` event to just be fired a single time after page load to allow for consistency across how state is setup across both SPA and non-SPA contexts. + +The architecture and design of the plugin system was intentionally left pretty vague here as this is described in much more depth in the guide on [[making plugins|making your own plugin]]. diff --git a/docs/advanced/creating components.md b/docs/advanced/creating components.md new file mode 100644 index 0000000..27369ab --- /dev/null +++ b/docs/advanced/creating components.md @@ -0,0 +1,234 @@ +--- +title: Creating your own Quartz components +--- + +> [!warning] +> This guide assumes you have experience writing JavaScript and are familiar with TypeScript. + +Normally on the web, we write layout code using HTML which looks something like the following: + +```html +
+

An article header

+

Some content

+
+``` + +This piece of HTML represents an article with a leading header that says "An article header" and a paragraph that contains the text "Some content". This is combined with CSS to style the page and JavaScript to add interactivity. + +However, HTML doesn't let you create reusable templates. If you wanted to create a new page, you would need to copy and paste the above snippet and edit the header and content yourself. This isn't great if we have a lot of content on our site that shares a lot of similar layout. The smart people who created React also had similar complaints and invented the concept of Components -- JavaScript functions that return JSX -- to solve the code duplication problem. + +In effect, components allow you to write a JavaScript function that takes some data and produces HTML as an output. **While Quartz doesn't use React, it uses the same component concept to allow you to easily express layout templates in your Quartz site.** + +## An Example Component + +### Constructor + +Component files are written in `.tsx` files that live in the `quartz/components` folder. These are re-exported in `quartz/components/index.ts` so you can use them in layouts and other components more easily. + +Each component file should have a default export that satisfies the `QuartzComponentConstructor` function signature. It's a function that takes in a single optional parameter `opts` and returns a Quartz Component. The type of the parameters `opts` is defined by the interface `Options` which you as the component creator also decide. + +In your component, you can use the values from the configuration option to change the rendering behaviour inside of your component. For example, the component in the code snippet below will not render if the `favouriteNumber` option is below 0. + +```tsx {11-17} +interface Options { + favouriteNumber: number +} + +const defaultOptions: Options = { + favouriteNumber: 42, +} + +export default ((userOpts?: Options) => { + const opts = { ...userOpts, ...defaultOpts } + function YourComponent(props: QuartzComponentProps) { + if (opts.favouriteNumber < 0) { + return null + } + + return

My favourite number is {opts.favouriteNumber}

+ } + + return YourComponent +}) satisfies QuartzComponentConstructor +``` + +### Props + +The Quartz component itself (lines 11-17 highlighted above) looks like a React component. It takes in properties (sometimes called [props](https://react.dev/learn/passing-props-to-a-component)) and returns JSX. + +All Quartz components accept the same set of props: + +```tsx title="quartz/components/types.ts" +// simplified for sake of demonstration +export type QuartzComponentProps = { + fileData: QuartzPluginData + cfg: GlobalConfiguration + tree: Node + allFiles: QuartzPluginData[] + displayClass?: "mobile-only" | "desktop-only" +} +``` + +- `fileData`: Any metadata [[making plugins|plugins]] may have added to the current page. + - `fileData.slug`: slug of the current page. + - `fileData.frontmatter`: any frontmatter parsed. +- `cfg`: The `configuration` field in `quartz.config.ts`. +- `tree`: the resulting [HTML AST](https://github.com/syntax-tree/hast) after processing and transforming the file. This is useful if you'd like to render the content using [hast-util-to-jsx-runtime](https://github.com/syntax-tree/hast-util-to-jsx-runtime) (you can find an example of this in `quartz/components/pages/Content.tsx`). +- `allFiles`: Metadata for all files that have been parsed. Useful for doing page listings or figuring out the overall site structure. +- `displayClass`: a utility class that indicates a preference from the user about how to render it in a mobile or desktop setting. Helpful if you want to conditionally hide a component on mobile or desktop. + +### Styling + +Quartz components can also define a `.css` property on the actual function component which will get picked up by Quartz. This is expected to be a CSS string which can either be inlined or imported from a `.scss` file. + +Note that inlined styles **must** be plain vanilla CSS: + +```tsx {6-10} title="quartz/components/YourComponent.tsx" +export default (() => { + function YourComponent() { + return

Example Component

+ } + + YourComponent.css = ` + p.red-text { + color: red; + } + ` + + return YourComponent +}) satisfies QuartzComponentConstructor +``` + +Imported styles, however, can be from SCSS files: + +```tsx {1-2,9} title="quartz/components/YourComponent.tsx" +// assuming your stylesheet is in quartz/components/styles/YourComponent.scss +import styles from "./styles/YourComponent.scss" + +export default (() => { + function YourComponent() { + return

Example Component

+ } + + YourComponent.css = styles + return YourComponent +}) satisfies QuartzComponentConstructor +``` + +> [!warning] +> Quartz does not use CSS modules so any styles you declare here apply _globally_. If you only want it to apply to your component, make sure you use specific class names and selectors. + +### Scripts and Interactivity + +What about interactivity? Suppose you want to add an-click handler for example. Like the `.css` property on the component, you can also declare `.beforeDOMLoaded` and `.afterDOMLoaded` properties that are strings that contain the script. + +```tsx title="quartz/components/YourComponent.tsx" +export default (() => { + function YourComponent() { + return + } + + YourComponent.beforeDOM = ` + console.log("hello from before the page loads!") + ` + + YourComponent.afterDOM = ` + document.getElementById('btn').onclick = () => { + alert('button clicked!') + } + ` + return YourComponent +}) satisfies QuartzComponentConstructor +``` + +> [!hint] +> For those coming from React, Quartz components are different from React components in that it only uses JSX for templating and layout. Hooks like `useEffect`, `useState`, etc. are not rendered and other properties that accept functions like `onClick` handlers will not work. Instead, do it using a regular JS script that modifies the DOM element directly. + +As the names suggest, the `.beforeDOMLoaded` scripts are executed _before_ the page is done loading so it doesn't have access to any elements on the page. This is mostly used to prefetch any critical data. + +The `.afterDOMLoaded` script executes once the page has been completely loaded. This is a good place to setup anything that should last for the duration of a site visit (e.g. getting something saved from local storage). + +If you need to create an `afterDOMLoaded` script that depends on _page specific_ elements that may change when navigating to a new page, you can listen for the `"nav"` event that gets fired whenever a page loads (which may happen on navigation if [[SPA Routing]] is enabled). + +```ts +document.addEventListener("nav", () => { + // do page specific logic here + // e.g. attach event listeners + const toggleSwitch = document.querySelector("#switch") as HTMLInputElement + toggleSwitch.addEventListener("change", switchTheme) + window.addCleanup(() => toggleSwitch.removeEventListener("change", switchTheme)) +}) +``` + +It is best practice to track any event handlers via `window.addCleanup` to prevent memory leaks. +This will get called on page navigation. + +#### Importing Code + +Of course, it isn't always practical (nor desired!) to write your code as a string literal in the component. + +Quartz supports importing component code through `.inline.ts` files. + +```tsx title="quartz/components/YourComponent.tsx" +// @ts-ignore: typescript doesn't know about our inline bundling system +// so we need to silence the error +import script from "./scripts/graph.inline" + +export default (() => { + function YourComponent() { + return + } + + YourComponent.afterDOM = script + return YourComponent +}) satisfies QuartzComponentConstructor +``` + +```ts title="quartz/components/scripts/graph.inline.ts" +// any imports here are bundled for the browser +import * as d3 from "d3" + +document.getElementById("btn").onclick = () => { + alert("button clicked!") +} +``` + +Additionally, like what is shown in the example above, you can import packages in `.inline.ts` files. This will be bundled by Quartz and included in the actual script. + +### Using a Component + +After creating your custom component, re-export it in `quartz/components/index.ts`: + +```ts title="quartz/components/index.ts" {4,10} +import ArticleTitle from "./ArticleTitle" +import Content from "./pages/Content" +import Darkmode from "./Darkmode" +import YourComponent from "./YourComponent" + +export { ArticleTitle, Content, Darkmode, YourComponent } +``` + +Then, you can use it like any other component in `quartz.layout.ts` via `Component.YourComponent()`. See the [[configuration#Layout|layout]] section for more details. + +As Quartz components are just functions that return React components, you can compositionally use them in other Quartz components. + +```tsx title="quartz/components/AnotherComponent.tsx" +import YourComponent from "./YourComponent" + +export default (() => { + function AnotherComponent(props: QuartzComponentProps) { + return ( +
+

It's nested!

+ +
+ ) + } + + return AnotherComponent +}) satisfies QuartzComponentConstructor +``` + +> [!hint] +> Look in `quartz/components` for more examples of components in Quartz as reference for your own components! diff --git a/docs/advanced/index.md b/docs/advanced/index.md new file mode 100644 index 0000000..4822589 --- /dev/null +++ b/docs/advanced/index.md @@ -0,0 +1,3 @@ +--- +title: "Advanced" +--- diff --git a/docs/advanced/making plugins.md b/docs/advanced/making plugins.md new file mode 100644 index 0000000..b2bacf0 --- /dev/null +++ b/docs/advanced/making plugins.md @@ -0,0 +1,303 @@ +--- +title: Making your own plugins +--- + +> [!warning] +> This part of the documentation will assume you have working knowledge in TypeScript and will include code snippets that describe the interface of what Quartz plugins should look like. + +Quartz's plugins are a series of transformations over content. This is illustrated in the diagram of the processing pipeline below: + +![[quartz transform pipeline.png]] + +All plugins are defined as a function that takes in a single parameter for options `type OptionType = object | undefined` and return an object that corresponds to the type of plugin it is. + +```ts +type OptionType = object | undefined +type QuartzPlugin = (opts?: Options) => QuartzPluginInstance +type QuartzPluginInstance = + | QuartzTransformerPluginInstance + | QuartzFilterPluginInstance + | QuartzEmitterPluginInstance +``` + +The following sections will go into detail for what methods can be implemented for each plugin type. Before we do that, let's clarify a few more ambiguous types: + +- `BuildCtx` is defined in `quartz/ctx.ts`. It consists of + - `argv`: The command line arguments passed to the Quartz [[build]] command + - `cfg`: The full Quartz [[configuration]] + - `allSlugs`: a list of all the valid content slugs (see [[paths]] for more information on what a `ServerSlug` is) +- `StaticResources` is defined in `quartz/resources.tsx`. It consists of + - `css`: a list of URLs for stylesheets that should be loaded + - `js`: a list of scripts that should be loaded. A script is described with the `JSResource` type which is also defined in `quartz/resources.tsx`. It allows you to define a load time (either before or after the DOM has been loaded), whether it should be a module, and either the source URL or the inline content of the script. + +## Transformers + +Transformers **map** over content, taking a Markdown file and outputting modified content or adding metadata to the file itself. + +```ts +export type QuartzTransformerPluginInstance = { + name: string + textTransform?: (ctx: BuildCtx, src: string | Buffer) => string | Buffer + markdownPlugins?: (ctx: BuildCtx) => PluggableList + htmlPlugins?: (ctx: BuildCtx) => PluggableList + externalResources?: (ctx: BuildCtx) => Partial +} +``` + +All transformer plugins must define at least a `name` field to register the plugin and a few optional functions that allow you to hook into various parts of transforming a single Markdown file. + +- `textTransform` performs a text-to-text transformation _before_ a file is parsed into the [Markdown AST](https://github.com/syntax-tree/mdast). +- `markdownPlugins` defines a list of [remark plugins](https://github.com/remarkjs/remark/blob/main/doc/plugins.md). `remark` is a tool that transforms Markdown to Markdown in a structured way. +- `htmlPlugins` defines a list of [rehype plugins](https://github.com/rehypejs/rehype/blob/main/doc/plugins.md). Similar to how `remark` works, `rehype` is a tool that transforms HTML to HTML in a structured way. +- `externalResources` defines any external resources the plugin may need to load on the client-side for it to work properly. + +Normally for both `remark` and `rehype`, you can find existing plugins that you can use to . If you'd like to create your own `remark` or `rehype` plugin, checkout the [guide to creating a plugin](https://unifiedjs.com/learn/guide/create-a-plugin/) using `unified` (the underlying AST parser and transformer library). + +A good example of a transformer plugin that borrows from the `remark` and `rehype` ecosystems is the [[plugins/Latex|Latex]] plugin: + +```ts title="quartz/plugins/transformers/latex.ts" +import remarkMath from "remark-math" +import rehypeKatex from "rehype-katex" +import rehypeMathjax from "rehype-mathjax/svg" +import { QuartzTransformerPlugin } from "../types" + +interface Options { + renderEngine: "katex" | "mathjax" +} + +export const Latex: QuartzTransformerPlugin = (opts?: Options) => { + const engine = opts?.renderEngine ?? "katex" + return { + name: "Latex", + markdownPlugins() { + return [remarkMath] + }, + htmlPlugins() { + if (engine === "katex") { + // if you need to pass options into a plugin, you + // can use a tuple of [plugin, options] + return [[rehypeKatex, { output: "html" }]] + } else { + return [rehypeMathjax] + } + }, + externalResources() { + if (engine === "katex") { + return { + css: [ + // base css + "https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/katex.min.css", + ], + js: [ + { + // fix copy behaviour: https://github.com/KaTeX/KaTeX/blob/main/contrib/copy-tex/README.md + src: "https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/contrib/copy-tex.min.js", + loadTime: "afterDOMReady", + contentType: "external", + }, + ], + } + } else { + return {} + } + }, + } +} +``` + +Another common thing that transformer plugins will do is parse a file and add extra data for that file: + +```ts +export const AddWordCount: QuartzTransformerPlugin = () => { + return { + name: "AddWordCount", + markdownPlugins() { + return [ + () => { + return (tree, file) => { + // tree is an `mdast` root element + // file is a `vfile` + const text = file.value + const words = text.split(" ").length + file.data.wordcount = words + } + }, + ] + }, + } +} + +// tell typescript about our custom data fields we are adding +// other plugins will then also be aware of this data field +declare module "vfile" { + interface DataMap { + wordcount: number + } +} +``` + +Finally, you can also perform transformations over Markdown or HTML ASTs using the `visit` function from the `unist-util-visit` package or the `findAndReplace` function from the `mdast-util-find-and-replace` package. + +```ts +export const TextTransforms: QuartzTransformerPlugin = () => { + return { + name: "TextTransforms", + markdownPlugins() { + return [() => { + return (tree, file) => { + // replace _text_ with the italics version + findAndReplace(tree, /_(.+)_/, (_value: string, ...capture: string[]) => { + // inner is the text inside of the () of the regex + const [inner] = capture + // return an mdast node + // https://github.com/syntax-tree/mdast + return { + type: "emphasis", + children: [{ type: 'text', value: inner }] + } + }) + + // remove all links (replace with just the link content) + // match by 'type' field on an mdast node + // https://github.com/syntax-tree/mdast#link in this example + visit(tree, "link", (link: Link) => { + return { + type: "paragraph" + children: [{ type: 'text', value: link.title }] + } + }) + } + }] + } + } +} +``` + +All transformer plugins can be found under `quartz/plugins/transformers`. If you decide to write your own transformer plugin, don't forget to re-export it under `quartz/plugins/transformers/index.ts` + +A parting word: transformer plugins are quite complex so don't worry if you don't get them right away. Take a look at the built in transformers and see how they operate over content to get a better sense for how to accomplish what you are trying to do. + +## Filters + +Filters **filter** content, taking the output of all the transformers and determining what files to actually keep and what to discard. + +```ts +export type QuartzFilterPlugin = ( + opts?: Options, +) => QuartzFilterPluginInstance + +export type QuartzFilterPluginInstance = { + name: string + shouldPublish(ctx: BuildCtx, content: ProcessedContent): boolean +} +``` + +A filter plugin must define a `name` field and a `shouldPublish` function that takes in a piece of content that has been processed by all the transformers and returns a `true` or `false` depending on whether it should be passed to the emitter plugins or not. + +For example, here is the built-in plugin for removing drafts: + +```ts title="quartz/plugins/filters/draft.ts" +import { QuartzFilterPlugin } from "../types" + +export const RemoveDrafts: QuartzFilterPlugin<{}> = () => ({ + name: "RemoveDrafts", + shouldPublish(_ctx, [_tree, vfile]) { + // uses frontmatter parsed from transformers + const draftFlag: boolean = vfile.data?.frontmatter?.draft ?? false + return !draftFlag + }, +}) +``` + +## Emitters + +Emitters **reduce** over content, taking in a list of all the transformed and filtered content and creating output files. + +```ts +export type QuartzEmitterPlugin = ( + opts?: Options, +) => QuartzEmitterPluginInstance + +export type QuartzEmitterPluginInstance = { + name: string + emit(ctx: BuildCtx, content: ProcessedContent[], resources: StaticResources): Promise + getQuartzComponents(ctx: BuildCtx): QuartzComponent[] +} +``` + +An emitter plugin must define a `name` field, an `emit` function, and a `getQuartzComponents` function. `emit` is responsible for looking at all the parsed and filtered content and then appropriately creating files and returning a list of paths to files the plugin created. + +Creating new files can be done via regular Node [fs module](https://nodejs.org/api/fs.html) (i.e. `fs.cp` or `fs.writeFile`) or via the `write` function in `quartz/plugins/emitters/helpers.ts` if you are creating files that contain text. `write` has the following signature: + +```ts +export type WriteOptions = (data: { + // the build context + ctx: BuildCtx + // the name of the file to emit (not including the file extension) + slug: ServerSlug + // the file extension + ext: `.${string}` | "" + // the file content to add + content: string +}) => Promise +``` + +This is a thin wrapper around writing to the appropriate output folder and ensuring that intermediate directories exist. If you choose to use the native Node `fs` APIs, ensure you emit to the `argv.output` folder as well. + +If you are creating an emitter plugin that needs to render components, there are three more things to be aware of: + +- Your component should use `getQuartzComponents` to declare a list of `QuartzComponents` that it uses to construct the page. See the page on [[creating components]] for more information. +- You can use the `renderPage` function defined in `quartz/components/renderPage.tsx` to render Quartz components into HTML. +- If you need to render an HTML AST to JSX, you can use the `htmlToJsx` function from `quartz/util/jsx.ts`. An example of this can be found in `quartz/components/pages/Content.tsx`. + +For example, the following is a simplified version of the content page plugin that renders every single page. + +```tsx title="quartz/plugins/emitters/contentPage.tsx" +export const ContentPage: QuartzEmitterPlugin = () => { + // construct the layout + const layout: FullPageLayout = { + ...sharedPageComponents, + ...defaultContentPageLayout, + pageBody: Content(), + } + const { head, header, beforeBody, pageBody, left, right, footer } = layout + return { + name: "ContentPage", + getQuartzComponents() { + return [head, ...header, ...beforeBody, pageBody, ...left, ...right, footer] + }, + async emit(ctx, content, resources, emit): Promise { + const cfg = ctx.cfg.configuration + const fps: FilePath[] = [] + const allFiles = content.map((c) => c[1].data) + for (const [tree, file] of content) { + const slug = canonicalizeServer(file.data.slug!) + const externalResources = pageResources(slug, resources) + const componentData: QuartzComponentProps = { + fileData: file.data, + externalResources, + cfg, + children: [], + tree, + allFiles, + } + + const content = renderPage(cfg, slug, componentData, opts, externalResources) + const fp = await emit({ + content, + slug: file.data.slug!, + ext: ".html", + }) + + fps.push(fp) + } + return fps + }, + } +} +``` + +Note that it takes in a `FullPageLayout` as the options. It's made by combining a `SharedLayout` and a `PageLayout` both of which are provided through the `quartz.layout.ts` file. + +> [!hint] +> Look in `quartz/plugins` for more examples of plugins in Quartz as reference for your own plugins! diff --git a/docs/advanced/paths.md b/docs/advanced/paths.md new file mode 100644 index 0000000..9455b98 --- /dev/null +++ b/docs/advanced/paths.md @@ -0,0 +1,51 @@ +--- +title: Paths in Quartz +--- + +Paths are pretty complex to reason about because, especially for a static site generator, they can come from so many places. + +A full file path to a piece of content? Also a path. What about a slug for a piece of content? Yet another path. + +It would be silly to type these all as `string` and call it a day as it's pretty common to accidentally mistake one type of path for another. Unfortunately, TypeScript does not have [nominal types](https://en.wikipedia.org/wiki/Nominal_type_system) for type aliases meaning even if you made custom types of a server-side slug or a client-slug slug, you can still accidentally assign one to another and TypeScript wouldn't catch it. + +Luckily, we can mimic nominal typing using [brands](https://www.typescriptlang.org/play#example/nominal-typing). + +```typescript +// instead of +type FullSlug = string + +// we do +type FullSlug = string & { __brand: "full" } + +// that way, the following will fail typechecking +const slug: FullSlug = "some random string" +``` + +While this prevents most typing mistakes _within_ our nominal typing system (e.g. mistaking a server slug for a client slug), it doesn't prevent us from _accidentally_ mistaking a string for a client slug when we forcibly cast it. + +Thus, we still need to be careful when casting from a string to one of these nominal types in the 'entrypoints', illustrated with hexagon shapes in the diagram below. + +The following diagram draws the relationships between all the path sources, nominal path types, and what functions in `quartz/path.ts` convert between them. + +```mermaid +graph LR + Browser{{Browser}} --> Window{{Body}} & LinkElement{{Link Element}} + Window --"getFullSlug()"--> FullSlug[Full Slug] + LinkElement --".href"--> Relative[Relative URL] + FullSlug --"simplifySlug()" --> SimpleSlug[Simple Slug] + SimpleSlug --"pathToRoot()"--> Relative + SimpleSlug --"resolveRelative()" --> Relative + MD{{Markdown File}} --> FilePath{{File Path}} & Links[Markdown links] + Links --"transformLink()"--> Relative + FilePath --"slugifyFilePath()"--> FullSlug[Full Slug] + style FullSlug stroke-width:4px +``` + +Here are the main types of slugs with a rough description of each type of path: + +- `FilePath`: a real file path to a file on disk. Cannot be relative and must have a file extension. +- `FullSlug`: cannot be relative and may not have leading or trailing slashes. It can have `index` as it's last segment. Use this wherever possible is it's the most 'general' interpretation of a slug. +- `SimpleSlug`: cannot be relative and shouldn't have `/index` as an ending or a file extension. It _can_ however have a trailing slash to indicate a folder path. +- `RelativeURL`: must start with `.` or `..` to indicate it's a relative URL. Shouldn't have `/index` as an ending or a file extension but can contain a trailing slash. + +To get a clearer picture of how these relate to each other, take a look at the path tests in `quartz/path.test.ts`. diff --git a/docs/authoring content.md b/docs/authoring content.md new file mode 100644 index 0000000..da174f9 --- /dev/null +++ b/docs/authoring content.md @@ -0,0 +1,44 @@ +--- +title: Authoring Content +--- + +All of the content in your Quartz should go in the `/content` folder. The content for the home page of your Quartz lives in `content/index.md`. If you've [[docs/index#🪴 Get Started|setup Quartz]] already, this folder should already be initialized. Any Markdown in this folder will get processed by Quartz. + +It is recommended that you use [Obsidian](https://obsidian.md/) as a way to edit and maintain your Quartz. It comes with a nice editor and graphical interface to preview, edit, and link your local files and attachments. + +Got everything setup? Let's [[build]] and preview your Quartz locally! + +## Syntax + +As Quartz uses Markdown files as the main way of writing content, it fully supports Markdown syntax. By default, Quartz also ships with a few syntax extensions like [Github Flavored Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) (footnotes, strikethrough, tables, tasklists) and [Obsidian Flavored Markdown](https://help.obsidian.md/Editing+and+formatting/Obsidian+Flavored+Markdown) ([[callouts]], [[wikilinks]]). + +Additionally, Quartz also allows you to specify additional metadata in your notes called **frontmatter**. + +```md title="content/note.md" +--- +title: Example Title +draft: false +tags: + - example-tag +--- + +The rest of your content lives here. You can use **Markdown** here :) +``` + +Some common frontmatter fields that are natively supported by Quartz: + +- `title`: Title of the page. If it isn't provided, Quartz will use the name of the file as the title. +- `description`: Description of the page used for link previews. +- `aliases`: Other names for this note. This is a list of strings. +- `tags`: Tags for this note. +- `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. + +## Syncing your Content + +When your Quartz is at a point you're happy with, you can save your changes to GitHub. +First, make sure you've [[setting up your GitHub repository|already setup your GitHub repository]] and then do `npx quartz sync`. + +## Customization + +Frontmatter parsing for `title`, `tags`, `aliases` and `cssclasses` is a functionality of the [[Frontmatter]] plugin, `date` is handled by the [[CreatedModifiedDate]] plugin and `description` by the [[Description]] plugin. See the plugin pages for customization options. diff --git a/docs/build.md b/docs/build.md new file mode 100644 index 0000000..a158378 --- /dev/null +++ b/docs/build.md @@ -0,0 +1,23 @@ +--- +title: "Building your Quartz" +--- + +Once you've [[docs/index#🪴 Get Started|initialized]] Quartz, let's see what it looks like locally: + +```bash +npx quartz build --serve +``` + +This will start a local web server to run your Quartz on your computer. Open a web browser and visit `http://localhost:8080/` to view it. + +> [!hint] Flags and options +> For full help options, you can run `npx quartz build --help`. +> +> Most of these have sensible defaults but you can override them if you have a custom setup: +> +> - `-d` or `--directory`: the content folder. This is normally just `content` +> - `-v` or `--verbose`: print out extra logging information +> - `-o` or `--output`: the output folder. This is normally just `public` +> - `--serve`: run a local hot-reloading server to preview your Quartz +> - `--port`: what port to run the local preview server on +> - `--concurrency`: how many threads to use to parse notes diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..64968fb --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,104 @@ +--- +title: Configuration +--- + +Quartz is meant to be extremely configurable, even if you don't know any coding. Most of the configuration you should need can be done by just editing `quartz.config.ts` or changing [[layout|the layout]] in `quartz.layout.ts`. + +> [!tip] +> If you edit Quartz configuration using a text-editor that has TypeScript language support like VSCode, it will warn you when you you've made an error in your configuration, helping you avoid configuration mistakes! + +The configuration of Quartz can be broken down into two main parts: + +```ts title="quartz.config.ts" +const config: QuartzConfig = { + configuration: { ... }, + plugins: { ... }, +} +``` + +## General Configuration + +This part of the configuration concerns anything that can affect the whole site. The following is a list breaking down all the things you can configure: + +- `pageTitle`: title of the site. This is also used when generating the [[RSS Feed]] for your site. +- `enableSPA`: whether to enable [[SPA Routing]] on your site. +- `enablePopovers`: whether to enable [[popover previews]] on your site. +- `analytics`: what to use for analytics on your site. Values can be + - `null`: don't use analytics; + - `{ provider: 'google', tagId: '' }`: use Google Analytics; + - `{ provider: 'plausible' }` (managed) or `{ provider: 'plausible', host: '' }` (self-hosted): use [Plausible](https://plausible.io/); + - `{ provider: 'umami', host: '', websiteId: '' }`: use [Umami](https://umami.is/); + - `{ provider: 'goatcounter', websiteId: 'my-goatcounter-id' }` (managed) or `{ provider: 'goatcounter', websiteId: 'my-goatcounter-id', host: 'my-goatcounter-domain.com', scriptSrc: 'https://my-url.to/counter.js' }` (self-hosted) use [GoatCounter](https://goatcounter.com) +- `locale`: used for [[i18n]] and date formatting +- `baseUrl`: this is used for sitemaps and RSS feeds that require an absolute URL to know where the canonical 'home' of your site lives. This is normally the deployed URL of your site (e.g. `quartz.jzhao.xyz` for this site). Do not include the protocol (i.e. `https://`) or any leading or trailing slashes. + - This should also include the subpath if you are [[hosting]] on GitHub pages without a custom domain. For example, if my repository is `jackyzha0/quartz`, GitHub pages would deploy to `https://jackyzha0.github.io/quartz` and the `baseUrl` would be `jackyzha0.github.io/quartz`. + - Note that Quartz 4 will avoid using this as much as possible and use relative URLs whenever it can to make sure your site works no matter _where_ you end up actually deploying it. +- `ignorePatterns`: a list of [glob]() patterns that Quartz should ignore and not search through when looking for files inside the `content` folder. See [[private pages]] for more details. +- `defaultDateType`: whether to use created, modified, or published as the default date to display on pages and page listings. +- `theme`: configure how the site looks. + - `cdnCaching`: If `true` (default), use Google CDN to cache the fonts. This will generally will be faster. Disable (`false`) this if you want Quartz to download the fonts to be self-contained. + - `typography`: what fonts to use. Any font available on [Google Fonts](https://fonts.google.com/) works here. + - `header`: Font to use for headers + - `code`: Font for inline and block quotes. + - `body`: Font for everything + - `colors`: controls the theming of the site. + - `light`: page background + - `lightgray`: borders + - `gray`: graph links, heavier borders + - `darkgray`: body text + - `dark`: header text and icons + - `secondary`: link colour, current [[graph view|graph]] node + - `tertiary`: hover states and visited [[graph view|graph]] nodes + - `highlight`: internal link background, highlighted text, [[syntax highlighting|highlighted lines of code]] + +## Plugins + +You can think of Quartz plugins as a series of transformations over content. + +![[quartz transform pipeline.png]] + +```ts title="quartz.config.ts" +plugins: { + transformers: [...], + filters: [...], + emitters: [...], +} +``` + +- [[tags/plugin/transformer|Transformers]] **map** over content (e.g. parsing frontmatter, generating a description) +- [[tags/plugin/filter|Filters]] **filter** content (e.g. filtering out drafts) +- [[tags/plugin/emitter|Emitters]] **reduce** over content (e.g. creating an RSS feed or pages that list all files with a specific tag) + +You can customize the behaviour of Quartz by adding, removing and reordering plugins in the `transformers`, `filters` and `emitters` fields. + +> [!note] +> Each node is modified by every transformer _in order_. Some transformers are position sensitive, so you may need to pay particular attention to whether they need to come before or after certain other plugins. + +You should take care to add the plugin to the right entry corresponding to its plugin type. For example, to add the [[ExplicitPublish]] plugin (a [[tags/plugin/filter|Filter]]), you would add the following line: + +```ts title="quartz.config.ts" +filters: [ + ... + Plugin.ExplicitPublish(), + ... +], +``` + +To remove a plugin, you should remove all occurrences of it in the `quartz.config.ts`. + +To customize plugins further, some plugins may also have their own configuration settings that you can pass in. If you do not pass in a configuration, the plugin will use its default settings. + +For example, the [[plugins/Latex|Latex]] plugin allows you to pass in a field specifying the `renderEngine` to choose between Katex and MathJax. + +```ts title="quartz.config.ts" +transformers: [ + Plugin.FrontMatter(), // use default options + Plugin.Latex({ renderEngine: "katex" }), // set some custom options +] +``` + +Some plugins are included by default in the[ `quartz.config.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz.config.ts), but there are more available. + +You can see a list of all plugins and their configuration options [[tags/plugin|here]]. + +If you'd like to make your own plugins, see the [[making plugins|making custom plugins]] guide. diff --git a/docs/features/Docker Support.md b/docs/features/Docker Support.md new file mode 100644 index 0000000..cf73b7f --- /dev/null +++ b/docs/features/Docker Support.md @@ -0,0 +1,7 @@ +Quartz comes shipped with a Docker image that will allow you to preview your Quartz locally without installing Node. + +You can run the below one-liner to run Quartz in Docker. + +```sh +docker run --rm -itp 8080:8080 $(docker build -q .) +``` diff --git a/docs/features/Latex.md b/docs/features/Latex.md new file mode 100644 index 0000000..fdc9d27 --- /dev/null +++ b/docs/features/Latex.md @@ -0,0 +1,82 @@ +--- +title: LaTeX +tags: + - feature/transformer +--- + +Quartz uses [Katex](https://katex.org/) by default to typeset both inline and block math expressions at build time. + +## Syntax + +### Block Math + +Block math can be rendered by delimiting math expression with `$$`. + +``` +$$ +f(x) = \int_{-\infty}^\infty + f\hat(\xi),e^{2 \pi i \xi x} + \,d\xi +$$ +``` + +$$ +f(x) = \int_{-\infty}^\infty + f\hat(\xi),e^{2 \pi i \xi x} + \,d\xi +$$ + +$$ +\begin{aligned} +a &= b + c \\ &= e + f \\ +\end{aligned} +$$ + +$$ +\begin{bmatrix} +1 & 2 & 3 \\ +a & b & c +\end{bmatrix} +$$ + +$$ +\begin{array}{rll} +E \psi &= H\psi & \text{Expanding the Hamiltonian Operator} \\ +&= -\frac{\hbar^2}{2m}\frac{\partial^2}{\partial x^2} \psi + \frac{1}{2}m\omega x^2 \psi & \text{Using the ansatz $\psi(x) = e^{-kx^2}f(x)$, hoping to cancel the $x^2$ term} \\ +&= -\frac{\hbar^2}{2m} [4k^2x^2f(x)+2(-2kx)f'(x) + f''(x)]e^{-kx^2} + \frac{1}{2}m\omega x^2 f(x)e^{-kx^2} &\text{Removing the $e^{-kx^2}$ term from both sides} \\ +& \Downarrow \\ +Ef(x) &= -\frac{\hbar^2}{2m} [4k^2x^2f(x)-4kxf'(x) + f''(x)] + \frac{1}{2}m\omega x^2 f(x) & \text{Choosing $k=\frac{im}{2}\sqrt{\frac{\omega}{\hbar}}$ to cancel the $x^2$ term, via $-\frac{\hbar^2}{2m}4k^2=\frac{1}{2}m \omega$} \\ +&= -\frac{\hbar^2}{2m} [-4kxf'(x) + f''(x)] \\ +\end{array} +$$ + +> [!warn] +> Due to limitations in the [underlying parsing library](https://github.com/remarkjs/remark-math), block math in Quartz requires the `$$` delimiters to be on newlines like above. + +### Inline Math + +Similarly, inline math can be rendered by delimiting math expression with a single `$`. For example, `$e^{i\pi} = -1$` produces $e^{i\pi} = -1$ + +### Escaping symbols + +There will be cases where you may have more than one `$` in a paragraph at once which may accidentally trigger MathJax/Katex. + +To get around this, you can escape the dollar sign by doing `\$` instead. + +For example: + +- Incorrect: `I have $1 and you have $2` produces I have $1 and you have $2 +- Correct: `I have \$1 and you have \$2` produces I have \$1 and you have \$2 + +### Using mhchem + +Add the following import to the top of `quartz/plugins/transformers/latex.ts` (before all the other +imports): + +```ts title="quartz/plugins/transformers/latex.ts" +import "katex/contrib/mhchem" +``` + +## Customization + +Latex parsing is a functionality of the [[plugins/Latex|Latex]] plugin. See the plugin page for customization options. diff --git a/docs/features/Mermaid diagrams.md b/docs/features/Mermaid diagrams.md new file mode 100644 index 0000000..9cc4089 --- /dev/null +++ b/docs/features/Mermaid diagrams.md @@ -0,0 +1,34 @@ +--- +title: "Mermaid Diagrams" +tags: + - feature/transformer +--- + +Quartz supports Mermaid which allows you to add diagrams and charts to your notes. Mermaid supports a range of diagrams, such as [flow charts](https://mermaid.js.org/syntax/flowchart.html), [sequence diagrams](https://mermaid.js.org/syntax/sequenceDiagram.html), and [timelines](https://mermaid.js.org/syntax/timeline.html). This is enabled as a part of [[Obsidian compatibility]] and can be configured and enabled/disabled from that plugin. + +By default, Quartz will render Mermaid diagrams to match the site theme. + +> [!warning] +> Wondering why Mermaid diagrams may not be showing up even if you have them enabled? You may need to reorder your plugins so that [[ObsidianFlavoredMarkdown]] is _after_ [[SyntaxHighlighting]]. + +## Syntax + +To add a Mermaid diagram, create a mermaid code block. + +```` +```mermaid +sequenceDiagram + Alice->>+John: Hello John, how are you? + Alice->>+John: John, can you hear me? + John-->>-Alice: Hi Alice, I can hear you! + John-->>-Alice: I feel great! +``` +```` + +```mermaid +sequenceDiagram + Alice->>+John: Hello John, how are you? + Alice->>+John: John, can you hear me? + John-->>-Alice: Hi Alice, I can hear you! + John-->>-Alice: I feel great! +``` diff --git a/docs/features/Obsidian compatibility.md b/docs/features/Obsidian compatibility.md new file mode 100644 index 0000000..e469f48 --- /dev/null +++ b/docs/features/Obsidian compatibility.md @@ -0,0 +1,17 @@ +--- +title: "Obsidian Compatibility" +tags: + - feature/transformer +--- + +Quartz was originally designed as a tool to publish Obsidian vaults as websites. Even as the scope of Quartz has widened over time, it hasn't lost the ability to seamlessly interoperate with Obsidian. + +By default, Quartz ships with the [[ObsidianFlavoredMarkdown]] plugin, which is a transformer plugin that adds support for [Obsidian Flavored Markdown](https://help.obsidian.md/Editing+and+formatting/Obsidian+Flavored+Markdown). This includes support for features like [[wikilinks]] and [[Mermaid diagrams]]. + +It also ships with support for [frontmatter parsing](https://help.obsidian.md/Editing+and+formatting/Properties) with the same fields that Obsidian uses through the [[Frontmatter]] transformer plugin. + +Finally, Quartz also provides [[CrawlLinks]] plugin, which allows you to customize Quartz's link resolution behaviour to match Obsidian. + +## Configuration + +This functionality is provided by the [[ObsidianFlavoredMarkdown]], [[Frontmatter]] and [[CrawlLinks]] plugins. See the plugin pages for customization options. diff --git a/docs/features/OxHugo compatibility.md b/docs/features/OxHugo compatibility.md new file mode 100644 index 0000000..e220511 --- /dev/null +++ b/docs/features/OxHugo compatibility.md @@ -0,0 +1,29 @@ +--- +title: "OxHugo Compatibility" +tags: + - feature/transformer +--- + +[org-roam](https://www.orgroam.com/) is a plain-text personal knowledge management system for [emacs](https://en.wikipedia.org/wiki/Emacs). [ox-hugo](https://github.com/kaushalmodi/ox-hugo) is org exporter backend that exports `org-mode` files to [Hugo](https://gohugo.io/) compatible Markdown. + +Because the Markdown generated by ox-hugo is not pure Markdown but Hugo specific, we need to transform it to fit into Quartz. This is done by the [[OxHugoFlavoredMarkdown]] plugin. Even though this plugin was written with `ox-hugo` in mind, it should work for any Hugo specific Markdown. + +```typescript title="quartz.config.ts" +plugins: { + transformers: [ + Plugin.FrontMatter({ delims: "+++", language: "toml" }), // if toml frontmatter + // ... + Plugin.OxHugoFlavouredMarkdown(), + Plugin.GitHubFlavoredMarkdown(), + // ... + ], +}, +``` + +## Usage + +Quartz by default doesn't understand `org-roam` files as they aren't Markdown. You're responsible for using an external tool like `ox-hugo` to export the `org-roam` files as Markdown content to Quartz and managing the static assets so that they're available in the final output. + +## Configuration + +This functionality is provided by the [[OxHugoFlavoredMarkdown]] plugin. See the plugin page for customization options. diff --git a/docs/features/RSS Feed.md b/docs/features/RSS Feed.md new file mode 100644 index 0000000..ed4138d --- /dev/null +++ b/docs/features/RSS Feed.md @@ -0,0 +1,5 @@ +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. + +## Configuration + +This functionality is provided by the [[ContentIndex]] plugin. See the plugin page for customization options. diff --git a/docs/features/SPA Routing.md b/docs/features/SPA Routing.md new file mode 100644 index 0000000..3004af9 --- /dev/null +++ b/docs/features/SPA Routing.md @@ -0,0 +1,7 @@ +Single-page-app style rendering. This prevents flashes of unstyled content and improves the smoothness of Quartz. + +Under the hood, this is done by hijacking page navigations and instead fetching the HTML via a `GET` request and then diffing and selectively replacing parts of the page using [micromorph](https://github.com/natemoo-re/micromorph). This allows us to change the content of the page without fully refreshing the page, reducing the amount of content that the browser needs to load. + +## Configuration + +- Disable SPA Routing: set the `enableSPA` field of the [[configuration]] in `quartz.config.ts` to be `false`. diff --git a/docs/features/backlinks.md b/docs/features/backlinks.md new file mode 100644 index 0000000..f558f4a --- /dev/null +++ b/docs/features/backlinks.md @@ -0,0 +1,14 @@ +--- +title: Backlinks +tags: + - component +--- + +A backlink for a note is a link from another note to that note. Links in the backlink pane also feature rich [[popover previews]] if you have that feature enabled. + +## Customization + +- Removing backlinks: delete all usages of `Component.Backlinks()` from `quartz.layout.ts`. +- Component: `quartz/components/Backlinks.tsx` +- Style: `quartz/components/styles/backlinks.scss` +- Script: `quartz/components/scripts/search.inline.ts` diff --git a/docs/features/breadcrumbs.md b/docs/features/breadcrumbs.md new file mode 100644 index 0000000..a701858 --- /dev/null +++ b/docs/features/breadcrumbs.md @@ -0,0 +1,36 @@ +--- +title: "Breadcrumbs" +tags: + - component +--- + +Breadcrumbs provide a way to navigate a hierarchy of pages within your site using a list of its parent folders. + +By default, the element at the very top of your page is the breadcrumb navigation bar (can also be seen at the top on this page!). + +## Customization + +Most configuration can be done by passing in options to `Component.Breadcrumbs()`. + +For example, here's what the default configuration looks like: + +```typescript title="quartz.layout.ts" +Component.Breadcrumbs({ + spacerSymbol: "❯", // symbol between crumbs + rootName: "Home", // name of first/root element + resolveFrontmatterTitle: true, // whether to resolve folder names through frontmatter titles + hideOnRoot: true, // whether to hide breadcrumbs on root `index.md` page + showCurrentPage: true, // whether to display the current page in the breadcrumbs +}) +``` + +When passing in your own options, you can omit any or all of these fields if you'd like to keep the default value for that field. + +You can also adjust where the breadcrumbs will be displayed by adjusting the [[layout]] (moving `Component.Breadcrumbs()` up or down) + +Want to customize it even more? + +- Removing breadcrumbs: delete all usages of `Component.Breadcrumbs()` from `quartz.layout.ts`. +- Component: `quartz/components/Breadcrumbs.tsx` +- Style: `quartz/components/styles/breadcrumbs.scss` +- Script: inline at `quartz/components/Breadcrumbs.tsx` diff --git a/docs/features/callouts.md b/docs/features/callouts.md new file mode 100644 index 0000000..4caeeb4 --- /dev/null +++ b/docs/features/callouts.md @@ -0,0 +1,96 @@ +--- +title: Callouts +tags: + - feature/transformer +--- + +Quartz supports the same Admonition-callout syntax as Obsidian. + +This includes + +- 12 Distinct callout types (each with several aliases) +- Collapsable callouts + +``` +> [!info] Title +> This is a callout! +``` + +See [documentation on supported types and syntax here](https://help.obsidian.md/Editing+and+formatting/Callouts). + +> [!warning] +> Wondering why callouts may not be showing up even if you have them enabled? You may need to reorder your plugins so that [[ObsidianFlavoredMarkdown]] is _after_ [[SyntaxHighlighting]]. + +## Customization + +The callouts are a functionality of the [[ObsidianFlavoredMarkdown]] plugin. See the plugin page for how to enable or disable them. + +You can edit the icons by customizing `quartz/styles/callouts.scss`. + +### Add custom callouts + +By default, custom callouts are handled by applying the `note` style. To make fancy ones, you have to add these lines to `custom.scss`. + +```scss title="quartz/styles/custom.scss" +.callout { + &[data-callout="custom"] { + --color: #customcolor; + --border: #custombordercolor; + --bg: #custombg; + --callout-icon: url("data:image/svg+xml; utf8, "); //SVG icon code + } +} +``` + +> [!warning] +> Don't forget to ensure that the SVG is URL encoded before putting it in the CSS. You can use tools like [this one](https://yoksel.github.io/url-encoder/) to help you do that. + +## Showcase + +> [!info] +> Default title + +> [!question]+ Can callouts be _nested_? +> +> > [!todo]- Yes!, they can. And collapsed! +> > +> > > [!example] You can even use multiple layers of nesting. + +> [!note] +> Aliases: "note" + +> [!abstract] +> Aliases: "abstract", "summary", "tldr" + +> [!info] +> Aliases: "info" + +> [!todo] +> Aliases: "todo" + +> [!tip] +> Aliases: "tip", "hint", "important" + +> [!success] +> Aliases: "success", "check", "done" + +> [!question] +> Aliases: "question", "help", "faq" + +> [!warning] +> Aliases: "warning", "attention", "caution" + +> [!failure] +> Aliases: "failure", "missing", "fail" + +> [!danger] +> Aliases: "danger", "error" + +> [!bug] +> Aliases: "bug" + +> [!example] +> Aliases: "example" + +> [!quote] +> Aliases: "quote", "cite" diff --git a/docs/features/darkmode.md b/docs/features/darkmode.md new file mode 100644 index 0000000..dff75b4 --- /dev/null +++ b/docs/features/darkmode.md @@ -0,0 +1,23 @@ +--- +title: "Darkmode" +tags: + - component +--- + +Quartz supports darkmode out of the box that respects the user's theme preference. Any future manual toggles of the darkmode switch will be saved in the browser's local storage so it can be persisted across future page loads. + +## Customization + +- Removing darkmode: delete all usages of `Component.Darkmode()` from `quartz.layout.ts`. +- Component: `quartz/components/Darkmode.tsx` +- Style: `quartz/components/styles/darkmode.scss` +- Script: `quartz/components/scripts/darkmode.inline.ts` + +You can also listen to the `themechange` event to perform any custom logic when the theme changes. + +```js +document.addEventListener("themechange", (e) => { + console.log("Theme changed to " + e.detail.theme) // either "light" or "dark" + // your logic here +}) +``` diff --git a/docs/features/explorer.md b/docs/features/explorer.md new file mode 100644 index 0000000..95878f7 --- /dev/null +++ b/docs/features/explorer.md @@ -0,0 +1,319 @@ +--- +title: "Explorer" +tags: + - component +--- + +Quartz features an explorer that allows you to navigate all files and folders on your site. It supports nested folders and is highly customizable. + +By default, it shows all folders and files on your page. To display the explorer in a different spot, you can edit the [[layout]]. + +Display names for folders get determined by the `title` frontmatter field in `folder/index.md` (more detail in [[authoring content | Authoring Content]]). If this file does not exist or does not contain frontmatter, the local folder name will be used instead. + +> [!info] +> The explorer uses local storage by default to save the state of your explorer. This is done to ensure a smooth experience when navigating to different pages. +> +> To clear/delete the explorer state from local storage, delete the `fileTree` entry (guide on how to delete a key from local storage in chromium based browsers can be found [here](https://docs.devolutions.net/kb/general-knowledge-base/clear-browser-local-storage/clear-chrome-local-storage/)). You can disable this by passing `useSavedState: false` as an argument. + +## Customization + +Most configuration can be done by passing in options to `Component.Explorer()`. + +For example, here's what the default configuration looks like: + +```typescript title="quartz.layout.ts" +Component.Explorer({ + title: "Explorer", // title of the explorer component + folderClickBehavior: "collapse", // what happens when you click a folder ("link" to navigate to folder page on click or "collapse" to collapse folder on click) + folderDefaultState: "collapsed", // default state of folders ("collapsed" or "open") + useSavedState: true, // whether to use local storage to save "state" (which folders are opened) of explorer + // Sort order: folders first, then files. Sort folders and files alphabetically + sortFn: (a, b) => { + ... // default implementation shown later + }, + filterFn: filterFn: (node) => node.name !== "tags", // filters out 'tags' folder + mapFn: undefined, + // what order to apply functions in + order: ["filter", "map", "sort"], +}) +``` + +When passing in your own options, you can omit any or all of these fields if you'd like to keep the default value for that field. + +Want to customize it even more? + +- Removing explorer: remove `Component.Explorer()` from `quartz.layout.ts` + - (optional): After removing the explorer component, you can move the [[table of contents | Table of Contents]] component back to the `left` part of the layout +- Changing `sort`, `filter` and `map` behavior: explained in [[#Advanced customization]] +- Component: + - Wrapper (Outer component, generates file tree, etc): `quartz/components/Explorer.tsx` + - Explorer node (recursive, either a folder or a file): `quartz/components/ExplorerNode.tsx` +- Style: `quartz/components/styles/explorer.scss` +- Script: `quartz/components/scripts/explorer.inline.ts` + +## Advanced customization + +This component allows you to fully customize all of its behavior. You can pass a custom `sort`, `filter` and `map` function. +All functions you can pass work with the `FileNode` class, which has the following properties: + +```ts title="quartz/components/ExplorerNode.tsx" {2-5} +export class FileNode { + children: FileNode[] // children of current node + name: string // last part of slug + displayName: string // what actually should be displayed in the explorer + file: QuartzPluginData | null // if node is a file, this is the file's metadata. See `QuartzPluginData` for more detail + depth: number // depth of current node + + ... // rest of implementation +} +``` + +Every function you can pass is optional. By default, only a `sort` function will be used: + +```ts title="Default sort function" +// Sort order: folders first, then files. Sort folders and files alphabetically +Component.Explorer({ + sortFn: (a, b) => { + if ((!a.file && !b.file) || (a.file && b.file)) { + // sensitivity: "base": Only strings that differ in base letters compare as unequal. Examples: a ≠ b, a = á, a = A + // numeric: true: Whether numeric collation should be used, such that "1" < "2" < "10" + return a.displayName.localeCompare(b.displayName, undefined, { + numeric: true, + sensitivity: "base", + }) + } + if (a.file && !b.file) { + return 1 + } else { + return -1 + } + }, +}) +``` + +--- + +You can pass your own functions for `sortFn`, `filterFn` and `mapFn`. All functions will be executed in the order provided by the `order` option (see [[#Customization]]). These functions behave similarly to their `Array.prototype` counterpart, except they modify the entire `FileNode` tree in place instead of returning a new one. + +For more information on how to use `sort`, `filter` and `map`, you can check [Array.prototype.sort()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort), [Array.prototype.filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) and [Array.prototype.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map). + +Type definitions look like this: + +```ts +sortFn: (a: FileNode, b: FileNode) => number +filterFn: (node: FileNode) => boolean +mapFn: (node: FileNode) => void +``` + +> [!tip] +> You can check if a `FileNode` is a folder or a file like this: +> +> ```ts +> if (node.file) { +> // node is a file +> } else { +> // node is a folder +> } +> ``` + +## Basic examples + +These examples show the basic usage of `sort`, `map` and `filter`. + +### Use `sort` to put files first + +Using this example, the explorer will alphabetically sort everything, but put all **files** above all **folders**. + +```ts title="quartz.layout.ts" +Component.Explorer({ + sortFn: (a, b) => { + if ((!a.file && !b.file) || (a.file && b.file)) { + return a.displayName.localeCompare(b.displayName) + } + if (a.file && !b.file) { + return -1 + } else { + return 1 + } + }, +}) +``` + +### Change display names (`map`) + +Using this example, the display names of all `FileNodes` (folders + files) will be converted to full upper case. + +```ts title="quartz.layout.ts" +Component.Explorer({ + mapFn: (node) => { + node.displayName = node.displayName.toUpperCase() + }, +}) +``` + +### Remove list of elements (`filter`) + +Using this example, you can remove elements from your explorer by providing an array of folders/files using the `omit` set. + +```ts title="quartz.layout.ts" +Component.Explorer({ + filterFn: (node) => { + // set containing names of everything you want to filter out + const omit = new Set(["authoring content", "tags", "hosting"]) + return !omit.has(node.name.toLowerCase()) + }, +}) +``` + +You can customize this by changing the entries of the `omit` set. Simply add all folder or file names you want to remove. + +### Remove files by tag + +You can access the frontmatter of a file by `node.file?.frontmatter?`. This allows you to filter out files based on their frontmatter, for example by their tags. + +```ts title="quartz.layout.ts" +Component.Explorer({ + filterFn: (node) => { + // exclude files with the tag "explorerexclude" + return node.file?.frontmatter?.tags?.includes("explorerexclude") !== true + }, +}) +``` + +### Show every element in explorer + +To override the default filter function that removes the `tags` folder from the explorer, you can set the filter function to `undefined`. + +```ts title="quartz.layout.ts" +Component.Explorer({ + filterFn: undefined, // apply no filter function, every file and folder will visible +}) +``` + +## Advanced examples + +> [!tip] +> When writing more complicated functions, the `layout` file can start to look very cramped. +> You can fix this by defining your functions in another file. +> +> ```ts title="functions.ts" +> import { Options } from "./quartz/components/ExplorerNode" +> export const mapFn: Options["mapFn"] = (node) => { +> // implement your function here +> } +> export const filterFn: Options["filterFn"] = (node) => { +> // implement your function here +> } +> export const sortFn: Options["sortFn"] = (a, b) => { +> // implement your function here +> } +> ``` +> +> You can then import them like this: +> +> ```ts title="quartz.layout.ts" +> import { mapFn, filterFn, sortFn } from "./functions.ts" +> Component.Explorer({ +> mapFn: mapFn, +> filterFn: filterFn, +> sortFn: sortFn, +> }) +> ``` + +### Add emoji prefix + +To add emoji prefixes (📁 for folders, 📄 for files), you could use a map function like this: + +```ts title="quartz.layout.ts" +Component.Explorer({ + mapFn: (node) => { + // dont change name of root node + if (node.depth > 0) { + // set emoji for file/folder + if (node.file) { + node.displayName = "📄 " + node.displayName + } else { + node.displayName = "📁 " + node.displayName + } + } + }, +}) +``` + +### Putting it all together + +In this example, we're going to customize the explorer by using functions from examples above to [[#Add emoji prefix | add emoji prefixes]], [[#remove-list-of-elements-filter| filter out some folders]] and [[#use-sort-to-put-files-first | sort with files above folders]]. + +```ts title="quartz.layout.ts" +Component.Explorer({ + filterFn: sampleFilterFn, + mapFn: sampleMapFn, + sortFn: sampleSortFn, + order: ["filter", "sort", "map"], +}) +``` + +Notice how we customized the `order` array here. This is done because the default order applies the `sort` function last. While this normally works well, it would cause unintended behavior here, since we changed the first characters of all display names. In our example, `sort` would be applied based off the emoji prefix instead of the first _real_ character. + +To fix this, we just changed around the order and apply the `sort` function before changing the display names in the `map` function. + +### Use `sort` with pre-defined sort order + +Here's another example where a map containing file/folder names (as slugs) is used to define the sort order of the explorer in quartz. All files/folders that aren't listed inside of `nameOrderMap` will appear at the top of that folders hierarchy level. + +It's also worth mentioning, that the smaller the number set in `nameOrderMap`, the higher up the entry will be in the explorer. Incrementing every folder/file by 100, makes ordering files in their folders a lot easier. Lastly, this example still allows you to use a `mapFn` or frontmatter titles to change display names, as it uses slugs for `nameOrderMap` (which is unaffected by display name changes). + +```ts title="quartz.layout.ts" +Component.Explorer({ + sortFn: (a, b) => { + const nameOrderMap: Record = { + "poetry-folder": 100, + "essay-folder": 200, + "research-paper-file": 201, + "dinosaur-fossils-file": 300, + "other-folder": 400, + } + + let orderA = 0 + let orderB = 0 + + if (a.file && a.file.slug) { + orderA = nameOrderMap[a.file.slug] || 0 + } else if (a.name) { + orderA = nameOrderMap[a.name] || 0 + } + + if (b.file && b.file.slug) { + orderB = nameOrderMap[b.file.slug] || 0 + } else if (b.name) { + orderB = nameOrderMap[b.name] || 0 + } + + return orderA - orderB + }, +}) +``` + +For reference, this is how the quartz explorer window would look like with that example: + +``` +📖 Poetry Folder +📑 Essay Folder + ⚗️ Research Paper File +🦴 Dinosaur Fossils File +🔮 Other Folder +``` + +And this is how the file structure would look like: + +``` +index.md +poetry-folder + index.md +essay-folder + index.md + research-paper-file.md +dinosaur-fossils-file.md +other-folder + index.md +``` diff --git a/docs/features/folder and tag listings.md b/docs/features/folder and tag listings.md new file mode 100644 index 0000000..d330f14 --- /dev/null +++ b/docs/features/folder and tag listings.md @@ -0,0 +1,33 @@ +--- +title: Folder and Tag Listings +tags: + - feature/emitter +--- + +Quartz emits listing pages for any folders and tags you have. + +## Folder Listings + +Quartz will generate an index page for all the pages under that folder. This includes any content that is multiple levels deep. + +Additionally, Quartz will also generate pages for subfolders. Say you have a note in a nested folder `content/abc/def/note.md`. Then Quartz would generate a page for all the notes under `abc` _and_ a page for all the notes under `abc/def`. + +You can link to the folder listing by referencing its name, plus a trailing slash, like this: `[[advanced/]]` (results in [[advanced/]]). + +By default, Quartz will title the page `Folder: ` and no description. You can override this by creating an `index.md` file in the folder with the `title` [[authoring content#Syntax|frontmatter]] field. Any content you write in this file will also be used in the folder description. + +For example, for the folder `content/posts`, you can add another file `content/posts/index.md` to add a specific description for it. + +## Tag Listings + +Quartz will also create an index page for each unique tag in your vault and render a list of all notes with that tag. + +Quartz also supports tag hierarchies as well (e.g. `plugin/emitter`) and will also render a separate tag page for each level of the tag hierarchy. It will also create a default global tag index page at `/tags` that displays a list of all the tags in your Quartz. + +You can link to the tag listing by referencing its name with a `tag/` prefix, like this: `[[tags/plugin]]` (results in [[tags/plugin]]). + +As with folder listings, you can also provide a description and title for a tag page by creating a file for each tag. For example, if you wanted to create a custom description for the #component tag, you would create a file at `content/tags/component.md` with a title and description. + +## Customization + +The folder listings are a functionality of the [[FolderPage]] plugin, the tag listings of the [[TagPage]] plugin. See the plugin pages for customization options. diff --git a/docs/features/full-text search.md b/docs/features/full-text search.md new file mode 100644 index 0000000..85ec030 --- /dev/null +++ b/docs/features/full-text search.md @@ -0,0 +1,30 @@ +--- +title: Full-text Search +tags: + - component +--- + +Full-text search in Quartz is powered by [Flexsearch](https://github.com/nextapps-de/flexsearch). It's fast enough to return search results in under 10ms for Quartzs as large as half a million words. + +It can be opened by either clicking on the search bar or pressing `⌘`/`ctrl` + `K`. The top 5 search results are shown on each query. Matching subterms are highlighted and the most relevant 30 words are excerpted. Clicking on a search result will navigate to that page. + +To search content by tags, you can either press `⌘`/`ctrl` + `shift` + `K` or start your query with `#` (e.g. `#components`). + +This component is also keyboard accessible: Tab and Shift+Tab will cycle forward and backward through search results and Enter will navigate to the highlighted result (first result by default). You are also able to navigate search results using `ArrowUp` and `ArrowDown`. + +> [!info] +> Search requires the `ContentIndex` emitter plugin to be present in the [[configuration]]. + +### Indexing Behaviour + +By default, it indexes every page on the site with **Markdown syntax removed**. This means link URLs for instance are not indexed. + +It properly tokenizes Chinese, Korean, and Japenese characters and constructs separate indexes for the title, content and tags, weighing title matches above content matches. + +## Customization + +- Removing search: delete all usages of `Component.Search()` from `quartz.layout.ts`. +- Component: `quartz/components/Search.tsx` +- Style: `quartz/components/styles/search.scss` +- Script: `quartz/components/scripts/search.inline.ts` + - You can edit `contextWindowWords`, `numSearchResults` or `numTagResults` to suit your needs diff --git a/docs/features/graph view.md b/docs/features/graph view.md new file mode 100644 index 0000000..4f905c7 --- /dev/null +++ b/docs/features/graph view.md @@ -0,0 +1,63 @@ +--- +title: "Graph View" +tags: + - component +--- + +Quartz features a graph-view that can show both a local graph view and a global graph view. + +- The local graph view shows files that either link to the current file or are linked from the current file. In other words, it shows all notes that are _at most_ one hop away. +- The global graph view can be toggled by clicking the graph icon on the top-right of the local graph view. It shows _all_ the notes in your graph and how they connect to each other. + +By default, the node radius is proportional to the total number of incoming and outgoing internal links from that file. + +Additionally, similar to how browsers highlight visited links a different colour, the graph view will also show nodes that you have visited in a different colour. + +> [!info] +> Graph View requires the `ContentIndex` emitter plugin to be present in the [[configuration]]. + +## Customization + +Most configuration can be done by passing in options to `Component.Graph()`. + +For example, here's what the default configuration looks like: + +```typescript title="quartz.layout.ts" +Component.Graph({ + localGraph: { + drag: true, // whether to allow panning the view around + zoom: true, // whether to allow zooming in and out + depth: 1, // how many hops of notes to display + scale: 1.1, // default view scale + repelForce: 0.5, // how much nodes should repel each other + centerForce: 0.3, // how much force to use when trying to center the nodes + linkDistance: 30, // how long should the links be by default? + fontSize: 0.6, // what size should the node labels be? + opacityScale: 1, // how quickly do we fade out the labels when zooming out? + removeTags: [], // what tags to remove from the graph + showTags: true, // whether to show tags in the graph + }, + globalGraph: { + drag: true, + zoom: true, + depth: -1, + scale: 0.9, + repelForce: 0.5, + centerForce: 0.3, + linkDistance: 30, + fontSize: 0.6, + opacityScale: 1, + removeTags: [], // what tags to remove from the graph + showTags: true, // whether to show tags in the graph + }, +}) +``` + +When passing in your own options, you can omit any or all of these fields if you'd like to keep the default value for that field. + +Want to customize it even more? + +- Removing graph view: delete all usages of `Component.Graph()` from `quartz.layout.ts`. +- Component: `quartz/components/Graph.tsx` +- Style: `quartz/components/styles/graph.scss` +- Script: `quartz/components/scripts/graph.inline.ts` diff --git a/docs/features/i18n.md b/docs/features/i18n.md new file mode 100644 index 0000000..57547dd --- /dev/null +++ b/docs/features/i18n.md @@ -0,0 +1,18 @@ +--- +title: Internationalization +--- + +Internationalization allows users to translate text in the Quartz interface into various supported languages without needing to make extensive code changes. This can be changed via the `locale` [[configuration]] field in `quartz.config.ts`. + +The locale field generally follows a certain format: `{language}-{REGION}` + +- `{language}` is usually a [2-letter lowercase language code](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes). +- `{REGION}` is usually a [2-letter uppercase region code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) + +> [!tip] Interested in contributing? +> We [gladly welcome translation PRs](https://github.com/jackyzha0/quartz/tree/v4/quartz/i18n/locales)! To contribute a translation, do the following things: +> +> 1. In the `quartz/i18n/locales` folder, copy the `en-US.ts` file. +> 2. Rename it to `{language}-{REGION}.ts` so it matches a locale of the format shown above. +> 3. Fill in the translations! +> 4. Add the entry under `TRANSLATIONS` in `quartz/i18n/index.ts`. diff --git a/docs/features/index.md b/docs/features/index.md new file mode 100644 index 0000000..2997b3a --- /dev/null +++ b/docs/features/index.md @@ -0,0 +1,3 @@ +--- +title: Feature List +--- diff --git a/docs/features/popover previews.md b/docs/features/popover previews.md new file mode 100644 index 0000000..0666047 --- /dev/null +++ b/docs/features/popover previews.md @@ -0,0 +1,17 @@ +--- +title: Popover Previews +--- + +Like Wikipedia, when you hover over a link in Quartz, there is a popup of a page preview that you can scroll to see the entire content. Links to headers will also scroll the popup to show that specific header in view. + +By default, Quartz only fetches previews for pages inside your vault due to [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). It does this by selecting all HTML elements with the `popover-hint` class. For most pages, this includes the page title, page metadata like words and time to read, tags, and the actual page content. + +When [[creating components|creating your own components]], you can include this `popover-hint` class to also include it in the popover. + +Similar to Obsidian, [[quartz layout.png|images referenced using wikilinks]] can also be viewed as popups. + +## Configuration + +- Remove popovers: set the `enablePopovers` field in `quartz.config.ts` to be `false`. +- Style: `quartz/components/styles/popover.scss` +- Script: `quartz/components/scripts/popover.inline.ts` diff --git a/docs/features/private pages.md b/docs/features/private pages.md new file mode 100644 index 0000000..1e8f8aa --- /dev/null +++ b/docs/features/private pages.md @@ -0,0 +1,35 @@ +--- +title: Private Pages +tags: + - feature/filter +--- + +There may be some notes you want to avoid publishing as a website. Quartz supports this through two mechanisms which can be used in conjunction: + +## Filter Plugins + +[[making plugins#Filters|Filter plugins]] are plugins that filter out content based off of certain criteria. By default, Quartz uses the [[RemoveDrafts]] plugin which filters out any note that has `draft: true` in the frontmatter. + +If you'd like to only publish a select number of notes, you can instead use [[ExplicitPublish]] which will filter out all notes except for any that have `publish: true` in the frontmatter. + +> [!warning] +> Regardless of the filter plugin used, **all non-markdown files will be emitted and available publically in the final build.** This includes files such as images, voice recordings, PDFs, etc. One way to prevent this and still be able to embed local images is to create a folder specifically for public media and add the following two patterns to the ignorePatterns array. +> +> `"!(PublicMedia)**/!(*.md)", "!(*.md)"` + +## `ignorePatterns` + +This is a field in `quartz.config.ts` under the main [[configuration]] which allows you to specify a list of patterns to effectively exclude from parsing all together. Any valid [fast-glob](https://github.com/mrmlnc/fast-glob#pattern-syntax) pattern works here. + +> [!note] +> Bash's glob syntax is slightly different from fast-glob's and using bash's syntax may lead to unexpected results. + +Common examples include: + +- `some/folder`: exclude the entire of `some/folder` +- `*.md`: exclude all files with a `.md` extension +- `!*.md` exclude all files that _don't_ have a `.md` extension +- `**/private`: exclude any files or folders named `private` at any level of nesting + +> [!warning] +> Marking something as private via either a plugin or through the `ignorePatterns` pattern will only prevent a page from being included in the final built site. If your GitHub repository is public, also be sure to include an ignore for those in the `.gitignore` of your Quartz. See the `git` [documentation](https://git-scm.com/docs/gitignore#_pattern_format) for more information. diff --git a/docs/features/recent notes.md b/docs/features/recent notes.md new file mode 100644 index 0000000..9236b7c --- /dev/null +++ b/docs/features/recent notes.md @@ -0,0 +1,16 @@ +--- +title: Recent Notes +tags: component +--- + +Quartz can generate a list of recent notes based on some filtering and sorting criteria. Though this component isn't included in any [[layout]] by default, you can add it by using `Component.RecentNotes` in `quartz.layout.ts`. + +## Customization + +- Changing the title from "Recent notes": pass in an additional parameter to `Component.RecentNotes({ title: "Recent writing" })` +- Changing the number of recent notes: pass in an additional parameter to `Component.RecentNotes({ limit: 5 })` +- Show a 'see more' link: pass in an additional parameter to `Component.RecentNotes({ linkToMore: "tags/components" })`. This field should be a full slug to a page that exists. +- Customize filtering: pass in an additional parameter to `Component.RecentNotes({ filter: someFilterFunction })`. The filter function should be a function that has the signature `(f: QuartzPluginData) => boolean`. +- Customize sorting: pass in an additional parameter to `Component.RecentNotes({ sort: someSortFunction })`. By default, Quartz will sort by date and then tie break lexographically. The sort function should be a function that has the signature `(f1: QuartzPluginData, f2: QuartzPluginData) => number`. See `byDateAndAlphabetical` in `quartz/components/PageList.tsx` for an example. +- Component: `quartz/components/RecentNotes.tsx` +- Style: `quartz/components/styles/recentNotes.scss` diff --git a/docs/features/syntax highlighting.md b/docs/features/syntax highlighting.md new file mode 100644 index 0000000..16fef25 --- /dev/null +++ b/docs/features/syntax highlighting.md @@ -0,0 +1,133 @@ +--- +title: Syntax Highlighting +tags: + - feature/transformer +--- + +Syntax highlighting in Quartz is completely done at build-time. This means that Quartz only ships pre-calculated CSS to highlight the right words so there is no heavy client-side bundle that does the syntax highlighting. + +And, unlike some client-side highlighters, it has a full TextMate parser grammar instead of using Regexes, allowing for highly accurate code highlighting. + +In short, it generates HTML that looks exactly like your code in an editor like VS Code. Under the hood, it's powered by [Rehype Pretty Code](https://rehype-pretty-code.netlify.app/) which uses [Shiki](https://github.com/shikijs/shiki). + +> [!warning] +> Syntax highlighting does have an impact on build speed if you have a lot of code snippets in your notes. + +## Formatting + +Text inside `backticks` on a line will be formatted like code. + +```` +```ts +export function trimPathSuffix(fp: string): string { + fp = clientSideSlug(fp) + let [cleanPath, anchor] = fp.split("#", 2) + anchor = anchor === undefined ? "" : "#" + anchor + + return cleanPath + anchor +} +``` +```` + +```ts +export function trimPathSuffix(fp: string): string { + fp = clientSideSlug(fp) + let [cleanPath, anchor] = fp.split("#", 2) + anchor = anchor === undefined ? "" : "#" + anchor + + return cleanPath + anchor +} +``` + +### Titles + +Add a file title to your code block, with text inside double quotes (`""`): + +```` +```js title="..." + +``` +```` + +```ts title="quartz/path.ts" +export function trimPathSuffix(fp: string): string { + fp = clientSideSlug(fp) + let [cleanPath, anchor] = fp.split("#", 2) + anchor = anchor === undefined ? "" : "#" + anchor + + return cleanPath + anchor +} +``` + +### Line highlighting + +Place a numeric range inside `{}`. + +```` +```js {1-3,4} + +``` +```` + +```ts {2-3,6} +export function trimPathSuffix(fp: string): string { + fp = clientSideSlug(fp) + let [cleanPath, anchor] = fp.split("#", 2) + anchor = anchor === undefined ? "" : "#" + anchor + + return cleanPath + anchor +} +``` + +### Word highlighting + +A series of characters, like a literal regex. + +```` +```js /useState/ +const [age, setAge] = useState(50); +const [name, setName] = useState('Taylor'); +``` +```` + +```js /useState/ +const [age, setAge] = useState(50) +const [name, setName] = useState("Taylor") +``` + +### Line numbers + +Syntax highlighting has line numbers configured automatically. If you want to start line numbers at a specific number, use `showLineNumbers{number}`: + +```` +```js showLineNumbers{number} + +``` +```` + +```ts showLineNumbers{20} +export function trimPathSuffix(fp: string): string { + fp = clientSideSlug(fp) + let [cleanPath, anchor] = fp.split("#", 2) + anchor = anchor === undefined ? "" : "#" + anchor + + return cleanPath + anchor +} +``` + +### Escaping code blocks + +You can format a codeblock inside of a codeblock by wrapping it with another level of backtick fences that has one more backtick than the previous fence. + +````` +```` +```js /useState/ +const [age, setAge] = useState(50); +const [name, setName] = useState('Taylor'); +``` +```` +````` + +## Customization + +Syntax highlighting is a functionality of the [[SyntaxHighlighting]] plugin. See the plugin page for customization options. diff --git a/docs/features/table of contents.md b/docs/features/table of contents.md new file mode 100644 index 0000000..4ecccc9 --- /dev/null +++ b/docs/features/table of contents.md @@ -0,0 +1,18 @@ +--- +title: "Table of Contents" +tags: + - component + - feature/transformer +--- + +Quartz can automatically generate a table of contents (TOC) from a list of headings on each page. It will also show you your current scrolling position on the page by highlighting headings you've scrolled through with a different color. + +You can hide the TOC on a page by adding `enableToc: false` to the frontmatter for that page. + +By default, the TOC shows all headings from H1 (`# Title`) to H3 (`### Title`) and is only displayed if there is more than one heading on the page. + +## Customization + +The table of contents is a functionality of the [[TableOfContents]] plugin. See the plugin page for more customization options. + +It also needs the `TableOfContents` component, which is displayed in the right sidebar by default. You can change this by customizing the [[layout]]. The TOC component can be configured with the `layout` parameter, which can either be `modern` (default) or `legacy`. diff --git a/docs/features/upcoming features.md b/docs/features/upcoming features.md new file mode 100644 index 0000000..76adda0 --- /dev/null +++ b/docs/features/upcoming features.md @@ -0,0 +1,23 @@ +--- +draft: true +--- + +## high priority backlog + +- static dead link detection +- block links: https://help.obsidian.md/Linking+notes+and+files/Internal+links#Link+to+a+block+in+a+note +- note/header/block transcludes: https://help.obsidian.md/Linking+notes+and+files/Embedding+files +- docker support + +## misc backlog + +- breadcrumbs component +- cursor chat extension +- https://giscus.app/ extension +- sidenotes? https://github.com/capnfabs/paperesque +- direct match in search using double quotes +- https://help.obsidian.md/Advanced+topics/Using+Obsidian+URI +- audio/video embed styling +- Canvas +- parse all images in page: use this for page lists if applicable? +- CV mode? with print stylesheet diff --git a/docs/features/wikilinks.md b/docs/features/wikilinks.md new file mode 100644 index 0000000..ad4f2d7 --- /dev/null +++ b/docs/features/wikilinks.md @@ -0,0 +1,24 @@ +--- +title: Wikilinks +--- + +Wikilinks were pioneered by earlier internet wikis to make it easier to write links across pages without needing to write Markdown or HTML links each time. + +Quartz supports Wikilinks by default and these links are resolved by Quartz using the [[CrawlLinks]] plugin. See the [Obsidian Help page on Internal Links](https://help.obsidian.md/Linking+notes+and+files/Internal+links) for more information on Wikilink syntax. + +This is enabled as a part of [[Obsidian compatibility]] and can be configured and enabled/disabled from that plugin. + +## Syntax + +- `[[Path to file]]`: produces a link to `Path to file.md` (or `Path-to-file.md`) with the text `Path to file` +- `[[Path to file | Here's the title override]]`: produces a link to `Path to file.md` with the text `Here's the title override` +- `[[Path to file#Anchor]]`: produces a link to the anchor `Anchor` in the file `Path to file.md` +- `[[Path to file#^block-ref]]`: produces a link to the specific block `block-ref` in the file `Path to file.md` + +### Embeds + +- `![[Path to image]]`: embeds an image into the page +- `![[Path to image|100x145]]`: embeds an image into the page with dimensions 100px by 145px +- `![[Path to file]]`: transclude an entire page +- `![[Path to file#Anchor]]`: transclude everything under the header `Anchor` +- `![[Path to file#^b15695]]`: transclude block with ID `^b15695` diff --git a/docs/hosting.md b/docs/hosting.md new file mode 100644 index 0000000..e5ef9a6 --- /dev/null +++ b/docs/hosting.md @@ -0,0 +1,270 @@ +--- +title: Hosting +--- + +Quartz effectively turns your Markdown files and other resources into a bundle of HTML, JS, and CSS files (a website!). + +However, if you'd like to publish your site to the world, you need a way to host it online. This guide will detail how to deploy with common hosting providers but any service that allows you to deploy static HTML should work as well. + +> [!warning] +> The rest of this guide assumes that you've already created your own GitHub repository for Quartz. If you haven't already, [[setting up your GitHub repository|make sure you do so]]. + +> [!hint] +> Some Quartz features (like [[RSS Feed]] and sitemap generation) require `baseUrl` to be configured properly in your [[configuration]] to work properly. Make sure you set this before deploying! + +## Cloudflare Pages + +1. Log in to the [Cloudflare dashboard](https://dash.cloudflare.com/) and select your account. +2. In Account Home, select **Workers & Pages** > **Create application** > **Pages** > **Connect to Git**. +3. Select the new GitHub repository that you created and, in the **Set up builds and deployments** section, provide the following information: + +| Configuration option | Value | +| ---------------------- | ------------------ | +| Production branch | `v4` | +| Framework preset | `None` | +| Build command | `npx quartz build` | +| Build output directory | `public` | + +Press "Save and deploy" and Cloudflare should have a deployed version of your site in about a minute. Then, every time you sync your Quartz changes to GitHub, your site should be updated. + +To add a custom domain, check out [Cloudflare's documentation](https://developers.cloudflare.com/pages/platform/custom-domains/). + +> [!warning] +> Cloudflare Pages performs a shallow clone by default, so if you rely on `git` for timestamps, it is recommended that you add `git fetch --unshallow &&` to the beginning of the build command (e.g., `git fetch --unshallow && npx quartz build`). + +## GitHub Pages + +In your local Quartz, create a new file `quartz/.github/workflows/deploy.yml`. + +```yaml title="quartz/.github/workflows/deploy.yml" +name: Deploy Quartz site to GitHub Pages + +on: + push: + branches: + - v4 + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # Fetch all history for git info + - uses: actions/setup-node@v3 + with: + node-version: 18.14 + - name: Install Dependencies + run: npm ci + - name: Build Quartz + run: npx quartz build + - name: Upload artifact + uses: actions/upload-pages-artifact@v2 + with: + path: public + + deploy: + needs: build + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 +``` + +Then: + +1. Head to "Settings" tab of your forked repository and in the sidebar, click "Pages". Under "Source", select "GitHub Actions". +2. Commit these changes by doing `npx quartz sync`. This should deploy your site to `.github.io/`. + +> [!hint] +> If you get an error about not being allowed to deploy to `github-pages` due to environment protection rules, make sure you remove any existing GitHub pages environments. +> +> You can do this by going to your Settings page on your GitHub fork and going to the Environments tab and pressing the trash icon. The GitHub action will recreate the environment for you correctly the next time you sync your Quartz. + +> [!info] +> Quartz generates files in the format of `file.html` instead of `file/index.html` which means the trailing slashes for _non-folder paths_ are dropped. As GitHub pages does not do this redirect, this may cause existing links to your site that use trailing slashes to break. If not breaking existing links is important to you (e.g. you are migrating from Quartz 3), consider using [[#Cloudflare Pages]]. + +### Custom Domain + +Here's how to add a custom domain to your GitHub pages deployment. + +1. Head to the "Settings" tab of your forked repository. +2. In the "Code and automation" section of the sidebar, click "Pages". +3. Under "Custom Domain", type your custom domain and click "Save". +4. This next step depends on whether you are using an apex domain (`example.com`) or a subdomain (`subdomain.example.com`). + - If you are using an apex domain, navigate to your DNS provider and create an `A` record that points your apex domain to GitHub's name servers which have the following IP addresses: + - `185.199.108.153` + - `185.199.109.153` + - `185.199.110.153` + - `185.199.111.153` + - If you are using a subdomain, navigate to your DNS provider and create a `CNAME` record that points your subdomain to the default domain for your site. For example, if you want to use the subdomain `quartz.example.com` for your user site, create a `CNAME` record that points `quartz.example.com` to `.github.io`. + +![[dns records.png]]_The above shows a screenshot of Google Domains configured for both `jzhao.xyz` (an apex domain) and `quartz.jzhao.xyz` (a subdomain)._ + +See the [GitHub documentation](https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site#configuring-a-subdomain) for more detail about how to setup your own custom domain with GitHub Pages. + +> [!question] Why aren't my changes showing up? +> There could be many different reasons why your changes aren't showing up but the most likely reason is that you forgot to push your changes to GitHub. +> +> Make sure you save your changes to Git and sync it to GitHub by doing `npx quartz sync`. This will also make sure to pull any updates you may have made from other devices so you have them locally. + +## Vercel + +### Fix URLs + +Before deploying to Vercel, a `vercel.json` file is required at the root of the project directory. It needs to contain the following configuration so that URLs don't require the `.html` extension: + +```json title="vercel.json" +{ + "cleanUrls": true +} +``` + +### Deploy to Vercel + +1. Log in to the [Vercel Dashboard](https://vercel.com/dashboard) and click "Add New..." > Project +2. Import the Git repository containing your Quartz project. +3. Give the project a name (lowercase characters and hyphens only) +4. Check that these configuration options are set: + +| Configuration option | Value | +| ----------------------------------------- | ------------------ | +| Framework Preset | `Other` | +| Root Directory | `./` | +| Build and Output Settings > Build Command | `npx quartz build` | + +5. Press Deploy. Once it's live, you'll have 2 `*.vercel.app` URLs to view the page. + +### Custom Domain + +> [!note] +> If there is something already hosted on the domain, these steps will not work without replacing the previous content. As a workaround, you could use Next.js rewrites or use the next section to create a subdomain. + +1. Update the `baseUrl` in `quartz.config.js` if necessary. +2. Go to the [Domains - Dashboard](https://vercel.com/dashboard/domains) page in Vercel. +3. Connect the domain to Vercel +4. Press "Add" to connect a custom domain to Vercel. +5. Select your Quartz repository and press Continue. +6. Enter the domain you want to connect it to. +7. Follow the instructions to update your DNS records until you see "Valid Configuration" + +### Use a Subdomain + +Using `docs.example.com` is an example of a subdomain. They're a simple way of connecting multiple deployments to one domain. + +1. Update the `baseUrl` in `quartz.config.js` if necessary. +2. Ensure your domain has been added to the [Domains - Dashboard](https://vercel.com/dashboard/domains) page in Vercel. +3. Go to the [Vercel Dashboard](https://vercel.com/dashboard) and select your Quartz project. +4. Go to the Settings tab and then click Domains in the sidebar +5. Enter your subdomain into the field and press Add + +## Netlify + +1. Log in to the [Netlify dashboard](https://app.netlify.com/) and click "Add new site". +2. Select your Git provider and repository containing your Quartz project. +3. Under "Build command", enter `npx quartz build`. +4. Under "Publish directory", enter `public`. +5. Press Deploy. Once it's live, you'll have a `*.netlify.app` URL to view the page. +6. To add a custom domain, check "Domain management" in the left sidebar, just like with Vercel. + +## GitLab Pages + +In your local Quartz, create a new file `.gitlab-ci.yaml`. + +```yaml title=".gitlab-ci.yaml" +stages: + - build + - deploy + +variables: + NODE_VERSION: "18.14" + +build: + stage: build + rules: + - if: '$CI_COMMIT_REF_NAME == "v4"' + before_script: + - apt-get update -q && apt-get install -y nodejs npm + - npm install -g n + - n $NODE_VERSION + - hash -r + - npm ci + script: + - npx quartz build + artifacts: + paths: + - public + cache: + paths: + - ~/.npm/ + key: "${CI_COMMIT_REF_SLUG}-node-${CI_COMMIT_REF_NAME}" + tags: + - docker + +pages: + stage: deploy + rules: + - if: '$CI_COMMIT_REF_NAME == "v4"' + script: + - echo "Deploying to GitLab Pages..." + artifacts: + paths: + - public +``` + +When `.gitlab-ci.yaml` is committed, GitLab will build and deploy the website as a GitLab Page. You can find the url under `Deploy > Pages` in the sidebar. + +By default, the page is private and only visible when logged in to a GitLab account with access to the repository but can be opened in the settings under `Deploy` -> `Pages`. + +## Self-Hosting + +Copy the `public` directory to your web server and configure it to serve the files. You can use any web server to host your site. Since Quartz generates links that do not include the `.html` extension, you need to let your web server know how to deal with it. + +### Using Nginx + +Here's an example of how to do this with Nginx: + +```nginx title="nginx.conf" +server { + listen 80; + server_name example.com; + root /path/to/quartz/public; + index index.html; + error_page 404 /404.html; + + location / { + try_files $uri $uri.html $uri/ =404; + } +} +``` + +### Using Caddy + +Here's and example of how to do this with Caddy: + +```caddy title="Caddyfile" +example.com { + root * /path/to/quartz/public + try_files {path} {path}.html {path}/ =404 + file_server + encode gzip + + handle_errors { + rewrite * /{err.status_code}.html + file_server + } +} +``` diff --git a/docs/images/dns records.png b/docs/images/dns records.png new file mode 100644 index 0000000000000000000000000000000000000000..bf9f854bdd4b10ffcf43f1d5193559fbea70e147 GIT binary patch literal 77842 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 a3O{<1f78MIRqYe-OH4>wu!R5pm;VPzscn`3 literal 0 HcmV?d00001 diff --git a/docs/images/github-init-repo-options.png b/docs/images/github-init-repo-options.png new file mode 100644 index 0000000000000000000000000000000000000000..dd8893157619456f20e6b71e3e32ea1ef61cf480 GIT binary patch literal 91774 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̄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 literal 0 HcmV?d00001 diff --git a/docs/images/github-quick-setup.png b/docs/images/github-quick-setup.png new file mode 100644 index 0000000000000000000000000000000000000000..5be333f133387679e62b9222f339971c25345305 GIT binary patch literal 153358 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�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 HN6!2KKr^r9 literal 0 HcmV?d00001 diff --git a/docs/images/quartz layout.png b/docs/images/quartz layout.png new file mode 100644 index 0000000000000000000000000000000000000000..03435f7d57874b5c7c438dd0866318fe3c8d61dd GIT binary patch literal 56729 zcmeEu2T+r1_h&%R3koWzbWy3&JJJOeq$|Dm8k+Q86jW5I^w314_uh-5p%*Ek2NeM! z6d^!_5ZEW6_kO#xv$Oln?#zE@zIWzcCn3rEl=GbPJHPWg?~4a&3S^|Tq!0*%O!59* z4G4s^0s=XG_OH|6or*IAcHke9NB8yJA&|3_`2PqXurzA$CV{(#f*hoDfPNXgAhMNJ zl?9)VK6_wJ41vt@DBhLT@*!BARgEwjOP4vAhu^+$v3jJkcu$~GH~aB535f?W{Wr=h zEI1!0#4<=$RX*(y39*fQKlvn9KSGGeu>48H42{eEE7TX?^VD8*bov@=BhNH7af;wz zOLFdIsiyJzy12HnXH(HFULqhCp7Pl69$It$DMCCWg>2E!VVw7q9GKeTMHC0O&*RFM*HGn|O zdW@(na)f=|9%&6fm~QrUpRBfzgHMIsa$i(QNl#yELS{($i;t5Z|B%@fnYeVV`_h08 zyp1kJo}rSjJ*43Ee9#rZ+Dk% zg-w;g1crBNhz$gKE;y74Ro6OBYV5Aha*j624P;0edPcgJ{PxBnhVS3ZTap+>`4@AV zcLqJ~HhE6YU{LeEv!S7(5LN!V^H&dN*|srT*8E7GI&Wk2-rhS#HVA~vD!2X8{ZQ`6 zrsr4v@&k^rbEY1JWG9P|tVJ`g6yupDPztx|j(DDJbRGiHp-j+A5%=xkQ;%L$?2uGU zUMY@|4^+*IW>Y^73Ly!vDWdcEP+@a~ zq$-TB=;o@6le*;QU0c7joOfT3z}%^hZyb^!FmM62y6R|qF0#84ES}r9k1twLPP2_Q z_@79*$+s~4WxB#rVGhD{GScGeD%(M?m(>w>2rE@GE`3)NMg*bQr%plIg(xf*5@Xnu zD6RVAw@yCH*By0L%0Ql**i_?1LU-Jw6<`f>DZ-Dgla$*K07Iy*ZRfrriP& zQ3_9ZQKi-H;p|*GVn(O)q!>R{wl47=d6|=OL}(ONL{;uDE|27$jdKB3n-0v?`LbNG zO1w(!=4S=Q9pjC@6v6b{E8THy5@hQKtvb@!1N2~$>ETw=?=Z{ynf*?2XEYW_j_hoN%`-tMq*$gYl+;3JM(*Dl{;i8h#r~ib&n#xh9J|KhJ2g>!SmI zA%FQ9LqZN*nbmW(Fy&_D z0nCsooyeti&t?fj!ag`KFo5-KLs8fp%>^If&K|CQqB7-b@>=8Quq*9!2|}l~Vq+a* z9b%L^^b(pg$LNK*za?T~|2w1u8IPu`MwHqwdvdv>%m2PZedccTuY-kbsDZ2uW$a|Q_H z%8idbT8g2&k)lZU^HSRn=1yzAG!N!G>1ku3(M&aib2&Sf=Ujz!a9w z1b?P+%VdYx4ccu^u}$$M7C)@t%4fl_Xd@mbhve#lK_~<631Sd2`8~0)CAE zk}N4moU1}BRYSHX1bZGUZ^hBdx+;4Ac7-l&^Cn(FukzTvTxa(?!|$Y`yc2#eqQ09+LZS; z3{Vf>`i|712T|_=IBQOGtu4@&sl00=T`IR-(_q&eoGiJ?Iz@6r6z8iW^X|Dz)?|IV z^PF7!Ou}>wT5ui91i#2JAQca7&wj1OKhl1@0=Ls6?04wKmFBQ-v{fm)R+q+fO}Vp2 z;XVddMwaxJ7qbrra@|&;34B}io&Ex?BBi&%K`MHVUw}wlka~%{(=GR8HUhzZ+#Ai= zJXyS-!x;0XLqdtpS25y`b(l=m>P&>fkop@`@7>;a`vPEjvPct({MPmx?-11^L zwe_`!y){N_>QtxjW4dh!CG|ch&}7zsO9TD_`s%|dw5rkuenVc&L?G%L|707CZ}VL@ zPVmQs8Ql6g4PAfW>|${Y1fr5rL!7HetB8{;w7+kw&+$|IeztHVj)1uErTBul-!+Wv$s+gNqwt(tLn6K!s_rv$x%rX$?&wfDUMu{@8Bq< z296v4XwKO*q_j}I<9}iDXe#ec)zMkw=(HoQ<@Wmu^8GkO*juWmxn~I|EC6WzXU#-m zdCm@*+gELW(i|IGt}0=ia^%sw!4|LL++3e7_M&offeeI8GqWWlO>{m@u;P#aDpEWq z)jiI&4H?*XOM~?7Ie`wd(rFM$6{K?od1|U;cRAmUL_{v`Zx^k=+=Zf0e?i(;_%zL4 za$ovr*rlOP!5XfMe)m;_0O4$3JVwNi)ov-C%yTTV>520AYt+MM^t7!*&P5}Ml0FLt zTF#Q%(6=^6l)HU_TGRGgBaivQJIh99tfFjD9AT6_GcLT;lS~0= zFHr`*OXMYKj4)Y^4J^XIerU|&?x=KyIO$|&UWVU_e71zT8f$s-=^qM=fus7T?IXtT z6C`-xvZMT!@b;`FsqLQdJStjb*W)n*kVy5goli8AMKo)1c=eXlaZD#C; zP!suk5rD90FHsD?tfXJfZ!q#;s?xgm!DYErvmY9XQ$eaMePL(3S0_G0?~T*?($;w? z#jt8gynxaT4-dS;dF`R$J@80^y9$I>5~k~90lye3y_pWH8NSpgE7@;c6$^p zzkB(R=q@90E&}DmNym65nl$M(f{6Ka!hn7?I5Sd82MF4$U~M#z1Z2IHe>cuyGK5?o~7{EpQwwE}M5 zDUapPC4xM1paAY?OvK|#QMfn>Sm@KYWvbWLTp|~w^(7r`#{gO_M4(5xj_*BMltRXRS40DG4Z+-D zm(%UrsBs-KhP(Yf18(q3&`y3GXtUL5f|ITiouCLIq+KpdFU7!~Z(K;np~zV6tx)cq z&wOO;8GSlmi@Jiosam6^FnismscS28xeMLKCr6i$Bz2dL?Y>!4`S59M1`g0a+nWD8wx|; zXUDTW*LBbNyr5z{f^a?G6y2Mic;@&g^^Batg)ML1RH}>zDMdsHyL>-}$5th4x2l(6 zT+jrhO`Miak8j5RGkOHe8cm0)P4hmpOE=A^rOG2__&W%wx5 zwKxafED-g2Gc`!StNL|irv5XUk$i^eu6m@~If>jj{(4$q0vb)hH}k88F@V4wxd)OY zjlyS$`yIgrrzi1g5Hw+as=?iMZ-F;>k|e~W{!vyleqH-T=eX3Rc19nJ8H^|j6t27h zX?P*>xno`Ksa&U25vM|fvRbso#2^{v?X_K`|3-rO=;=?on)fV^7buyTMqR%&!ZyP4 zN}K$e&s6z5OU>8eg{Y(me@R>6h@?>>{UptH0Qm6f!>HnftCv?TbXM!3v6AYY5|J)Z zl3||>^4g&N<-GJf7Ke^x{+IKr&y^F2pbm7a4W%7kS0j|2Y`gfDifi50*`FZdu2vp{ z#}Gs94|%<0GEq1FYKosC&FF|odRMR14?KNiqJPG-^s(~YdYuj*#s2z{d{QKUR(F6I zxNZOVyz(;LzHc{m^0H7(X3=+~pi3}V#3xJq7nQJjx`=w+?pZaP zrVhzZ`>#w7-cmC6i)=Z7|D(8GNj1PuyY3eFzesz2AleuGHE0L?hv zcAD)0=aP4F#Y2z{q+Z}GGN2?^3AK$OO&8YIV~WfXV#gAbR^enN2)3A)ILn%cy<5&w z@yv`qkbd;#_FQ0(?P+_fyevIS`o|x&EOCdJHd9YB_W`f9DdEJtXJ<;b4E%#X9^$aQ z8^K4|LUfX4ae*5Qduz$!((q)Af92xfJ@E)=QMvTN&yHg=fKZ#juL$r6{3wP#9NB0y z>QB~Etw@oDE1#x`rXKU@zDBIApDTEHw&CFNn)ECfaLDTt zQwS-U$=(W?T`?H`sd|Sm@~sMIo9jHh65z!F|IlaW)d7>%;;i~~%uxpWn(Z%#G%@$w z!j)x<$Tk?8aFx2f)wmyK*1HHn*Qxnd3{Ir)kP~W|+-@(kHy~TMC+|dz^za*9^mpZ5w9>zQ!)miBCQG|~|;mXiXCJj5A z_@Vt zspVvNVE;&oeX-Q`(4BO(Frm9tg^uE)qT}|z5IRxD#j>eUA+R@=wF$WUmhKgB|J`Lg zpogtnrKK~7<*wTHyOM2Pa%940{Cp%sLS@^M`^0WQ9Zj?;2i=IiUo*mZ`15(X|2qtJ zYflL2kok6C^W2E}n0k~9pd8Bl_cK>@k&NGOZj5bd@gGl7rE!WhWfsmKRh|rXM}T}J zBFQQs-(Q70Q~gVY%}gPG)O=sE>MoO|E)B1JZcFe+3r2k7ynxBDO|s7=*k)r8#-P;l zN%7f8e_@zw*AyX-6S4{!$fn2X=rlH6humk1w?#EOM%PEKnfK)G zyD(`caS6|=J^4(+H{MmT&^^P|FP%dmV`n`%(>YHXzSv4@qx9B9oc4HfW*!0}d|i(A zPG{Jn86*%iyahC9JGioi*gOl(E9!9{#oY=Un@>Rk@M_r0AZD&~w1N9-zh_?~O=w@T zfm}BY9SALs+K*G@NsHN|2#@i@LSif&QJ&Q(v_+{!|1PU>=E9evvew4$gASoJv8SzE z_}m}N7amM)Grr&Uz=?Jt6v_Bd;*(NYE_5ybJ66Nm zGk*AUIV^(%U+{y3D2m<>_hcb_JZa+et6~~>Z4#9zw^fcMM=?wLv-Xw&PV{bN!d2U2 zb~Zb7UI8988Gw3~x6EyiS8nGg=vWX57}hf^q+MdSHpLP{&}b1lcao&q&y%t9g=BWQ zvdzIqhlQT0D>WTmUi(rvVS!hz%RTt*KQ1XQK7{6ept;O&!r1cp(Vz<%P<9R~MY?eM zDQc!Jy^pCbt@`rzG5m!Fd1~n#McVMw3SvRk@evD!A4mbV!DFkOioHYZMs)G}l%HSf zLtln!MHs7jPRf3-pDr#;u~b^R6DdhxU})|swp6&-{aL8I(>M70i*FuYR{rr?;$`m3~g^X;OK zQ`FeNnEMhF1H0-w*h%d?ra^$t3+b7V( zLFuW*2+zfuEh=euFyy2yrA515a@8+zy$^w~Mgt;(iR{C12Ib?Krk|QDncVdxpm<@8kdDECFig5+CNDJL072i zu9_xa)y+F$22F+Uc=2D_N?L8rmUOSDm<<%SqG?cG{7{!$uq%Mn9M- zBQvx#VmB!8^2?kXhRuzfvbhf&#ZzzOY0CjOS}Yz#?-JF-`U_^+*Ah3_45;A1qT)pp zx@}r(oX5L!?oyR4Gb3V$wHv71m#zcKZSI-4z6KSA2(@F=hS({<<*snH#vbk~a@>jy z>|XV!L3j1}fzf{VQFwUE>cZqkw9H{Wy-ZMZ)f{eDY$=ch?l~2|`G%{isw$oT-2qHa zu*S`~@*VqUX28!w_{0!KtpR&}1oAf?e-g4lqvS@W^WLXg10JNr{6r+OjXN%xCTxcxE`VOpps7X^DNmhf&Iyfz+o;TF3mKIbx!zo#x~MCCxW~g z_PnkWa#Q%_KgI0!WTVWR$6r7G-;lt6l^XT$)iYbcW1d%3yt~FOxcwdLj%Q>iF&qr{ z&Asub|5~G;-bwkV+799VPj&e})#d+Gm;bHmGB{lSD}BoB)bDfF-euGl)DnR17hRaD zaY&y#1yM;Ac0j~Stk-Av_2p;%#RVeHiFauaXRhDRlff0B0WT%g5 zPIU=24&y6djGhDoG9bh?<)cW7$c6TmH>T0f9OZ&=;#R20e+-- zxFUl%WE(wmSAD#ei)t6%RP{S zt~}j4z=CzFwx&4wT-@>J^0O&8E!`J-66o3nN5#C6zP5Ssw;;;BLT8u1J0D){-@s4s zL*^a^xIOATMTa>1Qv}6V_jkZ8%pP%62#Cz8Q``2?r=uXAHa0fmp|2x7g3wg|(-Eaz z;4Hb{Ne05LLEo}RV3Ig@o@(Uo2$e_d%WKOp-f zhnuNX%ak@gU8D8JofdF=_4HDH)%&+Z!7N*UeS0O4pJd`^k3<0~zIw)cE|6F!E=-^D z`{96ht0sgRSlu6UVmL!i&koA?yiPuQ3rR~YYScR631f?R*U|H-Zr00i4^h{u4WaH+ z`Msf#FNsMjMNY(e=2OgFr0rk+SvzNafb$KumcTr?n!Go7>Yjw@HI>r9MayQRU*ObimgtkJ;-^IM?8e(6mc z!1)QTU77EJ#L!!=@>Ta>WdN9C`uN=8Zk+@Hn#hoVHKi@*&q_GE7TlT!Uoo~kyFh@; znOXtFciz^@8;XGbC5?Mxk99&6bDV|#d|Fh$EhvycN4~J|5EFcac|d@G<7Z@>8K}c! z+^7X%O;Gwsb7LOM#acq+mUoE&F0OEiEIY}ie^I1N{fA(Ha zmZW?g$ePk*f?LlOl2qh!IIK0kTI-NQ1>={zpg+kCP%BA+*xk!!zIX#25a0nz%?XrJ z3&&m|`O-g_7Q8LDFw$U?vaMba9?&n*liX+Qf_Y13Lz{(JtQT+9{ z2DCUPTOk^G$xKyy4B1nN-x(sqD#@KuO;KV~%3G>GfMQ5-^G9~HSK_CGd6~oc=5evb zPR8Ko_7ePBra!W$JKWBb0h&@u%iKRJSbl@TVyzbJNGf4*i$=;0psCTwCZA0Uv7iGV z0-Zb}<>{@3B-1M)idhtY4q`j$t-$^5C)#=r%w4c{)C)>B=Vnm}2}3}H*Z;6h++PP` zq<(Cv4K8dJU&*YD2Q~jDNbiWwWkkK-4zvN3gHuu-9$nD${g-w80_+ZL;xbv8L#bA> z^Rwgp;Mt7bAk#8XEF~G`xnzM`ts3m1G7Webve^iHu)XG!V!7gW=AWV`3Alf1-DhiZ0IRbZS zYud@$M&9vU^0`A$qWFF3$>M{vy>jX_;SbwC5eH)SX>F#VieAj^9akr?oU`To%zCf| zfKE8(ch_8kn;jnxQ z37?-|nlThAQodVObAvYNz$x_kyKEp^tc4puIb^XVXsu?v=jmB0(x0{@<*dMhF>lNf zc*u#K#CslMv)3hmPm^n9DC>?j(1(edm^S-VNCIBd8oRq@J(8;$D02kZdY=8NFg?nb z{oW!OKHpy8UE9E*->GV#tX$_JY4Yj~0&~OyB++wOT>Eu0Fncd;tq)7Wyhldi489ASU92zT`pc{PhTzUR1pucOG#_mBiki60 z)K?s=d$)1cy#=MF_;C~el4kUPMEV>Cjn}i`qwY{?CT`Vsq2s(vv55?38#qwitG?f3 zx4wj)_cBp$0!qIm;F@N9TEMw;TNy1li4^VI7x4gJZ^5slStXk2bro|Oz0A?xVDE^! z%t`qgVn*9{2|pZe0)(M*>5h8v{s_JjccKc0=&cdJF-E zdpmwZ7Js&9w+|VQes$sz$rd<-f4X3e@Tnqp;<0)HsF6$(p{p5w-au>_F12oMDBgkRRXf@cmw z3S0pS9Q4P%Kf@T0N8#x&6EcGjeV1CaO8Q>+B&NI#yaY5K&1ki~?{VsjPMp{(pe|E; zI;w(go#M1vtQUA1_#`d~0BqN^So;n4v}oylI~twVI*EASK`BgM5l<>AWo=2)+jP)T zVqCB-O0LvebD?@NbJ4(tE?RP{t7|=7_Pbuyc`;1nx&vQ0@G4uWa|gLVxf#q3QV3p< zx-@TAaU0ggKW7Z82LibgM|`mU0EE&Ky(xmWJ(E?o5*zrOrmDoa$txjU!aLF2s?FBc zcB!S#ek8XN*quQBdZ2?`Oo+ZwhnG3#dL8?_YS_~b?6aUZ~Iu47O2 z-RZ>!o(|YDbta>Sh{7^K;awmaR?M_Pdh)PpPqimAjr%G&7lHDPa`;=DdlEb+t3otyHtYIw!2fe1!TuFPqr zq!|XSsd3A+&2$gEa<<&7SDd8kI2n=fEH8z=`4zHqFJ<8g8;MXIY1OV;!(9p4 z8Ck2QmdnP^3l>iRRJ+;nAH1&yLngXel=Dy zj}Qt}jGc4-s?R5#qxd_gIL8I9f6o1|Avf*7(Mhi9>-B&!;OBeQ>9P{>>9i#C!g>*c zkz5UE3Frp!mTrn6($;hpPB2Kl?Szne^rgN1>*s^L{>%dBg4YsV7_LixYh;}#-j2@? zp8>RM*~>j8370nLx8W$^?G1L6;L|;I?;C68gth~OUwQH zyLD|iC*Q-pr6ni9xmeF6((5*KHCdxJxG9czIKt|FvE_siXX#XkBjx%5*iG^4LtL}d zjN8pT??{qZJ+SD^qk~*CXsNBskY~)=VpR(Pf_^^h&U5F+#8*zdn3>S?()C~Vv$u;( zaZn^G2%}#2l&~16QdWqBfp~F)Np_e>oRV;j{0*2kD81v0I%&WTb1_vdcylZs<5+$bbzwr&ZNY08mvP<`aZvVB@Tb>*FMH$Ur_NxBQ` zWTxad#;Y*R+|{$@V5=4;?h_9`{ZNUkO+srM64x2ZHR3(VlU{r%Q#0FFklFvN`hJxe};XGJyc@xaU96?)Kw=c{a@~coyIPcd6Ns|_z=e%tM1-E1k691a~dK{1{cLWHoV(VlXRv1 zp7||_lO`LuxQ!yIqsX*8WXOs}Wj~P}Rg_LQ}l_%0;)mQk9ihM$D-kWI}OcvsEwO7&D*4Dy&hB z`fj+RgTW)CEi`W11Pi;c?e>n=nPZmRZIHWK9S9j+3Ti;25AMwap#IG3%w@7h)^mn* zr2UOJxx813;WfH#&|N519Q3g;DQ9{>BIQ$QOZTgmhRjao9gvUVOf}apVl-%07!8=8!vs%Vp8liQZH?Jd{ zNy>Lm>o6tgH5C9Q1pXS5J}#^znm1_kQL0%9sO;DDVF39ERxn_`1)#4cd|vjGs-PbB zvT*kL7@)y?0nxPzQW~^$9Y~N*W^ude7;Pqg-5EC(O~=OjY<{BZJ#}940qZ1izqfcl z?gLDl1i*Fu8Gq*TDF%!FjVk6D+Li=;^dX)pIUWmS4Ti5iw--Lb(+=AvPm24J9zZ!N zy-+26Q>vFwW*3rhY<3XqooBMVfbUG^B|vCdu6Y4r-ar{ixkI1!L(0)(hl^S zfNFRz?Y&>Tnholyc=C#?GcPm9Kqq_bf`##qpkpYF;`QJ@BQ^j|K!U8(M7cQ<1rVA* zI5K3p+-rTtcLbA5ivl|OrIt*N;udYg6Gqw&v~qxSMVR6z9rGJZr_5o+Ip{n*5^Yk3 zXkXOui*_AC?{~9hZsP_Nhnm#Su3_Hk|Hx&IJ_QLk2bxZ0O^)MA7!^abi}%)=O@*1TBelCM%W9YCo6x^(Ay`!ZJF67O?f17;)gfw3S6& zkdhmonglhX4;o{%f}}trC`|&`2l5h-`3{`zUi5+Pp6qJK=7SR#Mq-H2A`4v3Npkip zV^x`IQj?2mF`xLvJd={Mr;_+I*Wc8BG7tIads)y<5piDSMgn@yU-~r3UaftW!z5k1 zvC6}g5OiYn1J zx=sBFLv&Fp;aj#cSpB@0OBNI>Myj33xRu$ur40X4ml)x3@ujzhQRrBOt2eWEm-8~c z22jyK+u5lcpKMZvFKhdA{#Z0vN48*d!Cp&dqUR z`Fq+=H0+SjW9Akhx z022NQ3ZHgr76{3X9;ap2{u18nt6ElTC1F!7CB;cjk-f*RaiD^Gss6<4Qb!7dhikN& zn4LR3@<+ip+5jzg1#)_ReUiq=y&&L(+vC^}k zZZ2*QN$i}{cifG0`lsjtx!wCu0Ri%FKkMXg(mi4Z zlo2HWYa^szWut0KKurufGX```1GVwhN+9rXNGEKcz*ol>rsod5Y>y`p;;dU{vKFDe zpbO|HAy6yC*J=j{%jrR*ctV)245(TX@;Q_A*(#p--sp)sfiibL6af^MI|)P_&JiiW zD8o4pNC(h*LIZ$PBrLv-+QJNgD$D1BlP2Ky5^nP-B*dXGo&cf}*B3F<7Zm_#-z}ih z$nPROrwY{mu@^YomF{1*J6>i8gyM@GbF&|yi4%aVZ|Tig zJiL{T@z5g_$VB8{GX8~^jc{a*Egp&Q$vn_Zxoq|Tq{!tev+1u-wh+R%Mq#!VWTp^) znL6=!n}5Ime?;&eG5TXD=@!x%gAN=luLVOppiKq^@{eZpIBz{^isJmp}?Kl%*B2y67YwZ(lb4vwq3AB&n;KY2H zExasA=mWY>5Pd-NJbBY!`b6=HALitX-xi90(WUk`%MXMNUN`WT`TMG*{JZE0fy5Y_ z5<>LepFVSj^3UJ*QvMIVdiIN--o}1okMB9IEa2tKM*dskT+>YbMpSK{@b;v)( zEAU>*zh#;KHFoCzt-bA2{9dx5MDYBFxlyAdP|_U+9Qv@52cY>qKqVbyQrSY%($Y(f zz%Z6UGvQOZx;P{PZ zu9qTcS7NrX{^Y;2qV0!XRQ1E{du}d)&^0nTs^w}3c69}XN^J$kQ2QG)Kw>&4CiKL8 zewuBv&9Yd04}U%Hwmh6u4ccgK{FnhXHbRKb>5H74!{5JK;giW%jNzQ`p;E4fVI{64 z6^Dh|MK5E7kr%I+|2d$_ciI~(Bch_NXexnF0GLk{!kH5@b0QRrext+ExkAa#xc_#?0!i!zLad&>hG|(WyNHwBY=Q5W8 zek-@x@>BSu9l*@HtC=|IgcX;Wv~V1{UXuMYgc5hKoG{eIyX5E3=Yd$yD&7G&RofQs zpy$B=B{x=V0o~9rNU~GGuOU*+h<*0A?OpAy+YK(p=?4Q&;oIg(Jcrmt0z5LP$7;65 z!_Nl=7O5u_ zgH&gP)JWXx^8-+QbZ*YH>HriuNgzL*!ErSp!Vg}lq1hMBiUZw!e-8QWdElr=@U2uZ z>+_7#Dv;aWDCMD?4Cz3FyEzT_gy^PCfT-&C3Af8h*3;hvYNKN-0z3uYl9s0}Vb%e6 zu|Q4plCS(S-jc4$1Q}J8T!>~&N{(p+S?uWYw_6)C4Vr1wz#lCyxSGsU0}84k-`x*b zBn-9bldvRI>kEwGo20HxWy(kFHu9!Qq{T_v0P`(Gd()5$t*uVkY)NZ&?YL%YD#&px(<;J3Bs}gy&X&`<)?< zgm8X~UQpu5-Q3)SO>=&=CVKf56uXyyZO+?g-N=;$GMnxB1npX?hu<6AmtOjK@lPA_ zTug!=>OL>B1~$braPMPrM@Prmmy-B~G50`l&_2!IYxV>6=f*)>$ukve6K&vePE<)x z!#nX0&uAXBcI5nsQFAQ1{M9krfS-%w?VwM_x53E(fbBkIUaWw-<24M`4?woA0>EtY zMcA|0*jNjhZ@YNCSADT*o3t>~jhXu38bGFAI>L!!Z-ED^6zF?>{Xh&NU9Z|lrSX*H zEhx-_$H9Mvnl-8G|l*L2sjFv6@{f-G#26fFc41AYrf!UAyLti zY=t+pC?7KEpfb88x7zZm3_T2B$c#L$8C~3jrq%6z+>2ad+HIIz8@M7h$g+F$+jpRs zv-Ricjnhp{H=(Vt4cHLo_%IN>pBD@xS4q7*s9;2EEGpEL4j$=p2~#31dhx3WT?Imd zmrw^X^*B7Clv2i{Ef6chUDw^+ZDDG#sdmyi1_^oTCZtPp&NlT1?e%X%Lk~15C7H~) z)yYc2J6AR~GLbgEP5fd)giW9>Z-Km-0U&$YPOabj1tXO1t2Ge{LGbLE{E4}z;a!5Y zRYTqRSrmslAAKX2i#KiQgIfX}yqY$tUDI~JMl4|Skh>N~-_n*y^*JduIKR!W(~UW{ z8#rBmY_DRL&S_RcRL>t-<0sMoEc-M4-7bSbNB&u1!kJ{|>HICLDc~GL8AwjGYk;__ zZ9$3xoemmJK8FXso6%=Seq*f=o$z|04Wr`42Wis$SMuW$q|h3qMK z0#oMLH%!Jc+50hOfls*hvbptw<&UDIekx)SL4ul;9gqMghZyiuM4}DEbX{L~eoF(1 zmOXrTUK0PjslWWG-ntD~NbqxuUM(_k6x|h+`@ZEx=PNybxWnoNXCC((v~F4ZR0*0? zuUtoIyhI-W(r=vWl~2`0-BP0hx^i&FW!5IE*qneUWFXz@lP1rEmp?}Dc( z>3CN{iPA1kB-GJNr#gwRx^b%CCIc>J=AIATQUyggeDfY3?b5YxgO&0!YONXJdYseT zXNnU7U)I+kk0-s^J18rObR1l8oPM>@WD`1Xqm_%%c?GHY{HFSV{^RyAAP{rFP9aq2 zBW33OxMbWH`t~P~WGsrMZuwml5)rT$`p}g;VZhDW zS(|+v6LdDx5ubUg5|X^pmysZCdE6iCwCqZ-jqG*7pIkys~c;1mreV zCO0Z0ZR^OgOowwVrG7KKNU7K>Yfn^&CN96&pICR1Qh9#3OQg3Ih$Cn=q53V4 zY9B6i7pP}SJK34u@okZxH-mI#@yaHJhwQ9P*A>jFsHt7S)&kP?w<|2C9;1|7qO7n` zsV@#65|De}ux`q`?#S$S?xKbK7k#O(+aSyJ%nR3r0V&wk`=~V7D$I{3!iwDVMVS{5OqwK7#Fq8OT`l175k0 zE=(u>RKHDbgnSn*r7F{3B|g7?er=Am6@x_>?^Lx+(xkJJ#n_=QJ&1Icc<1cVIL=z0nZg7R>Q^ zRpVuMex!;}$(7IcH6ZzaCx1`NAnJB+^!SPsj|VAq)j>=;GNeu%TCxNfap7^ME#Vhn zY&QUAu6AeN2THy_tE<;fl+#~qcrnYO=J4=vG{P_TyT`bS=@vVV62})`aupyU##4kOTbxvklFtTd5>UcYL2feWA+%(+*y#7h`#}R0_B^ zdJ{NxZH#4{+kl9yzcQKVEM)B+*l7?0?yo8RNz**uJ`F49lvNR46Y4+Ry%WziAz}d_ zNEPzRV>GDV1ES31^Biv7dl@LU$UbGERd1xLtK4#e1CKH6_JX9)^@Y3ECV0LRY9on!R|Uibu~3Lf2Rd+M($eq01WVK-1PqU`}5D9KX)i~nQasnE}UGd z)b7C=UBP++gyjL^^w>B!w=&{q1EB zAh`T!%5dw^n_IkNly<0l#Y{|0!W9k^fW0LQ@f!I9u|VnQ-Wz|=@(#1+SF9FCFy{bG z<>l)RHRz?mLA^5iaySkQHV*P;HW|zpWK;Ij_QfqNey%42dV}7^VDcV6etdTc6}Gem z%65uCP@GSqTV~Yo8a%-d_NxfQi1Dx@*Vdj9Jthdx9GjdD#7-1A5I8`0J|6c z^0TtCf-@tww!!aG!S4(U@9$65bvrKw{?7YDyefd)1cn;Z)ZZ≧itPg6L@|j0))h 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 [!note] +> There are two additional layout fields that are _not_ shown in the above diagram. +> +> 1. `head` is a single component that renders the `` [tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head) in the HTML. This doesn't appear visually on the page and is only is responsible for metadata about the document like the tab title, scripts, and styles. +> 2. `header` is a set of components that are laid out horizontally and appears _before_ the `beforeBody` section. This enables you to replicate the old Quartz 3 header bar where the title, search bar, and dark mode toggle. By default, Quartz 4 doesn't place any components in the `header`. + +Quartz **components**, like plugins, can take in additional properties as configuration options. If you're familiar with React terminology, you can think of them as Higher-order Components. + +See [a list of all the components](component.md) for all available components along with their configuration options. You can also checkout the guide on [[creating components]] if you're interested in further customizing the behaviour of Quartz. + +### Style + +Most meaningful style changes like colour scheme and font can be done simply through the [[configuration#General Configuration|general configuration]] options. However, if you'd like to make more involved style changes, you can do this by writing your own styles. Quartz 4, like Quartz 3, uses [Sass](https://sass-lang.com/guide/) for styling. + +You can see the base style sheet in `quartz/styles/base.scss` and write your own in `quartz/styles/custom.scss`. + +> [!note] +> Some components may provide their own styling as well! For example, `quartz/components/Darkmode.tsx` imports styles from `quartz/components/styles/darkmode.scss`. If you'd like to customize styling for a specific component, double check the component definition to see how its styles are defined. diff --git a/docs/migrating from Quartz 3.md b/docs/migrating from Quartz 3.md new file mode 100644 index 0000000..2fdc731 --- /dev/null +++ b/docs/migrating from Quartz 3.md @@ -0,0 +1,41 @@ +--- +title: "Migrating from Quartz 3" +--- + +As you already have Quartz locally, you don't need to fork or clone it again. Simply just checkout the alpha branch, install the dependencies, and import your old vault. + +```bash +git fetch +git checkout v4 +git pull upstream v4 +npm i +npx quartz create +``` + +If you get an error like `fatal: 'upstream' does not appear to be a git repository`, make sure you add `upstream` as a remote origin: + +```shell +git remote add upstream https://github.com/jackyzha0/quartz.git +``` + +When running `npx quartz create`, you will be prompted as to how to initialize your content folder. Here, you can choose to import or link your previous content folder and Quartz should work just as you expect it to. + +> [!note] +> If the existing content folder you'd like to use is at the _same_ path on a different branch, clone the repo again somewhere at a _different_ path in order to use it. + +## Key changes + +1. **Removing Hugo and `hugo-obsidian`**: Hugo worked well for earlier versions of Quartz but it also made it hard for people outside of the Golang and Hugo communities to fully understand what Quartz was doing under the hood and be able to properly customize it to their needs. Quartz 4 now uses a Node-based static-site generation process which should lead to a much more helpful error messages and an overall smoother user experience. +2. **Full-hot reload**: The many rough edges of how `hugo-obsidian` integrated with Hugo meant that watch mode didn't re-trigger `hugo-obsidian` to update the content index. This lead to a lot of weird cases where the watch mode output wasn't accurate. Quartz 4 now uses a cohesive parse, filter, and emit pipeline which gets run on every change so hot-reloads are always accurate. +3. **Replacing Go template syntax with JSX**: Quartz 3 used [Go templates](https://pkg.go.dev/text/template) to create layouts for pages. However, the syntax isn't great for doing any sort of complex rendering (like [text processing](https://github.com/jackyzha0/quartz/blob/hugo/layouts/partials/textprocessing.html)) and it got very difficult to make any meaningful layout changes to Quartz 3. Quartz 4 uses an extension of JavaScript syntax called JSX which allows you to write layout code that looks like HTML in JavaScript which is significantly easier to understand and maintain. +4. **A new extensible [[configuration]] and [[configuration#Plugins|plugin]] system**: Quartz 3 was hard to configure without technical knowledge of how Hugo's partials worked. Extensions were even hard to make. Quartz 4's configuration and plugin system is designed to be extended by users while making updating to new versions of Quartz easy. + +## Things to update + +- You will need to update your deploy scripts. See the [[hosting]] guide for more details. +- Ensure that your default branch on GitHub is updated from `hugo` to `v4`. +- [[folder and tag listings|Folder and tag listings]] have also changed. + - Folder descriptions should go under `content//index.md` where `` is the name of the folder. + - Tag descriptions should go under `content/tags/.md` where `` is the name of the tag. +- Some HTML layout may not be the same between Quartz 3 and Quartz 4. If you depended on a particular HTML hierarchy or class names, you may need to update your custom CSS to reflect these changes. +- If you customized the layout of Quartz 3, you may need to translate these changes from Go templates back to JSX as Quartz 4 no longer uses Hugo. For components, check out the guide on [[creating components]] for more details on this. diff --git a/docs/philosophy.md b/docs/philosophy.md new file mode 100644 index 0000000..af5510a --- /dev/null +++ b/docs/philosophy.md @@ -0,0 +1,47 @@ +--- +title: Philosophy of Quartz +--- + +## A garden should be a true hypertext + +> The garden is the web as topology. Every walk through the garden creates new paths, new meanings, and when we add things to the garden we add them in a way that allows many future, unpredicted relationships. +> +> _(The Garden and the Stream)_ + +The problem with the file cabinet is that it focuses on efficiency of access and interoperability rather than generativity and creativity. Thinking is not linear, nor is it hierarchical. In fact, not many things are linear or hierarchical at all. Then why is it that most tools and thinking strategies assume a nice chronological or hierarchical order for my thought processes? + +The ideal tool for thought for me would embrace the messiness of my mind, and organically help insights emerge from chaos instead of forcing an artificial order. A rhizomatic, not arboresecent, form of note taking. + +My goal with a digital garden is not purely as an organizing system and information store (though it works nicely for that). I want my digital garden to be a playground for new ways ideas can connect together. As a result, existing formal organizing systems like Zettelkasten or the hierarchical folder structures of Notion don’t work well for me. There is way too much upfront friction that by the time I’ve thought about how to organize my thought into folders categories, I’ve lost it. + +Quartz embraces the inherent rhizomatic and web-like nature of our thinking and tries to encourage note-taking in a similar form. + +--- + +## A garden should be shared + +The goal of digital gardening should be to tap into your network’s collective intelligence to create constructive feedback loops. If done well, I have a shareable representation of my thoughts that I can send out into the world and people can respond. Even for my most half-baked thoughts, this helps me create a feedback cycle to strengthen and fully flesh out that idea. + +Quartz is designed first and foremost as a tool for publishing [digital gardens](https://jzhao.xyz/posts/networked-thought) to the web. To me, digital gardening is not just passive knowledge collection. It’s a form of expression and sharing. + +> “[One] who works with the door open gets all kinds of interruptions, but [they] also occasionally gets clues as to what the world is and what might be important.” +> — Richard Hamming + +**The goal of Quartz is to make sharing your digital garden free and simple.** + +--- + +## A garden should be your own + +At its core, Quartz is designed to be easy to use enough for non-technical people to get going but also powerful enough that senior developers can tweak it to work how they'd like it to work. + +1. If you like the default configuration of Quartz and just want to change the content, the only thing that you need to change is the contents of the `content` folder. +2. If you'd like to make basic configuration tweaks but don't want to edit source code, one can tweak the plugins and components in `quartz.config.ts` and `quartz.layout.ts` in a guided manner to their liking. +3. If you'd like to tweak the actual source code of the underlying plugins, components, or even build process, Quartz purposefully ships its full source code to the end user to allow customization at this level too. + +Most software either confines you to either + +1. Makes it easy to tweak content but not the presentation +2. Gives you too many knobs to tune the presentation without good opinionated defaults + +**Quartz should feel powerful but ultimately be an intuitive tool fully within your control.** It should be a piece of [agentic software](https://jzhao.xyz/posts/agentic-computing). Ultimately, it should have the right affordances to nudge users towards good defaults but never dictate what the 'correct' way of using it is. diff --git a/docs/plugins/AliasRedirects.md b/docs/plugins/AliasRedirects.md new file mode 100644 index 0000000..8c03653 --- /dev/null +++ b/docs/plugins/AliasRedirects.md @@ -0,0 +1,37 @@ +--- +title: AliasRedirects +tags: + - plugin/emitter +--- + +This plugin emits HTML redirect pages for aliases and permalinks defined in the frontmatter of content files. + +For example, A `foo.md` has the following frontmatter + +```md title="foo.md" +--- +title: "Foo" +alias: + - "bar" +--- +``` + +The target `host.me/bar` will be redirected to `host.me/foo` + +Note that these are permanent redirect. + +The emitter supports the following aliases: + +- `aliases` +- `alias` + +> [!note] +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. + +This plugin has no configuration options. + +## API + +- Category: Emitter +- Function name: `Plugin.AliasRedirects()`. +- Source: [`quartz/plugins/emitters/aliases.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/aliases.ts). diff --git a/docs/plugins/Assets.md b/docs/plugins/Assets.md new file mode 100644 index 0000000..47589b2 --- /dev/null +++ b/docs/plugins/Assets.md @@ -0,0 +1,20 @@ +--- +title: Assets +tags: + - plugin/emitter +--- + +This plugin emits all non-Markdown static assets in your content folder (like images, videos, HTML, etc). The plugin respects the `ignorePatterns` in the global [[configuration]]. + +Note that all static assets will then be accessible through its path on your generated site, i.e: `host.me/path/to/static.pdf` + +> [!note] +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. + +This plugin has no configuration options. + +## API + +- Category: Emitter +- Function name: `Plugin.Assets()`. +- Source: [`quartz/plugins/emitters/assets.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/assets.ts). diff --git a/docs/plugins/CNAME.md b/docs/plugins/CNAME.md new file mode 100644 index 0000000..b81faee --- /dev/null +++ b/docs/plugins/CNAME.md @@ -0,0 +1,22 @@ +--- +title: CNAME +tags: + - plugin/emitter +--- + +This plugin emits a `CNAME` record that points your subdomain to the default domain of your site. + +If you want to use a custom domain name like `quartz.example.com` for the site, then this is needed. + +See [[Hosting]] for more information. + +> [!note] +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. + +This plugin has no configuration options. + +## API + +- Category: Emitter +- Function name: `Plugin.CNAME()`. +- Source: [`quartz/plugins/emitters/cname.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/cname.ts). diff --git a/docs/plugins/ComponentResources.md b/docs/plugins/ComponentResources.md new file mode 100644 index 0000000..6e8c82e --- /dev/null +++ b/docs/plugins/ComponentResources.md @@ -0,0 +1,18 @@ +--- +title: ComponentResources +tags: + - plugin/emitter +--- + +This plugin manages and emits the static resources required for the Quartz framework. This includes CSS stylesheets and JavaScript scripts that enhance the functionality and aesthetics of the generated site. See also the `cdnCaching` option in the `theme` section of the [[configuration]]. + +> [!note] +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. + +This plugin has no configuration options. + +## API + +- Category: Emitter +- Function name: `Plugin.ComponentResources()`. +- Source: [`quartz/plugins/emitters/componentResources.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/componentResources.ts). diff --git a/docs/plugins/ContentIndex.md b/docs/plugins/ContentIndex.md new file mode 100644 index 0000000..eb7265d --- /dev/null +++ b/docs/plugins/ContentIndex.md @@ -0,0 +1,26 @@ +--- +title: ContentIndex +tags: + - plugin/emitter +--- + +This plugin emits both RSS and an XML sitemap for your site. The [[RSS Feed]] allows users to subscribe to content on your site and the sitemap allows search engines to better index your site. The plugin also emits a `contentIndex.json` file which is used by dynamic frontend components like search and graph. + +This plugin emits a comprehensive index of the site's content, generating additional resources such as a sitemap, an RSS feed, and a + +> [!note] +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. + +This plugin accepts the following configuration options: + +- `enableSiteMap`: If `true` (default), generates a sitemap XML file (`sitemap.xml`) listing all site URLs for search engines in content discovery. +- `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. +- `includeEmptyFiles`: If `true` (default), content files with no body text are included in the generated index and resources. + +## API + +- Category: Emitter +- Function name: `Plugin.ContentIndex()`. +- Source: [`quartz/plugins/emitters/contentIndex.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/contentIndex.ts). diff --git a/docs/plugins/ContentPage.md b/docs/plugins/ContentPage.md new file mode 100644 index 0000000..bd33e4e --- /dev/null +++ b/docs/plugins/ContentPage.md @@ -0,0 +1,18 @@ +--- +title: ContentPage +tags: + - plugin/emitter +--- + +This plugin is a core component of the Quartz framework. It generates the HTML pages for each piece of Markdown content. It emits the full-page [[layout]], including headers, footers, and body content, among others. + +> [!note] +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. + +This plugin has no configuration options. + +## API + +- Category: Emitter +- Function name: `Plugin.ContentPage()`. +- Source: [`quartz/plugins/emitters/contentPage.tsx`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/contentPage.tsx). diff --git a/docs/plugins/CrawlLinks.md b/docs/plugins/CrawlLinks.md new file mode 100644 index 0000000..47b7bdd --- /dev/null +++ b/docs/plugins/CrawlLinks.md @@ -0,0 +1,30 @@ +--- +title: CrawlLinks +tags: + - plugin/transformer +--- + +This plugin parses links and processes them to point to the right places. It is also needed for embedded links (like images). See [[Obsidian compatibility]] for more information. + +> [!note] +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. + +This plugin accepts the following configuration options: + +- `markdownLinkResolution`: Sets the strategy for resolving Markdown paths, can be `"absolute"` (default), `"relative"` or `"shortest"`. You should use the same setting here as in [[Obsidian compatibility|Obsidian]]. + - `absolute`: Path relative to the root of the content folder. + - `relative`: Path relative to the file you are linking from. + - `shortest`: Name of the file. If this isn't enough to identify the file, use the full absolute path. +- `prettyLinks`: If `true` (default), simplifies links by removing folder paths, making them more user friendly (e.g. `folder/deeply/nested/note` becomes `note`). +- `openLinksInNewTab`: If `true`, configures external links to open in a new tab. Defaults to `false`. +- `lazyLoad`: If `true`, adds lazy loading to resource elements (`img`, `video`, etc.) to improve page load performance. Defaults to `false`. +- `externalLinkIcon`: Adds an icon next to external links when `true` (default) to visually distinguishing them from internal links. + +> [!warning] +> Removing this plugin is _not_ recommended and will likely break the page. + +## API + +- Category: Transformer +- Function name: `Plugin.CrawlLinks()`. +- Source: [`quartz/plugins/transformers/links.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/links.ts). diff --git a/docs/plugins/CreatedModifiedDate.md b/docs/plugins/CreatedModifiedDate.md new file mode 100644 index 0000000..5d772aa --- /dev/null +++ b/docs/plugins/CreatedModifiedDate.md @@ -0,0 +1,25 @@ +--- +title: "CreatedModifiedDate" +tags: + - plugin/transformer +--- + +This plugin determines the created, modified, and published dates for a document using three potential data sources: frontmatter metadata, Git history, and the filesystem. See [[authoring content#Syntax]] for more information. + +> [!note] +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. + +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"]`. + +> [!warning] +> If you rely on `git` for dates, make sure `defaultDateType` is set to `modified` in `quartz.config.ts`. +> +> Depending on how you [[hosting|host]] your Quartz, the `filesystem` dates of your local files may not match the final dates. In these cases, it may be better to use `git` or `frontmatter` to guarantee correct dates. + +## API + +- Category: Transformer +- Function name: `Plugin.CreatedModifiedDate()`. +- Source: [`quartz/plugins/transformers/lastmod.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/lastmod.ts). diff --git a/docs/plugins/Description.md b/docs/plugins/Description.md new file mode 100644 index 0000000..af1c8b7 --- /dev/null +++ b/docs/plugins/Description.md @@ -0,0 +1,23 @@ +--- +title: Description +tags: + - plugin/transformer +--- + +This plugin generates descriptions that are used as metadata for the HTML `head`, the [[RSS Feed]] and in [[folder and tag listings]] if there is no main body content, the description is used as the text between the title and the listing. + +If the frontmatter contains a `description` property, it is used (see [[authoring content#Syntax]]). Otherwise, the plugin will do its best to use the first few sentences of the content to reach the target description length. + +> [!note] +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. + +This plugin accepts the following configuration options: + +- `descriptionLength`: the maximum length of the generated description. Default is 150 characters. The cut off happens after the first _sentence_ that ends after the given length. +- `replaceExternalLinks`: If `true` (default), replace external links with their domain and path in the description (e.g. `https://domain.tld/some_page/another_page?query=hello&target=world` is replaced with `domain.tld/some_page/another_page`). + +## API + +- Category: Transformer +- Function name: `Plugin.Description()`. +- Source: [`quartz/plugins/transformers/description.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/description.ts). diff --git a/docs/plugins/ExplicitPublish.md b/docs/plugins/ExplicitPublish.md new file mode 100644 index 0000000..2fd929b --- /dev/null +++ b/docs/plugins/ExplicitPublish.md @@ -0,0 +1,18 @@ +--- +title: ExplicitPublish +tags: + - plugin/filter +--- + +This plugin filters content based on an explicit `publish` flag in the frontmatter, allowing only content that is explicitly marked for publication to pass through. It's the opt-in version of [[RemoveDrafts]]. See [[private pages]] for more information. + +> [!note] +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. + +This plugin has no configuration options. + +## API + +- Category: Filter +- Function name: `Plugin.ExplicitPublish()`. +- Source: [`quartz/plugins/filters/explicit.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/filters/explicit.ts). diff --git a/docs/plugins/FolderPage.md b/docs/plugins/FolderPage.md new file mode 100644 index 0000000..ead8e75 --- /dev/null +++ b/docs/plugins/FolderPage.md @@ -0,0 +1,22 @@ +--- +title: FolderPage +tags: + - plugin/emitter +--- + +This plugin generates index pages for folders, creating a listing page for each folder that contains multiple content files. See [[folder and tag listings]] for more information. + +Example: [[advanced/|Advanced]] + +> [!note] +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. + +This plugin has no configuration options. + +The pages are displayed using the `defaultListPageLayout` in `quartz.layouts.ts`. For the content, the `FolderContent` component is used. If you want to modify the layout, you must edit it directly (`quartz/components/pages/FolderContent.tsx`). + +## API + +- Category: Emitter +- Function name: `Plugin.FolderPage()`. +- Source: [`quartz/plugins/emitters/folderPage.tsx`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/folderPage.tsx). diff --git a/docs/plugins/Frontmatter.md b/docs/plugins/Frontmatter.md new file mode 100644 index 0000000..879d087 --- /dev/null +++ b/docs/plugins/Frontmatter.md @@ -0,0 +1,24 @@ +--- +title: "Frontmatter" +tags: + - plugin/transformer +--- + +This plugin parses the frontmatter of the page using the [gray-matter](https://github.com/jonschlinkert/gray-matter) library. See [[authoring content#Syntax]], [[Obsidian compatibility]] and [[OxHugo compatibility]] for more information. + +> [!note] +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. + +This plugin accepts the following configuration options: + +- `delimiters`: the delimiters to use for the frontmatter. Can have one value (e.g. `"---"`) or separate values for opening and closing delimiters (e.g. `["---", "~~~"]`). Defaults to `"---"`. +- `language`: the language to use for parsing the frontmatter. Can be `yaml` (default) or `toml`. + +> [!warning] +> This plugin must not be removed, otherwise Quartz will break. + +## API + +- Category: Transformer +- Function name: `Plugin.Frontmatter()`. +- Source: [`quartz/plugins/transformers/frontmatter.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/frontmatter.ts). diff --git a/docs/plugins/GitHubFlavoredMarkdown.md b/docs/plugins/GitHubFlavoredMarkdown.md new file mode 100644 index 0000000..41fab6b --- /dev/null +++ b/docs/plugins/GitHubFlavoredMarkdown.md @@ -0,0 +1,23 @@ +--- +title: GitHubFlavoredMarkdown +tags: + - plugin/transformer +--- + +This plugin enhances Markdown processing to support GitHub Flavored Markdown (GFM) which adds features like autolink literals, footnotes, strikethrough, tables and tasklists. + +In addition, this plugin adds optional features for typographic refinement (such as converting straight quotes to curly quotes, dashes to en-dashes/em-dashes, and ellipses) and automatic heading links as a symbol that appears next to the heading on hover. + +> [!note] +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. + +This plugin accepts the following configuration options: + +- `enableSmartyPants`: When true, enables typographic enhancements. Default is true. +- `linkHeadings`: When true, automatically adds links to headings. Default is true. + +## API + +- Category: Transformer +- Function name: `Plugin.GitHubFlavoredMarkdown()`. +- Source: [`quartz/plugins/transformers/gfm.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/gfm.ts). diff --git a/docs/plugins/HardLineBreaks.md b/docs/plugins/HardLineBreaks.md new file mode 100644 index 0000000..e24f7e1 --- /dev/null +++ b/docs/plugins/HardLineBreaks.md @@ -0,0 +1,18 @@ +--- +title: HardLineBreaks +tags: + - plugin/transformer +--- + +This plugin automatically converts single line breaks in Markdown text into hard line breaks in the HTML output. This plugin is not enabled by default as this doesn't follow the semantics of actual Markdown but you may enable it if you'd like parity with [[Obsidian compatibility|Obsidian]]. + +> [!note] +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. + +This plugin has no configuration options. + +## API + +- Category: Transformer +- Function name: `Plugin.HardLineBreaks()`. +- Source: [`quartz/plugins/transformers/linebreaks.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/linebreaks.ts). diff --git a/docs/plugins/Latex.md b/docs/plugins/Latex.md new file mode 100644 index 0000000..ac43678 --- /dev/null +++ b/docs/plugins/Latex.md @@ -0,0 +1,20 @@ +--- +title: "Latex" +tags: + - plugin/transformer +--- + +This plugin adds LaTeX support to Quartz. See [[features/Latex|Latex]] for more information. + +> [!note] +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. + +This plugin accepts the following configuration options: + +- `renderEngine`: the engine to use to render LaTeX equations. Can be `"katex"` for [KaTeX](https://katex.org/) or `"mathjax"` for [MathJax](https://www.mathjax.org/) [SVG rendering](https://docs.mathjax.org/en/latest/output/svg.html). Defaults to KaTeX. + +## API + +- Category: Transformer +- Function name: `Plugin.Latex()`. +- Source: [`quartz/plugins/transformers/latex.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/latex.ts). diff --git a/docs/plugins/NotFoundPage.md b/docs/plugins/NotFoundPage.md new file mode 100644 index 0000000..b679943 --- /dev/null +++ b/docs/plugins/NotFoundPage.md @@ -0,0 +1,18 @@ +--- +title: NotFoundPage +tags: + - plugin/emitter +--- + +This plugin emits a 404 (Not Found) page for broken or non-existent URLs. + +> [!note] +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. + +This plugin has no configuration options. + +## API + +- Category: Emitter +- Function name: `Plugin.NotFoundPage()`. +- Source: [`quartz/plugins/emitters/404.tsx`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/404.tsx). diff --git a/docs/plugins/ObsidianFlavoredMarkdown.md b/docs/plugins/ObsidianFlavoredMarkdown.md new file mode 100644 index 0000000..30d1f71 --- /dev/null +++ b/docs/plugins/ObsidianFlavoredMarkdown.md @@ -0,0 +1,34 @@ +--- +title: ObsidianFlavoredMarkdown +tags: + - plugin/transformer +--- + +This plugin provides support for [[Obsidian compatibility]]. + +> [!note] +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. + +This plugin accepts the following configuration options: + +- `comments`: If `true` (default), enables parsing of `%%` style Obsidian comment blocks. +- `highlight`: If `true` (default), enables parsing of `==` style highlights within content. +- `wikilinks`:If `true` (default), turns [[wikilinks]] into regular links. +- `callouts`: If `true` (default), adds support for [[callouts|callout]] blocks for emphasizing content. +- `mermaid`: If `true` (default), enables [[Mermaid diagrams|Mermaid diagram]] rendering within Markdown files. +- `parseTags`: If `true` (default), parses and links tags within the content. +- `parseArrows`: If `true` (default), transforms arrow symbols into their HTML character equivalents. +- `parseBlockReferences`: If `true` (default), handles block references, linking to specific content blocks. +- `enableInHtmlEmbed`: If `true`, allows embedding of content directly within HTML. Defaults to `false`. +- `enableYouTubeEmbed`: If `true` (default), enables the embedding of YouTube videos and playlists using external image Markdown syntax. +- `enableVideoEmbed`: If `true` (default), enables the embedding of video files. +- `enableCheckbox`: If `true`, adds support for interactive checkboxes in content. Defaults to `false`. + +> [!warning] +> Don't remove this plugin if you're using [[Obsidian compatibility|Obsidian]] to author the content! + +## API + +- Category: Transformer +- Function name: `Plugin.ObsidianFlavoredMarkdown()`. +- Source: [`quartz/plugins/transformers/toc.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/toc.ts). diff --git a/docs/plugins/OxHugoFlavoredMarkdown.md b/docs/plugins/OxHugoFlavoredMarkdown.md new file mode 100644 index 0000000..5c2afee --- /dev/null +++ b/docs/plugins/OxHugoFlavoredMarkdown.md @@ -0,0 +1,29 @@ +--- +title: OxHugoFlavoredMarkdown +tags: + - plugin/transformer +--- + +This plugin provides support for [ox-hugo](https://github.com/kaushalmodi/ox-hugo) compatibility. See [[OxHugo compatibility]] for more information. + +> [!note] +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. + +This plugin accepts the following configuration options: + +- `wikilinks`: If `true` (default), converts Hugo `{{ relref }}` shortcodes to Quartz [[wikilinks]]. +- `removePredefinedAnchor`: If `true` (default), strips predefined anchors from headings. +- `removeHugoShortcode`: If `true` (default), removes Hugo shortcode syntax (`{{}}`) from the content. +- `replaceFigureWithMdImg`: If `true` (default), replaces `

` with `![]()`. +- `replaceOrgLatex`: If `true` (default), converts Org-mode [[features/Latex|Latex]] fragments to Quartz-compatible LaTeX wrapped in `$` (for inline) and `$$` (for block equations). + +> [!warning] +> While you can use this together with [[ObsidianFlavoredMarkdown]], it's not recommended because it might mutate the file in unexpected ways. Use with caution. +> +> If you use `toml` frontmatter, make sure to configure the [[Frontmatter]] plugin accordingly. See [[OxHugo compatibility]] for an example. + +## API + +- Category: Transformer +- Function name: `Plugin.OxHugoFlavoredMarkdown()`. +- Source: [`quartz/plugins/transformers/oxhugofm.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/oxhugofm.ts). diff --git a/docs/plugins/RemoveDrafts.md b/docs/plugins/RemoveDrafts.md new file mode 100644 index 0000000..07fb4d0 --- /dev/null +++ b/docs/plugins/RemoveDrafts.md @@ -0,0 +1,18 @@ +--- +title: RemoveDrafts +tags: + - plugin/filter +--- + +This plugin filters out content from your vault, so that only finalized content is made available. This prevents [[private pages]] from being published. By default, it filters out all pages with `draft: true` in the frontmatter and leaves all other pages intact. + +> [!note] +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. + +This plugin has no configuration options. + +## API + +- Category: Filter +- Function name: `Plugin.RemoveDrafts()`. +- Source: [`quartz/plugins/filters/draft.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/filters/draft.ts). diff --git a/docs/plugins/Static.md b/docs/plugins/Static.md new file mode 100644 index 0000000..80bf5a1 --- /dev/null +++ b/docs/plugins/Static.md @@ -0,0 +1,21 @@ +--- +title: Static +tags: + - plugin/emitter +--- + +This plugin emits all static resources needed by Quartz. This is used, for example, for fonts and images that need a stable position, such as banners and icons. The plugin respects the `ignorePatterns` in the global [[configuration]]. + +> [!important] +> This is different from [[Assets]]. The resources from the [[Static]] plugin are located under `quartz/static`, whereas [[Assets]] renders all static resources under `content` and is used for images, videos, audio, etc. that are directly referenced by your markdown content. + +> [!note] +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. + +This plugin has no configuration options. + +## API + +- Category: Emitter +- Function name: `Plugin.Static()`. +- Source: [`quartz/plugins/emitters/static.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/static.ts). diff --git a/docs/plugins/SyntaxHighlighting.md b/docs/plugins/SyntaxHighlighting.md new file mode 100644 index 0000000..6fb67db --- /dev/null +++ b/docs/plugins/SyntaxHighlighting.md @@ -0,0 +1,23 @@ +--- +title: "SyntaxHighlighting" +tags: + - plugin/transformer +--- + +This plugin is used to add syntax highlighting to code blocks in Quartz. See [[syntax highlighting]] for more information. + +> [!note] +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. + +This plugin accepts the following configuration options: + +- `theme`: a separate id of one of the [themes bundled with Shikiji](https://shikiji.netlify.app/themes). One for light mode and one for dark mode. Defaults to `theme: { light: "github-light", dark: "github-dark" }`. +- `keepBackground`: If set to `true`, the background of the Shikiji theme will be used. With `false` (default) the Quartz theme color for background will be used instead. + +In addition, you can further override the colours in the `quartz/styles/syntax.scss` file. + +## API + +- Category: Transformer +- Function name: `Plugin.SyntaxHighlighting()`. +- Source: [`quartz/plugins/transformers/syntax.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/syntax.ts). diff --git a/docs/plugins/TableOfContents.md b/docs/plugins/TableOfContents.md new file mode 100644 index 0000000..0e9e4ea --- /dev/null +++ b/docs/plugins/TableOfContents.md @@ -0,0 +1,26 @@ +--- +title: TableOfContents +tags: + - plugin/transformer +--- + +This plugin generates a table of contents (TOC) for Markdown documents. See [[table of contents]] for more information. + +> [!note] +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. + +This plugin accepts the following configuration options: + +- `maxDepth`: Limits the depth of headings included in the TOC, ranging from `1` (top level headings only) to `6` (all heading levels). Default is `3`. +- `minEntries`: The minimum number of heading entries required for the TOC to be displayed. Default is `1`. +- `showByDefault`: If `true` (default), the TOC should be displayed by default. Can be overridden by frontmatter settings. +- `collapseByDefault`: If `true`, the TOC will start in a collapsed state. Default is `false`. + +> [!warning] +> This plugin needs the `Component.TableOfContents` component in `quartz.layout.ts` to determine where to display the TOC. Without it, nothing will be displayed. They should always be added or removed together. + +## API + +- Category: Transformer +- Function name: `Plugin.TableOfContents()`. +- Source: [`quartz/plugins/transformers/toc.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/toc.ts). diff --git a/docs/plugins/TagPage.md b/docs/plugins/TagPage.md new file mode 100644 index 0000000..9c704b3 --- /dev/null +++ b/docs/plugins/TagPage.md @@ -0,0 +1,20 @@ +--- +title: TagPage +tags: + - plugin/emitter +--- + +This plugin emits dedicated pages for each tag used in the content. See [[folder and tag listings]] for more information. + +> [!note] +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. + +This plugin has no configuration options. + +The pages are displayed using the `defaultListPageLayout` in `quartz.layouts.ts`. For the content, the `TagContent` component is used. If you want to modify the layout, you must edit it directly (`quartz/components/pages/TagContent.tsx`). + +## API + +- Category: Emitter +- Function name: `Plugin.TagPage()`. +- Source: [`quartz/plugins/emitters/tagPage.tsx`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/tagPage.tsx). diff --git a/docs/plugins/index.md b/docs/plugins/index.md new file mode 100644 index 0000000..298ff16 --- /dev/null +++ b/docs/plugins/index.md @@ -0,0 +1,3 @@ +--- +title: Plugins +--- diff --git a/docs/setting up your GitHub repository.md b/docs/setting up your GitHub repository.md new file mode 100644 index 0000000..229554f --- /dev/null +++ b/docs/setting up your GitHub repository.md @@ -0,0 +1,48 @@ +--- +title: Setting up your GitHub repository +--- + +First, make sure you have Quartz [[docs/index#🪴 Get Started|cloned and setup locally]]. + +Then, create a new repository on GitHub.com. Do **not** initialize the new repository with `README`, license, or `gitignore` files. + +![[github-init-repo-options.png]] + +At the top of your repository on GitHub.com's Quick Setup page, click the clipboard to copy the remote repository URL. + +![[github-quick-setup.png]] + +In your terminal of choice, navigate to the root of your Quartz folder. Then, run the following commands, replacing `REMOTE-URL` with the URL you just copied from the previous step. + +```bash +# list all the repositories that are tracked +git remote -v + +# if the origin doesn't match your own repository, set your repository as the origin +git remote set-url origin REMOTE-URL + +# if you don't have upstream as a remote, add it so updates work +git remote add upstream https://github.com/jackyzha0/quartz.git +``` + +Then, you can sync the content to upload it to your repository. This is a helper command that will do the initial push of your content to your repository. + +```bash +npx quartz sync --no-pull +``` + +> [!warning]- `fatal: --[no-]autostash option is only valid with --rebase` +> You may have an outdated version of `git`. Updating `git` should fix this issue. + +In future updates, you can simply run `npx quartz sync` every time you want to push updates to your repository. + +> [!hint] Flags and options +> For full help options, you can run `npx quartz sync --help`. +> +> Most of these have sensible defaults but you can override them if you have a custom setup: +> +> - `-d` or `--directory`: the content folder. This is normally just `content` +> - `-v` or `--verbose`: print out extra logging information +> - `--commit` or `--no-commit`: whether to make a `git` commit for your changes +> - `--push` or `--no-push`: whether to push updates to your GitHub fork of Quartz +> - `--pull` or `--no-pull`: whether to try and pull in any updates from your GitHub fork (i.e. from other devices) before pushing diff --git a/docs/showcase.md b/docs/showcase.md new file mode 100644 index 0000000..4860e0b --- /dev/null +++ b/docs/showcase.md @@ -0,0 +1,31 @@ +--- +title: "Quartz Showcase" +--- + +Want to see what Quartz can do? Here are some cool community gardens: + +- [Quartz Documentation (this site!)](https://quartz.jzhao.xyz/) +- [Jacky Zhao's Garden](https://jzhao.xyz/) +- [Socratica Toolbox](https://toolbox.socratica.info/) +- [oldwinter の数字花园](https://garden.oldwinter.top/) +- [Aaron Pham's Garden](https://aarnphm.xyz/) +- [The Quantum Garden](https://quantumgardener.blog/) +- [Abhijeet's Math Wiki](https://abhmul.github.io/quartz/Math-Wiki/) +- [Matt Dunn's Second Brain](https://mattdunn.info/) +- [Pelayo Arbues' Notes](https://pelayoarbues.github.io/) +- [Vince Imbat's Talahardin](https://vinceimbat.com/) +- [🧠🌳 Chad's Mind Garden](https://www.chadly.net/) +- [Pedro MC Fernandes's Topo da Mente](https://www.pmcf.xyz/topo-da-mente/) +- [Mau Camargo's Notkesto](https://notes.camargomau.com/) +- [Caicai's Novels](https://imoko.cc/blog/caicai/) +- [🌊 Collapsed Wave](https://collapsedwave.com/) +- [Sideny's 3D Artist's Handbook](https://sidney-eliot.github.io/3d-artists-handbook/) +- [Mike's AI Garden 🤖🪴](https://mwalton.me/) +- [Brandon Boswell's Garden](https://brandonkboswell.com) +- [Scaling Synthesis - A hypertext research notebook](https://scalingsynthesis.com/) +- [Data Dictionary 🧠](https://glossary.airbyte.com/) +- [sspaeti.com's Second Brain](https://brain.sspaeti.com/) +- [🪴Aster's notebook](https://notes.asterhu.com) +- [🥷🏻🌳🍃 Computer Science & Thinkering Garden](https://notes.yxy.ninja) + +If you want to see your own on here, submit a [Pull Request adding yourself to this file](https://github.com/jackyzha0/quartz/blob/v4/docs/showcase.md)! diff --git a/docs/tags/component.md b/docs/tags/component.md new file mode 100644 index 0000000..57592e8 --- /dev/null +++ b/docs/tags/component.md @@ -0,0 +1,5 @@ +--- +title: Components +--- + +Want to create your own custom component? Check out the advanced guide on [[creating components]] for more information. diff --git a/docs/tags/plugin.md b/docs/tags/plugin.md new file mode 100644 index 0000000..298ff16 --- /dev/null +++ b/docs/tags/plugin.md @@ -0,0 +1,3 @@ +--- +title: Plugins +--- diff --git a/docs/upgrading.md b/docs/upgrading.md new file mode 100644 index 0000000..a3a8275 --- /dev/null +++ b/docs/upgrading.md @@ -0,0 +1,19 @@ +--- +title: "Upgrading Quartz" +--- + +> [!note] +> This is specifically a guide for upgrading Quartz 4 version to a more recent update. If you are coming from Quartz 3, check out the [[migrating from Quartz 3|migration guide]] for more info. + +To fetch the latest Quartz updates, simply run + +```bash +npx quartz update +``` + +As Quartz uses [git](https://git-scm.com/) under the hood for versioning, updating effectively 'pulls' in the updates from the official Quartz GitHub repository. If you have local changes that might conflict with the updates, you may need to resolve these manually yourself (or, pull manually using `git pull origin upstream`). + +> [!hint] +> Quartz will try to cache your content before updating to try and prevent merge conflicts. If you get a conflict mid-merge, you can stop the merge and then run `npx quartz restore` to restore your content from the cache. + +If you have the [GitHub desktop app](https://desktop.github.com/), this will automatically open to help you resolve the conflicts. Otherwise, you will need to resolve this in a text editor like VSCode. For more help on resolving conflicts manually, check out the [GitHub guide on resolving merge conflicts](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/resolving-a-merge-conflict-using-the-command-line#competing-line-change-merge-conflicts). diff --git a/globals.d.ts b/globals.d.ts new file mode 100644 index 0000000..ee13005 --- /dev/null +++ b/globals.d.ts @@ -0,0 +1,13 @@ +export declare global { + interface Document { + addEventListener( + type: K, + listener: (this: Document, ev: CustomEventMap[K]) => void, + ): void + dispatchEvent(ev: CustomEventMap[K] | UIEvent): void + } + interface Window { + spaNavigate(url: URL, isBack: boolean = false) + addCleanup(fn: (...args: any[]) => void) + } +} diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..a6c594f --- /dev/null +++ b/index.d.ts @@ -0,0 +1,12 @@ +declare module "*.scss" { + const content: string + export = content +} + +// dom custom event +interface CustomEventMap { + nav: CustomEvent<{ url: FullSlug }> + themechange: CustomEvent<{ theme: "light" | "dark" }> +} + +declare const fetchData: Promise diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..156f294 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6534 @@ +{ + "name": "@jackyzha0/quartz", + "version": "4.2.3", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@jackyzha0/quartz", + "version": "4.2.3", + "license": "MIT", + "dependencies": { + "@clack/prompts": "^0.7.0", + "@floating-ui/dom": "^1.6.3", + "@napi-rs/simple-git": "0.1.16", + "async-mutex": "^0.5.0", + "chalk": "^5.3.0", + "chokidar": "^3.6.0", + "cli-spinner": "^0.2.10", + "d3": "^7.8.5", + "esbuild-sass-plugin": "^2.16.1", + "flexsearch": "0.7.43", + "github-slugger": "^2.0.0", + "globby": "^14.0.1", + "gray-matter": "^4.0.3", + "hast-util-to-html": "^9.0.0", + "hast-util-to-jsx-runtime": "^2.3.0", + "hast-util-to-string": "^3.0.0", + "is-absolute-url": "^4.0.1", + "js-yaml": "^4.1.0", + "lightningcss": "^1.24.1", + "mdast-util-find-and-replace": "^3.0.1", + "mdast-util-to-hast": "^13.1.0", + "mdast-util-to-string": "^4.0.0", + "micromorph": "^0.4.5", + "preact": "^10.20.1", + "preact-render-to-string": "^6.4.0", + "pretty-bytes": "^6.1.1", + "pretty-time": "^1.1.0", + "reading-time": "^1.5.0", + "rehype-autolink-headings": "^7.1.0", + "rehype-citation": "^2.0.0", + "rehype-katex": "^7.0.0", + "rehype-mathjax": "^6.0.0", + "rehype-pretty-code": "^0.13.0", + "rehype-raw": "^7.0.0", + "rehype-slug": "^6.0.0", + "remark": "^15.0.1", + "remark-breaks": "^4.0.0", + "remark-frontmatter": "^5.0.0", + "remark-gfm": "^4.0.0", + "remark-math": "^6.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.0", + "remark-smartypants": "^2.1.0", + "rfdc": "^1.3.1", + "rimraf": "^5.0.5", + "serve-handler": "^6.1.5", + "shiki": "^1.2.3", + "source-map-support": "^0.5.21", + "to-vfile": "^8.0.0", + "toml": "^3.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.1", + "workerpool": "^9.1.0", + "ws": "^8.15.1", + "yargs": "^17.7.2" + }, + "bin": { + "quartz": "quartz/bootstrap-cli.mjs" + }, + "devDependencies": { + "@types/cli-spinner": "^0.2.3", + "@types/d3": "^7.4.3", + "@types/hast": "^3.0.4", + "@types/js-yaml": "^4.0.9", + "@types/node": "^20.11.29", + "@types/pretty-time": "^1.1.5", + "@types/source-map-support": "^0.5.10", + "@types/ws": "^8.5.10", + "@types/yargs": "^17.0.32", + "esbuild": "^0.19.9", + "prettier": "^3.2.4", + "tsx": "^4.7.1", + "typescript": "^5.4.3" + }, + "engines": { + "node": ">=18.14", + "npm": ">=9.3.1" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-2.0.2.tgz", + "integrity": "sha512-x1KXOatwofR6ZAYzXRBL5wrdV0vwNxlTCK9NCuLqAzQYARqGcvFwiJA6A1ERuh+dgeA4Dxm3JBYictIes+SqUQ==", + "dependencies": { + "bidi-js": "^1.0.3", + "css-tree": "^2.3.1", + "is-potential-custom-element-name": "^1.0.1" + } + }, + "node_modules/@citation-js/core": { + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/@citation-js/core/-/core-0.7.9.tgz", + "integrity": "sha512-fSbkB32JayDChZnAYC/kB+sWHRvxxL7ibVetyBOyzOc+5aCnjb6UVsbcfhnkOIEyAMoRRvWDyFmakEoTtA5ttQ==", + "dependencies": { + "@citation-js/date": "^0.5.0", + "@citation-js/name": "^0.4.2", + "fetch-ponyfill": "^7.1.0", + "sync-fetch": "^0.4.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@citation-js/date": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@citation-js/date/-/date-0.5.1.tgz", + "integrity": "sha512-1iDKAZ4ie48PVhovsOXQ+C6o55dWJloXqtznnnKy6CltJBQLIuLLuUqa8zlIvma0ZigjVjgDUhnVaNU1MErtZw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@citation-js/name": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@citation-js/name/-/name-0.4.2.tgz", + "integrity": "sha512-brSPsjs2fOVzSnARLKu0qncn6suWjHVQtrqSUrnqyaRH95r/Ad4wPF5EsoWr+Dx8HzkCGb/ogmoAzfCsqlTwTQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@citation-js/plugin-bibjson": { + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/@citation-js/plugin-bibjson/-/plugin-bibjson-0.7.9.tgz", + "integrity": "sha512-YNCWIrkhqZ3cZKewHkLBixABo2PvOWnU+8dBx6KfN47ysdECR76xENe86YYpJ0ska2D5ZnTP0jKZIrUHQoxYfQ==", + "dependencies": { + "@citation-js/date": "^0.5.0", + "@citation-js/name": "^0.4.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@citation-js/core": "^0.7.0" + } + }, + "node_modules/@citation-js/plugin-bibtex": { + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/@citation-js/plugin-bibtex/-/plugin-bibtex-0.7.9.tgz", + "integrity": "sha512-gIJpCd6vmmTOcRfDrSOjtoNhw2Mi94UwFxmgJ7GwkXyTYcNheW5VlMMo1tlqjakJGARQ0eOsKcI57gSPqJSS2g==", + "dependencies": { + "@citation-js/date": "^0.5.0", + "@citation-js/name": "^0.4.2", + "moo": "^0.5.1" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@citation-js/core": "^0.7.0" + } + }, + "node_modules/@citation-js/plugin-csl": { + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/@citation-js/plugin-csl/-/plugin-csl-0.7.9.tgz", + "integrity": "sha512-mbD7CnUiPOuVnjeJwo+d0RGUcY0PE8n01gHyjq0qpTeS42EGmQ9+LzqfsTUVWWBndTwc6zLRuIF1qFAUHKE4oA==", + "dependencies": { + "@citation-js/date": "^0.5.0", + "citeproc": "^2.4.6" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@citation-js/core": "^0.7.0" + } + }, + "node_modules/@clack/core": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.3.3.tgz", + "integrity": "sha512-5ZGyb75BUBjlll6eOa1m/IZBxwk91dooBWhPSL67sWcLS0zt9SnswRL0l26TVdBhb0wnWORRxUn//uH6n4z7+A==", + "dependencies": { + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.7.0.tgz", + "integrity": "sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA==", + "bundleDependencies": [ + "is-unicode-supported" + ], + "dependencies": { + "@clack/core": "^0.3.3", + "is-unicode-supported": "*", + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts/node_modules/is-unicode-supported": { + "version": "1.3.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", + "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", + "dependencies": { + "@floating-ui/utils": "^0.2.1" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", + "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", + "dependencies": { + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@napi-rs/simple-git": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git/-/simple-git-0.1.16.tgz", + "integrity": "sha512-C5wRPw9waqL2jk3jEDeJv+f7ScuO3N0a39HVdyFLkwKxHH4Sya4ZbzZsu2JLi6eEqe7RuHipHL6mC7B2OfYZZw==", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@napi-rs/simple-git-android-arm-eabi": "0.1.16", + "@napi-rs/simple-git-android-arm64": "0.1.16", + "@napi-rs/simple-git-darwin-arm64": "0.1.16", + "@napi-rs/simple-git-darwin-x64": "0.1.16", + "@napi-rs/simple-git-linux-arm-gnueabihf": "0.1.16", + "@napi-rs/simple-git-linux-arm64-gnu": "0.1.16", + "@napi-rs/simple-git-linux-arm64-musl": "0.1.16", + "@napi-rs/simple-git-linux-x64-gnu": "0.1.16", + "@napi-rs/simple-git-linux-x64-musl": "0.1.16", + "@napi-rs/simple-git-win32-arm64-msvc": "0.1.16", + "@napi-rs/simple-git-win32-x64-msvc": "0.1.16" + } + }, + "node_modules/@napi-rs/simple-git-android-arm-eabi": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-android-arm-eabi/-/simple-git-android-arm-eabi-0.1.16.tgz", + "integrity": "sha512-dbrCL0Pl5KZG7x7tXdtVsA5CO6At5ohDX3myf5xIYn9kN4jDFxsocl8bNt6Vb/hZQoJd8fI+k5VlJt+rFhbdVw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-android-arm64": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-android-arm64/-/simple-git-android-arm64-0.1.16.tgz", + "integrity": "sha512-xYz+TW5J09iK8SuTAKK2D5MMIsBUXVSs8nYp7HcMi8q6FCRO7yJj96YfP9PvKsc/k64hOyqGmL5DhCzY9Cu1FQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-darwin-arm64": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-darwin-arm64/-/simple-git-darwin-arm64-0.1.16.tgz", + "integrity": "sha512-XfgsYqxhUE022MJobeiX563TJqyQyX4FmYCnqrtJwAfivESVeAJiH6bQIum8dDEYMHXCsG7nL8Ok0Dp8k2m42g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-darwin-x64": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-darwin-x64/-/simple-git-darwin-x64-0.1.16.tgz", + "integrity": "sha512-tkEVBhD6vgRCbeWsaAQqM3bTfpIVGeitamPPRVSbsq8qgzJ5Dx6ZedH27R7KSsA/uao7mZ3dsrNLXbu1Wy5MzA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-linux-arm-gnueabihf": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-arm-gnueabihf/-/simple-git-linux-arm-gnueabihf-0.1.16.tgz", + "integrity": "sha512-R6VAyNnp/yRaT7DV1Ao3r67SqTWDa+fNq2LrNy0Z8gXk2wB9ZKlrxFtLPE1WSpWknWtyRDLpRlsorh7Evk7+7w==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-linux-arm64-gnu": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-arm64-gnu/-/simple-git-linux-arm64-gnu-0.1.16.tgz", + "integrity": "sha512-LAGI0opFKw/HBMCV2qIBK3uWSEW9h4xd2ireZKLJy8DBPymX6NrWIamuxYNyCuACnFdPRxR4LaRFy4J5ZwuMdw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-linux-arm64-musl": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-arm64-musl/-/simple-git-linux-arm64-musl-0.1.16.tgz", + "integrity": "sha512-I57Ph0F0Yn2KW93ep+V1EzKhACqX0x49vvSiapqIsdDA2PifdEWLc1LJarBolmK7NKoPqKmf6lAKKO9lhiZzkg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-linux-x64-gnu": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-x64-gnu/-/simple-git-linux-x64-gnu-0.1.16.tgz", + "integrity": "sha512-AZYYFY2V7hlcQASPEOWyOa3e1skzTct9QPzz0LiDM3f/hCFY/wBaU2M6NC5iG3d2Kr38heuyFS/+JqxLm5WaKA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-linux-x64-musl": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-x64-musl/-/simple-git-linux-x64-musl-0.1.16.tgz", + "integrity": "sha512-9TyMcYSBJwjT8jwjY9m24BZbu7ozyWTjsmYBYNtK3B0Um1Ov6jthSNneLVvouQ6x+k3Ow+00TiFh6bvmT00r8g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-win32-arm64-msvc": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-win32-arm64-msvc/-/simple-git-win32-arm64-msvc-0.1.16.tgz", + "integrity": "sha512-uslJ1WuAHCYJWui6xjsyT47SjX6KOHDtClmNO8hqKz1pmDSNY7AjyUY8HxvD1lK9bDnWwc4JYhikS9cxCqHybw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-win32-x64-msvc": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-win32-x64-msvc/-/simple-git-win32-x64-msvc-0.1.16.tgz", + "integrity": "sha512-SoEaVeCZCDF1MP+M9bMSXsZWgEjk4On9GWADO5JOulvzR1bKjk0s9PMHwe/YztR9F0sJzrCxwtvBZowhSJsQPg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@shikijs/core": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.2.3.tgz", + "integrity": "sha512-SM+aiQVaEK2P53dEcsvhq9+LJPr0rzwezHbMQhHaSrPN4OlOB4vp1qTdhVEKfMg6atdq8s9ZotWW/CSCzWftwg==" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@types/cli-spinner": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@types/cli-spinner/-/cli-spinner-0.2.3.tgz", + "integrity": "sha512-TMO6mWltW0lCu1de8DMRq9+59OP/tEjghS+rs8ZEQ2EgYP5yV3bGw0tS14TMyJGqFaoVChNvhkVzv9RC1UgX+w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "dev": true, + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.5.tgz", + "integrity": "sha512-Qk7fpJ6qFp+26VeQ47WY0mkwXaiq8+76RJcncDEfMc2ocRzXLO67bLFRNI4OX1aGBoPzsM5Y2T+/m1pldOgD+A==", + "dev": true + }, + "node_modules/@types/d3-axis": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.2.tgz", + "integrity": "sha512-uGC7DBh0TZrU/LY43Fd8Qr+2ja1FKmH07q2FoZFHo1eYl8aj87GhfVoY1saJVJiq24rp1+wpI6BvQJMKgQm8oA==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.2.tgz", + "integrity": "sha512-2TEm8KzUG3N7z0TrSKPmbxByBx54M+S9lHoP2J55QuLU0VSQ9mE96EJSAOVNEqd1bbynMjeTS9VHmz8/bSw8rA==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.2.tgz", + "integrity": "sha512-abT/iLHD3sGZwqMTX1TYCMEulr+wBd0SzyOQnjYNLp7sngdOHYtNkMRI5v3w5thoN+BWtlHVDx2Osvq6fxhZWw==", + "dev": true + }, + "node_modules/@types/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==", + "dev": true + }, + "node_modules/@types/d3-contour": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.2.tgz", + "integrity": "sha512-k6/bGDoAGJZnZWaKzeB+9glgXCYGvh6YlluxzBREiVo8f/X2vpTEdgPy9DN7Z2i42PZOZ4JDhVdlTSTSkLDPlQ==", + "dev": true, + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz", + "integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==", + "dev": true + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.2.tgz", + "integrity": "sha512-rxN6sHUXEZYCKV05MEh4z4WpPSqIw+aP7n9ZN6WYAAvZoEAghEK1WeVZMZcHRBwyaKflU43PCUAJNjFxCzPDjg==", + "dev": true + }, + "node_modules/@types/d3-drag": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.2.tgz", + "integrity": "sha512-qmODKEDvyKWVHcWWCOVcuVcOwikLVsyc4q4EBJMREsoQnR2Qoc2cZQUyFUPgO9q4S3qdSqJKBsuefv+h0Qy+tw==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-76pBHCMTvPLt44wFOieouXcGXWOF0AJCceUvaFkxSZEu4VDUdv93JfpMa6VGNFs01FHfuP4a5Ou68eRG1KBfTw==", + "dev": true + }, + "node_modules/@types/d3-ease": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz", + "integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==", + "dev": true + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.2.tgz", + "integrity": "sha512-gllwYWozWfbep16N9fByNBDTkJW/SyhH6SGRlXloR7WdtAaBui4plTP+gbUgiEot7vGw/ZZop1yDZlgXXSuzjA==", + "dev": true, + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.4.tgz", + "integrity": "sha512-q7xbVLrWcXvSBBEoadowIUJ7sRpS1yvgMWnzHJggFy5cUZBq2HZL5k/pBSm0GdYWS1vs5/EDwMjSKF55PDY4Aw==", + "dev": true + }, + "node_modules/@types/d3-format": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz", + "integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==", + "dev": true + }, + "node_modules/@types/d3-geo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.0.3.tgz", + "integrity": "sha512-bK9uZJS3vuDCNeeXQ4z3u0E7OeJZXjUgzFdSOtNtMCJCLvDtWDwfpRVWlyt3y8EvRzI0ccOu9xlMVirawolSCw==", + "dev": true, + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-9hjRTVoZjRFR6xo8igAJyNXQyPX6Aq++Nhb5ebrUF414dv4jr2MitM2fWiOY475wa3Za7TOS2Gh9fmqEhLTt0A==", + "dev": true + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==", + "dev": true, + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz", + "integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==", + "dev": true + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.0.tgz", + "integrity": "sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw==", + "dev": true + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.2.tgz", + "integrity": "sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw==", + "dev": true + }, + "node_modules/@types/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==", + "dev": true + }, + "node_modules/@types/d3-scale": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.3.tgz", + "integrity": "sha512-PATBiMCpvHJSMtZAMEhc2WyL+hnzarKzI6wAHYjhsonjWJYGq5BXTzQjv4l8m2jO183/4wZ90rKvSeT7o72xNQ==", + "dev": true, + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==", + "dev": true + }, + "node_modules/@types/d3-selection": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.5.tgz", + "integrity": "sha512-xCB0z3Hi8eFIqyja3vW8iV01+OHGYR2di/+e+AiOcXIOrY82lcvWW8Ke1DYE/EUVMsBl4Db9RppSBS3X1U6J0w==", + "dev": true + }, + "node_modules/@types/d3-shape": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.1.tgz", + "integrity": "sha512-6Uh86YFF7LGg4PQkuO2oG6EMBRLuW9cbavUW46zkIO5kuS2PfTqo2o9SkgtQzguBHbLgNnU90UNsITpsX1My+A==", + "dev": true, + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", + "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==", + "dev": true + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.0.tgz", + "integrity": "sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw==", + "dev": true + }, + "node_modules/@types/d3-timer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz", + "integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==", + "dev": true + }, + "node_modules/@types/d3-transition": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.3.tgz", + "integrity": "sha512-/S90Od8Id1wgQNvIA8iFv9jRhCiZcGhPd2qX0bKF/PS+y0W5CrXKgIiELd2CvG1mlQrWK/qlYh3VxicqG1ZvgA==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.3.tgz", + "integrity": "sha512-OWk1yYIIWcZ07+igN6BeoG6rqhnJ/pYe+R1qWFM2DtW49zsoSjgb9G5xB0ZXA8hh2jAzey1XuRmMSoXdKw8MDA==", + "dev": true, + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.3.tgz", + "integrity": "sha512-pvQ+TKeRHeiUGRhvYwRrQ/ISnohKkSJR14fT2yqyZ4e9K5vqc7hrtY2Y1Dw0ZwAzQ6DQsxsaCUuSIIi8v0Cq6w==", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/geojson": { + "version": "7946.0.10", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", + "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==", + "dev": true + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "dev": true + }, + "node_modules/@types/katex": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==" + }, + "node_modules/@types/mathjax": { + "version": "0.0.40", + "resolved": "https://registry.npmjs.org/@types/mathjax/-/mathjax-0.0.40.tgz", + "integrity": "sha512-rHusx08LCg92WJxrsM3SPjvLTSvK5C+gealtSuhKbEOcUZfWlwigaFoPLf6Dfxhg4oryN5qP9Sj7zOQ4HYXINw==" + }, + "node_modules/@types/mdast": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.3.tgz", + "integrity": "sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" + }, + "node_modules/@types/nlcst": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-1.0.0.tgz", + "integrity": "sha512-3TGCfOcy8R8mMQ4CNSNOe3PG66HttvjcLzCoOpvXvDtfWOTi+uT/rxeOKm/qEwbM4SNe1O/PjdiBK2YcTjU4OQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/node": { + "version": "20.11.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.29.tgz", + "integrity": "sha512-P99thMkD/1YkCvAtOd6/zGedKNA0p2fj4ZpjCzcNiSCBWgm3cNRTBfa/qjFnsKkkojxu4vVLtWpesnZ9+ap+gA==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/pretty-time": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/pretty-time/-/pretty-time-1.1.5.tgz", + "integrity": "sha512-5yl+BYwmnRWZb783W8YYoHXvPY8q/rp7ctHBVaGBB9RxlzGpHNJ72tGQMK7TrUSnxzl1dbDcBDuBCSbtfnSQGg==", + "dev": true + }, + "node_modules/@types/source-map-support": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@types/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha512-tgVP2H469x9zq34Z0m/fgPewGhg/MLClalNOiPIzQlXrSS2YrKu/xCdSCKnEDwkFha51VKEKB6A9wW26/ZNwzA==", + "dev": true, + "dependencies": { + "source-map": "^0.6.0" + } + }, + "node_modules/@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" + }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" + }, + "node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/array-iterate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", + "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/async-mutex": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/citeproc": { + "version": "2.4.63", + "resolved": "https://registry.npmjs.org/citeproc/-/citeproc-2.4.63.tgz", + "integrity": "sha512-68F95Bp4UbgZU/DBUGQn0qV3HDZLCdI9+Bb2ByrTaNJDL5VEm9LqaiNaxljsvoaExSLEXe1/r6n2Z06SCzW3/Q==" + }, + "node_modules/cli-spinner": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/cli-spinner/-/cli-spinner-0.2.10.tgz", + "integrity": "sha512-U0sSQ+JJvSLi1pAYuJykwiA8Dsr15uHEy85iCJ6A+0DjVxivr3d+N2Wjvodeg89uP5K6TswFkKBfAD7B3YSn/Q==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "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==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssstyle": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.0.1.tgz", + "integrity": "sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==", + "dependencies": { + "rrweb-cssom": "^0.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/d3": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz", + "integrity": "sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/delaunator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", + "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", + "dependencies": { + "robust-predicates": "^3.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/esbuild-sass-plugin": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-2.16.1.tgz", + "integrity": "sha512-mBB2aEF0xk7yo+Q9pSUh8xYED/1O2wbAM6IauGkDrqy6pl9SbJNakLeLGXiNpNujWIudu8TJTZCv2L5AQYRXtA==", + "dependencies": { + "resolve": "^1.22.6", + "sass": "^1.7.3" + }, + "peerDependencies": { + "esbuild": "^0.19.4" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-url-parser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", + "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==", + "dependencies": { + "punycode": "^1.3.2" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fault": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", + "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/fetch-ponyfill": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/fetch-ponyfill/-/fetch-ponyfill-7.1.0.tgz", + "integrity": "sha512-FhbbL55dj/qdVO3YNK7ZEkshvj3eQ7EuIGV2I6ic/2YiocvyWv+7jg2s4AyS0wdRU75s3tA8ZxI/xPigb0v5Aw==", + "dependencies": { + "node-fetch": "~2.6.1" + } + }, + "node_modules/fetch-ponyfill/node_modules/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/fetch-ponyfill/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/fetch-ponyfill/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/fetch-ponyfill/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flexsearch": { + "version": "0.7.43", + "resolved": "https://registry.npmjs.org/flexsearch/-/flexsearch-0.7.43.tgz", + "integrity": "sha512-c5o/+Um8aqCSOXGcZoqZOm+NqtVwNsvVpWv6lfmSclU954O3wvQKxxK8zj74fPaSJbXpSLTs4PRhh+wnoCXnKg==" + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", + "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" + }, + "node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.1.tgz", + "integrity": "sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/gray-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/gray-matter/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-from-dom": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.0.tgz", + "integrity": "sha512-d6235voAp/XR3Hh5uy7aGLbM3S4KamdW0WEgOaU1YoewnuYw4HXb5eRtv9g65m/RFGEfUY1Mw4UqCc5Y8L4Stg==", + "dependencies": { + "@types/hast": "^3.0.0", + "hastscript": "^8.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.1.tgz", + "integrity": "sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz", + "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-dom": "^5.0.0", + "hast-util-from-html": "^2.0.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/hast-util-from-html/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", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html/node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz", + "integrity": "sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^8.0.0", + "property-information": "^6.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/hast-util-heading-rank": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz", + "integrity": "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.1.tgz", + "integrity": "sha512-5m1gmba658Q+lO5uqL5YNGQWeh1MYWZbZmWrM5lncdcuiXuo5E2HT/CIOp0rLF8ksfSwiCVJ3twlgVRyTGThGA==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/hast-util-to-html": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.0.tgz", + "integrity": "sha512-IVGhNgg7vANuUA2XKrT6sOIIPgaYZnmLx3l/CCOAK0PtgfoHrZwX7jCSYyFxHTrGmC6S9q8aQQekjp4JPZF+cw==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-raw": "^9.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz", + "integrity": "sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ==", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "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", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/@types/unist": { + "version": "3.0.2", + "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/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", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", + "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.0.tgz", + "integrity": "sha512-OGkAxX1Ua3cbcW6EJ5pT/tslVb90uViVkcJ4ZZIMW/R33DX/AkcJcRrPebPwJkHYwlDHXz4aIwvAAaAdtrACFA==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.0.tgz", + "integrity": "sha512-EWiE1FSArNBPUo1cKWtzqgnuRQwEeQbQtnFJRYV1hb1BWDgrAlBU0ExptvZMM/KSA82cDpm2sFGf3Dmc5Mza3w==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz", + "integrity": "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.3.tgz", + "integrity": "sha512-808ZFYMsIRAjLAu5xkKo0TsbY9LBy9H5MazTKIEHerNkg0ymgilGfBPMR/3G7d/ihGmuK2Hw8S1izY2d3kd3wA==" + }, + "node_modules/inline-style-parser": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.2.tgz", + "integrity": "sha512-EcKzdTHVe8wFVOGEYXiW9WmJXPjqi1T+234YpJr98RiFYKHV3cdy1+3mkTE+KHTHxFFLH51SfaGOoUdW+v7ViQ==" + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-absolute-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-4.0.1.tgz", + "integrity": "sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-23.2.0.tgz", + "integrity": "sha512-L88oL7D/8ufIES+Zjz7v0aes+oBMh2Xnh3ygWvL0OaICOomKEPKuPnIfBJekiXr+BHbbMjrWn/xqrDQuxFTeyA==", + "dependencies": { + "@asamuzakjp/dom-selector": "^2.0.1", + "cssstyle": "^4.0.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.6.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.3", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.16.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-ftuDnJbcbOckGY11OO+zg3OofESlbR5DRl2cmN8HeWeeFIV7wTXvAOx8kEjZjobhA+9wh2fbKeO6cdcA9Mnovg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lightningcss": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.24.1.tgz", + "integrity": "sha512-kUpHOLiH5GB0ERSv4pxqlL0RYKnOXtgGtVe7shDGfhS0AZ4D1ouKFYAcLcZhql8aMspDNzaUCumGHZ78tb2fTg==", + "dependencies": { + "detect-libc": "^1.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.24.1", + "lightningcss-darwin-x64": "1.24.1", + "lightningcss-freebsd-x64": "1.24.1", + "lightningcss-linux-arm-gnueabihf": "1.24.1", + "lightningcss-linux-arm64-gnu": "1.24.1", + "lightningcss-linux-arm64-musl": "1.24.1", + "lightningcss-linux-x64-gnu": "1.24.1", + "lightningcss-linux-x64-musl": "1.24.1", + "lightningcss-win32-x64-msvc": "1.24.1" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.24.1.tgz", + "integrity": "sha512-1jQ12jBy+AE/73uGQWGSafK5GoWgmSiIQOGhSEXiFJSZxzV+OXIx+a9h2EYHxdJfX864M+2TAxWPWb0Vv+8y4w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.24.1.tgz", + "integrity": "sha512-R4R1d7VVdq2mG4igMU+Di8GPf0b64ZLnYVkubYnGG0Qxq1KaXQtAzcLI43EkpnoWvB/kUg8JKCWH4S13NfiLcQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.24.1.tgz", + "integrity": "sha512-z6NberUUw5ALES6Ixn2shmjRRrM1cmEn1ZQPiM5IrZ6xHHL5a1lPin9pRv+w6eWfcrEo+qGG6R9XfJrpuY3e4g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.24.1.tgz", + "integrity": "sha512-NLQLnBQW/0sSg74qLNI8F8QKQXkNg4/ukSTa+XhtkO7v3BnK19TS1MfCbDHt+TTdSgNEBv0tubRuapcKho2EWw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.24.1.tgz", + "integrity": "sha512-AQxWU8c9E9JAjAi4Qw9CvX2tDIPjgzCTrZCSXKELfs4mCwzxRkHh2RCxX8sFK19RyJoJAjA/Kw8+LMNRHS5qEg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.24.1.tgz", + "integrity": "sha512-JCgH/SrNrhqsguUA0uJUM1PvN5+dVuzPIlXcoWDHSv2OU/BWlj2dUYr3XNzEw748SmNZPfl2NjQrAdzaPOn1lA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.24.1.tgz", + "integrity": "sha512-TYdEsC63bHV0h47aNRGN3RiK7aIeco3/keN4NkoSQ5T8xk09KHuBdySltWAvKLgT8JvR+ayzq8ZHnL1wKWY0rw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.24.1.tgz", + "integrity": "sha512-HLfzVik3RToot6pQ2Rgc3JhfZkGi01hFetHt40HrUMoeKitLoqUUT5owM6yTZPTytTUW9ukLBJ1pc3XNMSvlLw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.24.1.tgz", + "integrity": "sha512-joEupPjYJ7PjZtDsS5lzALtlAudAbgIBMGJPNeFe5HfdmJXFd13ECmEM+5rXNxYVMRHua2w8132R6ab5Z6K9Ow==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/markdown-table": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", + "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mathjax-full": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.2.tgz", + "integrity": "sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==", + "dependencies": { + "esm": "^3.2.25", + "mhchemparser": "^4.1.0", + "mj-context-menu": "^0.6.1", + "speech-rule-engine": "^4.0.6" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz", + "integrity": "sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/mdast-util-find-and-replace/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.0.tgz", + "integrity": "sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/mdast-util-from-markdown/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", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-frontmatter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", + "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "escape-string-regexp": "^5.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-extension-frontmatter": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz", + "integrity": "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.0.tgz", + "integrity": "sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz", + "integrity": "sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-math": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz", + "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "longest-streak": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.1.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.0.tgz", + "integrity": "sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.0.0.tgz", + "integrity": "sha512-XZuPPzQNBPAlaqsTTgRrcJnyFbSOBovSadFgbFu8SnuNgm+6Bdx1K+IWoitsmj6Lq6MNtI+ytOqwN70n//NaBA==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-remove-position": "^5.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/mdast-util-mdx-jsx/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", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-newline-to-break": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-newline-to-break/-/mdast-util-newline-to-break-2.0.0.tgz", + "integrity": "sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-find-and-replace": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.0.0.tgz", + "integrity": "sha512-xadSsJayQIucJ9n053dfQwVu1kuXg7jCTdYsMK8rqzKZh52nLfSH/k0sAxE0u+pj/zKZX+o5wB+ML5mRayOxFA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/mdast-util-phrasing/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.1.0.tgz", + "integrity": "sha512-/e2l/6+OdGp/FB+ctrJ9Avz71AN/GRH3oi/3KAx/kMnoUsD6q0woXlDT8lLEeViVKE7oZxE7RXzvO3T8kF2/sA==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz", + "integrity": "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/mhchemparser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.2.1.tgz", + "integrity": "sha512-kYmyrCirqJf3zZ9t/0wGgRZ4/ZJw//VwaRVGA75C4nhE60vtnIzhl9J9ndkX/h6hxSN7pjg/cE0VxbnNM+bnDQ==" + }, + "node_modules/micromark": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz", + "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.0.tgz", + "integrity": "sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-frontmatter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", + "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", + "dependencies": { + "fault": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.0.0.tgz", + "integrity": "sha512-rTHfnpt/Q7dEAK1Y5ii0W8bhfJlVJFnJMHIPisfPK3gpVNuOP0VnRl96+YJ3RYWV/P4gFeQoGKNlT3RhuvpqAg==", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.0.0.tgz", + "integrity": "sha512-6Rzu0CYRKDv3BfLAUnZsSlzx3ak6HAoI85KTiijuKIz5UxZxbUI+pD6oHgw+6UtQuiRwnGRhzMmPRv4smcz0fg==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-c3BR1ClMp5fxxmwP6AoOY2fXO9U8uFMKs4ADD66ahLTNcwzSCyRVU4k7LPV5Nxo/VJiR4TdzxRQY2v3qIUceCw==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.0.0.tgz", + "integrity": "sha512-PoHlhypg1ItIucOaHmKE8fbin3vTLpDOUg8KAr8gRCF1MOZI9Nquq2i/44wFvviM4WuxJzc3demT8Y3dkfvYrw==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.0.1.tgz", + "integrity": "sha512-cY5PzGcnULaN5O7T+cOzfMoHjBW7j+T9D2sucA5d/KbsBTPcYdebm9zUd9zzdgJGCwahV+/W78Z3nbulBYVbTw==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.0.0.tgz", + "integrity": "sha512-iJ2Q28vBoEovLN5o3GO12CpqorQRYDPT+p4zW50tGwTfJB+iv/VnB6Ini+gqa24K97DwptMBBIvVX6Bjk49oyQ==", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz", + "integrity": "sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz", + "integrity": "sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz", + "integrity": "sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz", + "integrity": "sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz", + "integrity": "sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz", + "integrity": "sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz", + "integrity": "sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz", + "integrity": "sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz", + "integrity": "sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz", + "integrity": "sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz", + "integrity": "sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz", + "integrity": "sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", + "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.0.tgz", + "integrity": "sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromorph": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/micromorph/-/micromorph-0.4.5.tgz", + "integrity": "sha512-Erasr0xiDvDeEhh7B/k7RFTwwfaAX10D7BMorNpokkwDh6XsRLYWDPaWF1m5JQeMSkGdqlEtQ8s68NcdDWuGgw==" + }, + "node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mj-context-menu": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz", + "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==" + }, + "node_modules/moo": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", + "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nlcst-to-string": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-3.1.1.tgz", + "integrity": "sha512-63mVyqaqt0cmn2VcI2aH6kxe1rLAmSROqHMA0i4qqg1tidkfExgpb0FGMikMCn86mw5dFtBtEANfmSSK7TjNHw==", + "dependencies": { + "@types/nlcst": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-entities": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", + "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-latin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-5.0.1.tgz", + "integrity": "sha512-b/K8ExXaWC9t34kKeDV8kGXBkXZ1HCSAZRYE7HR14eA1GlXX5L8iWhs8USJNhQU9q5ci413jCKF0gOyovvyRBg==", + "dependencies": { + "nlcst-to-string": "^3.0.0", + "unist-util-modify-children": "^3.0.0", + "unist-util-visit-children": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-numeric-range": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", + "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==" + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", + "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==" + }, + "node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/preact": { + "version": "10.20.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.20.1.tgz", + "integrity": "sha512-JIFjgFg9B2qnOoGiYMVBtrcFxHqn+dNXbq76bVmcaHYJFYR4lW67AOcXgAYQQTDYXDOg/kTZrKPNCdRgJ2UJmw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.4.0.tgz", + "integrity": "sha512-pzDwezZaLbK371OiJjXDsZJwVOALzFX5M1wEh2Kr0pEApq5AV6bRH/DFbA/zNA7Lck/duyREPQLLvzu2G6hEQQ==", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, + "node_modules/prettier": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", + "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-bytes": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", + "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" + }, + "node_modules/pretty-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz", + "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/property-information": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz", + "integrity": "sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reading-time": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.5.0.tgz", + "integrity": "sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==" + }, + "node_modules/rehype-autolink-headings": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/rehype-autolink-headings/-/rehype-autolink-headings-7.1.0.tgz", + "integrity": "sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==", + "dependencies": { + "@types/hast": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-heading-rank": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-citation": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/rehype-citation/-/rehype-citation-2.0.0.tgz", + "integrity": "sha512-rGawTBI8SJA1Y4IRyROvpYF6oXBVNFXlJYHIJ2jJH3HgeuCbAC9AO8wE/NMPLDOPQ8+Q8QkZm93fKsnUNbvwZA==", + "dependencies": { + "@citation-js/core": "^0.7.1", + "@citation-js/date": "^0.5.1", + "@citation-js/name": "^0.4.2", + "@citation-js/plugin-bibjson": "^0.7.2", + "@citation-js/plugin-bibtex": "^0.7.2", + "@citation-js/plugin-csl": "^0.7.2", + "citeproc": "^2.4.63", + "cross-fetch": "^4.0.0", + "hast-util-from-dom": "^5.0.0", + "hast-util-from-parse5": "^8.0.1", + "js-yaml": "^4.1.0", + "parse5": "^7.1.2", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0" + } + }, + "node_modules/rehype-katex": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.0.tgz", + "integrity": "sha512-h8FPkGE00r2XKU+/acgqwWUlyzve1IiOKwsEkg4pDL3k48PiE0Pt+/uLtVHDVkN1yA4iurZN6UES8ivHVEQV6Q==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/katex": "^0.16.0", + "hast-util-from-html-isomorphic": "^2.0.0", + "hast-util-to-text": "^4.0.0", + "katex": "^0.16.0", + "unist-util-visit-parents": "^6.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-katex/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/rehype-katex/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-katex/node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-mathjax": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/rehype-mathjax/-/rehype-mathjax-6.0.0.tgz", + "integrity": "sha512-SioRmn+0mRWtDc4QVKG9JG88bXhPazfhc11GQoQ68mwot2WWyfabyZ7tuJu3Z4LCf893wXkQTVTF8PUlntoDwA==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mathjax": "^0.0.40", + "hast-util-from-dom": "^5.0.0", + "hast-util-to-text": "^4.0.0", + "jsdom": "^23.0.0", + "mathjax-full": "^3.0.0", + "unified": "^11.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-mathjax/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/rehype-mathjax/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-mathjax/node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.0.tgz", + "integrity": "sha512-WG7nfvmWWkCR++KEkZevZb/uw41E8TsH4DsY9UxsTbIXCVGbAs4S+r8FrQ+OtH5EEQAs+5UxKC42VinkmpA1Yw==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-pretty-code": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/rehype-pretty-code/-/rehype-pretty-code-0.13.0.tgz", + "integrity": "sha512-+22dz1StXlF7dlMyOySNaVxgcGhMI4BCxq0JxJJPWYGiKsI6cu5jyuIKGHXHvH18D8sv1rdKtvsY9UEfN3++SQ==", + "dependencies": { + "@types/hast": "^3.0.4", + "hast-util-to-string": "^3.0.0", + "parse-numeric-range": "^1.3.0", + "rehype-parse": "^9.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "shiki": "^1.0.0" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-slug": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-6.0.0.tgz", + "integrity": "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==", + "dependencies": { + "@types/hast": "^3.0.0", + "github-slugger": "^2.0.0", + "hast-util-heading-rank": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/remark/-/remark-15.0.1.tgz", + "integrity": "sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==", + "dependencies": { + "@types/mdast": "^4.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-breaks": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-4.0.0.tgz", + "integrity": "sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-newline-to-break": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-frontmatter": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz", + "integrity": "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-frontmatter": "^2.0.0", + "micromark-extension-frontmatter": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz", + "integrity": "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-math": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz", + "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-math": "^3.0.0", + "micromark-extension-math": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.0.tgz", + "integrity": "sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-smartypants": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-2.1.0.tgz", + "integrity": "sha512-qoF6Vz3BjU2tP6OfZqHOvCU0ACmu/6jhGaINSQRI9mM7wCxNQTKB3JUAN4SVoN2ybElEDTxBIABRep7e569iJw==", + "dependencies": { + "retext": "^8.1.0", + "retext-smartypants": "^5.2.0", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/retext": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/retext/-/retext-8.1.0.tgz", + "integrity": "sha512-N9/Kq7YTn6ZpzfiGW45WfEGJqFf1IM1q8OsRa1CGzIebCJBNCANDRmOrholiDRGKo/We7ofKR4SEvcGAWEMD3Q==", + "dependencies": { + "@types/nlcst": "^1.0.0", + "retext-latin": "^3.0.0", + "retext-stringify": "^3.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-3.1.0.tgz", + "integrity": "sha512-5MrD1tuebzO8ppsja5eEu+ZbBeUNCjoEarn70tkXOS7Bdsdf6tNahsv2bY0Z8VooFF6cw7/6S+d3yI/TMlMVVQ==", + "dependencies": { + "@types/nlcst": "^1.0.0", + "parse-latin": "^5.0.0", + "unherit": "^3.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin/node_modules/unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "dependencies": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin/node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-5.2.0.tgz", + "integrity": "sha512-Do8oM+SsjrbzT2UNIKgheP0hgUQTDDQYyZaIY3kfq0pdFzoPk+ZClYJ+OERNXveog4xf1pZL4PfRxNoVL7a/jw==", + "dependencies": { + "@types/nlcst": "^1.0.0", + "nlcst-to-string": "^3.0.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants/node_modules/unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "dependencies": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants/node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants/node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-3.1.0.tgz", + "integrity": "sha512-767TLOaoXFXyOnjx/EggXlb37ZD2u4P1n0GJqVdpipqACsQP+20W+BNpMYrlJkq7hxffnFk+jc6mAK9qrbuB8w==", + "dependencies": { + "@types/nlcst": "^1.0.0", + "nlcst-to-string": "^3.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify/node_modules/unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "dependencies": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify/node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext/node_modules/unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "dependencies": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext/node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==" + }, + "node_modules/rimraf": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", + "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" + }, + "node_modules/rrweb-cssom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", + "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sass": { + "version": "1.66.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.66.1.tgz", + "integrity": "sha512-50c+zTsZOJVgFfTgwwEzkjA3/QACgdNsKueWPyAR0mRINIvLAStVQBbPg14iuqEQ74NPDbXzJARJ/O4SI1zftA==", + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/serve-handler": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.5.tgz", + "integrity": "sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==", + "dependencies": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "fast-url-parser": "1.1.3", + "mime-types": "2.1.18", + "minimatch": "3.1.2", + "path-is-inside": "1.0.2", + "path-to-regexp": "2.2.1", + "range-parser": "1.2.0" + } + }, + "node_modules/serve-handler/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/serve-handler/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/shiki": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.2.3.tgz", + "integrity": "sha512-+v7lO5cJMeV2N2ySK4l+51YX3wTh5I49SLjAOs1ch1DbUfeEytU1Ac9KaZPoZJCVBGycDZ09OBQN5nbcPFc5FQ==", + "dependencies": { + "@shikijs/core": "1.2.3" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/speech-rule-engine": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.0.7.tgz", + "integrity": "sha512-sJrL3/wHzNwJRLBdf6CjJWIlxC04iYKkyXvYSVsWVOiC2DSkHmxsqOhEeMsBA9XK+CHuNcsdkbFDnoUfAsmp9g==", + "dependencies": { + "commander": "9.2.0", + "wicked-good-xpath": "1.3.0", + "xmldom-sre": "0.1.31" + }, + "bin": { + "sre": "bin/sre" + } + }, + "node_modules/speech-rule-engine/node_modules/commander": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz", + "integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz", + "integrity": "sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/style-to-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.5.tgz", + "integrity": "sha512-rDRwHtoDD3UMMrmZ6BzOW0naTjMsVZLIjsGleSKS/0Oz+cgCfAPRspaqJuE8rDzpKha/nEvnM0IF4seEAZUTKQ==", + "dependencies": { + "inline-style-parser": "0.2.2" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, + "node_modules/sync-fetch": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/sync-fetch/-/sync-fetch-0.4.5.tgz", + "integrity": "sha512-esiWJ7ixSKGpd9DJPBTC4ckChqdOjIwJfYhVHkcQ2Gnm41323p1TRmEI+esTQ9ppD+b5opps2OTEGTCGX5kF+g==", + "dependencies": { + "buffer": "^5.7.1", + "node-fetch": "^2.6.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/to-vfile": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/to-vfile/-/to-vfile-8.0.0.tgz", + "integrity": "sha512-IcmH1xB5576MJc9qcfEC/m/nQCFt3fzMHz45sSlgJyTWjRbKW1HAkJpuf3DgE57YzIlZcwcBZA5ENQbBo4aLkg==", + "dependencies": { + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/toml": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", + "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==" + }, + "node_modules/tough-cookie": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tr46/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", + "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/tsx": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.1.tgz", + "integrity": "sha512-8d6VuibXHtlN5E3zFkgY8u4DX7Y3Z27zvvPKVmLon/D4AjuKzarkUBTLDBgj9iTQ0hg5xM7c/mYiRVM+HETf0g==", + "dev": true, + "dependencies": { + "esbuild": "~0.19.10", + "get-tsconfig": "^4.7.2" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", + "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/unherit": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-3.0.1.tgz", + "integrity": "sha512-akOOQ/Yln8a2sgcLj4U0Jmx0R5jpIg2IUyRrWOzmEbjBtGzBdHtSeFKgoEcoH4KYIG/Pb8GQ/BwtYm0GCq1Sqg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unified": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.4.tgz", + "integrity": "sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-find-after/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/unist-util-find-after/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-modify-children": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-3.1.1.tgz", + "integrity": "sha512-yXi4Lm+TG5VG+qvokP6tpnk+r1EPwyYL04JWDxLvgvPV40jANh7nm3udk65OOWquvbMDe+PL9+LmkxDpTv/7BA==", + "dependencies": { + "@types/unist": "^2.0.0", + "array-iterate": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-children": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-2.0.2.tgz", + "integrity": "sha512-+LWpMFqyUwLGpsQxpumsQ9o9DG2VGLFrpz+rpVXYIEdPy57GSy5HioC0g3bg/8WP9oCLlapQtklOzQ8uLS496Q==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/unist-util-visit/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit/node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/vfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", + "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.2.tgz", + "integrity": "sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/vfile/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", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile/node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wicked-good-xpath": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz", + "integrity": "sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw==" + }, + "node_modules/workerpool": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.1.0.tgz", + "integrity": "sha512-+wRWfm9yyJghvXLSHMQj3WXDxHbibHAQmRrWbqKBfy0RjftZNeQaW+Std5bSYc83ydkrxoPTPOWVlXUR9RWJdQ==" + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, + "node_modules/xmldom-sre": { + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/xmldom-sre/-/xmldom-sre-0.1.31.tgz", + "integrity": "sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==", + "engines": { + "node": ">=0.1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..84fc500 --- /dev/null +++ b/package.json @@ -0,0 +1,109 @@ +{ + "name": "@jackyzha0/quartz", + "description": "🌱 publish your digital garden and notes as a website", + "private": true, + "version": "4.2.3", + "type": "module", + "author": "jackyzha0 ", + "license": "MIT", + "homepage": "https://quartz.jzhao.xyz", + "repository": { + "type": "git", + "url": "https://github.com/jackyzha0/quartz.git" + }, + "scripts": { + "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", + "profile": "0x -D prof ./quartz/bootstrap-cli.mjs build --concurrency=1" + }, + "engines": { + "npm": ">=9.3.1", + "node": ">=18.14" + }, + "keywords": [ + "site generator", + "ssg", + "digital-garden", + "markdown", + "blog", + "quartz" + ], + "bin": { + "quartz": "./quartz/bootstrap-cli.mjs" + }, + "dependencies": { + "@clack/prompts": "^0.7.0", + "@floating-ui/dom": "^1.6.3", + "@napi-rs/simple-git": "0.1.16", + "async-mutex": "^0.5.0", + "chalk": "^5.3.0", + "chokidar": "^3.6.0", + "cli-spinner": "^0.2.10", + "d3": "^7.8.5", + "esbuild-sass-plugin": "^2.16.1", + "flexsearch": "0.7.43", + "github-slugger": "^2.0.0", + "globby": "^14.0.1", + "gray-matter": "^4.0.3", + "hast-util-to-html": "^9.0.0", + "hast-util-to-jsx-runtime": "^2.3.0", + "hast-util-to-string": "^3.0.0", + "is-absolute-url": "^4.0.1", + "js-yaml": "^4.1.0", + "lightningcss": "^1.24.1", + "mdast-util-find-and-replace": "^3.0.1", + "mdast-util-to-hast": "^13.1.0", + "mdast-util-to-string": "^4.0.0", + "micromorph": "^0.4.5", + "preact": "^10.20.1", + "preact-render-to-string": "^6.4.0", + "pretty-bytes": "^6.1.1", + "pretty-time": "^1.1.0", + "reading-time": "^1.5.0", + "rehype-autolink-headings": "^7.1.0", + "rehype-citation": "^2.0.0", + "rehype-katex": "^7.0.0", + "rehype-mathjax": "^6.0.0", + "rehype-pretty-code": "^0.13.0", + "rehype-raw": "^7.0.0", + "rehype-slug": "^6.0.0", + "remark": "^15.0.1", + "remark-breaks": "^4.0.0", + "remark-frontmatter": "^5.0.0", + "remark-gfm": "^4.0.0", + "remark-math": "^6.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.0", + "remark-smartypants": "^2.1.0", + "rfdc": "^1.3.1", + "rimraf": "^5.0.5", + "serve-handler": "^6.1.5", + "shiki": "^1.2.3", + "source-map-support": "^0.5.21", + "to-vfile": "^8.0.0", + "toml": "^3.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.1", + "workerpool": "^9.1.0", + "ws": "^8.15.1", + "yargs": "^17.7.2" + }, + "devDependencies": { + "@types/cli-spinner": "^0.2.3", + "@types/d3": "^7.4.3", + "@types/hast": "^3.0.4", + "@types/js-yaml": "^4.0.9", + "@types/node": "^20.11.29", + "@types/pretty-time": "^1.1.5", + "@types/source-map-support": "^0.5.10", + "@types/ws": "^8.5.10", + "@types/yargs": "^17.0.32", + "esbuild": "^0.19.9", + "prettier": "^3.2.4", + "tsx": "^4.7.1", + "typescript": "^5.4.3" + } +} diff --git a/quartz.config.ts b/quartz.config.ts new file mode 100644 index 0000000..966b777 --- /dev/null +++ b/quartz.config.ts @@ -0,0 +1,91 @@ +import { QuartzConfig } from "./quartz/cfg" +import * as Plugin from "./quartz/plugins" + +/** + * Quartz 4.0 Configuration + * + * See https://quartz.jzhao.xyz/configuration for more information. + */ +const config: QuartzConfig = { + configuration: { + pageTitle: "🌃 Nexie.net", + enableSPA: true, + enablePopovers: true, + analytics: { + provider: "plausible", + }, + locale: "en-US", + baseUrl: "nexie.net", + ignorePatterns: ["private", "templates", ".obsidian"], + defaultDateType: "created", + theme: { + fontOrigin: "googleFonts", + cdnCaching: true, + typography: { + header: "Schibsted Grotesk", + body: "Source Sans Pro", + code: "IBM Plex Mono", + }, + colors: { + lightMode: { + light: "#faf8f8", + lightgray: "#e5e5e5", + gray: "#b8b8b8", + darkgray: "#4e4e4e", + dark: "#2b2b2b", + secondary: "#284b63", + tertiary: "#84a59d", + highlight: "rgba(143, 159, 169, 0.15)", + }, + darkMode: { + light: "#161618", + lightgray: "#393639", + gray: "#646464", + darkgray: "#d4d4d4", + dark: "#ebebec", + secondary: "#7b97aa", + tertiary: "#84a59d", + highlight: "rgba(143, 159, 169, 0.15)", + }, + }, + }, + }, + plugins: { + transformers: [ + Plugin.FrontMatter(), + Plugin.CreatedModifiedDate({ + priority: ["frontmatter", "filesystem"], + }), + Plugin.Latex({ renderEngine: "katex" }), + Plugin.SyntaxHighlighting({ + theme: { + light: "github-light", + dark: "github-dark", + }, + keepBackground: false, + }), + Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }), + Plugin.GitHubFlavoredMarkdown(), + Plugin.TableOfContents(), + Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }), + Plugin.Description(), + ], + filters: [Plugin.RemoveDrafts()], + emitters: [ + Plugin.AliasRedirects(), + Plugin.ComponentResources(), + Plugin.ContentPage(), + Plugin.FolderPage(), + Plugin.TagPage(), + Plugin.ContentIndex({ + enableSiteMap: true, + enableRSS: true, + }), + Plugin.Assets(), + Plugin.Static(), + Plugin.NotFoundPage(), + ], + }, +} + +export default config diff --git a/quartz.layout.ts b/quartz.layout.ts new file mode 100644 index 0000000..06ee965 --- /dev/null +++ b/quartz.layout.ts @@ -0,0 +1,48 @@ +import { PageLayout, SharedLayout } from "./quartz/cfg" +import * as Component from "./quartz/components" + +// components shared across all pages +export const sharedPageComponents: SharedLayout = { + head: Component.Head(), + header: [], + footer: Component.Footer({ + links: { + "Main social profile": "https://derg.social/@ulysia", + }, + }), +} + +// components for pages that display a single page (e.g. a single note) +export const defaultContentPageLayout: PageLayout = { + beforeBody: [ + Component.Breadcrumbs(), + Component.ArticleTitle(), + Component.ContentMeta(), + Component.TagList(), + ], + left: [ + Component.PageTitle(), + Component.MobileOnly(Component.Spacer()), + Component.Search(), + Component.Darkmode(), + Component.DesktopOnly(Component.Explorer()), + ], + right: [ + Component.Graph(), + Component.DesktopOnly(Component.TableOfContents()), + Component.Backlinks(), + ], +} + +// components for pages that display lists of pages (e.g. tags or folders) +export const defaultListPageLayout: PageLayout = { + beforeBody: [Component.Breadcrumbs(), Component.ArticleTitle(), Component.ContentMeta()], + left: [ + Component.PageTitle(), + Component.MobileOnly(Component.Spacer()), + Component.Search(), + Component.Darkmode(), + Component.DesktopOnly(Component.Explorer()), + ], + right: [], +} diff --git a/quartz/bootstrap-cli.mjs b/quartz/bootstrap-cli.mjs new file mode 100644 index 0000000..35d06af --- /dev/null +++ b/quartz/bootstrap-cli.mjs @@ -0,0 +1,41 @@ +#!/usr/bin/env node +import yargs from "yargs" +import { hideBin } from "yargs/helpers" +import { + handleBuild, + handleCreate, + handleUpdate, + handleRestore, + handleSync, +} from "./cli/handlers.js" +import { CommonArgv, BuildArgv, CreateArgv, SyncArgv } from "./cli/args.js" +import { version } from "./cli/constants.js" + +yargs(hideBin(process.argv)) + .scriptName("quartz") + .version(version) + .usage("$0 [args]") + .command("create", "Initialize Quartz", CreateArgv, async (argv) => { + await handleCreate(argv) + }) + .command("update", "Get the latest Quartz updates", CommonArgv, async (argv) => { + await handleUpdate(argv) + }) + .command( + "restore", + "Try to restore your content folder from the cache", + CommonArgv, + async (argv) => { + await handleRestore(argv) + }, + ) + .command("sync", "Sync your Quartz to and from GitHub.", SyncArgv, async (argv) => { + await handleSync(argv) + }) + .command("build", "Build Quartz into a bundle of static HTML files", BuildArgv, async (argv) => { + await handleBuild(argv) + }) + .showHelpOnFail(false) + .help() + .strict() + .demandCommand().argv diff --git a/quartz/bootstrap-worker.mjs b/quartz/bootstrap-worker.mjs new file mode 100644 index 0000000..b08689c --- /dev/null +++ b/quartz/bootstrap-worker.mjs @@ -0,0 +1,7 @@ +#!/usr/bin/env node +import workerpool from "workerpool" +const cacheFile = "./.quartz-cache/transpiled-worker.mjs" +const { parseFiles } = await import(cacheFile) +workerpool.worker({ + parseFiles, +}) diff --git a/quartz/build.ts b/quartz/build.ts new file mode 100644 index 0000000..972a7e8 --- /dev/null +++ b/quartz/build.ts @@ -0,0 +1,411 @@ +import sourceMapSupport from "source-map-support" +sourceMapSupport.install(options) +import path from "path" +import { PerfTimer } from "./util/perf" +import { rimraf } from "rimraf" +import { GlobbyFilterFunction, isGitIgnored } from "globby" +import chalk from "chalk" +import { parseMarkdown } from "./processors/parse" +import { filterContent } from "./processors/filter" +import { emitContent } from "./processors/emit" +import cfg from "../quartz.config" +import { FilePath, FullSlug, joinSegments, slugifyFilePath } from "./util/path" +import chokidar from "chokidar" +import { ProcessedContent } from "./plugins/vfile" +import { Argv, BuildCtx } from "./util/ctx" +import { glob, toPosixPath } from "./util/glob" +import { trace } from "./util/trace" +import { options } from "./util/sourcemap" +import { Mutex } from "async-mutex" +import DepGraph from "./depgraph" +import { getStaticResourcesFromPlugins } from "./plugins" + +type Dependencies = Record | null> + +type BuildData = { + ctx: BuildCtx + ignored: GlobbyFilterFunction + mut: Mutex + initialSlugs: FullSlug[] + // TODO merge contentMap and trackedAssets + contentMap: Map + trackedAssets: Set + toRebuild: Set + toRemove: Set + lastBuildMs: number + dependencies: Dependencies +} + +type FileEvent = "add" | "change" | "delete" + +async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) { + const ctx: BuildCtx = { + argv, + cfg, + allSlugs: [], + } + + const perf = new PerfTimer() + const output = argv.output + + const pluginCount = Object.values(cfg.plugins).flat().length + const pluginNames = (key: "transformers" | "filters" | "emitters") => + cfg.plugins[key].map((plugin) => plugin.name) + if (argv.verbose) { + console.log(`Loaded ${pluginCount} plugins`) + console.log(` Transformers: ${pluginNames("transformers").join(", ")}`) + console.log(` Filters: ${pluginNames("filters").join(", ")}`) + console.log(` Emitters: ${pluginNames("emitters").join(", ")}`) + } + + const release = await mut.acquire() + perf.addEvent("clean") + await rimraf(path.join(output, "*"), { glob: true }) + console.log(`Cleaned output directory \`${output}\` in ${perf.timeSince("clean")}`) + + perf.addEvent("glob") + const allFiles = await glob("**/*.*", argv.directory, cfg.configuration.ignorePatterns) + const fps = allFiles.filter((fp) => fp.endsWith(".md")).sort() + console.log( + `Found ${fps.length} input files from \`${argv.directory}\` in ${perf.timeSince("glob")}`, + ) + + const filePaths = fps.map((fp) => joinSegments(argv.directory, fp) as FilePath) + ctx.allSlugs = allFiles.map((fp) => slugifyFilePath(fp as FilePath)) + + const parsedFiles = await parseMarkdown(ctx, filePaths) + const filteredContent = filterContent(ctx, parsedFiles) + + const dependencies: Record | null> = {} + + // Only build dependency graphs if we're doing a fast rebuild + if (argv.fastRebuild) { + const staticResources = getStaticResourcesFromPlugins(ctx) + for (const emitter of cfg.plugins.emitters) { + dependencies[emitter.name] = + (await emitter.getDependencyGraph?.(ctx, filteredContent, staticResources)) ?? null + } + } + + await emitContent(ctx, filteredContent) + console.log(chalk.green(`Done processing ${fps.length} files in ${perf.timeSince()}`)) + release() + + if (argv.serve) { + return startServing(ctx, mut, parsedFiles, clientRefresh, dependencies) + } +} + +// setup watcher for rebuilds +async function startServing( + ctx: BuildCtx, + mut: Mutex, + initialContent: ProcessedContent[], + clientRefresh: () => void, + dependencies: Dependencies, // emitter name: dep graph +) { + const { argv } = ctx + + // cache file parse results + const contentMap = new Map() + for (const content of initialContent) { + const [_tree, vfile] = content + contentMap.set(vfile.data.filePath!, content) + } + + const buildData: BuildData = { + ctx, + mut, + dependencies, + contentMap, + ignored: await isGitIgnored(), + initialSlugs: ctx.allSlugs, + toRebuild: new Set(), + toRemove: new Set(), + trackedAssets: new Set(), + lastBuildMs: 0, + } + + const watcher = chokidar.watch(".", { + persistent: true, + cwd: argv.directory, + ignoreInitial: true, + }) + + const buildFromEntry = argv.fastRebuild ? partialRebuildFromEntrypoint : rebuildFromEntrypoint + watcher + .on("add", (fp) => buildFromEntry(fp, "add", clientRefresh, buildData)) + .on("change", (fp) => buildFromEntry(fp, "change", clientRefresh, buildData)) + .on("unlink", (fp) => buildFromEntry(fp, "delete", clientRefresh, buildData)) + + return async () => { + await watcher.close() + } +} + +async function partialRebuildFromEntrypoint( + filepath: string, + action: FileEvent, + clientRefresh: () => void, + buildData: BuildData, // note: this function mutates buildData +) { + const { ctx, ignored, dependencies, contentMap, mut, toRemove } = buildData + const { argv, cfg } = ctx + + // don't do anything for gitignored files + if (ignored(filepath)) { + return + } + + const buildStart = new Date().getTime() + buildData.lastBuildMs = buildStart + const release = await mut.acquire() + if (buildData.lastBuildMs > buildStart) { + release() + return + } + + const perf = new PerfTimer() + console.log(chalk.yellow("Detected change, rebuilding...")) + + // UPDATE DEP GRAPH + const fp = joinSegments(argv.directory, toPosixPath(filepath)) as FilePath + + const staticResources = getStaticResourcesFromPlugins(ctx) + let processedFiles: ProcessedContent[] = [] + + switch (action) { + case "add": + // add to cache when new file is added + processedFiles = await parseMarkdown(ctx, [fp]) + processedFiles.forEach(([tree, vfile]) => contentMap.set(vfile.data.filePath!, [tree, vfile])) + + // update the dep graph by asking all emitters whether they depend on this file + for (const emitter of cfg.plugins.emitters) { + const emitterGraph = + (await emitter.getDependencyGraph?.(ctx, processedFiles, staticResources)) ?? null + + if (emitterGraph) { + const existingGraph = dependencies[emitter.name] + if (existingGraph !== null) { + existingGraph.mergeGraph(emitterGraph) + } else { + // might be the first time we're adding a mardown file + dependencies[emitter.name] = emitterGraph + } + } + } + break + case "change": + // invalidate cache when file is changed + processedFiles = await parseMarkdown(ctx, [fp]) + processedFiles.forEach(([tree, vfile]) => contentMap.set(vfile.data.filePath!, [tree, vfile])) + + // only content files can have added/removed dependencies because of transclusions + if (path.extname(fp) === ".md") { + for (const emitter of cfg.plugins.emitters) { + // get new dependencies from all emitters for this file + const emitterGraph = + (await emitter.getDependencyGraph?.(ctx, processedFiles, staticResources)) ?? null + + // only update the graph if the emitter plugin uses the changed file + // eg. Assets plugin ignores md files, so we skip updating the graph + if (emitterGraph?.hasNode(fp)) { + // merge the new dependencies into the dep graph + dependencies[emitter.name]?.updateIncomingEdgesForNode(emitterGraph, fp) + } + } + } + break + case "delete": + toRemove.add(fp) + break + } + + if (argv.verbose) { + console.log(`Updated dependency graphs in ${perf.timeSince()}`) + } + + // EMIT + perf.addEvent("rebuild") + let emittedFiles = 0 + + for (const emitter of cfg.plugins.emitters) { + const depGraph = dependencies[emitter.name] + + // emitter hasn't defined a dependency graph. call it with all processed files + if (depGraph === null) { + if (argv.verbose) { + console.log( + `Emitter ${emitter.name} doesn't define a dependency graph. Calling it with all files...`, + ) + } + + const files = [...contentMap.values()].filter( + ([_node, vfile]) => !toRemove.has(vfile.data.filePath!), + ) + + const emittedFps = await emitter.emit(ctx, files, staticResources) + + if (ctx.argv.verbose) { + for (const file of emittedFps) { + console.log(`[emit:${emitter.name}] ${file}`) + } + } + + emittedFiles += emittedFps.length + continue + } + + // only call the emitter if it uses this file + if (depGraph.hasNode(fp)) { + // re-emit using all files that are needed for the downstream of this file + // eg. for ContentIndex, the dep graph could be: + // a.md --> contentIndex.json + // b.md ------^ + // + // if a.md changes, we need to re-emit contentIndex.json, + // and supply [a.md, b.md] to the emitter + const upstreams = [...depGraph.getLeafNodeAncestors(fp)] as FilePath[] + + const upstreamContent = upstreams + // filter out non-markdown files + .filter((file) => contentMap.has(file)) + // if file was deleted, don't give it to the emitter + .filter((file) => !toRemove.has(file)) + .map((file) => contentMap.get(file)!) + + const emittedFps = await emitter.emit(ctx, upstreamContent, staticResources) + + if (ctx.argv.verbose) { + for (const file of emittedFps) { + console.log(`[emit:${emitter.name}] ${file}`) + } + } + + emittedFiles += emittedFps.length + } + } + + console.log(`Emitted ${emittedFiles} files to \`${argv.output}\` in ${perf.timeSince("rebuild")}`) + + // CLEANUP + const destinationsToDelete = new Set() + for (const file of toRemove) { + // remove from cache + contentMap.delete(file) + Object.values(dependencies).forEach((depGraph) => { + // remove the node from dependency graphs + depGraph?.removeNode(file) + // remove any orphan nodes. eg if a.md is deleted, a.html is orphaned and should be removed + const orphanNodes = depGraph?.removeOrphanNodes() + orphanNodes?.forEach((node) => { + // only delete files that are in the output directory + if (node.startsWith(argv.output)) { + destinationsToDelete.add(node) + } + }) + }) + } + await rimraf([...destinationsToDelete]) + + console.log(chalk.green(`Done rebuilding in ${perf.timeSince()}`)) + + toRemove.clear() + release() + clientRefresh() +} + +async function rebuildFromEntrypoint( + fp: string, + action: FileEvent, + clientRefresh: () => void, + buildData: BuildData, // note: this function mutates buildData +) { + const { ctx, ignored, mut, initialSlugs, contentMap, toRebuild, toRemove, trackedAssets } = + buildData + + const { argv } = ctx + + // don't do anything for gitignored files + if (ignored(fp)) { + return + } + + // dont bother rebuilding for non-content files, just track and refresh + fp = toPosixPath(fp) + const filePath = joinSegments(argv.directory, fp) as FilePath + if (path.extname(fp) !== ".md") { + if (action === "add" || action === "change") { + trackedAssets.add(filePath) + } else if (action === "delete") { + trackedAssets.delete(filePath) + } + clientRefresh() + return + } + + if (action === "add" || action === "change") { + toRebuild.add(filePath) + } else if (action === "delete") { + toRemove.add(filePath) + } + + const buildStart = new Date().getTime() + buildData.lastBuildMs = buildStart + const release = await mut.acquire() + + // there's another build after us, release and let them do it + if (buildData.lastBuildMs > buildStart) { + release() + return + } + + const perf = new PerfTimer() + console.log(chalk.yellow("Detected change, rebuilding...")) + try { + const filesToRebuild = [...toRebuild].filter((fp) => !toRemove.has(fp)) + + const trackedSlugs = [...new Set([...contentMap.keys(), ...toRebuild, ...trackedAssets])] + .filter((fp) => !toRemove.has(fp)) + .map((fp) => slugifyFilePath(path.posix.relative(argv.directory, fp) as FilePath)) + + ctx.allSlugs = [...new Set([...initialSlugs, ...trackedSlugs])] + const parsedContent = await parseMarkdown(ctx, filesToRebuild) + for (const content of parsedContent) { + const [_tree, vfile] = content + contentMap.set(vfile.data.filePath!, content) + } + + for (const fp of toRemove) { + contentMap.delete(fp) + } + + const parsedFiles = [...contentMap.values()] + const filteredContent = filterContent(ctx, parsedFiles) + + // TODO: we can probably traverse the link graph to figure out what's safe to delete here + // instead of just deleting everything + await rimraf(path.join(argv.output, ".*"), { glob: true }) + await emitContent(ctx, filteredContent) + console.log(chalk.green(`Done rebuilding in ${perf.timeSince()}`)) + } catch (err) { + console.log(chalk.yellow(`Rebuild failed. Waiting on a change to fix the error...`)) + if (argv.verbose) { + console.log(chalk.red(err)) + } + } + + release() + clientRefresh() + toRebuild.clear() + toRemove.clear() +} + +export default async (argv: Argv, mut: Mutex, clientRefresh: () => void) => { + try { + return await buildQuartz(argv, mut, clientRefresh) + } catch (err) { + trace("\nExiting Quartz due to a fatal error", err as Error) + } +} diff --git a/quartz/cfg.ts b/quartz/cfg.ts new file mode 100644 index 0000000..2e32b1f --- /dev/null +++ b/quartz/cfg.ts @@ -0,0 +1,73 @@ +import { ValidDateType } from "./components/Date" +import { QuartzComponent } from "./components/types" +import { ValidLocale } from "./i18n" +import { PluginTypes } from "./plugins/types" +import { Theme } from "./util/theme" + +export type Analytics = + | null + | { + provider: "plausible" + host?: string + } + | { + provider: "google" + tagId: string + } + | { + provider: "umami" + websiteId: string + host?: string + } + | { + provider: "goatcounter" + websiteId: string + host?: string + scriptSrc?: string + } + +export interface GlobalConfiguration { + pageTitle: string + /** Whether to enable single-page-app style rendering. this prevents flashes of unstyled content and improves smoothness of Quartz */ + enableSPA: boolean + /** Whether to display Wikipedia-style popovers when hovering over links */ + enablePopovers: boolean + /** Analytics mode */ + analytics: Analytics + /** Glob patterns to not search */ + ignorePatterns: string[] + /** Whether to use created, modified, or published as the default type of date */ + defaultDateType: ValidDateType + /** Base URL to use for CNAME files, sitemaps, and RSS feeds that require an absolute URL. + * Quartz will avoid using this as much as possible and use relative URLs most of the time + */ + baseUrl?: string + theme: Theme + /** + * Allow to translate the date in the language of your choice. + * Also used for UI translation (default: en-US) + * Need to be formated following BCP 47: https://en.wikipedia.org/wiki/IETF_language_tag + * The first part is the language (en) and the second part is the script/region (US) + * Language Codes: https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes + * Region Codes: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 + */ + locale: ValidLocale +} + +export interface QuartzConfig { + configuration: GlobalConfiguration + plugins: PluginTypes +} + +export interface FullPageLayout { + head: QuartzComponent + header: QuartzComponent[] + beforeBody: QuartzComponent[] + pageBody: QuartzComponent + left: QuartzComponent[] + right: QuartzComponent[] + footer: QuartzComponent +} + +export type PageLayout = Pick +export type SharedLayout = Pick diff --git a/quartz/cli/args.js b/quartz/cli/args.js new file mode 100644 index 0000000..123d0ac --- /dev/null +++ b/quartz/cli/args.js @@ -0,0 +1,108 @@ +export const CommonArgv = { + directory: { + string: true, + alias: ["d"], + default: "content", + describe: "directory to look for content files", + }, + verbose: { + boolean: true, + alias: ["v"], + default: false, + describe: "print out extra logging information", + }, +} + +export const CreateArgv = { + ...CommonArgv, + source: { + string: true, + alias: ["s"], + describe: "source directory to copy/create symlink from", + }, + strategy: { + string: true, + alias: ["X"], + choices: ["new", "copy", "symlink"], + describe: "strategy for content folder setup", + }, + links: { + string: true, + alias: ["l"], + choices: ["absolute", "shortest", "relative"], + describe: "strategy to resolve links", + }, +} + +export const SyncArgv = { + ...CommonArgv, + commit: { + boolean: true, + default: true, + describe: "create a git commit for your unsaved changes", + }, + message: { + string: true, + alias: ["m"], + describe: "option to override the default Quartz commit message", + }, + push: { + boolean: true, + default: true, + describe: "push updates to your Quartz fork", + }, + pull: { + boolean: true, + default: true, + describe: "pull updates from your Quartz fork", + }, +} + +export const BuildArgv = { + ...CommonArgv, + output: { + string: true, + alias: ["o"], + default: "public", + describe: "output folder for files", + }, + serve: { + boolean: true, + default: false, + describe: "run a local server to live-preview your Quartz", + }, + fastRebuild: { + boolean: true, + default: false, + describe: "[experimental] rebuild only the changed files", + }, + baseDir: { + string: true, + default: "", + describe: "base path to serve your local server on", + }, + port: { + number: true, + default: 8080, + describe: "port to serve Quartz on", + }, + wsPort: { + number: true, + default: 3001, + describe: "port to use for WebSocket-based hot-reload notifications", + }, + remoteDevHost: { + string: true, + default: "", + describe: "A URL override for the websocket connection if you are not developing on localhost", + }, + bundleInfo: { + boolean: true, + default: false, + describe: "show detailed bundle information", + }, + concurrency: { + number: true, + describe: "how many threads to use to parse notes", + }, +} diff --git a/quartz/cli/constants.js b/quartz/cli/constants.js new file mode 100644 index 0000000..f4a9ce5 --- /dev/null +++ b/quartz/cli/constants.js @@ -0,0 +1,15 @@ +import path from "path" +import { readFileSync } from "fs" + +/** + * All constants relating to helpers or handlers + */ +export const ORIGIN_NAME = "origin" +export const UPSTREAM_NAME = "upstream" +export const QUARTZ_SOURCE_BRANCH = "v4" +export const cwd = process.cwd() +export const cacheDir = path.join(cwd, ".quartz-cache") +export const cacheFile = "./quartz/.quartz-cache/transpiled-build.mjs" +export const fp = "./quartz/build.ts" +export const { version } = JSON.parse(readFileSync("./package.json").toString()) +export const contentCacheFolder = path.join(cacheDir, "content-cache") diff --git a/quartz/cli/handlers.js b/quartz/cli/handlers.js new file mode 100644 index 0000000..12e7e8e --- /dev/null +++ b/quartz/cli/handlers.js @@ -0,0 +1,544 @@ +import { promises } from "fs" +import path from "path" +import esbuild from "esbuild" +import chalk from "chalk" +import { sassPlugin } from "esbuild-sass-plugin" +import fs from "fs" +import { intro, outro, select, text } from "@clack/prompts" +import { rimraf } from "rimraf" +import chokidar from "chokidar" +import prettyBytes from "pretty-bytes" +import { execSync, spawnSync } from "child_process" +import http from "http" +import serveHandler from "serve-handler" +import { WebSocketServer } from "ws" +import { randomUUID } from "crypto" +import { Mutex } from "async-mutex" +import { CreateArgv } from "./args.js" +import { + exitIfCancel, + escapePath, + gitPull, + popContentFolder, + stashContentFolder, +} from "./helpers.js" +import { + UPSTREAM_NAME, + QUARTZ_SOURCE_BRANCH, + ORIGIN_NAME, + version, + fp, + cacheFile, + cwd, +} from "./constants.js" + +/** + * Handles `npx quartz create` + * @param {*} argv arguments for `create` + */ +export async function handleCreate(argv) { + console.log() + intro(chalk.bgGreen.black(` Quartz v${version} `)) + const contentFolder = path.join(cwd, argv.directory) + let setupStrategy = argv.strategy?.toLowerCase() + let linkResolutionStrategy = argv.links?.toLowerCase() + const sourceDirectory = argv.source + + // If all cmd arguments were provided, check if theyre valid + if (setupStrategy && linkResolutionStrategy) { + // If setup isn't, "new", source argument is required + if (setupStrategy !== "new") { + // Error handling + if (!sourceDirectory) { + outro( + chalk.red( + `Setup strategies (arg '${chalk.yellow( + `-${CreateArgv.strategy.alias[0]}`, + )}') other than '${chalk.yellow( + "new", + )}' require content folder argument ('${chalk.yellow( + `-${CreateArgv.source.alias[0]}`, + )}') to be set`, + ), + ) + process.exit(1) + } else { + if (!fs.existsSync(sourceDirectory)) { + outro( + chalk.red( + `Input directory to copy/symlink 'content' from not found ('${chalk.yellow( + sourceDirectory, + )}', invalid argument "${chalk.yellow(`-${CreateArgv.source.alias[0]}`)})`, + ), + ) + process.exit(1) + } else if (!fs.lstatSync(sourceDirectory).isDirectory()) { + outro( + chalk.red( + `Source directory to copy/symlink 'content' from is not a directory (found file at '${chalk.yellow( + sourceDirectory, + )}', invalid argument ${chalk.yellow(`-${CreateArgv.source.alias[0]}`)}")`, + ), + ) + process.exit(1) + } + } + } + } + + // Use cli process if cmd args werent provided + if (!setupStrategy) { + setupStrategy = exitIfCancel( + await select({ + message: `Choose how to initialize the content in \`${contentFolder}\``, + options: [ + { value: "new", label: "Empty Quartz" }, + { value: "copy", label: "Copy an existing folder", hint: "overwrites `content`" }, + { + value: "symlink", + label: "Symlink an existing folder", + hint: "don't select this unless you know what you are doing!", + }, + ], + }), + ) + } + + async function rmContentFolder() { + const contentStat = await fs.promises.lstat(contentFolder) + if (contentStat.isSymbolicLink()) { + await fs.promises.unlink(contentFolder) + } else { + await rimraf(contentFolder) + } + } + + const gitkeepPath = path.join(contentFolder, ".gitkeep") + if (fs.existsSync(gitkeepPath)) { + await fs.promises.unlink(gitkeepPath) + } + if (setupStrategy === "copy" || setupStrategy === "symlink") { + let originalFolder = sourceDirectory + + // If input directory was not passed, use cli + if (!sourceDirectory) { + originalFolder = escapePath( + exitIfCancel( + await text({ + message: "Enter the full path to existing content folder", + placeholder: + "On most terminal emulators, you can drag and drop a folder into the window and it will paste the full path", + validate(fp) { + const fullPath = escapePath(fp) + if (!fs.existsSync(fullPath)) { + return "The given path doesn't exist" + } else if (!fs.lstatSync(fullPath).isDirectory()) { + return "The given path is not a folder" + } + }, + }), + ), + ) + } + + await rmContentFolder() + if (setupStrategy === "copy") { + await fs.promises.cp(originalFolder, contentFolder, { + recursive: true, + preserveTimestamps: true, + }) + } else if (setupStrategy === "symlink") { + await fs.promises.symlink(originalFolder, contentFolder, "dir") + } + } else if (setupStrategy === "new") { + await fs.promises.writeFile( + path.join(contentFolder, "index.md"), + `--- +title: Welcome to Quartz +--- + +This is a blank Quartz installation. +See the [documentation](https://quartz.jzhao.xyz) for how to get started. +`, + ) + } + + // Use cli process if cmd args werent provided + if (!linkResolutionStrategy) { + // get a preferred link resolution strategy + linkResolutionStrategy = exitIfCancel( + await select({ + message: `Choose how Quartz should resolve links in your content. This should match Obsidian's link format. You can change this later in \`quartz.config.ts\`.`, + options: [ + { + value: "shortest", + label: "Treat links as shortest path", + hint: "(default)", + }, + { + value: "absolute", + label: "Treat links as absolute path", + }, + { + value: "relative", + label: "Treat links as relative paths", + }, + ], + }), + ) + } + + // now, do config changes + const configFilePath = path.join(cwd, "quartz.config.ts") + let configContent = await fs.promises.readFile(configFilePath, { encoding: "utf-8" }) + configContent = configContent.replace( + /markdownLinkResolution: '(.+)'/, + `markdownLinkResolution: '${linkResolutionStrategy}'`, + ) + await fs.promises.writeFile(configFilePath, configContent) + + // setup remote + execSync( + `git remote show upstream || git remote add upstream https://github.com/jackyzha0/quartz.git`, + { stdio: "ignore" }, + ) + + outro(`You're all set! Not sure what to do next? Try: + • Customizing Quartz a bit more by editing \`quartz.config.ts\` + • Running \`npx quartz build --serve\` to preview your Quartz locally + • Hosting your Quartz online (see: https://quartz.jzhao.xyz/hosting) +`) +} + +/** + * Handles `npx quartz build` + * @param {*} argv arguments for `build` + */ +export async function handleBuild(argv) { + console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`)) + const ctx = await esbuild.context({ + entryPoints: [fp], + outfile: cacheFile, + bundle: true, + keepNames: true, + minifyWhitespace: true, + minifySyntax: true, + platform: "node", + format: "esm", + jsx: "automatic", + jsxImportSource: "preact", + packages: "external", + metafile: true, + sourcemap: true, + sourcesContent: false, + plugins: [ + sassPlugin({ + type: "css-text", + cssImports: true, + }), + { + name: "inline-script-loader", + setup(build) { + build.onLoad({ filter: /\.inline\.(ts|js)$/ }, async (args) => { + let text = await promises.readFile(args.path, "utf8") + + // remove default exports that we manually inserted + text = text.replace("export default", "") + text = text.replace("export", "") + + const sourcefile = path.relative(path.resolve("."), args.path) + const resolveDir = path.dirname(sourcefile) + const transpiled = await esbuild.build({ + stdin: { + contents: text, + loader: "ts", + resolveDir, + sourcefile, + }, + write: false, + bundle: true, + minify: true, + platform: "browser", + format: "esm", + }) + const rawMod = transpiled.outputFiles[0].text + return { + contents: rawMod, + loader: "text", + } + }) + }, + }, + ], + }) + + const buildMutex = new Mutex() + let lastBuildMs = 0 + let cleanupBuild = null + const build = async (clientRefresh) => { + const buildStart = new Date().getTime() + lastBuildMs = buildStart + const release = await buildMutex.acquire() + if (lastBuildMs > buildStart) { + release() + return + } + + if (cleanupBuild) { + await cleanupBuild() + console.log(chalk.yellow("Detected a source code change, doing a hard rebuild...")) + } + + const result = await ctx.rebuild().catch((err) => { + console.error(`${chalk.red("Couldn't parse Quartz configuration:")} ${fp}`) + console.log(`Reason: ${chalk.grey(err)}`) + process.exit(1) + }) + release() + + if (argv.bundleInfo) { + const outputFileName = "quartz/.quartz-cache/transpiled-build.mjs" + const meta = result.metafile.outputs[outputFileName] + console.log( + `Successfully transpiled ${Object.keys(meta.inputs).length} files (${prettyBytes( + meta.bytes, + )})`, + ) + console.log(await esbuild.analyzeMetafile(result.metafile, { color: true })) + } + + // bypass module cache + // https://github.com/nodejs/modules/issues/307 + const { default: buildQuartz } = await import(`../../${cacheFile}?update=${randomUUID()}`) + // ^ this import is relative, so base "cacheFile" path can't be used + + cleanupBuild = await buildQuartz(argv, buildMutex, clientRefresh) + clientRefresh() + } + + if (argv.serve) { + const connections = [] + const clientRefresh = () => connections.forEach((conn) => conn.send("rebuild")) + + if (argv.baseDir !== "" && !argv.baseDir.startsWith("/")) { + argv.baseDir = "/" + argv.baseDir + } + + await build(clientRefresh) + const server = http.createServer(async (req, res) => { + if (argv.baseDir && !req.url?.startsWith(argv.baseDir)) { + console.log( + chalk.red( + `[404] ${req.url} (warning: link outside of site, this is likely a Quartz bug)`, + ), + ) + res.writeHead(404) + res.end() + return + } + + // strip baseDir prefix + req.url = req.url?.slice(argv.baseDir.length) + + const serve = async () => { + const release = await buildMutex.acquire() + await serveHandler(req, res, { + public: argv.output, + directoryListing: false, + headers: [ + { + source: "**/*.*", + headers: [{ key: "Content-Disposition", value: "inline" }], + }, + ], + }) + const status = res.statusCode + const statusString = + status >= 200 && status < 300 ? chalk.green(`[${status}]`) : chalk.red(`[${status}]`) + console.log(statusString + chalk.grey(` ${argv.baseDir}${req.url}`)) + release() + } + + const redirect = (newFp) => { + newFp = argv.baseDir + newFp + res.writeHead(302, { + Location: newFp, + }) + console.log(chalk.yellow("[302]") + chalk.grey(` ${argv.baseDir}${req.url} -> ${newFp}`)) + res.end() + } + + let fp = req.url?.split("?")[0] ?? "/" + + // handle redirects + if (fp.endsWith("/")) { + // /trailing/ + // does /trailing/index.html exist? if so, serve it + const indexFp = path.posix.join(fp, "index.html") + if (fs.existsSync(path.posix.join(argv.output, indexFp))) { + req.url = fp + return serve() + } + + // does /trailing.html exist? if so, redirect to /trailing + let base = fp.slice(0, -1) + if (path.extname(base) === "") { + base += ".html" + } + if (fs.existsSync(path.posix.join(argv.output, base))) { + return redirect(fp.slice(0, -1)) + } + } else { + // /regular + // does /regular.html exist? if so, serve it + let base = fp + if (path.extname(base) === "") { + base += ".html" + } + if (fs.existsSync(path.posix.join(argv.output, base))) { + req.url = fp + return serve() + } + + // does /regular/index.html exist? if so, redirect to /regular/ + let indexFp = path.posix.join(fp, "index.html") + if (fs.existsSync(path.posix.join(argv.output, indexFp))) { + return redirect(fp + "/") + } + } + + return serve() + }) + server.listen(argv.port) + const wss = new WebSocketServer({ port: argv.wsPort }) + wss.on("connection", (ws) => connections.push(ws)) + console.log( + chalk.cyan( + `Started a Quartz server listening at http://localhost:${argv.port}${argv.baseDir}`, + ), + ) + console.log("hint: exit with ctrl+c") + chokidar + .watch(["**/*.ts", "**/*.tsx", "**/*.scss", "package.json"], { + ignoreInitial: true, + }) + .on("all", async () => { + build(clientRefresh) + }) + } else { + await build(() => {}) + ctx.dispose() + } +} + +/** + * Handles `npx quartz update` + * @param {*} argv arguments for `update` + */ +export async function handleUpdate(argv) { + const contentFolder = path.join(cwd, argv.directory) + console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`)) + console.log("Backing up your content") + execSync( + `git remote show upstream || git remote add upstream https://github.com/jackyzha0/quartz.git`, + ) + await stashContentFolder(contentFolder) + console.log( + "Pulling updates... you may need to resolve some `git` conflicts if you've made changes to components or plugins.", + ) + + try { + gitPull(UPSTREAM_NAME, QUARTZ_SOURCE_BRANCH) + } catch { + console.log(chalk.red("An error occurred above while pulling updates.")) + await popContentFolder(contentFolder) + return + } + + await popContentFolder(contentFolder) + console.log("Ensuring dependencies are up to date") + const res = spawnSync("npm", ["i"], { stdio: "inherit" }) + if (res.status === 0) { + console.log(chalk.green("Done!")) + } else { + console.log(chalk.red("An error occurred above while installing dependencies.")) + } +} + +/** + * Handles `npx quartz restore` + * @param {*} argv arguments for `restore` + */ +export async function handleRestore(argv) { + const contentFolder = path.join(cwd, argv.directory) + await popContentFolder(contentFolder) +} + +/** + * Handles `npx quartz sync` + * @param {*} argv arguments for `sync` + */ +export async function handleSync(argv) { + const contentFolder = path.join(cwd, argv.directory) + console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`)) + console.log("Backing up your content") + + if (argv.commit) { + const contentStat = await fs.promises.lstat(contentFolder) + if (contentStat.isSymbolicLink()) { + const linkTarg = await fs.promises.readlink(contentFolder) + console.log(chalk.yellow("Detected symlink, trying to dereference before committing")) + + // stash symlink file + await stashContentFolder(contentFolder) + + // follow symlink and copy content + await fs.promises.cp(linkTarg, contentFolder, { + recursive: true, + preserveTimestamps: true, + }) + } + + const currentTimestamp = new Date().toLocaleString("en-US", { + dateStyle: "medium", + timeStyle: "short", + }) + const commitMessage = argv.message ?? `Quartz sync: ${currentTimestamp}` + spawnSync("git", ["add", "."], { stdio: "inherit" }) + spawnSync("git", ["commit", "-m", commitMessage], { stdio: "inherit" }) + + if (contentStat.isSymbolicLink()) { + // put symlink back + await popContentFolder(contentFolder) + } + } + + await stashContentFolder(contentFolder) + + if (argv.pull) { + console.log( + "Pulling updates from your repository. You may need to resolve some `git` conflicts if you've made changes to components or plugins.", + ) + try { + gitPull(ORIGIN_NAME, QUARTZ_SOURCE_BRANCH) + } catch { + console.log(chalk.red("An error occurred above while pulling updates.")) + await popContentFolder(contentFolder) + return + } + } + + await popContentFolder(contentFolder) + if (argv.push) { + console.log("Pushing your changes") + const res = spawnSync("git", ["push", "-uf", ORIGIN_NAME, QUARTZ_SOURCE_BRANCH], { + stdio: "inherit", + }) + if (res.status !== 0) { + console.log(chalk.red(`An error occurred above while pushing to remote ${ORIGIN_NAME}.`)) + return + } + } + + console.log(chalk.green("Done!")) +} diff --git a/quartz/cli/helpers.js b/quartz/cli/helpers.js new file mode 100644 index 0000000..702a1b7 --- /dev/null +++ b/quartz/cli/helpers.js @@ -0,0 +1,54 @@ +import { isCancel, outro } from "@clack/prompts" +import chalk from "chalk" +import { contentCacheFolder } from "./constants.js" +import { spawnSync } from "child_process" +import fs from "fs" + +export function escapePath(fp) { + return fp + .replace(/\\ /g, " ") // unescape spaces + .replace(/^".*"$/, "$1") + .replace(/^'.*"$/, "$1") + .trim() +} + +export function exitIfCancel(val) { + if (isCancel(val)) { + outro(chalk.red("Exiting")) + process.exit(0) + } else { + return val + } +} + +export async function stashContentFolder(contentFolder) { + await fs.promises.rm(contentCacheFolder, { force: true, recursive: true }) + await fs.promises.cp(contentFolder, contentCacheFolder, { + force: true, + recursive: true, + verbatimSymlinks: true, + preserveTimestamps: true, + }) + await fs.promises.rm(contentFolder, { force: true, recursive: true }) +} + +export function gitPull(origin, branch) { + const flags = ["--no-rebase", "--autostash", "-s", "recursive", "-X", "ours", "--no-edit"] + const out = spawnSync("git", ["pull", ...flags, origin, branch], { stdio: "inherit" }) + if (out.stderr) { + throw new Error(chalk.red(`Error while pulling updates: ${out.stderr}`)) + } else if (out.status !== 0) { + throw new Error(chalk.red("Error while pulling updates")) + } +} + +export async function popContentFolder(contentFolder) { + await fs.promises.rm(contentFolder, { force: true, recursive: true }) + await fs.promises.cp(contentCacheFolder, contentFolder, { + force: true, + recursive: true, + verbatimSymlinks: true, + preserveTimestamps: true, + }) + await fs.promises.rm(contentCacheFolder, { force: true, recursive: true }) +} diff --git a/quartz/components/ArticleTitle.tsx b/quartz/components/ArticleTitle.tsx new file mode 100644 index 0000000..318aeb2 --- /dev/null +++ b/quartz/components/ArticleTitle.tsx @@ -0,0 +1,19 @@ +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" +import { classNames } from "../util/lang" + +const ArticleTitle: QuartzComponent = ({ fileData, displayClass }: QuartzComponentProps) => { + const title = fileData.frontmatter?.title + if (title) { + return

{title}

+ } else { + return null + } +} + +ArticleTitle.css = ` +.article-title { + margin: 2rem 0 0 0; +} +` + +export default (() => ArticleTitle) satisfies QuartzComponentConstructor diff --git a/quartz/components/Backlinks.tsx b/quartz/components/Backlinks.tsx new file mode 100644 index 0000000..aa412a2 --- /dev/null +++ b/quartz/components/Backlinks.tsx @@ -0,0 +1,36 @@ +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" +import style from "./styles/backlinks.scss" +import { resolveRelative, simplifySlug } from "../util/path" +import { i18n } from "../i18n" +import { classNames } from "../util/lang" + +const Backlinks: QuartzComponent = ({ + fileData, + allFiles, + displayClass, + cfg, +}: QuartzComponentProps) => { + const slug = simplifySlug(fileData.slug!) + const backlinkFiles = allFiles.filter((file) => file.links?.includes(slug)) + return ( +
+ ) +} + +Backlinks.css = style +export default (() => Backlinks) satisfies QuartzComponentConstructor diff --git a/quartz/components/Body.tsx b/quartz/components/Body.tsx new file mode 100644 index 0000000..96b6278 --- /dev/null +++ b/quartz/components/Body.tsx @@ -0,0 +1,13 @@ +// @ts-ignore +import clipboardScript from "./scripts/clipboard.inline" +import clipboardStyle from "./styles/clipboard.scss" +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" + +const Body: QuartzComponent = ({ children }: QuartzComponentProps) => { + return
{children}
+} + +Body.afterDOMLoaded = clipboardScript +Body.css = clipboardStyle + +export default (() => Body) satisfies QuartzComponentConstructor diff --git a/quartz/components/Breadcrumbs.tsx b/quartz/components/Breadcrumbs.tsx new file mode 100644 index 0000000..9ccfb9a --- /dev/null +++ b/quartz/components/Breadcrumbs.tsx @@ -0,0 +1,139 @@ +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" +import breadcrumbsStyle from "./styles/breadcrumbs.scss" +import { FullSlug, SimpleSlug, joinSegments, resolveRelative } from "../util/path" +import { QuartzPluginData } from "../plugins/vfile" +import { classNames } from "../util/lang" + +type CrumbData = { + displayName: string + path: string +} + +interface BreadcrumbOptions { + /** + * Symbol between crumbs + */ + spacerSymbol: string + /** + * Name of first crumb + */ + rootName: string + /** + * Whether to look up frontmatter title for folders (could cause performance problems with big vaults) + */ + resolveFrontmatterTitle: boolean + /** + * Whether to display breadcrumbs on root `index.md` + */ + hideOnRoot: boolean + /** + * Whether to display the current page in the breadcrumbs. + */ + showCurrentPage: boolean +} + +const defaultOptions: BreadcrumbOptions = { + spacerSymbol: "❯", + rootName: "Home", + resolveFrontmatterTitle: true, + hideOnRoot: true, + showCurrentPage: true, +} + +function formatCrumb(displayName: string, baseSlug: FullSlug, currentSlug: SimpleSlug): CrumbData { + return { + displayName: displayName.replaceAll("-", " "), + path: resolveRelative(baseSlug, currentSlug), + } +} + +export default ((opts?: Partial) => { + // Merge options with defaults + const options: BreadcrumbOptions = { ...defaultOptions, ...opts } + + // computed index of folder name to its associated file data + let folderIndex: Map | undefined + + const Breadcrumbs: QuartzComponent = ({ + fileData, + allFiles, + displayClass, + }: QuartzComponentProps) => { + // Hide crumbs on root if enabled + if (options.hideOnRoot && fileData.slug === "index") { + return <> + } + + // Format entry for root element + const firstEntry = formatCrumb(options.rootName, fileData.slug!, "/" as SimpleSlug) + const crumbs: CrumbData[] = [firstEntry] + + if (!folderIndex && options.resolveFrontmatterTitle) { + folderIndex = new Map() + // construct the index for the first time + for (const file of allFiles) { + const folderParts = file.slug?.split("/") + if (folderParts?.at(-1) === "index") { + folderIndex.set(folderParts.slice(0, -1).join("/"), file) + } + } + } + + // Split slug into hierarchy/parts + const slugParts = fileData.slug?.split("/") + if (slugParts) { + // is tag breadcrumb? + const isTagPath = slugParts[0] === "tags" + + // full path until current part + let currentPath = "" + + for (let i = 0; i < slugParts.length - 1; i++) { + let curPathSegment = slugParts[i] + + // Try to resolve frontmatter folder title + const currentFile = folderIndex?.get(slugParts.slice(0, i + 1).join("/")) + if (currentFile) { + const title = currentFile.frontmatter!.title + if (title !== "index") { + curPathSegment = title + } + } + + // Add current slug to full path + currentPath = joinSegments(currentPath, slugParts[i]) + const includeTrailingSlash = !isTagPath || i < 1 + + // Format and add current crumb + const crumb = formatCrumb( + curPathSegment, + fileData.slug!, + (currentPath + (includeTrailingSlash ? "/" : "")) as SimpleSlug, + ) + crumbs.push(crumb) + } + + // Add current file to crumb (can directly use frontmatter title) + if (options.showCurrentPage && slugParts.at(-1) !== "index") { + crumbs.push({ + displayName: fileData.frontmatter!.title, + path: "", + }) + } + } + + return ( + + ) + } + Breadcrumbs.css = breadcrumbsStyle + + return Breadcrumbs +}) satisfies QuartzComponentConstructor diff --git a/quartz/components/ContentMeta.tsx b/quartz/components/ContentMeta.tsx new file mode 100644 index 0000000..5dfec14 --- /dev/null +++ b/quartz/components/ContentMeta.tsx @@ -0,0 +1,60 @@ +import { formatDate, getDate } from "./Date" +import { QuartzComponentConstructor, QuartzComponentProps } from "./types" +import readingTime from "reading-time" +import { classNames } from "../util/lang" +import { i18n } from "../i18n" +import { JSX } from "preact" +import style from "./styles/contentMeta.scss" + +interface ContentMetaOptions { + /** + * Whether to display reading time + */ + showReadingTime: boolean + showComma: boolean +} + +const defaultOptions: ContentMetaOptions = { + showReadingTime: true, + showComma: true, +} + +export default ((opts?: Partial) => { + // Merge options with defaults + const options: ContentMetaOptions = { ...defaultOptions, ...opts } + + function ContentMetadata({ cfg, fileData, displayClass }: QuartzComponentProps) { + const text = fileData.text + + if (text) { + const segments: (string | JSX.Element)[] = [] + + if (fileData.dates) { + segments.push(formatDate(getDate(cfg, fileData)!, cfg.locale)) + } + + // Display reading time if enabled + if (options.showReadingTime) { + const { minutes, words: _words } = readingTime(text) + const displayedTime = i18n(cfg.locale).components.contentMeta.readingTime({ + minutes: Math.ceil(minutes), + }) + segments.push(displayedTime) + } + + const segmentsElements = segments.map((segment) => {segment}) + + return ( +

+ {segmentsElements} +

+ ) + } else { + return null + } + } + + ContentMetadata.css = style + + return ContentMetadata +}) satisfies QuartzComponentConstructor diff --git a/quartz/components/Darkmode.tsx b/quartz/components/Darkmode.tsx new file mode 100644 index 0000000..8ed7c99 --- /dev/null +++ b/quartz/components/Darkmode.tsx @@ -0,0 +1,53 @@ +// @ts-ignore: this is safe, we don't want to actually make darkmode.inline.ts a module as +// modules are automatically deferred and we don't want that to happen for critical beforeDOMLoads +// see: https://v8.dev/features/modules#defer +import darkmodeScript from "./scripts/darkmode.inline" +import styles from "./styles/darkmode.scss" +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" +import { i18n } from "../i18n" +import { classNames } from "../util/lang" + +const Darkmode: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => { + return ( +
+ + + +
+ ) +} + +Darkmode.beforeDOMLoaded = darkmodeScript +Darkmode.css = styles + +export default (() => Darkmode) satisfies QuartzComponentConstructor diff --git a/quartz/components/Date.tsx b/quartz/components/Date.tsx new file mode 100644 index 0000000..26b5964 --- /dev/null +++ b/quartz/components/Date.tsx @@ -0,0 +1,31 @@ +import { GlobalConfiguration } from "../cfg" +import { ValidLocale } from "../i18n" +import { QuartzPluginData } from "../plugins/vfile" + +interface Props { + date: Date + locale?: ValidLocale +} + +export type ValidDateType = keyof Required["dates"] + +export function getDate(cfg: GlobalConfiguration, data: QuartzPluginData): Date | undefined { + if (!cfg.defaultDateType) { + throw new Error( + `Field 'defaultDateType' was not set in the configuration object of quartz.config.ts. See https://quartz.jzhao.xyz/configuration#general-configuration for more details.`, + ) + } + return data.dates?.[cfg.defaultDateType] +} + +export function formatDate(d: Date, locale: ValidLocale = "en-US"): string { + return d.toLocaleDateString(locale, { + year: "numeric", + month: "short", + day: "2-digit", + }) +} + +export function Date({ date, locale }: Props) { + return <>{formatDate(date, locale)} +} diff --git a/quartz/components/DesktopOnly.tsx b/quartz/components/DesktopOnly.tsx new file mode 100644 index 0000000..fe2a27f --- /dev/null +++ b/quartz/components/DesktopOnly.tsx @@ -0,0 +1,18 @@ +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" + +export default ((component?: QuartzComponent) => { + if (component) { + const Component = component + const DesktopOnly: QuartzComponent = (props: QuartzComponentProps) => { + return + } + + DesktopOnly.displayName = component.displayName + DesktopOnly.afterDOMLoaded = component?.afterDOMLoaded + DesktopOnly.beforeDOMLoaded = component?.beforeDOMLoaded + DesktopOnly.css = component?.css + return DesktopOnly + } else { + return () => <> + } +}) satisfies QuartzComponentConstructor diff --git a/quartz/components/Explorer.tsx b/quartz/components/Explorer.tsx new file mode 100644 index 0000000..cffc079 --- /dev/null +++ b/quartz/components/Explorer.tsx @@ -0,0 +1,124 @@ +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" +import explorerStyle 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" + +// Options interface defined in `ExplorerNode` to avoid circular dependency +const defaultOptions = { + folderClickBehavior: "collapse", + folderDefaultState: "collapsed", + 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)) { + // 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, { + numeric: true, + sensitivity: "base", + }) + } + + if (a.file && !b.file) { + return 1 + } else { + return -1 + } + }, + filterFn: (node) => node.name !== "tags", + order: ["filter", "map", "sort"], +} satisfies Options + +export default ((userOpts?: Partial) => { + // Parse config + const opts: Options = { ...defaultOptions, ...userOpts } + + // memoized + let fileTree: FileNode + let jsonTree: string + + function constructFileTree(allFiles: QuartzPluginData[]) { + if (fileTree) { + return + } + + // 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 = ({ + cfg, + allFiles, + displayClass, + fileData, + }: QuartzComponentProps) => { + constructFileTree(allFiles) + return ( +
+ +
+
    + +
  • +
+
+
+ ) + } + + Explorer.css = explorerStyle + Explorer.afterDOMLoaded = script + return Explorer +}) satisfies QuartzComponentConstructor diff --git a/quartz/components/ExplorerNode.tsx b/quartz/components/ExplorerNode.tsx new file mode 100644 index 0000000..2968a03 --- /dev/null +++ b/quartz/components/ExplorerNode.tsx @@ -0,0 +1,248 @@ +// @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 + let folderPath = "" + if (node.name !== "") { + folderPath = joinSegments(fullPath ?? "", node.name) + } + + 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/Footer.tsx b/quartz/components/Footer.tsx new file mode 100644 index 0000000..5c9f394 --- /dev/null +++ b/quartz/components/Footer.tsx @@ -0,0 +1,30 @@ +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" +import style from "./styles/footer.scss" +import { version } from "../../package.json" +import { i18n } from "../i18n" + +interface Options { + links: Record +} + +export default ((opts?: Options) => { + const Footer: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => { + const year = new Date().getFullYear() + const links = opts?.links ?? [] + return ( +
    +
    +
      + {Object.entries(links).map(([text, link]) => ( +
    • + {text} +
    • + ))} +
    +
    + ) + } + + Footer.css = style + return Footer +}) satisfies QuartzComponentConstructor diff --git a/quartz/components/Graph.tsx b/quartz/components/Graph.tsx new file mode 100644 index 0000000..f7ebcc9 --- /dev/null +++ b/quartz/components/Graph.tsx @@ -0,0 +1,105 @@ +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" +// @ts-ignore +import script from "./scripts/graph.inline" +import style from "./styles/graph.scss" +import { i18n } from "../i18n" +import { classNames } from "../util/lang" + +export interface D3Config { + drag: boolean + zoom: boolean + depth: number + scale: number + repelForce: number + centerForce: number + linkDistance: number + fontSize: number + opacityScale: number + removeTags: string[] + showTags: boolean + focusOnHover?: boolean +} + +interface GraphOptions { + localGraph: Partial | undefined + globalGraph: Partial | undefined +} + +const defaultOptions: GraphOptions = { + localGraph: { + drag: true, + zoom: true, + depth: 1, + scale: 1.1, + repelForce: 0.5, + centerForce: 0.3, + linkDistance: 30, + fontSize: 0.6, + opacityScale: 1, + showTags: true, + removeTags: [], + focusOnHover: false, + }, + globalGraph: { + drag: true, + zoom: true, + depth: -1, + scale: 0.9, + repelForce: 0.5, + centerForce: 0.3, + linkDistance: 30, + fontSize: 0.6, + opacityScale: 1, + showTags: true, + removeTags: [], + focusOnHover: true, + }, +} + +export default ((opts?: GraphOptions) => { + const Graph: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => { + const localGraph = { ...defaultOptions.localGraph, ...opts?.localGraph } + const globalGraph = { ...defaultOptions.globalGraph, ...opts?.globalGraph } + return ( +
    +

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

    +
    +
    + + + +
    +
    +
    +
    +
    + ) + } + + Graph.css = style + Graph.afterDOMLoaded = script + + return Graph +}) satisfies QuartzComponentConstructor diff --git a/quartz/components/Head.tsx b/quartz/components/Head.tsx new file mode 100644 index 0000000..b878a81 --- /dev/null +++ b/quartz/components/Head.tsx @@ -0,0 +1,52 @@ +import { i18n } from "../i18n" +import { FullSlug, joinSegments, pathToRoot } from "../util/path" +import { JSResourceToScriptElement } from "../util/resources" +import { googleFontHref } from "../util/theme" +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" + +export default (() => { + const Head: QuartzComponent = ({ cfg, fileData, externalResources }: QuartzComponentProps) => { + const title = fileData.frontmatter?.title ?? i18n(cfg.locale).propertyDefaults.title + const description = + fileData.description?.trim() ?? i18n(cfg.locale).propertyDefaults.description + const { css, js } = externalResources + + const url = new URL(`https://${cfg.baseUrl ?? "example.com"}`) + const path = url.pathname as FullSlug + const baseDir = fileData.slug === "404" ? path : pathToRoot(fileData.slug!) + + const iconPath = "https://f003.backblazeb2.com/file/Solweaver-Site/favicon.png" + const ogImagePath = "https://f003.backblazeb2.com/file/Solweaver-Site/favicon.png" + + return ( + + {title} + + {cfg.theme.cdnCaching && cfg.theme.fontOrigin === "googleFonts" && ( + <> + + + + + )} + + + + {cfg.baseUrl && } + + + + + + {css.map((href) => ( + + ))} + {js + .filter((resource) => resource.loadTime === "beforeDOMReady") + .map((res) => JSResourceToScriptElement(res, true))} + + ) + } + + return Head +}) satisfies QuartzComponentConstructor diff --git a/quartz/components/Header.tsx b/quartz/components/Header.tsx new file mode 100644 index 0000000..eba17ae --- /dev/null +++ b/quartz/components/Header.tsx @@ -0,0 +1,22 @@ +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" + +const Header: QuartzComponent = ({ children }: QuartzComponentProps) => { + return children.length > 0 ?
    {children}
    : null +} + +Header.css = ` +header { + display: flex; + flex-direction: row; + align-items: center; + margin: 2rem 0; + gap: 1.5rem; +} + +header h1 { + margin: 0; + flex: auto; +} +` + +export default (() => Header) satisfies QuartzComponentConstructor diff --git a/quartz/components/MobileOnly.tsx b/quartz/components/MobileOnly.tsx new file mode 100644 index 0000000..7d2108d --- /dev/null +++ b/quartz/components/MobileOnly.tsx @@ -0,0 +1,18 @@ +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" + +export default ((component?: QuartzComponent) => { + if (component) { + const Component = component + const MobileOnly: QuartzComponent = (props: QuartzComponentProps) => { + return + } + + MobileOnly.displayName = component.displayName + MobileOnly.afterDOMLoaded = component?.afterDOMLoaded + MobileOnly.beforeDOMLoaded = component?.beforeDOMLoaded + MobileOnly.css = component?.css + return MobileOnly + } else { + return () => <> + } +}) satisfies QuartzComponentConstructor diff --git a/quartz/components/PageList.tsx b/quartz/components/PageList.tsx new file mode 100644 index 0000000..1e5d232 --- /dev/null +++ b/quartz/components/PageList.tsx @@ -0,0 +1,87 @@ +import { FullSlug, resolveRelative } from "../util/path" +import { QuartzPluginData } from "../plugins/vfile" +import { Date, getDate } from "./Date" +import { QuartzComponent, QuartzComponentProps } from "./types" +import { GlobalConfiguration } from "../cfg" + +export function byDateAndAlphabetical( + cfg: GlobalConfiguration, +): (f1: QuartzPluginData, f2: QuartzPluginData) => number { + return (f1, f2) => { + if (f1.dates && f2.dates) { + // sort descending + return getDate(cfg, f2)!.getTime() - getDate(cfg, f1)!.getTime() + } else if (f1.dates && !f2.dates) { + // prioritize files with dates + return -1 + } else if (!f1.dates && f2.dates) { + return 1 + } + + // otherwise, sort lexographically by title + const f1Title = f1.frontmatter?.title.toLowerCase() ?? "" + const f2Title = f2.frontmatter?.title.toLowerCase() ?? "" + return f1Title.localeCompare(f2Title) + } +} + +type Props = { + limit?: number +} & QuartzComponentProps + +export const PageList: QuartzComponent = ({ cfg, fileData, allFiles, limit }: Props) => { + let list = allFiles.sort(byDateAndAlphabetical(cfg)) + if (limit) { + list = list.slice(0, limit) + } + + return ( +
      + {list.map((page) => { + const title = page.frontmatter?.title + const tags = page.frontmatter?.tags ?? [] + + return ( +
    • +
      + {page.dates && ( +

      + +

      + )} + + +
      +
    • + ) + })} +
    + ) +} + +PageList.css = ` +.section h3 { + margin: 0; +} + +.section > .tags { + margin: 0; +} +` diff --git a/quartz/components/PageTitle.tsx b/quartz/components/PageTitle.tsx new file mode 100644 index 0000000..2362f10 --- /dev/null +++ b/quartz/components/PageTitle.tsx @@ -0,0 +1,22 @@ +import { pathToRoot } from "../util/path" +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" +import { classNames } from "../util/lang" +import { i18n } from "../i18n" + +const PageTitle: QuartzComponent = ({ fileData, cfg, displayClass }: QuartzComponentProps) => { + const title = cfg?.pageTitle ?? i18n(cfg.locale).propertyDefaults.title + const baseDir = pathToRoot(fileData.slug!) + return ( +

    + {title} +

    + ) +} + +PageTitle.css = ` +.page-title { + margin: 0; +} +` + +export default (() => PageTitle) satisfies QuartzComponentConstructor diff --git a/quartz/components/RecentNotes.tsx b/quartz/components/RecentNotes.tsx new file mode 100644 index 0000000..d99878d --- /dev/null +++ b/quartz/components/RecentNotes.tsx @@ -0,0 +1,89 @@ +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" +import { FullSlug, SimpleSlug, resolveRelative } from "../util/path" +import { QuartzPluginData } from "../plugins/vfile" +import { byDateAndAlphabetical } from "./PageList" +import style from "./styles/recentNotes.scss" +import { Date, getDate } from "./Date" +import { GlobalConfiguration } from "../cfg" +import { i18n } from "../i18n" +import { classNames } from "../util/lang" + +interface Options { + title?: string + limit: number + linkToMore: SimpleSlug | false + filter: (f: QuartzPluginData) => boolean + sort: (f1: QuartzPluginData, f2: QuartzPluginData) => number +} + +const defaultOptions = (cfg: GlobalConfiguration): Options => ({ + limit: 3, + linkToMore: false, + filter: () => true, + sort: byDateAndAlphabetical(cfg), +}) + +export default ((userOpts?: Partial) => { + const RecentNotes: QuartzComponent = ({ + allFiles, + fileData, + displayClass, + cfg, + }: QuartzComponentProps) => { + const opts = { ...defaultOptions(cfg), ...userOpts } + const pages = allFiles.filter(opts.filter).sort(opts.sort) + const remaining = Math.max(0, pages.length - opts.limit) + return ( +
    +

    {opts.title ?? i18n(cfg.locale).components.recentNotes.title}

    +
      + {pages.slice(0, opts.limit).map((page) => { + const title = page.frontmatter?.title ?? i18n(cfg.locale).propertyDefaults.title + const tags = page.frontmatter?.tags ?? [] + + return ( +
    • +
      + + {page.dates && ( +

      + +

      + )} + +
      +
    • + ) + })} +
    + {opts.linkToMore && remaining > 0 && ( +

    + + {i18n(cfg.locale).components.recentNotes.seeRemainingMore({ remaining })} + +

    + )} +
    + ) + } + + RecentNotes.css = style + return RecentNotes +}) satisfies QuartzComponentConstructor diff --git a/quartz/components/Search.tsx b/quartz/components/Search.tsx new file mode 100644 index 0000000..01e5a35 --- /dev/null +++ b/quartz/components/Search.tsx @@ -0,0 +1,61 @@ +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" +import style from "./styles/search.scss" +// @ts-ignore +import script from "./scripts/search.inline" +import { classNames } from "../util/lang" +import { i18n } from "../i18n" + +export interface SearchOptions { + enablePreview: boolean +} + +const defaultOptions: SearchOptions = { + enablePreview: true, +} + +export default ((userOpts?: Partial) => { + const Search: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => { + const opts = { ...defaultOptions, ...userOpts } + const searchPlaceholder = i18n(cfg.locale).components.search.searchBarPlaceholder + return ( +
    +
    +

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

    +
    + + Search + Search + + + + + +
    +
    +
    + +
    +
    +
    +
    + ) + } + + Search.afterDOMLoaded = script + Search.css = style + + return Search +}) satisfies QuartzComponentConstructor diff --git a/quartz/components/Spacer.tsx b/quartz/components/Spacer.tsx new file mode 100644 index 0000000..5288752 --- /dev/null +++ b/quartz/components/Spacer.tsx @@ -0,0 +1,8 @@ +import { QuartzComponentConstructor, QuartzComponentProps } from "./types" +import { classNames } from "../util/lang" + +function Spacer({ displayClass }: QuartzComponentProps) { + return
    +} + +export default (() => Spacer) satisfies QuartzComponentConstructor diff --git a/quartz/components/TableOfContents.tsx b/quartz/components/TableOfContents.tsx new file mode 100644 index 0000000..77ff0eb --- /dev/null +++ b/quartz/components/TableOfContents.tsx @@ -0,0 +1,89 @@ +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" +import legacyStyle from "./styles/legacyToc.scss" +import modernStyle from "./styles/toc.scss" +import { classNames } from "../util/lang" + +// @ts-ignore +import script from "./scripts/toc.inline" +import { i18n } from "../i18n" + +interface Options { + layout: "modern" | "legacy" +} + +const defaultOptions: Options = { + layout: "modern", +} + +const TableOfContents: QuartzComponent = ({ + fileData, + displayClass, + cfg, +}: QuartzComponentProps) => { + if (!fileData.toc) { + return null + } + + return ( +
    + +
    + +
    +
    + ) +} +TableOfContents.css = modernStyle +TableOfContents.afterDOMLoaded = script + +const LegacyTableOfContents: QuartzComponent = ({ fileData, cfg }: QuartzComponentProps) => { + if (!fileData.toc) { + return null + } + return ( +
    + +

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

    +
    + +
    + ) +} +LegacyTableOfContents.css = legacyStyle + +export default ((opts?: Partial) => { + const layout = opts?.layout ?? defaultOptions.layout + return layout === "modern" ? TableOfContents : LegacyTableOfContents +}) satisfies QuartzComponentConstructor diff --git a/quartz/components/TagList.tsx b/quartz/components/TagList.tsx new file mode 100644 index 0000000..ba48098 --- /dev/null +++ b/quartz/components/TagList.tsx @@ -0,0 +1,58 @@ +import { pathToRoot, slugTag } from "../util/path" +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" +import { classNames } from "../util/lang" + +const TagList: QuartzComponent = ({ fileData, displayClass }: QuartzComponentProps) => { + const tags = fileData.frontmatter?.tags + const baseDir = pathToRoot(fileData.slug!) + if (tags && tags.length > 0) { + return ( +
      + {tags.map((tag) => { + const linkDest = baseDir + `/tags/${slugTag(tag)}` + return ( +
    • + + {tag} + +
    • + ) + })} +
    + ) + } else { + return null + } +} + +TagList.css = ` +.tags { + list-style: none; + display: flex; + padding-left: 0; + gap: 0.4rem; + margin: 1rem 0; + flex-wrap: wrap; + justify-self: end; +} + +.section-li > .section > .tags { + justify-content: flex-end; +} + +.tags > li { + display: inline-block; + white-space: nowrap; + margin: 0; + overflow-wrap: normal; +} + +a.internal.tag-link { + border-radius: 8px; + background-color: var(--highlight); + padding: 0.2rem 0.4rem; + margin: 0 0.1rem; +} +` + +export default (() => TagList) satisfies QuartzComponentConstructor diff --git a/quartz/components/index.ts b/quartz/components/index.ts new file mode 100644 index 0000000..b3db76b --- /dev/null +++ b/quartz/components/index.ts @@ -0,0 +1,45 @@ +import Content from "./pages/Content" +import TagContent from "./pages/TagContent" +import FolderContent from "./pages/FolderContent" +import NotFound from "./pages/404" +import ArticleTitle from "./ArticleTitle" +import Darkmode from "./Darkmode" +import Head from "./Head" +import PageTitle from "./PageTitle" +import ContentMeta from "./ContentMeta" +import Spacer from "./Spacer" +import TableOfContents from "./TableOfContents" +import Explorer from "./Explorer" +import TagList from "./TagList" +import Graph from "./Graph" +import Backlinks from "./Backlinks" +import Search from "./Search" +import Footer from "./Footer" +import DesktopOnly from "./DesktopOnly" +import MobileOnly from "./MobileOnly" +import RecentNotes from "./RecentNotes" +import Breadcrumbs from "./Breadcrumbs" + +export { + ArticleTitle, + Content, + TagContent, + FolderContent, + Darkmode, + Head, + PageTitle, + ContentMeta, + Spacer, + TableOfContents, + Explorer, + TagList, + Graph, + Backlinks, + Search, + Footer, + DesktopOnly, + MobileOnly, + RecentNotes, + NotFound, + Breadcrumbs, +} diff --git a/quartz/components/pages/404.tsx b/quartz/components/pages/404.tsx new file mode 100644 index 0000000..4ef1b91 --- /dev/null +++ b/quartz/components/pages/404.tsx @@ -0,0 +1,13 @@ +import { i18n } from "../../i18n" +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../types" + +const NotFound: QuartzComponent = ({ cfg }: QuartzComponentProps) => { + return ( +
    +

    404

    +

    {i18n(cfg.locale).pages.error.notFound}

    +
    + ) +} + +export default (() => NotFound) satisfies QuartzComponentConstructor diff --git a/quartz/components/pages/Content.tsx b/quartz/components/pages/Content.tsx new file mode 100644 index 0000000..8222d78 --- /dev/null +++ b/quartz/components/pages/Content.tsx @@ -0,0 +1,11 @@ +import { htmlToJsx } from "../../util/jsx" +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../types" + +const Content: QuartzComponent = ({ fileData, tree }: QuartzComponentProps) => { + const content = htmlToJsx(fileData.filePath!, tree) + const classes: string[] = fileData.frontmatter?.cssclasses ?? [] + const classString = ["popover-hint", ...classes].join(" ") + return
    {content}
    +} + +export default (() => Content) satisfies QuartzComponentConstructor diff --git a/quartz/components/pages/FolderContent.tsx b/quartz/components/pages/FolderContent.tsx new file mode 100644 index 0000000..a13f135 --- /dev/null +++ b/quartz/components/pages/FolderContent.tsx @@ -0,0 +1,69 @@ +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../types" +import path from "path" + +import style from "../styles/listPage.scss" +import { PageList } from "../PageList" +import { stripSlashes, simplifySlug } from "../../util/path" +import { Root } from "hast" +import { htmlToJsx } from "../../util/jsx" +import { i18n } from "../../i18n" + +interface FolderContentOptions { + /** + * Whether to display number of folders + */ + showFolderCount: boolean +} + +const defaultOptions: FolderContentOptions = { + showFolderCount: true, +} + +export default ((opts?: Partial) => { + const options: FolderContentOptions = { ...defaultOptions, ...opts } + + const FolderContent: QuartzComponent = (props: QuartzComponentProps) => { + const { tree, fileData, allFiles, cfg } = props + const folderSlug = stripSlashes(simplifySlug(fileData.slug!)) + const allPagesInFolder = allFiles.filter((file) => { + const fileSlug = stripSlashes(simplifySlug(file.slug!)) + const prefixed = fileSlug.startsWith(folderSlug) && fileSlug !== folderSlug + const folderParts = folderSlug.split(path.posix.sep) + const fileParts = fileSlug.split(path.posix.sep) + const isDirectChild = fileParts.length === folderParts.length + 1 + return prefixed && isDirectChild + }) + const cssClasses: string[] = fileData.frontmatter?.cssclasses ?? [] + const classes = ["popover-hint", ...cssClasses].join(" ") + const listProps = { + ...props, + allFiles: allPagesInFolder, + } + + const content = + (tree as Root).children.length === 0 + ? fileData.description + : htmlToJsx(fileData.filePath!, tree) + + return ( +
    +
    {content}
    +
    + {options.showFolderCount && ( +

    + {i18n(cfg.locale).pages.folderContent.itemsUnderFolder({ + count: allPagesInFolder.length, + })} +

    + )} +
    + +
    +
    +
    + ) + } + + FolderContent.css = style + PageList.css + return FolderContent +}) satisfies QuartzComponentConstructor diff --git a/quartz/components/pages/TagContent.tsx b/quartz/components/pages/TagContent.tsx new file mode 100644 index 0000000..9e04359 --- /dev/null +++ b/quartz/components/pages/TagContent.tsx @@ -0,0 +1,113 @@ +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../types" +import style from "../styles/listPage.scss" +import { PageList } from "../PageList" +import { FullSlug, getAllSegmentPrefixes, simplifySlug } from "../../util/path" +import { QuartzPluginData } from "../../plugins/vfile" +import { Root } from "hast" +import { htmlToJsx } from "../../util/jsx" +import { i18n } from "../../i18n" + +const numPages = 10 +const TagContent: QuartzComponent = (props: QuartzComponentProps) => { + const { tree, fileData, allFiles, cfg } = props + const slug = fileData.slug + + if (!(slug?.startsWith("tags/") || slug === "tags")) { + throw new Error(`Component "TagContent" tried to render a non-tag page: ${slug}`) + } + + const tag = simplifySlug(slug.slice("tags/".length) as FullSlug) + const allPagesWithTag = (tag: string) => + allFiles.filter((file) => + (file.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes).includes(tag), + ) + + const content = + (tree as Root).children.length === 0 + ? fileData.description + : htmlToJsx(fileData.filePath!, tree) + const cssClasses: string[] = fileData.frontmatter?.cssclasses ?? [] + const classes = ["popover-hint", ...cssClasses].join(" ") + if (tag === "/") { + const tags = [ + ...new Set( + allFiles.flatMap((data) => data.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes), + ), + ].sort((a, b) => a.localeCompare(b)) + const tagItemMap: Map = new Map() + for (const tag of tags) { + tagItemMap.set(tag, allPagesWithTag(tag)) + } + return ( +
    +
    +

    {content}

    +
    +

    {i18n(cfg.locale).pages.tagContent.totalTags({ count: tags.length })}

    +
    + {tags.map((tag) => { + const pages = tagItemMap.get(tag)! + const listProps = { + ...props, + allFiles: pages, + } + + const contentPage = allFiles.filter((file) => file.slug === `tags/${tag}`).at(0) + + const root = contentPage?.htmlAst + const content = + !root || root?.children.length === 0 + ? contentPage?.description + : htmlToJsx(contentPage.filePath!, root) + + return ( +
    +

    + + {tag} + +

    + {content &&

    {content}

    } +
    +

    + {i18n(cfg.locale).pages.tagContent.itemsUnderTag({ count: pages.length })} + {pages.length > numPages && ( + <> + {" "} + + {i18n(cfg.locale).pages.tagContent.showingFirst({ count: numPages })} + + + )} +

    + +
    +
    + ) + })} +
    +
    + ) + } else { + const pages = allPagesWithTag(tag) + const listProps = { + ...props, + allFiles: pages, + } + + return ( +
    +
    {content}
    +
    +

    {i18n(cfg.locale).pages.tagContent.itemsUnderTag({ count: pages.length })}

    +
    + +
    +
    +
    + ) + } +} + +TagContent.css = style + PageList.css +export default (() => TagContent) satisfies QuartzComponentConstructor diff --git a/quartz/components/renderPage.tsx b/quartz/components/renderPage.tsx new file mode 100644 index 0000000..251a53f --- /dev/null +++ b/quartz/components/renderPage.tsx @@ -0,0 +1,248 @@ +import { render } from "preact-render-to-string" +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 { visit } from "unist-util-visit" +import { Root, Element, ElementContent } from "hast" +import { GlobalConfiguration } from "../cfg" +import { i18n } from "../i18n" + +interface RenderComponents { + head: QuartzComponent + header: QuartzComponent[] + beforeBody: QuartzComponent[] + pageBody: QuartzComponent + left: QuartzComponent[] + right: QuartzComponent[] + footer: QuartzComponent +} + +const headerRegex = new RegExp(/h[1-6]/) +export function pageResources( + baseDir: FullSlug | RelativeURL, + staticResources: StaticResources, +): StaticResources { + const contentIndexPath = joinSegments(baseDir, "static/contentIndex.json") + const contentIndexScript = `const fetchData = fetch("${contentIndexPath}").then(data => data.json())` + + return { + css: [joinSegments(baseDir, "index.css"), ...staticResources.css], + js: [ + { + src: joinSegments(baseDir, "prescript.js"), + loadTime: "beforeDOMReady", + contentType: "external", + }, + { + loadTime: "beforeDOMReady", + contentType: "inline", + spaPreserve: true, + script: contentIndexScript, + }, + ...staticResources.js, + { + src: joinSegments(baseDir, "postscript.js"), + loadTime: "afterDOMReady", + moduleType: "module", + contentType: "external", + }, + ], + } +} + +export function renderPage( + cfg: GlobalConfiguration, + slug: FullSlug, + componentData: QuartzComponentProps, + components: RenderComponents, + pageResources: StaticResources, +): string { + // make a deep copy of the tree so we don't remove the transclusion references + // for the file cached in contentMap in build.ts + const root = clone(componentData.tree) as Root + + // process transcludes in componentData + visit(root, "element", (node, _index, _parent) => { + if (node.tagName === "blockquote") { + const classNames = (node.properties?.className ?? []) as string[] + if (classNames.includes("transclude")) { + const inner = node.children[0] as Element + const transcludeTarget = inner.properties["data-slug"] as FullSlug + const page = componentData.allFiles.find((f) => f.slug === transcludeTarget) + if (!page) { + return + } + + let blockRef = node.properties.dataBlock as string | undefined + if (blockRef?.startsWith("#^")) { + // block transclude + blockRef = blockRef.slice("#^".length) + let blockNode = page.blocks?.[blockRef] + if (blockNode) { + if (blockNode.tagName === "li") { + blockNode = { + type: "element", + tagName: "ul", + properties: {}, + children: [blockNode], + } + } + + node.children = [ + normalizeHastElement(blockNode, slug, transcludeTarget), + { + type: "element", + tagName: "a", + properties: { href: inner.properties?.href, class: ["internal", "transclude-src"] }, + children: [ + { type: "text", value: i18n(cfg.locale).components.transcludes.linkToOriginal }, + ], + }, + ] + } + } else if (blockRef?.startsWith("#") && page.htmlAst) { + // header transclude + blockRef = blockRef.slice(1) + let startIdx = undefined + let startDepth = undefined + let endIdx = undefined + for (const [i, el] of page.htmlAst.children.entries()) { + // skip non-headers + if (!(el.type === "element" && el.tagName.match(headerRegex))) continue + const depth = Number(el.tagName.substring(1)) + + // lookin for our blockref + if (startIdx === undefined || startDepth === undefined) { + // skip until we find the blockref that matches + if (el.properties?.id === blockRef) { + startIdx = i + startDepth = depth + } + } else if (depth <= startDepth) { + // looking for new header that is same level or higher + endIdx = i + break + } + } + + if (startIdx === undefined) { + return + } + + node.children = [ + ...(page.htmlAst.children.slice(startIdx, endIdx) as ElementContent[]).map((child) => + normalizeHastElement(child as Element, slug, transcludeTarget), + ), + { + type: "element", + tagName: "a", + properties: { href: inner.properties?.href, class: ["internal", "transclude-src"] }, + children: [ + { type: "text", value: i18n(cfg.locale).components.transcludes.linkToOriginal }, + ], + }, + ] + } else if (page.htmlAst) { + // page transclude + node.children = [ + { + type: "element", + tagName: "h1", + properties: {}, + children: [ + { + type: "text", + value: + page.frontmatter?.title ?? + i18n(cfg.locale).components.transcludes.transcludeOf({ + targetSlug: page.slug!, + }), + }, + ], + }, + ...(page.htmlAst.children as ElementContent[]).map((child) => + normalizeHastElement(child as Element, slug, transcludeTarget), + ), + { + type: "element", + tagName: "a", + properties: { href: inner.properties?.href, class: ["internal", "transclude-src"] }, + children: [ + { type: "text", value: i18n(cfg.locale).components.transcludes.linkToOriginal }, + ], + }, + ] + } + } + } + }) + + // set componentData.tree to the edited html that has transclusions rendered + componentData.tree = root + + const { + head: Head, + header, + beforeBody, + pageBody: Content, + left, + right, + footer: Footer, + } = components + const Header = HeaderConstructor() + const Body = BodyConstructor() + + const LeftComponent = ( + + ) + + const RightComponent = ( + + ) + + const lang = componentData.fileData.frontmatter?.lang ?? cfg.locale?.split("-")[0] ?? "en" + const doc = ( + + + +
    + + {LeftComponent} +
    + + +
    + {RightComponent} + +
    +
    + + {pageResources.js + .filter((resource) => resource.loadTime === "afterDOMReady") + .map((res) => JSResourceToScriptElement(res))} + + ) + + return "\n" + render(doc) +} diff --git a/quartz/components/scripts/callout.inline.ts b/quartz/components/scripts/callout.inline.ts new file mode 100644 index 0000000..8f63df3 --- /dev/null +++ b/quartz/components/scripts/callout.inline.ts @@ -0,0 +1,44 @@ +function toggleCallout(this: HTMLElement) { + const outerBlock = this.parentElement! + outerBlock.classList.toggle("is-collapsed") + const collapsed = outerBlock.classList.contains("is-collapsed") + const height = collapsed ? this.scrollHeight : outerBlock.scrollHeight + outerBlock.style.maxHeight = height + "px" + + // walk and adjust height of all parents + let current = outerBlock + let parent = outerBlock.parentElement + while (parent) { + if (!parent.classList.contains("callout")) { + return + } + + const collapsed = parent.classList.contains("is-collapsed") + const height = collapsed ? parent.scrollHeight : parent.scrollHeight + current.scrollHeight + parent.style.maxHeight = height + "px" + + current = parent + parent = parent.parentElement + } +} + +function setupCallout() { + const collapsible = document.getElementsByClassName( + `callout is-collapsible`, + ) as HTMLCollectionOf + for (const div of collapsible) { + const title = div.firstElementChild + + if (title) { + title.addEventListener("click", toggleCallout) + window.addCleanup(() => title.removeEventListener("click", toggleCallout)) + + const collapsed = div.classList.contains("is-collapsed") + const height = collapsed ? title.scrollHeight : div.scrollHeight + div.style.maxHeight = height + "px" + } + } +} + +document.addEventListener("nav", setupCallout) +window.addEventListener("resize", setupCallout) diff --git a/quartz/components/scripts/checkbox.inline.ts b/quartz/components/scripts/checkbox.inline.ts new file mode 100644 index 0000000..50ab042 --- /dev/null +++ b/quartz/components/scripts/checkbox.inline.ts @@ -0,0 +1,23 @@ +import { getFullSlug } from "../../util/path" + +const checkboxId = (index: number) => `${getFullSlug(window)}-checkbox-${index}` + +document.addEventListener("nav", () => { + const checkboxes = document.querySelectorAll( + "input.checkbox-toggle", + ) as NodeListOf + checkboxes.forEach((el, index) => { + const elId = checkboxId(index) + + const switchState = (e: Event) => { + const newCheckboxState = (e.target as HTMLInputElement)?.checked ? "true" : "false" + localStorage.setItem(elId, newCheckboxState) + } + + el.addEventListener("change", switchState) + window.addCleanup(() => el.removeEventListener("change", switchState)) + if (localStorage.getItem(elId) === "true") { + el.checked = true + } + }) +}) diff --git a/quartz/components/scripts/clipboard.inline.ts b/quartz/components/scripts/clipboard.inline.ts new file mode 100644 index 0000000..87182a1 --- /dev/null +++ b/quartz/components/scripts/clipboard.inline.ts @@ -0,0 +1,35 @@ +const svgCopy = + '' +const svgCheck = + '' + +document.addEventListener("nav", () => { + const els = document.getElementsByTagName("pre") + for (let i = 0; i < els.length; i++) { + const codeBlock = els[i].getElementsByTagName("code")[0] + if (codeBlock) { + const source = codeBlock.innerText.replace(/\n\n/g, "\n") + const button = document.createElement("button") + button.className = "clipboard-button" + button.type = "button" + button.innerHTML = svgCopy + button.ariaLabel = "Copy source" + function onClick() { + navigator.clipboard.writeText(source).then( + () => { + button.blur() + button.innerHTML = svgCheck + setTimeout(() => { + button.innerHTML = svgCopy + button.style.borderColor = "" + }, 2000) + }, + (error) => console.error(error), + ) + } + button.addEventListener("click", onClick) + window.addCleanup(() => button.removeEventListener("click", onClick)) + els[i].prepend(button) + } + } +}) diff --git a/quartz/components/scripts/darkmode.inline.ts b/quartz/components/scripts/darkmode.inline.ts new file mode 100644 index 0000000..48e0aa1 --- /dev/null +++ b/quartz/components/scripts/darkmode.inline.ts @@ -0,0 +1,40 @@ +const userPref = window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark" +const currentTheme = localStorage.getItem("theme") ?? userPref +document.documentElement.setAttribute("saved-theme", currentTheme) + +const emitThemeChangeEvent = (theme: "light" | "dark") => { + const event: CustomEventMap["themechange"] = new CustomEvent("themechange", { + detail: { theme }, + }) + document.dispatchEvent(event) +} + +document.addEventListener("nav", () => { + const switchTheme = (e: Event) => { + const newTheme = (e.target as HTMLInputElement)?.checked ? "dark" : "light" + document.documentElement.setAttribute("saved-theme", newTheme) + localStorage.setItem("theme", newTheme) + emitThemeChangeEvent(newTheme) + } + + const themeChange = (e: MediaQueryListEvent) => { + const newTheme = e.matches ? "dark" : "light" + document.documentElement.setAttribute("saved-theme", newTheme) + localStorage.setItem("theme", newTheme) + toggleSwitch.checked = e.matches + emitThemeChangeEvent(newTheme) + } + + // Darkmode toggle + const toggleSwitch = document.querySelector("#darkmode-toggle") as HTMLInputElement + toggleSwitch.addEventListener("change", switchTheme) + window.addCleanup(() => toggleSwitch.removeEventListener("change", switchTheme)) + if (currentTheme === "dark") { + toggleSwitch.checked = true + } + + // Listen for changes in prefers-color-scheme + const colorSchemeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)") + colorSchemeMediaQuery.addEventListener("change", themeChange) + window.addCleanup(() => colorSchemeMediaQuery.removeEventListener("change", themeChange)) +}) diff --git a/quartz/components/scripts/explorer.inline.ts b/quartz/components/scripts/explorer.inline.ts new file mode 100644 index 0000000..3eb25ea --- /dev/null +++ b/quartz/components/scripts/explorer.inline.ts @@ -0,0 +1,132 @@ +import { FolderState } from "../ExplorerNode" + +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") + } + } +}) + +function toggleExplorer(this: HTMLElement) { + this.classList.toggle("collapsed") + const content = this.nextElementSibling as MaybeHTMLElement + if (!content) return + + content.classList.toggle("collapsed") + content.style.maxHeight = content.style.maxHeight === "0px" ? content.scrollHeight + "px" : "0px" +} + +function toggleFolder(evt: MouseEvent) { + evt.stopPropagation() + const target = evt.target as MaybeHTMLElement + if (!target) return + + const isSvg = target.nodeName === "svg" + const childFolderContainer = ( + isSvg + ? target.parentElement?.nextSibling + : target.parentElement?.parentElement?.nextElementSibling + ) as MaybeHTMLElement + const currentFolderParent = ( + isSvg ? target.nextElementSibling : target.parentElement + ) as MaybeHTMLElement + if (!(childFolderContainer && currentFolderParent)) return + + childFolderContainer.classList.toggle("open") + const isCollapsed = childFolderContainer.classList.contains("open") + setFolderState(childFolderContainer, !isCollapsed) + const fullFolderPath = currentFolderParent.dataset.folderpath as string + toggleCollapsedByPath(currentExplorerState, fullFolderPath) + const stringifiedFileTree = JSON.stringify(currentExplorerState) + localStorage.setItem("fileTree", stringifiedFileTree) +} + +function setupExplorer() { + const explorer = document.getElementById("explorer") + if (!explorer) return + + if (explorer.dataset.behavior === "collapse") { + for (const item of document.getElementsByClassName( + "folder-button", + ) as HTMLCollectionOf) { + item.addEventListener("click", toggleFolder) + window.addCleanup(() => item.removeEventListener("click", toggleFolder)) + } + } + + explorer.addEventListener("click", toggleExplorer) + window.addCleanup(() => explorer.removeEventListener("click", toggleExplorer)) + + // Set up click handlers for each folder (click handler on folder "icon") + for (const item of document.getElementsByClassName( + "folder-icon", + ) as HTMLCollectionOf) { + item.addEventListener("click", toggleFolder) + window.addCleanup(() => item.removeEventListener("click", toggleFolder)) + } + + // Get folder state from local storage + const storageTree = localStorage.getItem("fileTree") + const useSavedFolderState = explorer?.dataset.savestate === "true" + const oldExplorerState: FolderState[] = + storageTree && useSavedFolderState ? JSON.parse(storageTree) : [] + const oldIndex = new Map(oldExplorerState.map((entry) => [entry.path, entry.collapsed])) + const newExplorerState: FolderState[] = explorer.dataset.tree + ? JSON.parse(explorer.dataset.tree) + : [] + currentExplorerState = [] + for (const { path, collapsed } of newExplorerState) { + currentExplorerState.push({ path, collapsed: oldIndex.get(path) ?? collapsed }) + } + + currentExplorerState.map((folderState) => { + const folderLi = document.querySelector( + `[data-folderpath='${folderState.path}']`, + ) as MaybeHTMLElement + const folderUl = folderLi?.parentElement?.nextElementSibling as MaybeHTMLElement + if (folderUl) { + setFolderState(folderUl, folderState.collapsed) + } + }) +} + +window.addEventListener("resize", setupExplorer) +document.addEventListener("nav", () => { + setupExplorer() + observer.disconnect() + + // select pseudo element at end of list + const lastItem = document.getElementById("explorer-end") + if (lastItem) { + observer.observe(lastItem) + } +}) + +/** + * Toggles the state of a given folder + * @param folderElement
    Element of folder (parent) + * @param collapsed if folder should be set to collapsed or not + */ +function setFolderState(folderElement: HTMLElement, collapsed: boolean) { + return collapsed ? folderElement.classList.remove("open") : folderElement.classList.add("open") +} + +/** + * Toggles visibility of a folder + * @param array array of FolderState (`fileTree`, either get from local storage or data attribute) + * @param path path to folder (e.g. 'advanced/more/more2') + */ +function toggleCollapsedByPath(array: FolderState[], path: string) { + const entry = array.find((item) => item.path === path) + if (entry) { + entry.collapsed = !entry.collapsed + } +} diff --git a/quartz/components/scripts/graph.inline.ts b/quartz/components/scripts/graph.inline.ts new file mode 100644 index 0000000..1c9bb5d --- /dev/null +++ b/quartz/components/scripts/graph.inline.ts @@ -0,0 +1,345 @@ +import type { ContentDetails, ContentIndex } from "../../plugins/emitters/contentIndex" +import * as d3 from "d3" +import { registerEscapeHandler, removeAllChildren } from "./util" +import { FullSlug, SimpleSlug, getFullSlug, resolveRelative, simplifySlug } from "../../util/path" + +type NodeData = { + id: SimpleSlug + text: string + tags: string[] +} & d3.SimulationNodeDatum + +type LinkData = { + source: SimpleSlug + target: SimpleSlug +} + +const localStorageKey = "graph-visited" +function getVisited(): Set { + return new Set(JSON.parse(localStorage.getItem(localStorageKey) ?? "[]")) +} + +function addToVisited(slug: SimpleSlug) { + const visited = getVisited() + visited.add(slug) + localStorage.setItem(localStorageKey, JSON.stringify([...visited])) +} + +async function renderGraph(container: string, fullSlug: FullSlug) { + const slug = simplifySlug(fullSlug) + const visited = getVisited() + const graph = document.getElementById(container) + if (!graph) return + removeAllChildren(graph) + + let { + drag: enableDrag, + zoom: enableZoom, + depth, + scale, + repelForce, + centerForce, + linkDistance, + fontSize, + opacityScale, + removeTags, + showTags, + focusOnHover, + } = JSON.parse(graph.dataset["cfg"]!) + + const data: Map = new Map( + Object.entries(await fetchData).map(([k, v]) => [ + simplifySlug(k as FullSlug), + v, + ]), + ) + const links: LinkData[] = [] + const tags: SimpleSlug[] = [] + + const validLinks = new Set(data.keys()) + for (const [source, details] of data.entries()) { + const outgoing = details.links ?? [] + + for (const dest of outgoing) { + if (validLinks.has(dest)) { + links.push({ source: source, target: dest }) + } + } + + if (showTags) { + const localTags = details.tags + .filter((tag) => !removeTags.includes(tag)) + .map((tag) => simplifySlug(("tags/" + tag) as FullSlug)) + + tags.push(...localTags.filter((tag) => !tags.includes(tag))) + + for (const tag of localTags) { + links.push({ source: source, target: tag }) + } + } + } + + const neighbourhood = new Set() + const wl: (SimpleSlug | "__SENTINEL")[] = [slug, "__SENTINEL"] + if (depth >= 0) { + while (depth >= 0 && wl.length > 0) { + // compute neighbours + const cur = wl.shift()! + if (cur === "__SENTINEL") { + depth-- + wl.push("__SENTINEL") + } else { + neighbourhood.add(cur) + const outgoing = links.filter((l) => l.source === cur) + const incoming = links.filter((l) => l.target === cur) + wl.push(...outgoing.map((l) => l.target), ...incoming.map((l) => l.source)) + } + } + } else { + validLinks.forEach((id) => neighbourhood.add(id)) + if (showTags) tags.forEach((tag) => neighbourhood.add(tag)) + } + + const graphData: { nodes: NodeData[]; links: LinkData[] } = { + nodes: [...neighbourhood].map((url) => { + const text = url.startsWith("tags/") ? "#" + url.substring(5) : data.get(url)?.title ?? url + return { + id: url, + text: text, + tags: data.get(url)?.tags ?? [], + } + }), + links: links.filter((l) => neighbourhood.has(l.source) && neighbourhood.has(l.target)), + } + + const simulation: d3.Simulation = d3 + .forceSimulation(graphData.nodes) + .force("charge", d3.forceManyBody().strength(-100 * repelForce)) + .force( + "link", + d3 + .forceLink(graphData.links) + .id((d: any) => d.id) + .distance(linkDistance), + ) + .force("center", d3.forceCenter().strength(centerForce)) + + const height = Math.max(graph.offsetHeight, 250) + const width = graph.offsetWidth + + const svg = d3 + .select("#" + container) + .append("svg") + .attr("width", width) + .attr("height", height) + .attr("viewBox", [-width / 2 / scale, -height / 2 / scale, width / scale, height / scale]) + + // draw links between nodes + const link = svg + .append("g") + .selectAll("line") + .data(graphData.links) + .join("line") + .attr("class", "link") + .attr("stroke", "var(--lightgray)") + .attr("stroke-width", 1) + + // svg groups + const graphNode = svg.append("g").selectAll("g").data(graphData.nodes).enter().append("g") + + // calculate color + const color = (d: NodeData) => { + const isCurrent = d.id === slug + if (isCurrent) { + return "var(--secondary)" + } else if (visited.has(d.id) || d.id.startsWith("tags/")) { + return "var(--tertiary)" + } else { + return "var(--gray)" + } + } + + const drag = (simulation: d3.Simulation) => { + function dragstarted(event: any, d: NodeData) { + if (!event.active) simulation.alphaTarget(1).restart() + d.fx = d.x + d.fy = d.y + } + + function dragged(event: any, d: NodeData) { + d.fx = event.x + d.fy = event.y + } + + function dragended(event: any, d: NodeData) { + if (!event.active) simulation.alphaTarget(0) + d.fx = null + d.fy = null + } + + const noop = () => {} + return d3 + .drag() + .on("start", enableDrag ? dragstarted : noop) + .on("drag", enableDrag ? dragged : noop) + .on("end", enableDrag ? dragended : noop) + } + + function nodeRadius(d: NodeData) { + const numLinks = links.filter((l: any) => l.source.id === d.id || l.target.id === d.id).length + return 2 + Math.sqrt(numLinks) + } + + let connectedNodes: SimpleSlug[] = [] + + // draw individual nodes + const node = graphNode + .append("circle") + .attr("class", "node") + .attr("id", (d) => d.id) + .attr("r", nodeRadius) + .attr("fill", color) + .style("cursor", "pointer") + .on("click", (_, d) => { + const targ = resolveRelative(fullSlug, d.id) + window.spaNavigate(new URL(targ, window.location.toString())) + }) + .on("mouseover", function (_, d) { + const currentId = d.id + const linkNodes = d3 + .selectAll(".link") + .filter((d: any) => d.source.id === currentId || d.target.id === currentId) + + if (focusOnHover) { + // fade out non-neighbour nodes + connectedNodes = linkNodes.data().flatMap((d: any) => [d.source.id, d.target.id]) + + d3.selectAll(".link") + .transition() + .duration(200) + .style("opacity", 0.2) + d3.selectAll(".node") + .filter((d) => !connectedNodes.includes(d.id)) + .transition() + .duration(200) + .style("opacity", 0.2) + } + + // highlight links + linkNodes.transition().duration(200).attr("stroke", "var(--gray)").attr("stroke-width", 1) + + const bigFont = fontSize * 1.5 + + // show text for self + const parent = this.parentNode as HTMLElement + d3.select(parent) + .raise() + .select("text") + .transition() + .duration(200) + .attr("opacityOld", d3.select(parent).select("text").style("opacity")) + .style("opacity", 1) + .style("font-size", bigFont + "em") + }) + .on("mouseleave", function (_, d) { + if (focusOnHover) { + d3.selectAll(".link").transition().duration(200).style("opacity", 1) + d3.selectAll(".node").transition().duration(200).style("opacity", 1) + } + const currentId = d.id + const linkNodes = d3 + .selectAll(".link") + .filter((d: any) => d.source.id === currentId || d.target.id === currentId) + + linkNodes.transition().duration(200).attr("stroke", "var(--lightgray)") + + const parent = this.parentNode as HTMLElement + d3.select(parent) + .select("text") + .transition() + .duration(200) + .style("opacity", d3.select(parent).select("text").attr("opacityOld")) + .style("font-size", fontSize + "em") + }) + // @ts-ignore + .call(drag(simulation)) + + // draw labels + const labels = graphNode + .append("text") + .attr("dx", 0) + .attr("dy", (d) => -nodeRadius(d) + "px") + .attr("text-anchor", "middle") + .text((d) => d.text) + .style("opacity", (opacityScale - 1) / 3.75) + .style("pointer-events", "none") + .style("font-size", fontSize + "em") + .raise() + // @ts-ignore + .call(drag(simulation)) + + // set panning + if (enableZoom) { + svg.call( + d3 + .zoom() + .extent([ + [0, 0], + [width, height], + ]) + .scaleExtent([0.25, 4]) + .on("zoom", ({ transform }) => { + link.attr("transform", transform) + node.attr("transform", transform) + const scale = transform.k * opacityScale + const scaledOpacity = Math.max((scale - 1) / 3.75, 0) + labels.attr("transform", transform).style("opacity", scaledOpacity) + }), + ) + } + + // progress the simulation + simulation.on("tick", () => { + link + .attr("x1", (d: any) => d.source.x) + .attr("y1", (d: any) => d.source.y) + .attr("x2", (d: any) => d.target.x) + .attr("y2", (d: any) => d.target.y) + node.attr("cx", (d: any) => d.x).attr("cy", (d: any) => d.y) + labels.attr("x", (d: any) => d.x).attr("y", (d: any) => d.y) + }) +} + +function renderGlobalGraph() { + const slug = getFullSlug(window) + const container = document.getElementById("global-graph-outer") + const sidebar = container?.closest(".sidebar") as HTMLElement + container?.classList.add("active") + if (sidebar) { + sidebar.style.zIndex = "1" + } + + renderGraph("global-graph-container", slug) + + function hideGlobalGraph() { + container?.classList.remove("active") + const graph = document.getElementById("global-graph-container") + if (sidebar) { + sidebar.style.zIndex = "unset" + } + if (!graph) return + removeAllChildren(graph) + } + + registerEscapeHandler(container, hideGlobalGraph) +} + +document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { + const slug = e.detail.url + addToVisited(slug) + await renderGraph("graph-container", slug) + + const containerIcon = document.getElementById("global-graph-icon") + containerIcon?.addEventListener("click", renderGlobalGraph) + window.addCleanup(() => containerIcon?.removeEventListener("click", renderGlobalGraph)) +}) diff --git a/quartz/components/scripts/popover.inline.ts b/quartz/components/scripts/popover.inline.ts new file mode 100644 index 0000000..972d3c6 --- /dev/null +++ b/quartz/components/scripts/popover.inline.ts @@ -0,0 +1,108 @@ +import { computePosition, flip, inline, shift } from "@floating-ui/dom" +import { normalizeRelativeURLs } from "../../util/path" + +const p = new DOMParser() +async function mouseEnterHandler( + this: HTMLLinkElement, + { clientX, clientY }: { clientX: number; clientY: number }, +) { + const link = this + if (link.dataset.noPopover === "true") { + return + } + + async function setPosition(popoverElement: HTMLElement) { + const { x, y } = await computePosition(link, popoverElement, { + middleware: [inline({ x: clientX, y: clientY }), shift(), flip()], + }) + Object.assign(popoverElement.style, { + left: `${x}px`, + top: `${y}px`, + }) + } + + const hasAlreadyBeenFetched = () => + [...link.children].some((child) => child.classList.contains("popover")) + + // dont refetch if there's already a popover + if (hasAlreadyBeenFetched()) { + return setPosition(link.lastChild as HTMLElement) + } + + const thisUrl = new URL(document.location.href) + thisUrl.hash = "" + thisUrl.search = "" + const targetUrl = new URL(link.href) + const hash = targetUrl.hash + targetUrl.hash = "" + targetUrl.search = "" + + const response = await fetch(`${targetUrl}`).catch((err) => { + console.error(err) + }) + + // bailout if another popover exists + if (hasAlreadyBeenFetched()) { + return + } + + if (!response) return + const [contentType] = response.headers.get("Content-Type")!.split(";") + const [contentTypeCategory, typeInfo] = contentType.split("/") + + const popoverElement = document.createElement("div") + popoverElement.classList.add("popover") + const popoverInner = document.createElement("div") + popoverInner.classList.add("popover-inner") + popoverElement.appendChild(popoverInner) + + popoverInner.dataset.contentType = contentType ?? undefined + + switch (contentTypeCategory) { + case "image": + const img = document.createElement("img") + img.src = targetUrl.toString() + img.alt = targetUrl.pathname + + popoverInner.appendChild(img) + break + case "application": + switch (typeInfo) { + case "pdf": + const pdf = document.createElement("iframe") + pdf.src = targetUrl.toString() + popoverInner.appendChild(pdf) + break + default: + break + } + break + default: + const contents = await response.text() + const html = p.parseFromString(contents, "text/html") + normalizeRelativeURLs(html, targetUrl) + const elts = [...html.getElementsByClassName("popover-hint")] + if (elts.length === 0) return + + elts.forEach((elt) => popoverInner.appendChild(elt)) + } + + setPosition(popoverElement) + link.appendChild(popoverElement) + + if (hash !== "") { + const heading = popoverInner.querySelector(hash) as HTMLElement | null + if (heading) { + // leave ~12px of buffer when scrolling to a heading + popoverInner.scroll({ top: heading.offsetTop - 12, behavior: "instant" }) + } + } +} + +document.addEventListener("nav", () => { + const links = [...document.getElementsByClassName("internal")] as HTMLLinkElement[] + for (const link of links) { + link.addEventListener("mouseenter", mouseEnterHandler) + window.addCleanup(() => link.removeEventListener("mouseenter", mouseEnterHandler)) + } +}) diff --git a/quartz/components/scripts/search.inline.ts b/quartz/components/scripts/search.inline.ts new file mode 100644 index 0000000..72be6b8 --- /dev/null +++ b/quartz/components/scripts/search.inline.ts @@ -0,0 +1,491 @@ +import FlexSearch from "flexsearch" +import { ContentDetails } from "../../plugins/emitters/contentIndex" +import { registerEscapeHandler, removeAllChildren } from "./util" +import { FullSlug, normalizeRelativeURLs, resolveRelative } from "../../util/path" + +interface Item { + id: number + slug: FullSlug + title: string + content: string + tags: string[] +} + +// Can be expanded with things like "term" in the future +type SearchType = "basic" | "tags" +let searchType: SearchType = "basic" +let currentSearchTerm: string = "" +const encoder = (str: string) => str.toLowerCase().split(/([^a-z]|[^\x00-\x7F])/) +let index = new FlexSearch.Document({ + charset: "latin:extra", + encode: encoder, + document: { + id: "id", + tag: "tags", + index: [ + { + field: "title", + tokenize: "forward", + }, + { + field: "content", + tokenize: "forward", + }, + { + field: "tags", + tokenize: "forward", + }, + ], + }, +}) + +const p = new DOMParser() +const fetchContentCache: Map = new Map() +const contextWindowWords = 30 +const numSearchResults = 8 +const numTagResults = 5 + +const tokenizeTerm = (term: string) => { + const tokens = term.split(/\s+/).filter((t) => t.trim() !== "") + const tokenLen = tokens.length + if (tokenLen > 1) { + for (let i = 1; i < tokenLen; i++) { + tokens.push(tokens.slice(0, i + 1).join(" ")) + } + } + + return tokens.sort((a, b) => b.length - a.length) // always highlight longest terms first +} + +function highlight(searchTerm: string, text: string, trim?: boolean) { + const tokenizedTerms = tokenizeTerm(searchTerm) + let tokenizedText = text.split(/\s+/).filter((t) => t !== "") + + let startIndex = 0 + let endIndex = tokenizedText.length - 1 + if (trim) { + const includesCheck = (tok: string) => + tokenizedTerms.some((term) => tok.toLowerCase().startsWith(term.toLowerCase())) + const occurrencesIndices = tokenizedText.map(includesCheck) + + let bestSum = 0 + let bestIndex = 0 + for (let i = 0; i < Math.max(tokenizedText.length - contextWindowWords, 0); i++) { + const window = occurrencesIndices.slice(i, i + contextWindowWords) + const windowSum = window.reduce((total, cur) => total + (cur ? 1 : 0), 0) + if (windowSum >= bestSum) { + bestSum = windowSum + bestIndex = i + } + } + + startIndex = Math.max(bestIndex - contextWindowWords, 0) + endIndex = Math.min(startIndex + 2 * contextWindowWords, tokenizedText.length - 1) + tokenizedText = tokenizedText.slice(startIndex, endIndex) + } + + const slice = tokenizedText + .map((tok) => { + // see if this tok is prefixed by any search terms + for (const searchTok of tokenizedTerms) { + if (tok.toLowerCase().includes(searchTok.toLowerCase())) { + const regex = new RegExp(searchTok.toLowerCase(), "gi") + return tok.replace(regex, `$&`) + } + } + return tok + }) + .join(" ") + + return `${startIndex === 0 ? "" : "..."}${slice}${ + endIndex === tokenizedText.length - 1 ? "" : "..." + }` +} + +function highlightHTML(searchTerm: string, el: HTMLElement) { + const p = new DOMParser() + const tokenizedTerms = tokenizeTerm(searchTerm) + const html = p.parseFromString(el.innerHTML, "text/html") + + const createHighlightSpan = (text: string) => { + const span = document.createElement("span") + span.className = "highlight" + span.textContent = text + return span + } + + const highlightTextNodes = (node: Node, term: string) => { + if (node.nodeType === Node.TEXT_NODE) { + const nodeText = node.nodeValue ?? "" + const regex = new RegExp(term.toLowerCase(), "gi") + const matches = nodeText.match(regex) + if (!matches || matches.length === 0) return + const spanContainer = document.createElement("span") + let lastIndex = 0 + for (const match of matches) { + const matchIndex = nodeText.indexOf(match, lastIndex) + spanContainer.appendChild(document.createTextNode(nodeText.slice(lastIndex, matchIndex))) + spanContainer.appendChild(createHighlightSpan(match)) + lastIndex = matchIndex + match.length + } + spanContainer.appendChild(document.createTextNode(nodeText.slice(lastIndex))) + node.parentNode?.replaceChild(spanContainer, node) + } else if (node.nodeType === Node.ELEMENT_NODE) { + if ((node as HTMLElement).classList.contains("highlight")) return + Array.from(node.childNodes).forEach((child) => highlightTextNodes(child, term)) + } + } + + for (const term of tokenizedTerms) { + highlightTextNodes(html.body, term) + } + + return html.body +} + +document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { + const currentSlug = e.detail.url + const data = await fetchData + const container = document.getElementById("search-container") + const sidebar = container?.closest(".sidebar") as HTMLElement + const searchIcon = document.getElementById("search-icon") + const searchBar = document.getElementById("search-bar") as HTMLInputElement | null + const searchLayout = document.getElementById("search-layout") + const idDataMap = Object.keys(data) as FullSlug[] + + const appendLayout = (el: HTMLElement) => { + if (searchLayout?.querySelector(`#${el.id}`) === null) { + searchLayout?.appendChild(el) + } + } + + const enablePreview = searchLayout?.dataset?.preview === "true" + let preview: HTMLDivElement | undefined = undefined + let previewInner: HTMLDivElement | undefined = undefined + const results = document.createElement("div") + results.id = "results-container" + appendLayout(results) + + if (enablePreview) { + preview = document.createElement("div") + preview.id = "preview-container" + appendLayout(preview) + } + + function hideSearch() { + container?.classList.remove("active") + if (searchBar) { + searchBar.value = "" // clear the input when we dismiss the search + } + if (sidebar) { + sidebar.style.zIndex = "unset" + } + if (results) { + removeAllChildren(results) + } + if (preview) { + removeAllChildren(preview) + } + if (searchLayout) { + searchLayout.classList.remove("display-results") + } + + searchType = "basic" // reset search type after closing + } + + function showSearch(searchTypeNew: SearchType) { + searchType = searchTypeNew + if (sidebar) { + sidebar.style.zIndex = "1" + } + container?.classList.add("active") + searchBar?.focus() + } + + let currentHover: HTMLInputElement | null = null + + async function shortcutHandler(e: HTMLElementEventMap["keydown"]) { + if (e.key === "k" && (e.ctrlKey || e.metaKey) && !e.shiftKey) { + e.preventDefault() + const searchBarOpen = container?.classList.contains("active") + searchBarOpen ? hideSearch() : showSearch("basic") + return + } else if (e.shiftKey && (e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") { + // Hotkey to open tag search + e.preventDefault() + const searchBarOpen = container?.classList.contains("active") + searchBarOpen ? hideSearch() : showSearch("tags") + + // add "#" prefix for tag search + if (searchBar) searchBar.value = "#" + return + } + + if (currentHover) { + currentHover.classList.remove("focus") + } + + // If search is active, then we will render the first result and display accordingly + if (!container?.classList.contains("active")) return + if (e.key === "Enter") { + // If result has focus, navigate to that one, otherwise pick first result + if (results?.contains(document.activeElement)) { + const active = document.activeElement as HTMLInputElement + if (active.classList.contains("no-match")) return + await displayPreview(active) + active.click() + } else { + const anchor = document.getElementsByClassName("result-card")[0] as HTMLInputElement | null + if (!anchor || anchor?.classList.contains("no-match")) return + await displayPreview(anchor) + anchor.click() + } + } else if (e.key === "ArrowUp" || (e.shiftKey && e.key === "Tab")) { + e.preventDefault() + if (results?.contains(document.activeElement)) { + // If an element in results-container already has focus, focus previous one + const currentResult = currentHover + ? currentHover + : (document.activeElement as HTMLInputElement | null) + const prevResult = currentResult?.previousElementSibling as HTMLInputElement | null + currentResult?.classList.remove("focus") + prevResult?.focus() + if (prevResult) currentHover = prevResult + await displayPreview(prevResult) + } + } else if (e.key === "ArrowDown" || e.key === "Tab") { + e.preventDefault() + // The results should already been focused, so we need to find the next one. + // The activeElement is the search bar, so we need to find the first result and focus it. + if (document.activeElement === searchBar || currentHover !== null) { + const firstResult = currentHover + ? currentHover + : (document.getElementsByClassName("result-card")[0] as HTMLInputElement | null) + const secondResult = firstResult?.nextElementSibling as HTMLInputElement | null + firstResult?.classList.remove("focus") + secondResult?.focus() + if (secondResult) currentHover = secondResult + await displayPreview(secondResult) + } + } + } + + const formatForDisplay = (term: string, id: number) => { + const slug = idDataMap[id] + return { + id, + slug, + title: searchType === "tags" ? data[slug].title : highlight(term, data[slug].title ?? ""), + content: highlight(term, data[slug].content ?? "", true), + tags: highlightTags(term.substring(1), data[slug].tags), + } + } + + function highlightTags(term: string, tags: string[]) { + if (!tags || searchType !== "tags") { + return [] + } + + return tags + .map((tag) => { + if (tag.toLowerCase().includes(term.toLowerCase())) { + return `
  • #${tag}

  • ` + } else { + return `
  • #${tag}

  • ` + } + }) + .slice(0, numTagResults) + } + + function resolveUrl(slug: FullSlug): URL { + return new URL(resolveRelative(currentSlug, slug), location.toString()) + } + + const resultToHTML = ({ slug, title, content, tags }: Item) => { + const htmlTags = tags.length > 0 ? `
      ${tags.join("")}
    ` : `` + const itemTile = document.createElement("a") + itemTile.classList.add("result-card") + itemTile.id = slug + itemTile.href = resolveUrl(slug).toString() + itemTile.innerHTML = `

    ${title}

    ${htmlTags}${ + enablePreview && window.innerWidth > 600 ? "" : `

    ${content}

    ` + }` + itemTile.addEventListener("click", (event) => { + if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return + hideSearch() + }) + + const handler = (event: MouseEvent) => { + if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return + hideSearch() + } + + async function onMouseEnter(ev: MouseEvent) { + if (!ev.target) return + const target = ev.target as HTMLInputElement + await displayPreview(target) + } + + itemTile.addEventListener("mouseenter", onMouseEnter) + window.addCleanup(() => itemTile.removeEventListener("mouseenter", onMouseEnter)) + itemTile.addEventListener("click", handler) + window.addCleanup(() => itemTile.removeEventListener("click", handler)) + + return itemTile + } + + async function displayResults(finalResults: Item[]) { + if (!results) return + + removeAllChildren(results) + if (finalResults.length === 0) { + results.innerHTML = ` +

    No results.

    +

    Try another search term?

    +
    ` + } else { + results.append(...finalResults.map(resultToHTML)) + } + + if (finalResults.length === 0 && preview) { + // no results, clear previous preview + removeAllChildren(preview) + } else { + // focus on first result, then also dispatch preview immediately + const firstChild = results.firstElementChild as HTMLElement + firstChild.classList.add("focus") + currentHover = firstChild as HTMLInputElement + await displayPreview(firstChild) + } + } + + async function fetchContent(slug: FullSlug): Promise { + if (fetchContentCache.has(slug)) { + return fetchContentCache.get(slug) as Element[] + } + + const targetUrl = resolveUrl(slug).toString() + const contents = await fetch(targetUrl) + .then((res) => res.text()) + .then((contents) => { + if (contents === undefined) { + throw new Error(`Could not fetch ${targetUrl}`) + } + const html = p.parseFromString(contents ?? "", "text/html") + normalizeRelativeURLs(html, targetUrl) + return [...html.getElementsByClassName("popover-hint")] + }) + + fetchContentCache.set(slug, contents) + return contents + } + + async function displayPreview(el: HTMLElement | null) { + if (!searchLayout || !enablePreview || !el || !preview) return + const slug = el.id as FullSlug + const innerDiv = await fetchContent(slug).then((contents) => + contents.flatMap((el) => [...highlightHTML(currentSearchTerm, el as HTMLElement).children]), + ) + previewInner = document.createElement("div") + previewInner.classList.add("preview-inner") + previewInner.append(...innerDiv) + preview.replaceChildren(previewInner) + + // scroll to longest + const highlights = [...preview.querySelectorAll(".highlight")].sort( + (a, b) => b.innerHTML.length - a.innerHTML.length, + ) + highlights[0]?.scrollIntoView({ block: "start" }) + } + + async function onType(e: HTMLElementEventMap["input"]) { + if (!searchLayout || !index) return + currentSearchTerm = (e.target as HTMLInputElement).value + searchLayout.classList.toggle("display-results", currentSearchTerm !== "") + searchType = currentSearchTerm.startsWith("#") ? "tags" : "basic" + + let searchResults: FlexSearch.SimpleDocumentSearchResultSetUnit[] + if (searchType === "tags") { + currentSearchTerm = currentSearchTerm.substring(1).trim() + const separatorIndex = currentSearchTerm.indexOf(" ") + if (separatorIndex != -1) { + // search by title and content index and then filter by tag (implemented in flexsearch) + const tag = currentSearchTerm.substring(0, separatorIndex) + const query = currentSearchTerm.substring(separatorIndex + 1).trim() + searchResults = await index.searchAsync({ + query: query, + // return at least 10000 documents, so it is enough to filter them by tag (implemented in flexsearch) + limit: Math.max(numSearchResults, 10000), + index: ["title", "content"], + tag: tag, + }) + for (let searchResult of searchResults) { + searchResult.result = searchResult.result.slice(0, numSearchResults) + } + // set search type to basic and remove tag from term for proper highlightning and scroll + searchType = "basic" + currentSearchTerm = query + } else { + // default search by tags index + searchResults = await index.searchAsync({ + query: currentSearchTerm, + limit: numSearchResults, + index: ["tags"], + }) + } + } else if (searchType === "basic") { + searchResults = await index.searchAsync({ + query: currentSearchTerm, + limit: numSearchResults, + index: ["title", "content"], + }) + } + + const getByField = (field: string): number[] => { + const results = searchResults.filter((x) => x.field === field) + return results.length === 0 ? [] : ([...results[0].result] as number[]) + } + + // order titles ahead of content + const allIds: Set = new Set([ + ...getByField("title"), + ...getByField("content"), + ...getByField("tags"), + ]) + const finalResults = [...allIds].map((id) => formatForDisplay(currentSearchTerm, id)) + await displayResults(finalResults) + } + + document.addEventListener("keydown", shortcutHandler) + window.addCleanup(() => document.removeEventListener("keydown", shortcutHandler)) + searchIcon?.addEventListener("click", () => showSearch("basic")) + window.addCleanup(() => searchIcon?.removeEventListener("click", () => showSearch("basic"))) + searchBar?.addEventListener("input", onType) + window.addCleanup(() => searchBar?.removeEventListener("input", onType)) + + registerEscapeHandler(container, hideSearch) + await fillDocument(data) +}) + +/** + * Fills flexsearch document with data + * @param index index to fill + * @param data data to fill index with + */ +async function fillDocument(data: { [key: FullSlug]: ContentDetails }) { + let id = 0 + const promises: Array> = [] + for (const [slug, fileData] of Object.entries(data)) { + promises.push( + index.addAsync(id++, { + id, + slug: slug as FullSlug, + title: fileData.title, + content: fileData.content, + tags: fileData.tags, + }), + ) + } + + return await Promise.all(promises) +} diff --git a/quartz/components/scripts/spa.inline.ts b/quartz/components/scripts/spa.inline.ts new file mode 100644 index 0000000..1790bca --- /dev/null +++ b/quartz/components/scripts/spa.inline.ts @@ -0,0 +1,187 @@ +import micromorph from "micromorph" +import { FullSlug, RelativeURL, getFullSlug, normalizeRelativeURLs } from "../../util/path" + +// adapted from `micromorph` +// https://github.com/natemoo-re/micromorph +const NODE_TYPE_ELEMENT = 1 +let announcer = document.createElement("route-announcer") +const isElement = (target: EventTarget | null): target is Element => + (target as Node)?.nodeType === NODE_TYPE_ELEMENT +const isLocalUrl = (href: string) => { + try { + const url = new URL(href) + if (window.location.origin === url.origin) { + return true + } + } catch (e) {} + return false +} + +const isSamePage = (url: URL): boolean => { + const sameOrigin = url.origin === window.location.origin + const samePath = url.pathname === window.location.pathname + return sameOrigin && samePath +} + +const getOpts = ({ target }: Event): { url: URL; scroll?: boolean } | undefined => { + if (!isElement(target)) return + if (target.attributes.getNamedItem("target")?.value === "_blank") return + const a = target.closest("a") + if (!a) return + if ("routerIgnore" in a.dataset) return + const { href } = a + if (!isLocalUrl(href)) return + return { url: new URL(href), scroll: "routerNoscroll" in a.dataset ? false : undefined } +} + +function notifyNav(url: FullSlug) { + const event: CustomEventMap["nav"] = new CustomEvent("nav", { detail: { url } }) + document.dispatchEvent(event) +} + +const cleanupFns: Set<(...args: any[]) => void> = new Set() +window.addCleanup = (fn) => cleanupFns.add(fn) + +let p: DOMParser +async function navigate(url: URL, isBack: boolean = false) { + p = p || new DOMParser() + const contents = await fetch(`${url}`) + .then((res) => { + const contentType = res.headers.get("content-type") + if (contentType?.startsWith("text/html")) { + return res.text() + } else { + window.location.assign(url) + } + }) + .catch(() => { + window.location.assign(url) + }) + + if (!contents) return + + // cleanup old + cleanupFns.forEach((fn) => fn()) + cleanupFns.clear() + + const html = p.parseFromString(contents, "text/html") + normalizeRelativeURLs(html, url) + + let title = html.querySelector("title")?.textContent + if (title) { + document.title = title + } else { + const h1 = document.querySelector("h1") + title = h1?.innerText ?? h1?.textContent ?? url.pathname + } + if (announcer.textContent !== title) { + announcer.textContent = title + } + announcer.dataset.persist = "" + html.body.appendChild(announcer) + + // morph body + micromorph(document.body, html.body) + + // scroll into place and add history + if (!isBack) { + if (url.hash) { + const el = document.getElementById(decodeURIComponent(url.hash.substring(1))) + el?.scrollIntoView() + } else { + window.scrollTo({ top: 0 }) + } + } + + // now, patch head + const elementsToRemove = document.head.querySelectorAll(":not([spa-preserve])") + elementsToRemove.forEach((el) => el.remove()) + const elementsToAdd = html.head.querySelectorAll(":not([spa-preserve])") + elementsToAdd.forEach((el) => document.head.appendChild(el)) + + // delay setting the url until now + // at this point everything is loaded so changing the url should resolve to the correct addresses + if (!isBack) { + history.pushState({}, "", url) + } + notifyNav(getFullSlug(window)) + delete announcer.dataset.persist +} + +window.spaNavigate = navigate + +function createRouter() { + if (typeof window !== "undefined") { + window.addEventListener("click", async (event) => { + const { url } = getOpts(event) ?? {} + // dont hijack behaviour, just let browser act normally + if (!url || event.ctrlKey || event.metaKey) return + event.preventDefault() + + if (isSamePage(url) && url.hash) { + const el = document.getElementById(decodeURIComponent(url.hash.substring(1))) + el?.scrollIntoView() + history.pushState({}, "", url) + return + } + + try { + navigate(url, false) + } catch (e) { + window.location.assign(url) + } + }) + + window.addEventListener("popstate", (event) => { + const { url } = getOpts(event) ?? {} + if (window.location.hash && window.location.pathname === url?.pathname) return + try { + navigate(new URL(window.location.toString()), true) + } catch (e) { + window.location.reload() + } + return + }) + } + + return new (class Router { + go(pathname: RelativeURL) { + const url = new URL(pathname, window.location.toString()) + return navigate(url, false) + } + + back() { + return window.history.back() + } + + forward() { + return window.history.forward() + } + })() +} + +createRouter() +notifyNav(getFullSlug(window)) + +if (!customElements.get("route-announcer")) { + const attrs = { + "aria-live": "assertive", + "aria-atomic": "true", + style: + "position: absolute; left: 0; top: 0; clip: rect(0 0 0 0); clip-path: inset(50%); overflow: hidden; white-space: nowrap; width: 1px; height: 1px", + } + + customElements.define( + "route-announcer", + class RouteAnnouncer extends HTMLElement { + constructor() { + super() + } + connectedCallback() { + for (const [key, value] of Object.entries(attrs)) { + this.setAttribute(key, value) + } + } + }, + ) +} diff --git a/quartz/components/scripts/toc.inline.ts b/quartz/components/scripts/toc.inline.ts new file mode 100644 index 0000000..546859e --- /dev/null +++ b/quartz/components/scripts/toc.inline.ts @@ -0,0 +1,45 @@ +const bufferPx = 150 +const observer = new IntersectionObserver((entries) => { + for (const entry of entries) { + const slug = entry.target.id + const tocEntryElement = document.querySelector(`a[data-for="${slug}"]`) + const windowHeight = entry.rootBounds?.height + if (windowHeight && tocEntryElement) { + if (entry.boundingClientRect.y < windowHeight) { + tocEntryElement.classList.add("in-view") + } else { + tocEntryElement.classList.remove("in-view") + } + } + } +}) + +function toggleToc(this: HTMLElement) { + this.classList.toggle("collapsed") + const content = this.nextElementSibling as HTMLElement | undefined + if (!content) return + content.classList.toggle("collapsed") + content.style.maxHeight = content.style.maxHeight === "0px" ? content.scrollHeight + "px" : "0px" +} + +function setupToc() { + const toc = document.getElementById("toc") + if (toc) { + const collapsed = toc.classList.contains("collapsed") + const content = toc.nextElementSibling as HTMLElement | undefined + if (!content) return + content.style.maxHeight = collapsed ? "0px" : content.scrollHeight + "px" + toc.addEventListener("click", toggleToc) + window.addCleanup(() => toc.removeEventListener("click", toggleToc)) + } +} + +window.addEventListener("resize", setupToc) +document.addEventListener("nav", () => { + setupToc() + + // update toc entry highlighting + observer.disconnect() + const headers = document.querySelectorAll("h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]") + headers.forEach((header) => observer.observe(header)) +}) diff --git a/quartz/components/scripts/util.ts b/quartz/components/scripts/util.ts new file mode 100644 index 0000000..4ffff29 --- /dev/null +++ b/quartz/components/scripts/util.ts @@ -0,0 +1,25 @@ +export function registerEscapeHandler(outsideContainer: HTMLElement | null, cb: () => void) { + if (!outsideContainer) return + function click(this: HTMLElement, e: HTMLElementEventMap["click"]) { + if (e.target !== this) return + e.preventDefault() + cb() + } + + function esc(e: HTMLElementEventMap["keydown"]) { + if (!e.key.startsWith("Esc")) return + e.preventDefault() + cb() + } + + outsideContainer?.addEventListener("click", click) + window.addCleanup(() => outsideContainer?.removeEventListener("click", click)) + document.addEventListener("keydown", esc) + window.addCleanup(() => document.removeEventListener("keydown", esc)) +} + +export function removeAllChildren(node: HTMLElement) { + while (node.firstChild) { + node.removeChild(node.firstChild) + } +} diff --git a/quartz/components/styles/backlinks.scss b/quartz/components/styles/backlinks.scss new file mode 100644 index 0000000..04302f2 --- /dev/null +++ b/quartz/components/styles/backlinks.scss @@ -0,0 +1,20 @@ +.backlinks { + position: relative; + + & > h3 { + font-size: 1rem; + margin: 0; + } + + & > ul { + list-style: none; + padding: 0; + margin: 0.5rem 0; + + & > li { + & > a { + background-color: transparent; + } + } + } +} diff --git a/quartz/components/styles/breadcrumbs.scss b/quartz/components/styles/breadcrumbs.scss new file mode 100644 index 0000000..789808b --- /dev/null +++ b/quartz/components/styles/breadcrumbs.scss @@ -0,0 +1,22 @@ +.breadcrumb-container { + margin: 0; + margin-top: 0.75rem; + padding: 0; + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 0.5rem; +} + +.breadcrumb-element { + p { + margin: 0; + margin-left: 0.5rem; + padding: 0; + line-height: normal; + } + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} diff --git a/quartz/components/styles/clipboard.scss b/quartz/components/styles/clipboard.scss new file mode 100644 index 0000000..196b894 --- /dev/null +++ b/quartz/components/styles/clipboard.scss @@ -0,0 +1,36 @@ +.clipboard-button { + position: absolute; + display: flex; + float: right; + right: 0; + padding: 0.4rem; + margin: 0.3rem; + color: var(--gray); + border-color: var(--dark); + background-color: var(--light); + border: 1px solid; + border-radius: 5px; + opacity: 0; + transition: 0.2s; + + & > svg { + fill: var(--light); + filter: contrast(0.3); + } + + &:hover { + cursor: pointer; + border-color: var(--secondary); + } + + &:focus { + outline: 0; + } +} + +pre { + &:hover > .clipboard-button { + opacity: 1; + transition: 0.2s; + } +} diff --git a/quartz/components/styles/contentMeta.scss b/quartz/components/styles/contentMeta.scss new file mode 100644 index 0000000..4d89f65 --- /dev/null +++ b/quartz/components/styles/contentMeta.scss @@ -0,0 +1,14 @@ +.content-meta { + margin-top: 0; + color: var(--gray); + + &[show-comma="true"] { + > span:not(:last-child) { + margin-right: 8px; + + &::after { + content: ","; + } + } + } +} diff --git a/quartz/components/styles/darkmode.scss b/quartz/components/styles/darkmode.scss new file mode 100644 index 0000000..348c6f7 --- /dev/null +++ b/quartz/components/styles/darkmode.scss @@ -0,0 +1,48 @@ +.darkmode { + position: relative; + width: 20px; + height: 20px; + margin: 0 10px; + + & > .toggle { + display: none; + box-sizing: border-box; + } + + & svg { + cursor: pointer; + opacity: 0; + position: absolute; + width: 20px; + height: 20px; + top: calc(50% - 10px); + fill: var(--darkgray); + transition: opacity 0.1s ease; + } +} + +:root[saved-theme="dark"] { + color-scheme: dark; +} + +:root[saved-theme="light"] { + color-scheme: light; +} + +:root[saved-theme="dark"] .toggle ~ label { + & > #dayIcon { + opacity: 0; + } + & > #nightIcon { + opacity: 1; + } +} + +:root .toggle ~ label { + & > #dayIcon { + opacity: 1; + } + & > #nightIcon { + opacity: 0; + } +} diff --git a/quartz/components/styles/explorer.scss b/quartz/components/styles/explorer.scss new file mode 100644 index 0000000..55ea8aa --- /dev/null +++ b/quartz/components/styles/explorer.scss @@ -0,0 +1,148 @@ +@use "../../styles/variables.scss" as *; + +button#explorer { + all: unset; + background-color: transparent; + border: none; + text-align: left; + cursor: pointer; + padding: 0; + color: var(--dark); + display: flex; + align-items: center; + + & h1 { + font-size: 1rem; + display: inline-block; + margin: 0; + } + + & .fold { + margin-left: 0.5rem; + transition: transform 0.3s ease; + opacity: 0.8; + } + + &.collapsed .fold { + transform: rotateZ(-90deg); + } +} + +.folder-outer { + display: grid; + grid-template-rows: 0fr; + transition: grid-template-rows 0.3s ease-in-out; +} + +.folder-outer.open { + grid-template-rows: 1fr; +} + +.folder-outer > ul { + overflow: hidden; +} + +#explorer-content { + list-style: none; + overflow: hidden; + max-height: none; + transition: max-height 0.35s ease; + margin-top: 0.5rem; + + &.collapsed > .overflow::after { + opacity: 0; + } + + & ul { + list-style: none; + margin: 0.08rem 0; + padding: 0; + transition: + max-height 0.35s ease, + transform 0.35s ease, + opacity 0.2s ease; + & li > a { + color: var(--dark); + opacity: 0.75; + pointer-events: all; + } + } +} + +svg { + pointer-events: all; + + & > polyline { + pointer-events: none; + } +} + +.folder-container { + flex-direction: row; + display: flex; + align-items: center; + user-select: none; + + & div > a { + color: var(--secondary); + font-family: var(--headerFont); + font-size: 0.95rem; + font-weight: $semiBoldWeight; + line-height: 1.5rem; + display: inline-block; + } + + & div > a:hover { + color: var(--tertiary); + } + + & div > button { + color: var(--dark); + background-color: transparent; + border: none; + text-align: left; + cursor: pointer; + padding-left: 0; + padding-right: 0; + display: flex; + align-items: center; + font-family: var(--headerFont); + + & span { + font-size: 0.95rem; + display: inline-block; + color: var(--secondary); + font-weight: $semiBoldWeight; + margin: 0; + line-height: 1.5rem; + pointer-events: none; + } + } +} + +.folder-icon { + margin-right: 5px; + color: var(--secondary); + cursor: pointer; + transition: transform 0.3s ease; + backface-visibility: visible; +} + +li:has(> .folder-outer:not(.open)) > .folder-container > svg { + transform: rotate(-90deg); +} + +.folder-icon:hover { + color: var(--tertiary); +} + +.no-background::after { + background: none !important; +} + +#explorer-end { + // needs height so IntersectionObserver gets triggered + height: 4px; + // remove default margin from li + margin: 0; +} diff --git a/quartz/components/styles/footer.scss b/quartz/components/styles/footer.scss new file mode 100644 index 0000000..9c8dbf8 --- /dev/null +++ b/quartz/components/styles/footer.scss @@ -0,0 +1,15 @@ +footer { + text-align: left; + margin-bottom: 4rem; + opacity: 0.7; + + & ul { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: row; + gap: 1rem; + margin-top: -1rem; + } +} diff --git a/quartz/components/styles/graph.scss b/quartz/components/styles/graph.scss new file mode 100644 index 0000000..3deaa1f --- /dev/null +++ b/quartz/components/styles/graph.scss @@ -0,0 +1,70 @@ +@use "../../styles/variables.scss" as *; + +.graph { + & > h3 { + font-size: 1rem; + margin: 0; + } + + & > .graph-outer { + border-radius: 5px; + border: 1px solid var(--lightgray); + box-sizing: border-box; + height: 250px; + margin: 0.5em 0; + position: relative; + overflow: hidden; + + & > #global-graph-icon { + color: var(--dark); + opacity: 0.5; + width: 18px; + height: 18px; + position: absolute; + padding: 0.2rem; + margin: 0.3rem; + top: 0; + right: 0; + border-radius: 4px; + background-color: transparent; + transition: background-color 0.5s ease; + cursor: pointer; + &:hover { + background-color: var(--lightgray); + } + } + } + + & > #global-graph-outer { + position: fixed; + z-index: 9999; + left: 0; + top: 0; + width: 100vw; + height: 100%; + backdrop-filter: blur(4px); + display: none; + overflow: hidden; + + &.active { + display: inline-block; + } + + & > #global-graph-container { + border: 1px solid var(--lightgray); + background-color: var(--light); + border-radius: 5px; + box-sizing: border-box; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + height: 60vh; + width: 50vw; + + @media all and (max-width: $fullPageWidth) { + width: 90%; + } + } + } +} diff --git a/quartz/components/styles/legacyToc.scss b/quartz/components/styles/legacyToc.scss new file mode 100644 index 0000000..7a98f34 --- /dev/null +++ b/quartz/components/styles/legacyToc.scss @@ -0,0 +1,27 @@ +details#toc { + & summary { + cursor: pointer; + + &::marker { + color: var(--dark); + } + + & > * { + padding-left: 0.25rem; + display: inline-block; + margin: 0; + } + } + + & ul { + list-style: none; + margin: 0.5rem 1.25rem; + padding: 0; + } + + @for $i from 1 through 6 { + & .depth-#{$i} { + padding-left: calc(1rem * #{$i}); + } + } +} diff --git a/quartz/components/styles/listPage.scss b/quartz/components/styles/listPage.scss new file mode 100644 index 0000000..c8fc9e9 --- /dev/null +++ b/quartz/components/styles/listPage.scss @@ -0,0 +1,40 @@ +@use "../../styles/variables.scss" as *; + +ul.section-ul { + list-style: none; + margin-top: 2em; + padding-left: 0; +} + +li.section-li { + margin-bottom: 1em; + + & > .section { + display: grid; + grid-template-columns: 6em 3fr 1fr; + + @media all and (max-width: $mobileBreakpoint) { + & > .tags { + display: none; + } + } + + & > .desc > h3 > a { + background-color: transparent; + } + + & > .meta { + margin: 0; + flex-basis: 6em; + opacity: 0.6; + } + } +} + +// modifications in popover context +.popover .section { + grid-template-columns: 6em 1fr !important; + & > .tags { + display: none; + } +} diff --git a/quartz/components/styles/popover.scss b/quartz/components/styles/popover.scss new file mode 100644 index 0000000..b1694f9 --- /dev/null +++ b/quartz/components/styles/popover.scss @@ -0,0 +1,83 @@ +@use "../../styles/variables.scss" as *; + +@keyframes dropin { + 0% { + opacity: 0; + visibility: hidden; + } + 1% { + opacity: 0; + } + 100% { + opacity: 1; + visibility: visible; + } +} + +.popover { + z-index: 999; + position: absolute; + overflow: visible; + padding: 1rem; + + & > .popover-inner { + position: relative; + width: 30rem; + max-height: 20rem; + padding: 0 1rem 1rem 1rem; + font-weight: initial; + font-style: initial; + line-height: normal; + font-size: initial; + font-family: var(--bodyFont); + border: 1px solid var(--lightgray); + background-color: var(--light); + border-radius: 5px; + box-shadow: 6px 6px 36px 0 rgba(0, 0, 0, 0.25); + overflow: auto; + white-space: normal; + } + + & > .popover-inner[data-content-type] { + &[data-content-type*="pdf"], + &[data-content-type*="image"] { + padding: 0; + max-height: 100%; + } + + &[data-content-type*="image"] { + img { + margin: 0; + border-radius: 0; + display: block; + } + } + + &[data-content-type*="pdf"] { + iframe { + width: 100%; + } + } + } + + h1 { + font-size: 1.5rem; + } + + visibility: hidden; + opacity: 0; + transition: + opacity 0.3s ease, + visibility 0.3s ease; + + @media all and (max-width: $mobileBreakpoint) { + display: none !important; + } +} + +a:hover .popover, +.popover:hover { + animation: dropin 0.3s ease; + animation-fill-mode: forwards; + animation-delay: 0.2s; +} diff --git a/quartz/components/styles/recentNotes.scss b/quartz/components/styles/recentNotes.scss new file mode 100644 index 0000000..7267671 --- /dev/null +++ b/quartz/components/styles/recentNotes.scss @@ -0,0 +1,24 @@ +.recent-notes { + & > h3 { + margin: 0.5rem 0 0 0; + font-size: 1rem; + } + + & > ul.recent-ul { + list-style: none; + margin-top: 1rem; + padding-left: 0; + + & > li { + margin: 1rem 0; + .section > .desc > h3 > a { + background-color: transparent; + } + + .section > .meta { + margin: 0 0 0.5rem 0; + opacity: 0.6; + } + } + } +} diff --git a/quartz/components/styles/search.scss b/quartz/components/styles/search.scss new file mode 100644 index 0000000..8a9ec67 --- /dev/null +++ b/quartz/components/styles/search.scss @@ -0,0 +1,228 @@ +@use "../../styles/variables.scss" as *; + +.search { + min-width: fit-content; + max-width: 14rem; + flex-grow: 0.3; + + & > #search-icon { + background-color: var(--lightgray); + border-radius: 4px; + height: 2rem; + display: flex; + align-items: center; + cursor: pointer; + white-space: nowrap; + + & > div { + flex-grow: 1; + } + + & > p { + display: inline; + padding: 0 1rem; + } + + & svg { + cursor: pointer; + width: 18px; + min-width: 18px; + margin: 0 0.5rem; + + .search-path { + stroke: var(--darkgray); + stroke-width: 2px; + transition: stroke 0.5s ease; + } + } + } + + & > #search-container { + position: fixed; + contain: layout; + z-index: 999; + left: 0; + top: 0; + width: 100vw; + height: 100vh; + overflow-y: auto; + display: none; + backdrop-filter: blur(4px); + + &.active { + display: inline-block; + } + + & > #search-space { + width: 65%; + margin-top: 12vh; + margin-left: auto; + margin-right: auto; + + @media all and (max-width: $fullPageWidth) { + width: 90%; + } + + & > * { + width: 100%; + border-radius: 7px; + background: var(--light); + box-shadow: + 0 14px 50px rgba(27, 33, 48, 0.12), + 0 10px 30px rgba(27, 33, 48, 0.16); + margin-bottom: 2em; + } + + & > input { + box-sizing: border-box; + padding: 0.5em 1em; + font-family: var(--bodyFont); + color: var(--dark); + font-size: 1.1em; + border: 1px solid var(--lightgray); + + &:focus { + outline: none; + } + } + + & > #search-layout { + display: none; + flex-direction: row; + border: 1px solid var(--lightgray); + flex: 0 0 100%; + box-sizing: border-box; + + &.display-results { + display: flex; + } + + &[data-preview] > #results-container { + flex: 0 0 min(30%, 450px); + } + + @media all and (min-width: $tabletBreakpoint) { + &[data-preview] { + & .result-card > p.preview { + display: none; + } + + & > div { + &:first-child { + border-right: 1px solid var(--lightgray); + border-top-right-radius: unset; + border-bottom-right-radius: unset; + } + + &:last-child { + border-top-left-radius: unset; + border-bottom-left-radius: unset; + } + } + } + } + + & > div { + height: calc(75vh - 12vh); + border-radius: 5px; + } + + @media all and (max-width: $tabletBreakpoint) { + & > #preview-container { + display: none !important; + } + + &[data-preview] > #results-container { + width: 100%; + height: auto; + flex: 0 0 100%; + } + } + + & .highlight { + background: color-mix(in srgb, var(--tertiary) 60%, rgba(255, 255, 255, 0)); + border-radius: 5px; + scroll-margin-top: 2rem; + } + + & > #preview-container { + display: block; + overflow: hidden; + font-family: inherit; + color: var(--dark); + line-height: 1.5em; + font-weight: $normalWeight; + overflow-y: auto; + padding: 0 2rem; + + & .preview-inner { + margin: 0 auto; + width: min($pageWidth, 100%); + } + + a[role="anchor"] { + background-color: transparent; + } + } + + & > #results-container { + overflow-y: auto; + + & .result-card { + overflow: hidden; + padding: 1em; + cursor: pointer; + transition: background 0.2s ease; + border-bottom: 1px solid var(--lightgray); + width: 100%; + display: block; + box-sizing: border-box; + + // normalize card props + font-family: inherit; + font-size: 100%; + line-height: 1.15; + margin: 0; + text-transform: none; + text-align: left; + outline: none; + font-weight: inherit; + + &:hover, + &:focus, + &.focus { + background: var(--lightgray); + } + + & > h3 { + margin: 0; + } + + & > ul.tags { + margin-top: 0.45rem; + margin-bottom: 0; + } + + & > ul > li > p { + border-radius: 8px; + background-color: var(--highlight); + padding: 0.2rem 0.4rem; + margin: 0 0.1rem; + line-height: 1.4rem; + font-weight: $boldWeight; + color: var(--secondary); + + &.match-tag { + color: var(--tertiary); + } + } + + & > p { + margin-bottom: 0; + } + } + } + } + } + } +} diff --git a/quartz/components/styles/toc.scss b/quartz/components/styles/toc.scss new file mode 100644 index 0000000..27ff62a --- /dev/null +++ b/quartz/components/styles/toc.scss @@ -0,0 +1,60 @@ +button#toc { + background-color: transparent; + border: none; + text-align: left; + cursor: pointer; + padding: 0; + color: var(--dark); + display: flex; + align-items: center; + + & h3 { + font-size: 1rem; + display: inline-block; + margin: 0; + } + + & .fold { + margin-left: 0.5rem; + transition: transform 0.3s ease; + opacity: 0.8; + } + + &.collapsed .fold { + transform: rotateZ(-90deg); + } +} + +#toc-content { + list-style: none; + overflow: hidden; + max-height: none; + transition: max-height 0.5s ease; + position: relative; + + &.collapsed > .overflow::after { + opacity: 0; + } + + & ul { + list-style: none; + margin: 0.5rem 0; + padding: 0; + & > li > a { + color: var(--dark); + opacity: 0.35; + transition: + 0.5s ease opacity, + 0.3s ease color; + &.in-view { + opacity: 0.75; + } + } + } + + @for $i from 0 through 6 { + & .depth-#{$i} { + padding-left: calc(1rem * #{$i}); + } + } +} diff --git a/quartz/components/types.ts b/quartz/components/types.ts new file mode 100644 index 0000000..a6b90d3 --- /dev/null +++ b/quartz/components/types.ts @@ -0,0 +1,29 @@ +import { ComponentType, JSX } from "preact" +import { StaticResources } from "../util/resources" +import { QuartzPluginData } from "../plugins/vfile" +import { GlobalConfiguration } from "../cfg" +import { Node } from "hast" +import { BuildCtx } from "../util/ctx" + +export type QuartzComponentProps = { + ctx: BuildCtx + externalResources: StaticResources + fileData: QuartzPluginData + cfg: GlobalConfiguration + children: (QuartzComponent | JSX.Element)[] + tree: Node + allFiles: QuartzPluginData[] + displayClass?: "mobile-only" | "desktop-only" +} & JSX.IntrinsicAttributes & { + [key: string]: any + } + +export type QuartzComponent = ComponentType & { + css?: string + beforeDOMLoaded?: string + afterDOMLoaded?: string +} + +export type QuartzComponentConstructor = ( + opts: Options, +) => QuartzComponent diff --git a/quartz/depgraph.test.ts b/quartz/depgraph.test.ts new file mode 100644 index 0000000..062f13e --- /dev/null +++ b/quartz/depgraph.test.ts @@ -0,0 +1,118 @@ +import test, { describe } from "node:test" +import DepGraph from "./depgraph" +import assert from "node:assert" + +describe("DepGraph", () => { + test("getLeafNodes", () => { + const graph = new DepGraph() + graph.addEdge("A", "B") + graph.addEdge("B", "C") + graph.addEdge("D", "C") + assert.deepStrictEqual(graph.getLeafNodes("A"), new Set(["C"])) + assert.deepStrictEqual(graph.getLeafNodes("B"), new Set(["C"])) + assert.deepStrictEqual(graph.getLeafNodes("C"), new Set(["C"])) + assert.deepStrictEqual(graph.getLeafNodes("D"), new Set(["C"])) + }) + + describe("getLeafNodeAncestors", () => { + test("gets correct ancestors in a graph without cycles", () => { + const graph = new DepGraph() + graph.addEdge("A", "B") + graph.addEdge("B", "C") + graph.addEdge("D", "B") + assert.deepStrictEqual(graph.getLeafNodeAncestors("A"), new Set(["A", "B", "D"])) + assert.deepStrictEqual(graph.getLeafNodeAncestors("B"), new Set(["A", "B", "D"])) + assert.deepStrictEqual(graph.getLeafNodeAncestors("C"), new Set(["A", "B", "D"])) + assert.deepStrictEqual(graph.getLeafNodeAncestors("D"), new Set(["A", "B", "D"])) + }) + + test("gets correct ancestors in a graph with cycles", () => { + const graph = new DepGraph() + graph.addEdge("A", "B") + graph.addEdge("B", "C") + graph.addEdge("C", "A") + graph.addEdge("C", "D") + assert.deepStrictEqual(graph.getLeafNodeAncestors("A"), new Set(["A", "B", "C"])) + assert.deepStrictEqual(graph.getLeafNodeAncestors("B"), new Set(["A", "B", "C"])) + assert.deepStrictEqual(graph.getLeafNodeAncestors("C"), new Set(["A", "B", "C"])) + assert.deepStrictEqual(graph.getLeafNodeAncestors("D"), new Set(["A", "B", "C"])) + }) + }) + + describe("mergeGraph", () => { + test("merges two graphs", () => { + const graph = new DepGraph() + graph.addEdge("A.md", "A.html") + + const other = new DepGraph() + other.addEdge("B.md", "B.html") + + graph.mergeGraph(other) + + const expected = { + nodes: ["A.md", "A.html", "B.md", "B.html"], + edges: [ + ["A.md", "A.html"], + ["B.md", "B.html"], + ], + } + + assert.deepStrictEqual(graph.export(), expected) + }) + }) + + describe("updateIncomingEdgesForNode", () => { + test("merges when node exists", () => { + // A.md -> B.md -> B.html + const graph = new DepGraph() + graph.addEdge("A.md", "B.md") + graph.addEdge("B.md", "B.html") + + // B.md is edited so it removes the A.md transclusion + // and adds C.md transclusion + // C.md -> B.md + const other = new DepGraph() + other.addEdge("C.md", "B.md") + other.addEdge("B.md", "B.html") + + // A.md -> B.md removed, C.md -> B.md added + // C.md -> B.md -> B.html + graph.updateIncomingEdgesForNode(other, "B.md") + + const expected = { + nodes: ["A.md", "B.md", "B.html", "C.md"], + edges: [ + ["B.md", "B.html"], + ["C.md", "B.md"], + ], + } + + assert.deepStrictEqual(graph.export(), expected) + }) + + test("adds node if it does not exist", () => { + // A.md -> B.md + const graph = new DepGraph() + graph.addEdge("A.md", "B.md") + + // Add a new file C.md that transcludes B.md + // B.md -> C.md + const other = new DepGraph() + other.addEdge("B.md", "C.md") + + // B.md -> C.md added + // A.md -> B.md -> C.md + graph.updateIncomingEdgesForNode(other, "C.md") + + const expected = { + nodes: ["A.md", "B.md", "C.md"], + edges: [ + ["A.md", "B.md"], + ["B.md", "C.md"], + ], + } + + assert.deepStrictEqual(graph.export(), expected) + }) + }) +}) diff --git a/quartz/depgraph.ts b/quartz/depgraph.ts new file mode 100644 index 0000000..3d048cd --- /dev/null +++ b/quartz/depgraph.ts @@ -0,0 +1,228 @@ +export default class DepGraph { + // node: incoming and outgoing edges + _graph = new Map; outgoing: Set }>() + + constructor() { + this._graph = new Map() + } + + export(): Object { + return { + nodes: this.nodes, + edges: this.edges, + } + } + + toString(): string { + return JSON.stringify(this.export(), null, 2) + } + + // BASIC GRAPH OPERATIONS + + get nodes(): T[] { + return Array.from(this._graph.keys()) + } + + get edges(): [T, T][] { + let edges: [T, T][] = [] + this.forEachEdge((edge) => edges.push(edge)) + return edges + } + + hasNode(node: T): boolean { + return this._graph.has(node) + } + + addNode(node: T): void { + if (!this._graph.has(node)) { + this._graph.set(node, { incoming: new Set(), outgoing: new Set() }) + } + } + + // Remove node and all edges connected to it + removeNode(node: T): void { + if (this._graph.has(node)) { + // first remove all edges so other nodes don't have references to this node + for (const target of this._graph.get(node)!.outgoing) { + this.removeEdge(node, target) + } + for (const source of this._graph.get(node)!.incoming) { + this.removeEdge(source, node) + } + this._graph.delete(node) + } + } + + forEachNode(callback: (node: T) => void): void { + for (const node of this._graph.keys()) { + callback(node) + } + } + + hasEdge(from: T, to: T): boolean { + return Boolean(this._graph.get(from)?.outgoing.has(to)) + } + + addEdge(from: T, to: T): void { + this.addNode(from) + this.addNode(to) + + this._graph.get(from)!.outgoing.add(to) + this._graph.get(to)!.incoming.add(from) + } + + removeEdge(from: T, to: T): void { + if (this._graph.has(from) && this._graph.has(to)) { + this._graph.get(from)!.outgoing.delete(to) + this._graph.get(to)!.incoming.delete(from) + } + } + + // returns -1 if node does not exist + outDegree(node: T): number { + return this.hasNode(node) ? this._graph.get(node)!.outgoing.size : -1 + } + + // returns -1 if node does not exist + inDegree(node: T): number { + return this.hasNode(node) ? this._graph.get(node)!.incoming.size : -1 + } + + forEachOutNeighbor(node: T, callback: (neighbor: T) => void): void { + this._graph.get(node)?.outgoing.forEach(callback) + } + + forEachInNeighbor(node: T, callback: (neighbor: T) => void): void { + this._graph.get(node)?.incoming.forEach(callback) + } + + forEachEdge(callback: (edge: [T, T]) => void): void { + for (const [source, { outgoing }] of this._graph.entries()) { + for (const target of outgoing) { + callback([source, target]) + } + } + } + + // DEPENDENCY ALGORITHMS + + // Add all nodes and edges from other graph to this graph + mergeGraph(other: DepGraph): void { + other.forEachEdge(([source, target]) => { + this.addNode(source) + this.addNode(target) + this.addEdge(source, target) + }) + } + + // For the node provided: + // If node does not exist, add it + // If an incoming edge was added in other, it is added in this graph + // If an incoming edge was deleted in other, it is deleted in this graph + updateIncomingEdgesForNode(other: DepGraph, node: T): void { + this.addNode(node) + + // Add edge if it is present in other + other.forEachInNeighbor(node, (neighbor) => { + this.addEdge(neighbor, node) + }) + + // For node provided, remove incoming edge if it is absent in other + this.forEachEdge(([source, target]) => { + if (target === node && !other.hasEdge(source, target)) { + this.removeEdge(source, target) + } + }) + } + + // Remove all nodes that do not have any incoming or outgoing edges + // A node may be orphaned if the only node pointing to it was removed + removeOrphanNodes(): Set { + let orphanNodes = new Set() + + this.forEachNode((node) => { + if (this.inDegree(node) === 0 && this.outDegree(node) === 0) { + orphanNodes.add(node) + } + }) + + orphanNodes.forEach((node) => { + this.removeNode(node) + }) + + return orphanNodes + } + + // Get all leaf nodes (i.e. destination paths) reachable from the node provided + // Eg. if the graph is A -> B -> C + // D ---^ + // and the node is B, this function returns [C] + getLeafNodes(node: T): Set { + let stack: T[] = [node] + let visited = new Set() + let leafNodes = new Set() + + // DFS + while (stack.length > 0) { + let node = stack.pop()! + + // If the node is already visited, skip it + if (visited.has(node)) { + continue + } + visited.add(node) + + // Check if the node is a leaf node (i.e. destination path) + if (this.outDegree(node) === 0) { + leafNodes.add(node) + } + + // Add all unvisited neighbors to the stack + this.forEachOutNeighbor(node, (neighbor) => { + if (!visited.has(neighbor)) { + stack.push(neighbor) + } + }) + } + + return leafNodes + } + + // Get all ancestors of the leaf nodes reachable from the node provided + // Eg. if the graph is A -> B -> C + // D ---^ + // and the node is B, this function returns [A, B, D] + getLeafNodeAncestors(node: T): Set { + const leafNodes = this.getLeafNodes(node) + let visited = new Set() + let upstreamNodes = new Set() + + // Backwards DFS for each leaf node + leafNodes.forEach((leafNode) => { + let stack: T[] = [leafNode] + + while (stack.length > 0) { + let node = stack.pop()! + + if (visited.has(node)) { + continue + } + visited.add(node) + // Add node if it's not a leaf node (i.e. destination path) + // Assumes destination file cannot depend on another destination file + if (this.outDegree(node) !== 0) { + upstreamNodes.add(node) + } + + // Add all unvisited parents to the stack + this.forEachInNeighbor(node, (parentNode) => { + if (!visited.has(parentNode)) { + stack.push(parentNode) + } + }) + } + }) + + return upstreamNodes + } +} diff --git a/quartz/i18n/index.ts b/quartz/i18n/index.ts new file mode 100644 index 0000000..6707ea3 --- /dev/null +++ b/quartz/i18n/index.ts @@ -0,0 +1,62 @@ +import { Translation, CalloutTranslation } from "./locales/definition" +import en from "./locales/en-US" +import fr from "./locales/fr-FR" +import it from "./locales/it-IT" +import ja from "./locales/ja-JP" +import de from "./locales/de-DE" +import nl from "./locales/nl-NL" +import ro from "./locales/ro-RO" +import es from "./locales/es-ES" +import ar from "./locales/ar-SA" +import uk from "./locales/uk-UA" +import ru from "./locales/ru-RU" +import ko from "./locales/ko-KR" +import zh from "./locales/zh-CN" +import vi from "./locales/vi-VN" +import pt from "./locales/pt-BR" +import hu from "./locales/hu-HU" + +export const TRANSLATIONS = { + "en-US": en, + "fr-FR": fr, + "it-IT": it, + "ja-JP": ja, + "de-DE": de, + "nl-NL": nl, + "nl-BE": nl, + "ro-RO": ro, + "ro-MD": ro, + "es-ES": es, + "ar-SA": ar, + "ar-AE": ar, + "ar-QA": ar, + "ar-BH": ar, + "ar-KW": ar, + "ar-OM": ar, + "ar-YE": ar, + "ar-IR": ar, + "ar-SY": ar, + "ar-IQ": ar, + "ar-JO": ar, + "ar-PL": ar, + "ar-LB": ar, + "ar-EG": ar, + "ar-SD": ar, + "ar-LY": ar, + "ar-MA": ar, + "ar-TN": ar, + "ar-DZ": ar, + "ar-MR": ar, + "uk-UA": uk, + "ru-RU": ru, + "ko-KR": ko, + "zh-CN": zh, + "vi-VN": vi, + "pt-BR": pt, + "hu-HU": hu, +} as const + +export const defaultTranslation = "en-US" +export const i18n = (locale: ValidLocale): Translation => TRANSLATIONS[locale ?? defaultTranslation] +export type ValidLocale = keyof typeof TRANSLATIONS +export type ValidCallout = keyof CalloutTranslation diff --git a/quartz/i18n/locales/ar-SA.ts b/quartz/i18n/locales/ar-SA.ts new file mode 100644 index 0000000..f704810 --- /dev/null +++ b/quartz/i18n/locales/ar-SA.ts @@ -0,0 +1,88 @@ +import { Translation } from "./definition" + +export default { + propertyDefaults: { + title: "غير معنون", + description: "لم يتم تقديم أي وصف", + }, + components: { + callout: { + note: "ملاحظة", + abstract: "ملخص", + info: "معلومات", + todo: "للقيام", + tip: "نصيحة", + success: "نجاح", + question: "سؤال", + warning: "تحذير", + failure: "فشل", + danger: "خطر", + bug: "خلل", + example: "مثال", + quote: "اقتباس", + }, + backlinks: { + title: "وصلات العودة", + noBacklinksFound: "لا يوجد وصلات عودة", + }, + themeToggle: { + lightMode: "الوضع النهاري", + darkMode: "الوضع الليلي", + }, + explorer: { + title: "المستعرض", + }, + footer: { + createdWith: "أُنشئ باستخدام", + }, + graph: { + title: "التمثيل التفاعلي", + }, + recentNotes: { + title: "آخر الملاحظات", + seeRemainingMore: ({ remaining }) => `تصفح ${remaining} أكثر →`, + }, + transcludes: { + transcludeOf: ({ targetSlug }) => `مقتبس من ${targetSlug}`, + linkToOriginal: "وصلة للملاحظة الرئيسة", + }, + search: { + title: "بحث", + searchBarPlaceholder: "ابحث عن شيء ما", + }, + tableOfContents: { + title: "فهرس المحتويات", + }, + contentMeta: { + readingTime: ({ minutes }) => + minutes == 1 + ? `دقيقة أو أقل للقراءة` + : minutes == 2 + ? `دقيقتان للقراءة` + : `${minutes} دقائق للقراءة`, + }, + }, + pages: { + rss: { + recentNotes: "آخر الملاحظات", + lastFewNotes: ({ count }) => `آخر ${count} ملاحظة`, + }, + error: { + title: "غير موجود", + notFound: "إما أن هذه الصفحة خاصة أو غير موجودة.", + }, + folderContent: { + folder: "مجلد", + itemsUnderFolder: ({ count }) => + count === 1 ? "يوجد عنصر واحد فقط تحت هذا المجلد" : `يوجد ${count} عناصر تحت هذا المجلد.`, + }, + tagContent: { + tag: "الوسم", + tagIndex: "مؤشر الوسم", + itemsUnderTag: ({ count }) => + count === 1 ? "يوجد عنصر واحد فقط تحت هذا الوسم" : `يوجد ${count} عناصر تحت هذا الوسم.`, + showingFirst: ({ count }) => `إظهار أول ${count} أوسمة.`, + totalTags: ({ count }) => `يوجد ${count} أوسمة.`, + }, + }, +} as const satisfies Translation diff --git a/quartz/i18n/locales/de-DE.ts b/quartz/i18n/locales/de-DE.ts new file mode 100644 index 0000000..64c9ba9 --- /dev/null +++ b/quartz/i18n/locales/de-DE.ts @@ -0,0 +1,83 @@ +import { Translation } from "./definition" + +export default { + propertyDefaults: { + title: "Unbenannt", + description: "Keine Beschreibung angegeben", + }, + components: { + callout: { + note: "Hinweis", + abstract: "Zusammenfassung", + info: "Info", + todo: "Zu erledigen", + tip: "Tipp", + success: "Erfolg", + question: "Frage", + warning: "Warnung", + failure: "Misserfolg", + danger: "Gefahr", + bug: "Fehler", + example: "Beispiel", + quote: "Zitat", + }, + backlinks: { + title: "Backlinks", + noBacklinksFound: "Keine Backlinks gefunden", + }, + themeToggle: { + lightMode: "Light Mode", + darkMode: "Dark Mode", + }, + explorer: { + title: "Explorer", + }, + footer: { + createdWith: "Erstellt mit", + }, + graph: { + title: "Graphansicht", + }, + recentNotes: { + title: "Zuletzt bearbeitete Seiten", + seeRemainingMore: ({ remaining }) => `${remaining} weitere ansehen →`, + }, + transcludes: { + transcludeOf: ({ targetSlug }) => `Transklusion von ${targetSlug}`, + linkToOriginal: "Link zum Original", + }, + search: { + title: "Suche", + searchBarPlaceholder: "Suche nach etwas", + }, + tableOfContents: { + title: "Inhaltsverzeichnis", + }, + contentMeta: { + readingTime: ({ minutes }) => `${minutes} min read`, + }, + }, + pages: { + rss: { + recentNotes: "Zuletzt bearbeitete Seiten", + lastFewNotes: ({ count }) => `Letzte ${count} Seiten`, + }, + error: { + title: "Nicht gefunden", + notFound: "Diese Seite ist entweder nicht öffentlich oder existiert nicht.", + }, + folderContent: { + folder: "Ordner", + itemsUnderFolder: ({ count }) => + count === 1 ? "1 Datei in diesem Ordner." : `${count} Dateien in diesem Ordner.`, + }, + tagContent: { + tag: "Tag", + tagIndex: "Tag-Übersicht", + itemsUnderTag: ({ count }) => + count === 1 ? "1 Datei mit diesem Tag." : `${count} Dateien mit diesem Tag.`, + showingFirst: ({ count }) => `Die ersten ${count} Tags werden angezeigt.`, + totalTags: ({ count }) => `${count} Tags insgesamt.`, + }, + }, +} as const satisfies Translation diff --git a/quartz/i18n/locales/definition.ts b/quartz/i18n/locales/definition.ts new file mode 100644 index 0000000..1d5d3dd --- /dev/null +++ b/quartz/i18n/locales/definition.ts @@ -0,0 +1,83 @@ +import { FullSlug } from "../../util/path" + +export interface CalloutTranslation { + note: string + abstract: string + info: string + todo: string + tip: string + success: string + question: string + warning: string + failure: string + danger: string + bug: string + example: string + quote: string +} + +export interface Translation { + propertyDefaults: { + title: string + description: string + } + components: { + callout: CalloutTranslation + backlinks: { + title: string + noBacklinksFound: string + } + themeToggle: { + lightMode: string + darkMode: string + } + explorer: { + title: string + } + footer: { + createdWith: string + } + graph: { + title: string + } + recentNotes: { + title: string + seeRemainingMore: (variables: { remaining: number }) => string + } + transcludes: { + transcludeOf: (variables: { targetSlug: FullSlug }) => string + linkToOriginal: string + } + search: { + title: string + searchBarPlaceholder: string + } + tableOfContents: { + title: string + } + contentMeta: { + readingTime: (variables: { minutes: number }) => string + } + } + pages: { + rss: { + recentNotes: string + lastFewNotes: (variables: { count: number }) => string + } + error: { + title: string + notFound: string + } + folderContent: { + folder: string + itemsUnderFolder: (variables: { count: number }) => string + } + tagContent: { + tag: string + tagIndex: string + itemsUnderTag: (variables: { count: number }) => string + showingFirst: (variables: { count: number }) => string + totalTags: (variables: { count: number }) => string + } + } +} diff --git a/quartz/i18n/locales/en-US.ts b/quartz/i18n/locales/en-US.ts new file mode 100644 index 0000000..ac283fd --- /dev/null +++ b/quartz/i18n/locales/en-US.ts @@ -0,0 +1,83 @@ +import { Translation } from "./definition" + +export default { + propertyDefaults: { + title: "Untitled", + description: "No description provided", + }, + components: { + callout: { + note: "Note", + abstract: "Abstract", + info: "Info", + todo: "Todo", + tip: "Tip", + success: "Success", + question: "Question", + warning: "Warning", + failure: "Failure", + danger: "Danger", + bug: "Bug", + example: "Example", + quote: "Quote", + }, + backlinks: { + title: "Backlinks", + noBacklinksFound: "No backlinks found", + }, + themeToggle: { + lightMode: "Light mode", + darkMode: "Dark mode", + }, + explorer: { + title: "Explorer", + }, + footer: { + createdWith: "Created with", + }, + graph: { + title: "Graph View", + }, + recentNotes: { + title: "Recent Notes", + seeRemainingMore: ({ remaining }) => `See ${remaining} more →`, + }, + transcludes: { + transcludeOf: ({ targetSlug }) => `Transclude of ${targetSlug}`, + linkToOriginal: "Link to original", + }, + search: { + title: "Search", + searchBarPlaceholder: "Search for something", + }, + tableOfContents: { + title: "Table of Contents", + }, + contentMeta: { + readingTime: ({ minutes }) => `${minutes} min read`, + }, + }, + pages: { + rss: { + recentNotes: "Recent notes", + lastFewNotes: ({ count }) => `Last ${count} notes`, + }, + error: { + title: "Not Found", + notFound: "Either this page is private or doesn't exist.", + }, + folderContent: { + folder: "Folder", + itemsUnderFolder: ({ count }) => + count === 1 ? "1 item under this folder." : `${count} items under this folder.`, + }, + tagContent: { + tag: "Tag", + tagIndex: "Tag Index", + itemsUnderTag: ({ count }) => + count === 1 ? "1 item with this tag." : `${count} items with this tag.`, + showingFirst: ({ count }) => `Showing first ${count} tags.`, + totalTags: ({ count }) => `Found ${count} total tags.`, + }, + }, +} as const satisfies Translation diff --git a/quartz/i18n/locales/es-ES.ts b/quartz/i18n/locales/es-ES.ts new file mode 100644 index 0000000..37a2a79 --- /dev/null +++ b/quartz/i18n/locales/es-ES.ts @@ -0,0 +1,83 @@ +import { Translation } from "./definition" + +export default { + propertyDefaults: { + title: "Sin título", + description: "Sin descripción", + }, + components: { + callout: { + note: "Nota", + abstract: "Resumen", + info: "Información", + todo: "Por hacer", + tip: "Consejo", + success: "Éxito", + question: "Pregunta", + warning: "Advertencia", + failure: "Fallo", + danger: "Peligro", + bug: "Error", + example: "Ejemplo", + quote: "Cita", + }, + backlinks: { + title: "Enlaces de Retroceso", + noBacklinksFound: "No se han encontrado enlaces traseros", + }, + themeToggle: { + lightMode: "Modo claro", + darkMode: "Modo oscuro", + }, + explorer: { + title: "Explorador", + }, + footer: { + createdWith: "Creado con", + }, + graph: { + title: "Vista Gráfica", + }, + recentNotes: { + title: "Notas Recientes", + seeRemainingMore: ({ remaining }) => `Vea ${remaining} más →`, + }, + transcludes: { + transcludeOf: ({ targetSlug }) => `Transcluido de ${targetSlug}`, + linkToOriginal: "Enlace al original", + }, + search: { + title: "Buscar", + searchBarPlaceholder: "Busca algo", + }, + tableOfContents: { + title: "Tabla de Contenidos", + }, + contentMeta: { + readingTime: ({ minutes }) => `${minutes} min read`, + }, + }, + pages: { + rss: { + recentNotes: "Notas recientes", + lastFewNotes: ({ count }) => `Últimás ${count} notas`, + }, + error: { + title: "No se encontró.", + notFound: "Esta página es privada o no existe.", + }, + folderContent: { + folder: "Carpeta", + itemsUnderFolder: ({ count }) => + count === 1 ? "1 artículo en esta carpeta." : `${count} artículos en esta carpeta.`, + }, + tagContent: { + tag: "Etiqueta", + tagIndex: "Índice de Etiquetas", + itemsUnderTag: ({ count }) => + count === 1 ? "1 artículo con esta etiqueta." : `${count} artículos con esta etiqueta.`, + showingFirst: ({ count }) => `Mostrando las primeras ${count} etiquetas.`, + totalTags: ({ count }) => `Se encontraron ${count} etiquetas en total.`, + }, + }, +} as const satisfies Translation diff --git a/quartz/i18n/locales/fr-FR.ts b/quartz/i18n/locales/fr-FR.ts new file mode 100644 index 0000000..e1dfa48 --- /dev/null +++ b/quartz/i18n/locales/fr-FR.ts @@ -0,0 +1,83 @@ +import { Translation } from "./definition" + +export default { + propertyDefaults: { + title: "Sans titre", + description: "Aucune description fournie", + }, + components: { + callout: { + note: "Note", + abstract: "Résumé", + info: "Info", + todo: "À faire", + tip: "Conseil", + success: "Succès", + question: "Question", + warning: "Avertissement", + failure: "Échec", + danger: "Danger", + bug: "Bogue", + example: "Exemple", + quote: "Citation", + }, + backlinks: { + title: "Liens retour", + noBacklinksFound: "Aucun lien retour trouvé", + }, + themeToggle: { + lightMode: "Mode clair", + darkMode: "Mode sombre", + }, + explorer: { + title: "Explorateur", + }, + footer: { + createdWith: "Créé avec", + }, + graph: { + title: "Vue Graphique", + }, + recentNotes: { + title: "Notes Récentes", + seeRemainingMore: ({ remaining }) => `Voir ${remaining} de plus →`, + }, + transcludes: { + transcludeOf: ({ targetSlug }) => `Transclusion de ${targetSlug}`, + linkToOriginal: "Lien vers l'original", + }, + search: { + title: "Recherche", + searchBarPlaceholder: "Rechercher quelque chose", + }, + tableOfContents: { + title: "Table des Matières", + }, + contentMeta: { + readingTime: ({ minutes }) => `${minutes} min de lecture`, + }, + }, + pages: { + rss: { + recentNotes: "Notes récentes", + lastFewNotes: ({ count }) => `Les dernières ${count} notes`, + }, + error: { + title: "Introuvable", + notFound: "Cette page est soit privée, soit elle n'existe pas.", + }, + folderContent: { + folder: "Dossier", + itemsUnderFolder: ({ count }) => + count === 1 ? "1 élément sous ce dossier." : `${count} éléments sous ce dossier.`, + }, + tagContent: { + tag: "Étiquette", + tagIndex: "Index des étiquettes", + itemsUnderTag: ({ count }) => + count === 1 ? "1 élément avec cette étiquette." : `${count} éléments avec cette étiquette.`, + showingFirst: ({ count }) => `Affichage des premières ${count} étiquettes.`, + totalTags: ({ count }) => `Trouvé ${count} étiquettes au total.`, + }, + }, +} as const satisfies Translation diff --git a/quartz/i18n/locales/hu-HU.ts b/quartz/i18n/locales/hu-HU.ts new file mode 100644 index 0000000..6397309 --- /dev/null +++ b/quartz/i18n/locales/hu-HU.ts @@ -0,0 +1,81 @@ +import { Translation } from "./definition" + +export default { + propertyDefaults: { + title: "Névtelen", + description: "Nincs leírás", + }, + components: { + callout: { + note: "Jegyzet", + abstract: "Abstract", + info: "Információ", + todo: "Tennivaló", + tip: "Tipp", + success: "Siker", + question: "Kérdés", + warning: "Figyelmeztetés", + failure: "Hiba", + danger: "Veszély", + bug: "Bug", + example: "Példa", + quote: "Idézet", + }, + backlinks: { + title: "Visszautalások", + noBacklinksFound: "Nincs visszautalás", + }, + themeToggle: { + lightMode: "Világos mód", + darkMode: "Sötét mód", + }, + explorer: { + title: "Fájlböngésző", + }, + footer: { + createdWith: "Készítve ezzel:", + }, + graph: { + title: "Grafikonnézet", + }, + recentNotes: { + title: "Legutóbbi jegyzetek", + seeRemainingMore: ({ remaining }) => `${remaining} további megtekintése →`, + }, + transcludes: { + transcludeOf: ({ targetSlug }) => `${targetSlug} áthivatkozása`, + linkToOriginal: "Hivatkozás az eredetire", + }, + search: { + title: "Keresés", + searchBarPlaceholder: "Keress valamire", + }, + tableOfContents: { + title: "Tartalomjegyzék", + }, + contentMeta: { + readingTime: ({ minutes }) => `${minutes} perces olvasás`, + }, + }, + pages: { + rss: { + recentNotes: "Legutóbbi jegyzetek", + lastFewNotes: ({ count }) => `Legutóbbi ${count} jegyzet`, + }, + error: { + title: "Nem található", + notFound: "Ez a lap vagy privát vagy nem létezik.", + }, + folderContent: { + folder: "Mappa", + itemsUnderFolder: ({ count }) => `Ebben a mappában ${count} elem található.`, + }, + tagContent: { + tag: "Címke", + tagIndex: "Címke index", + itemsUnderTag: ({ count }) => `${count} elem található ezzel a címkével.`, + showingFirst: ({ count }) => `Első ${count} címke megjelenítve.`, + totalTags: ({ count }) => `Összesen ${count} címke található.`, + }, + }, +} as const satisfies Translation diff --git a/quartz/i18n/locales/it-IT.ts b/quartz/i18n/locales/it-IT.ts new file mode 100644 index 0000000..ca8818a --- /dev/null +++ b/quartz/i18n/locales/it-IT.ts @@ -0,0 +1,83 @@ +import { Translation } from "./definition" + +export default { + propertyDefaults: { + title: "Senza titolo", + description: "Nessuna descrizione", + }, + components: { + callout: { + note: "Nota", + abstract: "Astratto", + info: "Info", + todo: "Da fare", + tip: "Consiglio", + success: "Completato", + question: "Domanda", + warning: "Attenzione", + failure: "Errore", + danger: "Pericolo", + bug: "Bug", + example: "Esempio", + quote: "Citazione", + }, + backlinks: { + title: "Link entranti", + noBacklinksFound: "Nessun link entrante", + }, + themeToggle: { + lightMode: "Tema chiaro", + darkMode: "Tema scuro", + }, + explorer: { + title: "Esplora", + }, + footer: { + createdWith: "Creato con", + }, + graph: { + title: "Vista grafico", + }, + recentNotes: { + title: "Note recenti", + seeRemainingMore: ({ remaining }) => `Vedi ${remaining} altro →`, + }, + transcludes: { + transcludeOf: ({ targetSlug }) => `Transclusione di ${targetSlug}`, + linkToOriginal: "Link all'originale", + }, + search: { + title: "Cerca", + searchBarPlaceholder: "Cerca qualcosa", + }, + tableOfContents: { + title: "Tabella dei contenuti", + }, + contentMeta: { + readingTime: ({ minutes }) => `${minutes} minuti`, + }, + }, + pages: { + rss: { + recentNotes: "Note recenti", + lastFewNotes: ({ count }) => `Ultime ${count} note`, + }, + error: { + title: "Non trovato", + notFound: "Questa pagina è privata o non esiste.", + }, + folderContent: { + folder: "Cartella", + itemsUnderFolder: ({ count }) => + count === 1 ? "1 oggetto in questa cartella." : `${count} oggetti in questa cartella.`, + }, + tagContent: { + tag: "Etichetta", + tagIndex: "Indice etichette", + itemsUnderTag: ({ count }) => + count === 1 ? "1 oggetto con questa etichetta." : `${count} oggetti con questa etichetta.`, + showingFirst: ({ count }) => `Prime ${count} etichette.`, + totalTags: ({ count }) => `Trovate ${count} etichette totali.`, + }, + }, +} as const satisfies Translation diff --git a/quartz/i18n/locales/ja-JP.ts b/quartz/i18n/locales/ja-JP.ts new file mode 100644 index 0000000..d429db4 --- /dev/null +++ b/quartz/i18n/locales/ja-JP.ts @@ -0,0 +1,81 @@ +import { Translation } from "./definition" + +export default { + propertyDefaults: { + title: "無題", + description: "説明なし", + }, + components: { + callout: { + note: "ノート", + abstract: "抄録", + info: "情報", + todo: "やるべきこと", + tip: "ヒント", + success: "成功", + question: "質問", + warning: "警告", + failure: "失敗", + danger: "危険", + bug: "バグ", + example: "例", + quote: "引用", + }, + backlinks: { + title: "バックリンク", + noBacklinksFound: "バックリンクはありません", + }, + themeToggle: { + lightMode: "ライトモード", + darkMode: "ダークモード", + }, + explorer: { + title: "エクスプローラー", + }, + footer: { + createdWith: "作成", + }, + graph: { + title: "グラフビュー", + }, + recentNotes: { + title: "最近の記事", + seeRemainingMore: ({ remaining }) => `さらに${remaining}件 →`, + }, + transcludes: { + transcludeOf: ({ targetSlug }) => `${targetSlug}のまとめ`, + linkToOriginal: "元記事へのリンク", + }, + search: { + title: "検索", + searchBarPlaceholder: "検索ワードを入力", + }, + tableOfContents: { + title: "目次", + }, + contentMeta: { + readingTime: ({ minutes }) => `${minutes} min read`, + }, + }, + pages: { + rss: { + recentNotes: "最近の記事", + lastFewNotes: ({ count }) => `最新の${count}件`, + }, + error: { + title: "Not Found", + notFound: "ページが存在しないか、非公開設定になっています。", + }, + folderContent: { + folder: "フォルダ", + itemsUnderFolder: ({ count }) => `${count}件のページ`, + }, + tagContent: { + tag: "タグ", + tagIndex: "タグ一覧", + itemsUnderTag: ({ count }) => `${count}件のページ`, + showingFirst: ({ count }) => `のうち最初の${count}件を表示しています`, + totalTags: ({ count }) => `全${count}個のタグを表示中`, + }, + }, +} as const satisfies Translation diff --git a/quartz/i18n/locales/ko-KR.ts b/quartz/i18n/locales/ko-KR.ts new file mode 100644 index 0000000..ea735b0 --- /dev/null +++ b/quartz/i18n/locales/ko-KR.ts @@ -0,0 +1,81 @@ +import { Translation } from "./definition" + +export default { + propertyDefaults: { + title: "제목 없음", + description: "설명 없음", + }, + components: { + callout: { + note: "노트", + abstract: "개요", + info: "정보", + todo: "할일", + tip: "팁", + success: "성공", + question: "질문", + warning: "주의", + failure: "실패", + danger: "위험", + bug: "버그", + example: "예시", + quote: "인용", + }, + backlinks: { + title: "백링크", + noBacklinksFound: "백링크가 없습니다.", + }, + themeToggle: { + lightMode: "라이트 모드", + darkMode: "다크 모드", + }, + explorer: { + title: "탐색기", + }, + footer: { + createdWith: "Created with", + }, + graph: { + title: "그래프 뷰", + }, + recentNotes: { + title: "최근 게시글", + seeRemainingMore: ({ remaining }) => `${remaining}건 더보기 →`, + }, + transcludes: { + transcludeOf: ({ targetSlug }) => `${targetSlug}의 포함`, + linkToOriginal: "원본 링크", + }, + search: { + title: "검색", + searchBarPlaceholder: "검색어를 입력하세요", + }, + tableOfContents: { + title: "목차", + }, + contentMeta: { + readingTime: ({ minutes }) => `${minutes} min read`, + }, + }, + pages: { + rss: { + recentNotes: "최근 게시글", + lastFewNotes: ({ count }) => `최근 ${count} 건`, + }, + error: { + title: "Not Found", + notFound: "페이지가 존재하지 않거나 비공개 설정이 되어 있습니다.", + }, + folderContent: { + folder: "폴더", + itemsUnderFolder: ({ count }) => `${count}건의 항목`, + }, + tagContent: { + tag: "태그", + tagIndex: "태그 목록", + itemsUnderTag: ({ count }) => `${count}건의 항목`, + showingFirst: ({ count }) => `처음 ${count}개의 태그`, + totalTags: ({ count }) => `총 ${count}개의 태그를 찾았습니다.`, + }, + }, +} as const satisfies Translation diff --git a/quartz/i18n/locales/nl-NL.ts b/quartz/i18n/locales/nl-NL.ts new file mode 100644 index 0000000..d075d58 --- /dev/null +++ b/quartz/i18n/locales/nl-NL.ts @@ -0,0 +1,85 @@ +import { Translation } from "./definition" + +export default { + propertyDefaults: { + title: "Naamloos", + description: "Geen beschrijving gegeven.", + }, + components: { + callout: { + note: "Notitie", + abstract: "Samenvatting", + info: "Info", + todo: "Te doen", + tip: "Tip", + success: "Succes", + question: "Vraag", + warning: "Waarschuwing", + failure: "Mislukking", + danger: "Gevaar", + bug: "Bug", + example: "Voorbeeld", + quote: "Citaat", + }, + backlinks: { + title: "Backlinks", + noBacklinksFound: "Geen backlinks gevonden", + }, + themeToggle: { + lightMode: "Lichte modus", + darkMode: "Donkere modus", + }, + explorer: { + title: "Verkenner", + }, + footer: { + createdWith: "Gemaakt met", + }, + graph: { + title: "Grafiekweergave", + }, + recentNotes: { + title: "Recente notities", + seeRemainingMore: ({ remaining }) => `Zie ${remaining} meer →`, + }, + transcludes: { + transcludeOf: ({ targetSlug }) => `Invoeging van ${targetSlug}`, + linkToOriginal: "Link naar origineel", + }, + search: { + title: "Zoeken", + searchBarPlaceholder: "Doorzoek de website", + }, + tableOfContents: { + title: "Inhoudsopgave", + }, + contentMeta: { + readingTime: ({ minutes }) => + minutes === 1 ? "1 minuut leestijd" : `${minutes} minuten leestijd`, + }, + }, + pages: { + rss: { + recentNotes: "Recente notities", + lastFewNotes: ({ count }) => `Laatste ${count} notities`, + }, + error: { + title: "Niet gevonden", + notFound: "Deze pagina is niet zichtbaar of bestaat niet.", + }, + folderContent: { + folder: "Map", + itemsUnderFolder: ({ count }) => + count === 1 ? "1 item in deze map." : `${count} items in deze map.`, + }, + tagContent: { + tag: "Label", + tagIndex: "Label-index", + itemsUnderTag: ({ count }) => + count === 1 ? "1 item met dit label." : `${count} items met dit label.`, + showingFirst: ({ count }) => + count === 1 ? "Eerste label tonen." : `Eerste ${count} labels tonen.`, + totalTags: ({ count }) => `${count} labels gevonden.`, + }, + }, +} as const satisfies Translation diff --git a/quartz/i18n/locales/pt-BR.ts b/quartz/i18n/locales/pt-BR.ts new file mode 100644 index 0000000..b59c7b4 --- /dev/null +++ b/quartz/i18n/locales/pt-BR.ts @@ -0,0 +1,83 @@ +import { Translation } from "./definition" + +export default { + propertyDefaults: { + title: "Sem título", + description: "Sem descrição", + }, + components: { + callout: { + note: "Nota", + abstract: "Abstrato", + info: "Info", + todo: "Pendência", + tip: "Dica", + success: "Sucesso", + question: "Pergunta", + warning: "Aviso", + failure: "Falha", + danger: "Perigo", + bug: "Bug", + example: "Exemplo", + quote: "Citação", + }, + backlinks: { + title: "Backlinks", + noBacklinksFound: "Sem backlinks encontrados", + }, + themeToggle: { + lightMode: "Tema claro", + darkMode: "Tema escuro", + }, + explorer: { + title: "Explorador", + }, + footer: { + createdWith: "Criado com", + }, + graph: { + title: "Visão de gráfico", + }, + recentNotes: { + title: "Notas recentes", + seeRemainingMore: ({ remaining }) => `Veja mais ${remaining} →`, + }, + transcludes: { + transcludeOf: ({ targetSlug }) => `Transcrever de ${targetSlug}`, + linkToOriginal: "Link ao original", + }, + search: { + title: "Pesquisar", + searchBarPlaceholder: "Pesquisar por algo", + }, + tableOfContents: { + title: "Sumário", + }, + contentMeta: { + readingTime: ({ minutes }) => `Leitura de ${minutes} min`, + }, + }, + pages: { + rss: { + recentNotes: "Notas recentes", + lastFewNotes: ({ count }) => `Últimas ${count} notas`, + }, + error: { + title: "Não encontrado", + notFound: "Esta página é privada ou não existe.", + }, + folderContent: { + folder: "Arquivo", + itemsUnderFolder: ({ count }) => + count === 1 ? "1 item mneste arquivo." : `${count} items neste arquivo.`, + }, + tagContent: { + tag: "Tag", + tagIndex: "Sumário de Tags", + itemsUnderTag: ({ count }) => + count === 1 ? "1 item com esta tag." : `${count} items com esta tag.`, + showingFirst: ({ count }) => `Mostrando as ${count} primeiras tags.`, + totalTags: ({ count }) => `Encontradas ${count} tags.`, + }, + }, +} as const satisfies Translation diff --git a/quartz/i18n/locales/ro-RO.ts b/quartz/i18n/locales/ro-RO.ts new file mode 100644 index 0000000..556b189 --- /dev/null +++ b/quartz/i18n/locales/ro-RO.ts @@ -0,0 +1,84 @@ +import { Translation } from "./definition" + +export default { + propertyDefaults: { + title: "Fără titlu", + description: "Nici o descriere furnizată", + }, + components: { + callout: { + note: "Notă", + abstract: "Rezumat", + info: "Informație", + todo: "De făcut", + tip: "Sfat", + success: "Succes", + question: "Întrebare", + warning: "Avertisment", + failure: "Eșec", + danger: "Pericol", + bug: "Bug", + example: "Exemplu", + quote: "Citat", + }, + backlinks: { + title: "Legături înapoi", + noBacklinksFound: "Nu s-au găsit legături înapoi", + }, + themeToggle: { + lightMode: "Modul luminos", + darkMode: "Modul întunecat", + }, + explorer: { + title: "Explorator", + }, + footer: { + createdWith: "Creat cu", + }, + graph: { + title: "Graf", + }, + recentNotes: { + title: "Notițe recente", + seeRemainingMore: ({ remaining }) => `Vezi încă ${remaining} →`, + }, + transcludes: { + transcludeOf: ({ targetSlug }) => `Extras din ${targetSlug}`, + linkToOriginal: "Legătură către original", + }, + search: { + title: "Căutare", + searchBarPlaceholder: "Introduceți termenul de căutare...", + }, + tableOfContents: { + title: "Cuprins", + }, + contentMeta: { + readingTime: ({ minutes }) => + minutes == 1 ? `lectură de 1 minut` : `lectură de ${minutes} minute`, + }, + }, + pages: { + rss: { + recentNotes: "Notițe recente", + lastFewNotes: ({ count }) => `Ultimele ${count} notițe`, + }, + error: { + title: "Pagina nu a fost găsită", + notFound: "Fie această pagină este privată, fie nu există.", + }, + folderContent: { + folder: "Dosar", + itemsUnderFolder: ({ count }) => + count === 1 ? "1 articol în acest dosar." : `${count} elemente în acest dosar.`, + }, + tagContent: { + tag: "Etichetă", + tagIndex: "Indexul etichetelor", + itemsUnderTag: ({ count }) => + count === 1 ? "1 articol cu această etichetă." : `${count} articole cu această etichetă.`, + showingFirst: ({ count }) => `Se afișează primele ${count} etichete.`, + totalTags: ({ count }) => `Au fost găsite ${count} etichete în total.`, + }, + }, +} as const satisfies Translation diff --git a/quartz/i18n/locales/ru-RU.ts b/quartz/i18n/locales/ru-RU.ts new file mode 100644 index 0000000..8ead3ca --- /dev/null +++ b/quartz/i18n/locales/ru-RU.ts @@ -0,0 +1,95 @@ +import { Translation } from "./definition" + +export default { + propertyDefaults: { + title: "Без названия", + description: "Описание отсутствует", + }, + components: { + callout: { + note: "Заметка", + abstract: "Резюме", + info: "Инфо", + todo: "Сделать", + tip: "Подсказка", + success: "Успех", + question: "Вопрос", + warning: "Предупреждение", + failure: "Неудача", + danger: "Опасность", + bug: "Баг", + example: "Пример", + quote: "Цитата", + }, + backlinks: { + title: "Обратные ссылки", + noBacklinksFound: "Обратные ссылки отсутствуют", + }, + themeToggle: { + lightMode: "Светлый режим", + darkMode: "Тёмный режим", + }, + explorer: { + title: "Проводник", + }, + footer: { + createdWith: "Создано с помощью", + }, + graph: { + title: "Вид графа", + }, + recentNotes: { + title: "Недавние заметки", + seeRemainingMore: ({ remaining }) => + `Посмотреть оставш${getForm(remaining, "уюся", "иеся", "иеся")} ${remaining} →`, + }, + transcludes: { + transcludeOf: ({ targetSlug }) => `Переход из ${targetSlug}`, + linkToOriginal: "Ссылка на оригинал", + }, + search: { + title: "Поиск", + searchBarPlaceholder: "Найти что-нибудь", + }, + tableOfContents: { + title: "Оглавление", + }, + contentMeta: { + readingTime: ({ minutes }) => `время чтения ~${minutes} мин.`, + }, + }, + pages: { + rss: { + recentNotes: "Недавние заметки", + lastFewNotes: ({ count }) => + `Последн${getForm(count, "яя", "ие", "ие")} ${count} замет${getForm(count, "ка", "ки", "ок")}`, + }, + error: { + title: "Страница не найдена", + notFound: "Эта страница приватная или не существует", + }, + folderContent: { + folder: "Папка", + itemsUnderFolder: ({ count }) => + `в этой папке ${count} элемент${getForm(count, "", "а", "ов")}`, + }, + tagContent: { + tag: "Тег", + tagIndex: "Индекс тегов", + itemsUnderTag: ({ count }) => `с этим тегом ${count} элемент${getForm(count, "", "а", "ов")}`, + showingFirst: ({ count }) => + `Показыва${getForm(count, "ется", "ются", "ются")} ${count} тег${getForm(count, "", "а", "ов")}`, + totalTags: ({ count }) => `Всего ${count} тег${getForm(count, "", "а", "ов")}`, + }, + }, +} as const satisfies Translation + +function getForm(number: number, form1: string, form2: string, form5: string): string { + const remainder100 = number % 100 + const remainder10 = remainder100 % 10 + + if (remainder100 >= 10 && remainder100 <= 20) return form5 + if (remainder10 > 1 && remainder10 < 5) return form2 + if (remainder10 == 1) return form1 + return form5 +} diff --git a/quartz/i18n/locales/uk-UA.ts b/quartz/i18n/locales/uk-UA.ts new file mode 100644 index 0000000..b636938 --- /dev/null +++ b/quartz/i18n/locales/uk-UA.ts @@ -0,0 +1,83 @@ +import { Translation } from "./definition" + +export default { + propertyDefaults: { + title: "Без назви", + description: "Опис не надано", + }, + components: { + callout: { + note: "Примітка", + abstract: "Абстракт", + info: "Інформація", + todo: "Завдання", + tip: "Порада", + success: "Успіх", + question: "Питання", + warning: "Попередження", + failure: "Невдача", + danger: "Небезпека", + bug: "Баг", + example: "Приклад", + quote: "Цитата", + }, + backlinks: { + title: "Зворотні посилання", + noBacklinksFound: "Зворотних посилань не знайдено", + }, + themeToggle: { + lightMode: "Світлий режим", + darkMode: "Темний режим", + }, + explorer: { + title: "Провідник", + }, + footer: { + createdWith: "Створено за допомогою", + }, + graph: { + title: "Вигляд графа", + }, + recentNotes: { + title: "Останні нотатки", + seeRemainingMore: ({ remaining }) => `Переглянути ще ${remaining} →`, + }, + transcludes: { + transcludeOf: ({ targetSlug }) => `Видобуто з ${targetSlug}`, + linkToOriginal: "Посилання на оригінал", + }, + search: { + title: "Пошук", + searchBarPlaceholder: "Шукати щось", + }, + tableOfContents: { + title: "Зміст", + }, + contentMeta: { + readingTime: ({ minutes }) => `${minutes} min read`, + }, + }, + pages: { + rss: { + recentNotes: "Останні нотатки", + lastFewNotes: ({ count }) => `Останні нотатки: ${count}`, + }, + error: { + title: "Не знайдено", + notFound: "Ця сторінка або приватна, або не існує.", + }, + folderContent: { + folder: "Папка", + itemsUnderFolder: ({ count }) => + count === 1 ? "У цій папці 1 елемент." : `Елементів у цій папці: ${count}.`, + }, + tagContent: { + tag: "Тег", + tagIndex: "Індекс тегу", + itemsUnderTag: ({ count }) => + count === 1 ? "1 елемент з цим тегом." : `Елементів з цим тегом: ${count}.`, + showingFirst: ({ count }) => `Показ перших ${count} тегів.`, + totalTags: ({ count }) => `Всього знайдено тегів: ${count}.`, + }, + }, +} as const satisfies Translation diff --git a/quartz/i18n/locales/vi-VN.ts b/quartz/i18n/locales/vi-VN.ts new file mode 100644 index 0000000..b72ced4 --- /dev/null +++ b/quartz/i18n/locales/vi-VN.ts @@ -0,0 +1,83 @@ +import { Translation } from "./definition" + +export default { + propertyDefaults: { + title: "Không có tiêu đề", + description: "Không có mô tả được cung cấp", + }, + components: { + callout: { + note: "Ghi Chú", + abstract: "Tóm Tắt", + info: "Thông tin", + todo: "Cần Làm", + tip: "Gợi Ý", + success: "Thành Công", + question: "Nghi Vấn", + warning: "Cảnh Báo", + failure: "Thất Bại", + danger: "Nguy Hiểm", + bug: "Lỗi", + example: "Ví Dụ", + quote: "Trích Dẫn", + }, + backlinks: { + title: "Liên Kết Ngược", + noBacklinksFound: "Không có liên kết ngược được tìm thấy", + }, + themeToggle: { + lightMode: "Sáng", + darkMode: "Tối", + }, + explorer: { + title: "Trong bài này", + }, + footer: { + createdWith: "Được tạo bởi", + }, + graph: { + title: "Biểu Đồ", + }, + recentNotes: { + title: "Bài viết gần đây", + seeRemainingMore: ({ remaining }) => `Xem ${remaining} thêm →`, + }, + transcludes: { + transcludeOf: ({ targetSlug }) => `Bao gồm ${targetSlug}`, + linkToOriginal: "Liên Kết Gốc", + }, + search: { + title: "Tìm Kiếm", + searchBarPlaceholder: "Tìm kiếm thông tin", + }, + tableOfContents: { + title: "Bảng Nội Dung", + }, + contentMeta: { + readingTime: ({ minutes }) => `đọc ${minutes} phút`, + }, + }, + pages: { + rss: { + recentNotes: "Những bài gần đây", + lastFewNotes: ({ count }) => `${count} Bài gần đây`, + }, + error: { + title: "Không Tìm Thấy", + notFound: "Trang này được bảo mật hoặc không tồn tại.", + }, + folderContent: { + folder: "Thư Mục", + itemsUnderFolder: ({ count }) => + count === 1 ? "1 mục trong thư mục này." : `${count} mục trong thư mục này.`, + }, + tagContent: { + tag: "Thẻ", + tagIndex: "Thẻ Mục Lục", + itemsUnderTag: ({ count }) => + count === 1 ? "1 mục gắn thẻ này." : `${count} mục gắn thẻ này.`, + showingFirst: ({ count }) => `Hiển thị trước ${count} thẻ.`, + totalTags: ({ count }) => `Tìm thấy ${count} thẻ tổng cộng.`, + }, + }, +} as const satisfies Translation diff --git a/quartz/i18n/locales/zh-CN.ts b/quartz/i18n/locales/zh-CN.ts new file mode 100644 index 0000000..43d0111 --- /dev/null +++ b/quartz/i18n/locales/zh-CN.ts @@ -0,0 +1,81 @@ +import { Translation } from "./definition" + +export default { + propertyDefaults: { + title: "无题", + description: "无描述", + }, + components: { + callout: { + note: "笔记", + abstract: "摘要", + info: "提示", + todo: "待办", + tip: "提示", + success: "成功", + question: "问题", + warning: "警告", + failure: "失败", + danger: "危险", + bug: "错误", + example: "示例", + quote: "引用", + }, + backlinks: { + title: "反向链接", + noBacklinksFound: "无法找到反向链接", + }, + themeToggle: { + lightMode: "亮色模式", + darkMode: "暗色模式", + }, + explorer: { + title: "探索", + }, + footer: { + createdWith: "Created with", + }, + graph: { + title: "关系图谱", + }, + recentNotes: { + title: "最近的笔记", + seeRemainingMore: ({ remaining }) => `查看更多${remaining}篇笔记 →`, + }, + transcludes: { + transcludeOf: ({ targetSlug }) => `包含${targetSlug}`, + linkToOriginal: "指向原始笔记的链接", + }, + search: { + title: "搜索", + searchBarPlaceholder: "搜索些什么", + }, + tableOfContents: { + title: "目录", + }, + contentMeta: { + readingTime: ({ minutes }) => `${minutes}分钟阅读`, + }, + }, + pages: { + rss: { + recentNotes: "最近的笔记", + lastFewNotes: ({ count }) => `最近的${count}条笔记`, + }, + error: { + title: "无法找到", + notFound: "私有笔记或笔记不存在。", + }, + folderContent: { + folder: "文件夹", + itemsUnderFolder: ({ count }) => `此文件夹下有${count}条笔记。`, + }, + tagContent: { + tag: "标签", + tagIndex: "标签索引", + itemsUnderTag: ({ count }) => `此标签下有${count}条笔记。`, + showingFirst: ({ count }) => `显示前${count}个标签。`, + totalTags: ({ count }) => `总共有${count}个标签。`, + }, + }, +} as const satisfies Translation diff --git a/quartz/plugins/emitters/404.tsx b/quartz/plugins/emitters/404.tsx new file mode 100644 index 0000000..e4605cf --- /dev/null +++ b/quartz/plugins/emitters/404.tsx @@ -0,0 +1,68 @@ +import { QuartzEmitterPlugin } from "../types" +import { QuartzComponentProps } from "../../components/types" +import BodyConstructor from "../../components/Body" +import { pageResources, renderPage } from "../../components/renderPage" +import { FullPageLayout } from "../../cfg" +import { FilePath, FullSlug } from "../../util/path" +import { sharedPageComponents } from "../../../quartz.layout" +import { NotFound } from "../../components" +import { defaultProcessedContent } from "../vfile" +import { write } from "./helpers" +import { i18n } from "../../i18n" +import DepGraph from "../../depgraph" + +export const NotFoundPage: QuartzEmitterPlugin = () => { + const opts: FullPageLayout = { + ...sharedPageComponents, + pageBody: NotFound(), + beforeBody: [], + left: [], + right: [], + } + + const { head: Head, pageBody, footer: Footer } = opts + const Body = BodyConstructor() + + return { + name: "404Page", + getQuartzComponents() { + return [Head, Body, pageBody, Footer] + }, + async getDependencyGraph(_ctx, _content, _resources) { + return new DepGraph() + }, + async emit(ctx, _content, resources): Promise { + const cfg = ctx.cfg.configuration + const slug = "404" as FullSlug + + const url = new URL(`https://${cfg.baseUrl ?? "example.com"}`) + const path = url.pathname as FullSlug + const externalResources = pageResources(path, resources) + const notFound = i18n(cfg.locale).pages.error.title + const [tree, vfile] = defaultProcessedContent({ + slug, + text: notFound, + description: notFound, + frontmatter: { title: notFound, tags: [] }, + }) + const componentData: QuartzComponentProps = { + ctx, + fileData: vfile.data, + externalResources, + cfg, + children: [], + tree, + allFiles: [], + } + + return [ + await write({ + ctx, + content: renderPage(cfg, slug, componentData, opts, externalResources), + slug, + ext: ".html", + }), + ] + }, + } +} diff --git a/quartz/plugins/emitters/aliases.ts b/quartz/plugins/emitters/aliases.ts new file mode 100644 index 0000000..af3578e --- /dev/null +++ b/quartz/plugins/emitters/aliases.ts @@ -0,0 +1,81 @@ +import { FilePath, FullSlug, joinSegments, resolveRelative, simplifySlug } from "../../util/path" +import { QuartzEmitterPlugin } from "../types" +import path from "path" +import { write } from "./helpers" +import DepGraph from "../../depgraph" + +export const AliasRedirects: QuartzEmitterPlugin = () => ({ + name: "AliasRedirects", + getQuartzComponents() { + return [] + }, + async getDependencyGraph(ctx, content, _resources) { + const graph = new DepGraph() + + 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 + } + + graph.addEdge(file.data.filePath!, joinSegments(argv.output, slug + ".html") as FilePath) + } + } + + return graph + }, + async emit(ctx, content, _resources): Promise { + const { argv } = ctx + const fps: FilePath[] = [] + + 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 + } + + const redirUrl = resolveRelative(slug, file.data.slug!) + const fp = await write({ + ctx, + content: ` + + + + ${ogSlug} + + + + + + + `, + slug, + ext: ".html", + }) + + fps.push(fp) + } + } + return fps + }, +}) diff --git a/quartz/plugins/emitters/assets.ts b/quartz/plugins/emitters/assets.ts new file mode 100644 index 0000000..036b27d --- /dev/null +++ b/quartz/plugins/emitters/assets.ts @@ -0,0 +1,58 @@ +import { FilePath, joinSegments, slugifyFilePath } from "../../util/path" +import { QuartzEmitterPlugin } from "../types" +import path from "path" +import fs from "fs" +import { glob } from "../../util/glob" +import DepGraph from "../../depgraph" +import { Argv } from "../../util/ctx" +import { QuartzConfig } from "../../cfg" + +const filesToCopy = async (argv: Argv, cfg: QuartzConfig) => { + // glob all non MD files in content folder and copy it over + return await glob("**", argv.directory, ["**/*.md", ...cfg.configuration.ignorePatterns]) +} + +export const Assets: QuartzEmitterPlugin = () => { + return { + name: "Assets", + getQuartzComponents() { + return [] + }, + async getDependencyGraph(ctx, _content, _resources) { + const { argv, cfg } = ctx + const graph = new DepGraph() + + const fps = await filesToCopy(argv, cfg) + + for (const fp of fps) { + const ext = path.extname(fp) + const src = joinSegments(argv.directory, fp) as FilePath + const name = (slugifyFilePath(fp as FilePath, true) + ext) as FilePath + + const dest = joinSegments(argv.output, name) as FilePath + + graph.addEdge(src, dest) + } + + return graph + }, + async emit({ argv, cfg }, _content, _resources): Promise { + const assetsPath = argv.output + const fps = await filesToCopy(argv, cfg) + const res: FilePath[] = [] + for (const fp of fps) { + const ext = path.extname(fp) + const src = joinSegments(argv.directory, fp) as FilePath + const name = (slugifyFilePath(fp as FilePath, true) + ext) as FilePath + + const dest = joinSegments(assetsPath, name) as FilePath + const dir = path.dirname(dest) as FilePath + await fs.promises.mkdir(dir, { recursive: true }) // ensure dir exists + await fs.promises.copyFile(src, dest) + res.push(dest) + } + + return res + }, + } +} diff --git a/quartz/plugins/emitters/cname.ts b/quartz/plugins/emitters/cname.ts new file mode 100644 index 0000000..cbed2a8 --- /dev/null +++ b/quartz/plugins/emitters/cname.ts @@ -0,0 +1,33 @@ +import { FilePath, joinSegments } from "../../util/path" +import { QuartzEmitterPlugin } from "../types" +import fs from "fs" +import chalk from "chalk" +import DepGraph from "../../depgraph" + +export function extractDomainFromBaseUrl(baseUrl: string) { + const url = new URL(`https://${baseUrl}`) + return url.hostname +} + +export const CNAME: QuartzEmitterPlugin = () => ({ + name: "CNAME", + getQuartzComponents() { + return [] + }, + async getDependencyGraph(_ctx, _content, _resources) { + return new DepGraph() + }, + async emit({ argv, cfg }, _content, _resources): Promise { + if (!cfg.configuration.baseUrl) { + console.warn(chalk.yellow("CNAME emitter requires `baseUrl` to be set in your configuration")) + return [] + } + const path = joinSegments(argv.output, "CNAME") + const content = extractDomainFromBaseUrl(cfg.configuration.baseUrl) + if (!content) { + return [] + } + fs.writeFileSync(path, content) + return [path] as FilePath[] + }, +}) diff --git a/quartz/plugins/emitters/componentResources.ts b/quartz/plugins/emitters/componentResources.ts new file mode 100644 index 0000000..0bccb60 --- /dev/null +++ b/quartz/plugins/emitters/componentResources.ts @@ -0,0 +1,258 @@ +import { FilePath, FullSlug, joinSegments } from "../../util/path" +import { QuartzEmitterPlugin } from "../types" + +// @ts-ignore +import spaRouterScript from "../../components/scripts/spa.inline" +// @ts-ignore +import popoverScript from "../../components/scripts/popover.inline" +import styles from "../../styles/custom.scss" +import popoverStyle from "../../components/styles/popover.scss" +import { BuildCtx } from "../../util/ctx" +import { QuartzComponent } from "../../components/types" +import { googleFontHref, joinStyles } from "../../util/theme" +import { Features, transform } from "lightningcss" +import { transform as transpile } from "esbuild" +import { write } from "./helpers" +import DepGraph from "../../depgraph" + +type ComponentResources = { + css: string[] + beforeDOMLoaded: string[] + afterDOMLoaded: string[] +} + +function getComponentResources(ctx: BuildCtx): ComponentResources { + const allComponents: Set = new Set() + for (const emitter of ctx.cfg.plugins.emitters) { + const components = emitter.getQuartzComponents(ctx) + for (const component of components) { + allComponents.add(component) + } + } + + const componentResources = { + css: new Set(), + beforeDOMLoaded: new Set(), + afterDOMLoaded: new Set(), + } + + for (const component of allComponents) { + const { css, beforeDOMLoaded, afterDOMLoaded } = component + if (css) { + componentResources.css.add(css) + } + if (beforeDOMLoaded) { + componentResources.beforeDOMLoaded.add(beforeDOMLoaded) + } + if (afterDOMLoaded) { + componentResources.afterDOMLoaded.add(afterDOMLoaded) + } + } + + return { + css: [...componentResources.css], + beforeDOMLoaded: [...componentResources.beforeDOMLoaded], + afterDOMLoaded: [...componentResources.afterDOMLoaded], + } +} + +async function joinScripts(scripts: string[]): Promise { + // wrap with iife to prevent scope collision + const script = scripts.map((script) => `(function () {${script}})();`).join("\n") + + // minify with esbuild + const res = await transpile(script, { + minify: true, + }) + + return res.code +} + +function addGlobalPageResources(ctx: BuildCtx, componentResources: ComponentResources) { + const cfg = ctx.cfg.configuration + + // popovers + if (cfg.enablePopovers) { + componentResources.afterDOMLoaded.push(popoverScript) + componentResources.css.push(popoverStyle) + } + + if (cfg.analytics?.provider === "google") { + const tagId = cfg.analytics.tagId + componentResources.afterDOMLoaded.push(` + const gtagScript = document.createElement("script") + gtagScript.src = "https://www.googletagmanager.com/gtag/js?id=${tagId}" + gtagScript.async = true + document.head.appendChild(gtagScript) + + window.dataLayer = window.dataLayer || []; + function gtag() { dataLayer.push(arguments); } + gtag("js", new Date()); + gtag("config", "${tagId}", { send_page_view: false }); + + document.addEventListener("nav", () => { + gtag("event", "page_view", { + page_title: document.title, + page_location: location.href, + }); + });`) + } else if (cfg.analytics?.provider === "plausible") { + const plausibleHost = cfg.analytics.host ?? "https://plausible.io" + componentResources.afterDOMLoaded.push(` + const plausibleScript = document.createElement("script") + plausibleScript.src = "${plausibleHost}/js/script.manual.js" + plausibleScript.setAttribute("data-domain", location.hostname) + plausibleScript.defer = true + document.head.appendChild(plausibleScript) + + window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) } + + document.addEventListener("nav", () => { + plausible("pageview") + }) + `) + } else if (cfg.analytics?.provider === "umami") { + componentResources.afterDOMLoaded.push(` + 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.async = true + + document.head.appendChild(umamiScript) + `) + } 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) + `) + } + + if (cfg.enableSPA) { + componentResources.afterDOMLoaded.push(spaRouterScript) + } else { + componentResources.afterDOMLoaded.push(` + window.spaNavigate = (url, _) => window.location.assign(url) + window.addCleanup = () => {} + const event = new CustomEvent("nav", { detail: { url: document.body.dataset.slug } }) + document.dispatchEvent(event) + `) + } +} + +// This emitter should not update the `resources` parameter. If it does, partial +// rebuilds may not work as expected. +export const ComponentResources: QuartzEmitterPlugin = () => { + return { + name: "ComponentResources", + getQuartzComponents() { + return [] + }, + async getDependencyGraph(_ctx, _content, _resources) { + return new DepGraph() + }, + async emit(ctx, _content, _resources): Promise { + const promises: Promise[] = [] + const cfg = ctx.cfg.configuration + // component specific scripts and styles + const componentResources = getComponentResources(ctx) + let googleFontsStyleSheet = "" + if (cfg.theme.fontOrigin === "local") { + // let the user do it themselves in css + } else if (cfg.theme.fontOrigin === "googleFonts" && !cfg.theme.cdnCaching) { + // when cdnCaching is true, we link to google fonts in Head.tsx + let match + + const fontSourceRegex = /url\((https:\/\/fonts.gstatic.com\/s\/[^)]+\.(woff2|ttf))\)/g + + googleFontsStyleSheet = await ( + await fetch(googleFontHref(ctx.cfg.configuration.theme)) + ).text() + + while ((match = fontSourceRegex.exec(googleFontsStyleSheet)) !== null) { + // match[0] is the `url(path)`, match[1] is the `path` + const url = match[1] + // the static name of this file. + const [filename, ext] = url.split("/").pop()!.split(".") + + googleFontsStyleSheet = googleFontsStyleSheet.replace( + url, + `https://${cfg.baseUrl}/static/fonts/${filename}.ttf`, + ) + + promises.push( + fetch(url) + .then((res) => { + if (!res.ok) { + throw new Error(`Failed to fetch font`) + } + return res.arrayBuffer() + }) + .then((buf) => + write({ + ctx, + slug: joinSegments("static", "fonts", filename) as FullSlug, + ext: `.${ext}`, + content: Buffer.from(buf), + }), + ), + ) + } + } + + // important that this goes *after* component scripts + // as the "nav" event gets triggered here and we should make sure + // that everyone else had the chance to register a listener for it + addGlobalPageResources(ctx, componentResources) + + const stylesheet = joinStyles( + ctx.cfg.configuration.theme, + googleFontsStyleSheet, + ...componentResources.css, + styles, + ) + const [prescript, postscript] = await Promise.all([ + joinScripts(componentResources.beforeDOMLoaded), + joinScripts(componentResources.afterDOMLoaded), + ]) + + promises.push( + write({ + ctx, + slug: "index" as FullSlug, + ext: ".css", + content: transform({ + filename: "index.css", + code: Buffer.from(stylesheet), + minify: true, + targets: { + safari: (15 << 16) | (6 << 8), // 15.6 + ios_saf: (15 << 16) | (6 << 8), // 15.6 + edge: 115 << 16, + firefox: 102 << 16, + chrome: 109 << 16, + }, + include: Features.MediaQueries, + }).code.toString(), + }), + write({ + ctx, + slug: "prescript" as FullSlug, + ext: ".js", + content: prescript, + }), + write({ + ctx, + slug: "postscript" as FullSlug, + ext: ".js", + content: postscript, + }), + ) + + return await Promise.all(promises) + }, + } +} diff --git a/quartz/plugins/emitters/contentIndex.ts b/quartz/plugins/emitters/contentIndex.ts new file mode 100644 index 0000000..c0fef86 --- /dev/null +++ b/quartz/plugins/emitters/contentIndex.ts @@ -0,0 +1,185 @@ +import { Root } from "hast" +import { GlobalConfiguration } from "../../cfg" +import { getDate } from "../../components/Date" +import { escapeHTML } from "../../util/escape" +import { FilePath, FullSlug, SimpleSlug, joinSegments, simplifySlug } from "../../util/path" +import { QuartzEmitterPlugin } from "../types" +import { toHtml } from "hast-util-to-html" +import { write } from "./helpers" +import { i18n } from "../../i18n" +import DepGraph from "../../depgraph" + +export type ContentIndex = Map +export type ContentDetails = { + title: string + links: SimpleSlug[] + tags: string[] + content: string + richContent?: string + date?: Date + description?: string +} + +interface Options { + enableSiteMap: boolean + enableRSS: boolean + rssLimit?: number + rssFullHtml: boolean + includeEmptyFiles: boolean +} + +const defaultOptions: Options = { + enableSiteMap: true, + enableRSS: true, + rssLimit: 10, + rssFullHtml: false, + includeEmptyFiles: true, +} + +function generateSiteMap(cfg: GlobalConfiguration, idx: ContentIndex): string { + const base = cfg.baseUrl ?? "" + const createURLEntry = (slug: SimpleSlug, content: ContentDetails): string => ` + https://${joinSegments(base, encodeURI(slug))} + ${content.date && `${content.date.toISOString()}`} + ` + const urls = Array.from(idx) + .map(([slug, content]) => createURLEntry(simplifySlug(slug), content)) + .join("") + return `${urls}` +} + +function generateRSSFeed(cfg: GlobalConfiguration, idx: ContentIndex, limit?: number): string { + const base = cfg.baseUrl ?? "" + + const createURLEntry = (slug: SimpleSlug, content: ContentDetails): string => ` + ${escapeHTML(content.title)} + https://${joinSegments(base, encodeURI(slug))} + https://${joinSegments(base, encodeURI(slug))} + ${content.richContent ?? content.description} + ${content.date?.toUTCString()} + ` + + const items = Array.from(idx) + .sort(([_, f1], [__, f2]) => { + if (f1.date && f2.date) { + return f2.date.getTime() - f1.date.getTime() + } else if (f1.date && !f2.date) { + return -1 + } else if (!f1.date && f2.date) { + return 1 + } + + return f1.title.localeCompare(f2.title) + }) + .map(([slug, content]) => createURLEntry(simplifySlug(slug), content)) + .slice(0, limit ?? idx.size) + .join("") + + return ` + + + ${escapeHTML(cfg.pageTitle)} + https://${base} + ${!!limit ? i18n(cfg.locale).pages.rss.lastFewNotes({ count: limit }) : i18n(cfg.locale).pages.rss.recentNotes} on ${escapeHTML( + cfg.pageTitle, + )} + Quartz -- quartz.jzhao.xyz + ${items} + + ` +} + +export const ContentIndex: QuartzEmitterPlugin> = (opts) => { + opts = { ...defaultOptions, ...opts } + return { + name: "ContentIndex", + async getDependencyGraph(ctx, content, _resources) { + const graph = new DepGraph() + + for (const [_tree, file] of content) { + const sourcePath = file.data.filePath! + + graph.addEdge( + sourcePath, + joinSegments(ctx.argv.output, "static/contentIndex.json") as FilePath, + ) + if (opts?.enableSiteMap) { + graph.addEdge(sourcePath, joinSegments(ctx.argv.output, "sitemap.xml") as FilePath) + } + if (opts?.enableRSS) { + graph.addEdge(sourcePath, joinSegments(ctx.argv.output, "index.xml") as FilePath) + } + } + + return graph + }, + async emit(ctx, content, _resources) { + const cfg = ctx.cfg.configuration + const emitted: FilePath[] = [] + const linkIndex: ContentIndex = new Map() + for (const [tree, file] of content) { + const slug = file.data.slug! + const date = getDate(ctx.cfg.configuration, file.data) ?? new Date() + if (opts?.includeEmptyFiles || (file.data.text && file.data.text !== "")) { + linkIndex.set(slug, { + title: file.data.frontmatter?.title!, + links: file.data.links ?? [], + tags: file.data.frontmatter?.tags ?? [], + content: file.data.text ?? "", + richContent: opts?.rssFullHtml + ? escapeHTML(toHtml(tree as Root, { allowDangerousHtml: true })) + : undefined, + date: date, + description: file.data.description ?? "", + }) + } + } + + if (opts?.enableSiteMap) { + emitted.push( + await write({ + ctx, + content: generateSiteMap(cfg, linkIndex), + slug: "sitemap" as FullSlug, + ext: ".xml", + }), + ) + } + + if (opts?.enableRSS) { + emitted.push( + await write({ + ctx, + content: generateRSSFeed(cfg, linkIndex, opts.rssLimit), + slug: "index" as FullSlug, + ext: ".xml", + }), + ) + } + + const fp = joinSegments("static", "contentIndex") as FullSlug + const simplifiedIndex = Object.fromEntries( + Array.from(linkIndex).map(([slug, content]) => { + // remove description and from content index as nothing downstream + // actually uses it. we only keep it in the index as we need it + // for the RSS feed + delete content.description + delete content.date + return [slug, content] + }), + ) + + emitted.push( + await write({ + ctx, + content: JSON.stringify(simplifiedIndex), + slug: fp, + ext: ".json", + }), + ) + + return emitted + }, + getQuartzComponents: () => [], + } +} diff --git a/quartz/plugins/emitters/contentPage.tsx b/quartz/plugins/emitters/contentPage.tsx new file mode 100644 index 0000000..f493802 --- /dev/null +++ b/quartz/plugins/emitters/contentPage.tsx @@ -0,0 +1,131 @@ +import path from "path" +import { visit } from "unist-util-visit" +import { Root } from "hast" +import { VFile } from "vfile" +import { QuartzEmitterPlugin } from "../types" +import { QuartzComponentProps } from "../../components/types" +import HeaderConstructor from "../../components/Header" +import BodyConstructor from "../../components/Body" +import { pageResources, renderPage } from "../../components/renderPage" +import { FullPageLayout } from "../../cfg" +import { Argv } from "../../util/ctx" +import { FilePath, isRelativeURL, joinSegments, pathToRoot } from "../../util/path" +import { defaultContentPageLayout, sharedPageComponents } from "../../../quartz.layout" +import { Content } from "../../components" +import chalk from "chalk" +import { write } from "./helpers" +import DepGraph from "../../depgraph" + +// get all the dependencies for the markdown file +// eg. images, scripts, stylesheets, transclusions +const parseDependencies = (argv: Argv, hast: Root, file: VFile): string[] => { + const dependencies: string[] = [] + + visit(hast, "element", (elem): void => { + let ref: string | null = null + + if ( + ["script", "img", "audio", "video", "source", "iframe"].includes(elem.tagName) && + elem?.properties?.src + ) { + ref = elem.properties.src.toString() + } else if (["a", "link"].includes(elem.tagName) && elem?.properties?.href) { + // transclusions will create a tags with relative hrefs + ref = elem.properties.href.toString() + } + + // if it is a relative url, its a local file and we need to add + // it to the dependency graph. otherwise, ignore + if (ref === null || !isRelativeURL(ref)) { + return + } + + let fp = path.join(file.data.filePath!, path.relative(argv.directory, ref)).replace(/\\/g, "/") + // markdown files have the .md extension stripped in hrefs, add it back here + if (!fp.split("/").pop()?.includes(".")) { + fp += ".md" + } + dependencies.push(fp) + }) + + return dependencies +} + +export const ContentPage: QuartzEmitterPlugin> = (userOpts) => { + const opts: FullPageLayout = { + ...sharedPageComponents, + ...defaultContentPageLayout, + pageBody: Content(), + ...userOpts, + } + + const { head: Head, header, beforeBody, pageBody, left, right, footer: Footer } = opts + const Header = HeaderConstructor() + const Body = BodyConstructor() + + return { + name: "ContentPage", + getQuartzComponents() { + return [Head, Header, Body, ...header, ...beforeBody, pageBody, ...left, ...right, Footer] + }, + async getDependencyGraph(ctx, content, _resources) { + const graph = new DepGraph() + + for (const [tree, file] of content) { + const sourcePath = file.data.filePath! + const slug = file.data.slug! + graph.addEdge(sourcePath, joinSegments(ctx.argv.output, slug + ".html") as FilePath) + + parseDependencies(ctx.argv, tree as Root, file).forEach((dep) => { + graph.addEdge(dep as FilePath, sourcePath) + }) + } + + return graph + }, + async emit(ctx, content, resources): Promise { + const cfg = ctx.cfg.configuration + const fps: FilePath[] = [] + const allFiles = content.map((c) => c[1].data) + + let containsIndex = false + for (const [tree, file] of content) { + const slug = file.data.slug! + if (slug === "index") { + containsIndex = true + } + + const externalResources = pageResources(pathToRoot(slug), resources) + const componentData: QuartzComponentProps = { + ctx, + fileData: file.data, + externalResources, + cfg, + children: [], + tree, + allFiles, + } + + const content = renderPage(cfg, slug, componentData, opts, externalResources) + const fp = await write({ + ctx, + content, + slug, + ext: ".html", + }) + + fps.push(fp) + } + + 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.`, + ), + ) + } + + return fps + }, + } +} diff --git a/quartz/plugins/emitters/folderPage.tsx b/quartz/plugins/emitters/folderPage.tsx new file mode 100644 index 0000000..d892b28 --- /dev/null +++ b/quartz/plugins/emitters/folderPage.tsx @@ -0,0 +1,120 @@ +import { QuartzEmitterPlugin } from "../types" +import { QuartzComponentProps } from "../../components/types" +import HeaderConstructor from "../../components/Header" +import BodyConstructor from "../../components/Body" +import { pageResources, renderPage } from "../../components/renderPage" +import { ProcessedContent, defaultProcessedContent } from "../vfile" +import { FullPageLayout } from "../../cfg" +import path from "path" +import { + FilePath, + FullSlug, + SimpleSlug, + stripSlashes, + joinSegments, + pathToRoot, + simplifySlug, +} from "../../util/path" +import { defaultListPageLayout, sharedPageComponents } from "../../../quartz.layout" +import { FolderContent } from "../../components" +import { write } from "./helpers" +import { i18n } from "../../i18n" +import DepGraph from "../../depgraph" + +export const FolderPage: QuartzEmitterPlugin> = (userOpts) => { + const opts: FullPageLayout = { + ...sharedPageComponents, + ...defaultListPageLayout, + pageBody: FolderContent(), + ...userOpts, + } + + const { head: Head, header, beforeBody, pageBody, left, right, footer: Footer } = opts + const Header = HeaderConstructor() + const Body = BodyConstructor() + + return { + name: "FolderPage", + getQuartzComponents() { + return [Head, Header, Body, ...header, ...beforeBody, pageBody, ...left, ...right, Footer] + }, + async getDependencyGraph(_ctx, content, _resources) { + // Example graph: + // nested/file.md --> nested/index.html + // nested/file2.md ------^ + const graph = new DepGraph() + + content.map(([_tree, vfile]) => { + const slug = vfile.data.slug + const folderName = path.dirname(slug ?? "") as SimpleSlug + if (slug && folderName !== "." && folderName !== "tags") { + graph.addEdge(vfile.data.filePath!, joinSegments(folderName, "index.html") as FilePath) + } + }) + + return graph + }, + async emit(ctx, content, resources): Promise { + const fps: FilePath[] = [] + const allFiles = content.map((c) => c[1].data) + const cfg = ctx.cfg.configuration + + const folders: Set = new Set( + allFiles.flatMap((data) => { + const slug = data.slug + const folderName = path.dirname(slug ?? "") as SimpleSlug + if (slug && folderName !== "." && folderName !== "tags") { + return [folderName] + } + return [] + }), + ) + + const folderDescriptions: Record = Object.fromEntries( + [...folders].map((folder) => [ + folder, + defaultProcessedContent({ + slug: joinSegments(folder, "index") as FullSlug, + frontmatter: { + title: `${i18n(cfg.locale).pages.folderContent.folder}: ${folder}`, + tags: [], + }, + }), + ]), + ) + + for (const [tree, file] of content) { + const slug = stripSlashes(simplifySlug(file.data.slug!)) as SimpleSlug + if (folders.has(slug)) { + folderDescriptions[slug] = [tree, file] + } + } + + for (const folder of folders) { + const slug = joinSegments(folder, "index") as FullSlug + const externalResources = pageResources(pathToRoot(slug), resources) + const [tree, file] = folderDescriptions[folder] + const componentData: QuartzComponentProps = { + ctx, + fileData: file.data, + externalResources, + cfg, + children: [], + tree, + allFiles, + } + + const content = renderPage(cfg, slug, componentData, opts, externalResources) + const fp = await write({ + ctx, + content, + slug, + ext: ".html", + }) + + fps.push(fp) + } + return fps + }, + } +} diff --git a/quartz/plugins/emitters/helpers.ts b/quartz/plugins/emitters/helpers.ts new file mode 100644 index 0000000..523151c --- /dev/null +++ b/quartz/plugins/emitters/helpers.ts @@ -0,0 +1,19 @@ +import path from "path" +import fs from "fs" +import { BuildCtx } from "../../util/ctx" +import { FilePath, FullSlug, joinSegments } from "../../util/path" + +type WriteOptions = { + ctx: BuildCtx + slug: FullSlug + ext: `.${string}` | "" + content: string | Buffer +} + +export const write = async ({ ctx, slug, ext, content }: WriteOptions): Promise => { + const pathToPage = joinSegments(ctx.argv.output, slug + ext) as FilePath + const dir = path.dirname(pathToPage) + await fs.promises.mkdir(dir, { recursive: true }) + await fs.promises.writeFile(pathToPage, content) + return pathToPage +} diff --git a/quartz/plugins/emitters/index.ts b/quartz/plugins/emitters/index.ts new file mode 100644 index 0000000..bc378c4 --- /dev/null +++ b/quartz/plugins/emitters/index.ts @@ -0,0 +1,10 @@ +export { ContentPage } from "./contentPage" +export { TagPage } from "./tagPage" +export { FolderPage } from "./folderPage" +export { ContentIndex } from "./contentIndex" +export { AliasRedirects } from "./aliases" +export { Assets } from "./assets" +export { Static } from "./static" +export { ComponentResources } from "./componentResources" +export { NotFoundPage } from "./404" +export { CNAME } from "./cname" diff --git a/quartz/plugins/emitters/static.ts b/quartz/plugins/emitters/static.ts new file mode 100644 index 0000000..c52c628 --- /dev/null +++ b/quartz/plugins/emitters/static.ts @@ -0,0 +1,35 @@ +import { FilePath, QUARTZ, joinSegments } from "../../util/path" +import { QuartzEmitterPlugin } from "../types" +import fs from "fs" +import { glob } from "../../util/glob" +import DepGraph from "../../depgraph" + +export const Static: QuartzEmitterPlugin = () => ({ + name: "Static", + getQuartzComponents() { + return [] + }, + async getDependencyGraph({ argv, cfg }, _content, _resources) { + const graph = new DepGraph() + + const staticPath = joinSegments(QUARTZ, "static") + const fps = await glob("**", staticPath, cfg.configuration.ignorePatterns) + for (const fp of fps) { + graph.addEdge( + joinSegments("static", fp) as FilePath, + joinSegments(argv.output, "static", fp) as FilePath, + ) + } + + return graph + }, + async emit({ argv, cfg }, _content, _resources): Promise { + const staticPath = joinSegments(QUARTZ, "static") + const fps = await glob("**", staticPath, cfg.configuration.ignorePatterns) + await fs.promises.cp(staticPath, joinSegments(argv.output, "static"), { + recursive: true, + dereference: true, + }) + return fps.map((fp) => joinSegments(argv.output, "static", fp)) as FilePath[] + }, +}) diff --git a/quartz/plugins/emitters/tagPage.tsx b/quartz/plugins/emitters/tagPage.tsx new file mode 100644 index 0000000..d88d072 --- /dev/null +++ b/quartz/plugins/emitters/tagPage.tsx @@ -0,0 +1,124 @@ +import { QuartzEmitterPlugin } from "../types" +import { QuartzComponentProps } from "../../components/types" +import HeaderConstructor from "../../components/Header" +import BodyConstructor from "../../components/Body" +import { pageResources, renderPage } from "../../components/renderPage" +import { ProcessedContent, defaultProcessedContent } from "../vfile" +import { FullPageLayout } from "../../cfg" +import { + FilePath, + FullSlug, + getAllSegmentPrefixes, + joinSegments, + pathToRoot, +} from "../../util/path" +import { defaultListPageLayout, sharedPageComponents } from "../../../quartz.layout" +import { TagContent } from "../../components" +import { write } from "./helpers" +import { i18n } from "../../i18n" +import DepGraph from "../../depgraph" + +export const TagPage: QuartzEmitterPlugin> = (userOpts) => { + const opts: FullPageLayout = { + ...sharedPageComponents, + ...defaultListPageLayout, + pageBody: TagContent(), + ...userOpts, + } + + const { head: Head, header, beforeBody, pageBody, left, right, footer: Footer } = opts + const Header = HeaderConstructor() + const Body = BodyConstructor() + + return { + name: "TagPage", + getQuartzComponents() { + return [Head, Header, Body, ...header, ...beforeBody, pageBody, ...left, ...right, Footer] + }, + async getDependencyGraph(ctx, content, _resources) { + const graph = new DepGraph() + + for (const [_tree, file] of content) { + const sourcePath = file.data.filePath! + const tags = (file.data.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes) + // if the file has at least one tag, it is used in the tag index page + if (tags.length > 0) { + tags.push("index") + } + + for (const tag of tags) { + graph.addEdge( + sourcePath, + joinSegments(ctx.argv.output, "tags", tag + ".html") as FilePath, + ) + } + } + + return graph + }, + async emit(ctx, content, resources): Promise { + const fps: FilePath[] = [] + const allFiles = content.map((c) => c[1].data) + const cfg = ctx.cfg.configuration + + const tags: Set = new Set( + allFiles.flatMap((data) => data.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes), + ) + + // add base tag + tags.add("index") + + const tagDescriptions: Record = Object.fromEntries( + [...tags].map((tag) => { + const title = + tag === "index" + ? i18n(cfg.locale).pages.tagContent.tagIndex + : `${i18n(cfg.locale).pages.tagContent.tag}: ${tag}` + return [ + tag, + defaultProcessedContent({ + slug: joinSegments("tags", tag) as FullSlug, + frontmatter: { title, tags: [] }, + }), + ] + }), + ) + + for (const [tree, file] of content) { + const slug = file.data.slug! + if (slug.startsWith("tags/")) { + const tag = slug.slice("tags/".length) + if (tags.has(tag)) { + tagDescriptions[tag] = [tree, file] + } + } + } + + for (const tag of tags) { + const slug = joinSegments("tags", tag) as FullSlug + const externalResources = pageResources(pathToRoot(slug), resources) + const [tree, file] = tagDescriptions[tag] + const componentData: QuartzComponentProps = { + ctx, + fileData: file.data, + externalResources, + cfg, + children: [], + tree, + allFiles, + } + + const content = renderPage(cfg, slug, componentData, opts, externalResources) + const fp = await write({ + ctx, + content, + slug: file.data.slug!, + ext: ".html", + }) + + fps.push(fp) + } + return fps + }, + } +} diff --git a/quartz/plugins/filters/draft.ts b/quartz/plugins/filters/draft.ts new file mode 100644 index 0000000..65e2d6b --- /dev/null +++ b/quartz/plugins/filters/draft.ts @@ -0,0 +1,9 @@ +import { QuartzFilterPlugin } from "../types" + +export const RemoveDrafts: QuartzFilterPlugin<{}> = () => ({ + name: "RemoveDrafts", + shouldPublish(_ctx, [_tree, vfile]) { + const draftFlag: boolean = vfile.data?.frontmatter?.draft ?? false + return !draftFlag + }, +}) diff --git a/quartz/plugins/filters/explicit.ts b/quartz/plugins/filters/explicit.ts new file mode 100644 index 0000000..79a46a8 --- /dev/null +++ b/quartz/plugins/filters/explicit.ts @@ -0,0 +1,8 @@ +import { QuartzFilterPlugin } from "../types" + +export const ExplicitPublish: QuartzFilterPlugin = () => ({ + name: "ExplicitPublish", + shouldPublish(_ctx, [_tree, vfile]) { + return vfile.data?.frontmatter?.publish ?? false + }, +}) diff --git a/quartz/plugins/filters/index.ts b/quartz/plugins/filters/index.ts new file mode 100644 index 0000000..d937143 --- /dev/null +++ b/quartz/plugins/filters/index.ts @@ -0,0 +1,2 @@ +export { RemoveDrafts } from "./draft" +export { ExplicitPublish } from "./explicit" diff --git a/quartz/plugins/index.ts b/quartz/plugins/index.ts new file mode 100644 index 0000000..554b117 --- /dev/null +++ b/quartz/plugins/index.ts @@ -0,0 +1,52 @@ +import { StaticResources } from "../util/resources" +import { FilePath, FullSlug } from "../util/path" +import { BuildCtx } from "../util/ctx" + +export function getStaticResourcesFromPlugins(ctx: BuildCtx) { + const staticResources: StaticResources = { + css: [], + js: [], + } + + for (const transformer of ctx.cfg.plugins.transformers) { + const res = transformer.externalResources ? transformer.externalResources(ctx) : {} + if (res?.js) { + staticResources.js.push(...res.js) + } + if (res?.css) { + staticResources.css.push(...res.css) + } + } + + // if serving locally, listen for rebuilds and reload the page + if (ctx.argv.serve) { + const wsUrl = ctx.argv.remoteDevHost + ? `wss://${ctx.argv.remoteDevHost}:${ctx.argv.wsPort}` + : `ws://localhost:${ctx.argv.wsPort}` + + staticResources.js.push({ + loadTime: "afterDOMReady", + contentType: "inline", + script: ` + const socket = new WebSocket('${wsUrl}') + // reload(true) ensures resources like images and scripts are fetched again in firefox + socket.addEventListener('message', () => document.location.reload(true)) + `, + }) + } + + return staticResources +} + +export * from "./transformers" +export * from "./filters" +export * from "./emitters" + +declare module "vfile" { + // inserted in processors.ts + interface DataMap { + slug: FullSlug + filePath: FilePath + relativePath: FilePath + } +} diff --git a/quartz/plugins/transformers/citations.ts b/quartz/plugins/transformers/citations.ts new file mode 100644 index 0000000..bb302e4 --- /dev/null +++ b/quartz/plugins/transformers/citations.ts @@ -0,0 +1,52 @@ +import rehypeCitation from "rehype-citation" +import { PluggableList } from "unified" +import { visit } from "unist-util-visit" +import { QuartzTransformerPlugin } from "../types" + +export interface Options { + bibliographyFile: string + suppressBibliography: boolean + linkCitations: boolean + csl: string +} + +const defaultOptions: Options = { + bibliographyFile: "./bibliography.bib", + suppressBibliography: false, + linkCitations: false, + csl: "apa", +} + +export const Citations: QuartzTransformerPlugin | undefined> = (userOpts) => { + const opts = { ...defaultOptions, ...userOpts } + return { + name: "Citations", + htmlPlugins() { + const plugins: PluggableList = [] + + // Add rehype-citation to the list of plugins + plugins.push([ + rehypeCitation, + { + bibliography: opts.bibliographyFile, + suppressBibliography: opts.suppressBibliography, + linkCitations: opts.linkCitations, + }, + ]) + + // Transform the HTML of the citattions; add data-no-popover property to the citation links + // using https://github.com/syntax-tree/unist-util-visit as they're just anochor links + plugins.push(() => { + return (tree, _file) => { + visit(tree, "element", (node, index, parent) => { + if (node.tagName === "a" && node.properties?.href?.startsWith("#bib")) { + node.properties["data-no-popover"] = true + } + }) + } + }) + + return plugins + }, + } +} diff --git a/quartz/plugins/transformers/description.ts b/quartz/plugins/transformers/description.ts new file mode 100644 index 0000000..5900745 --- /dev/null +++ b/quartz/plugins/transformers/description.ts @@ -0,0 +1,82 @@ +import { Root as HTMLRoot } from "hast" +import { toString } from "hast-util-to-string" +import { QuartzTransformerPlugin } from "../types" +import { escapeHTML } from "../../util/escape" + +export interface Options { + descriptionLength: number + replaceExternalLinks: boolean +} + +const defaultOptions: Options = { + descriptionLength: 150, + replaceExternalLinks: true, +} + +const urlRegex = new RegExp( + /(https?:\/\/)?(?([\da-z\.-]+)\.([a-z\.]{2,6})(:\d+)?)(?[\/\w\.-]*)(\?[\/\w\.=&;-]*)?/, + "g", +) + +export const Description: QuartzTransformerPlugin | undefined> = (userOpts) => { + const opts = { ...defaultOptions, ...userOpts } + return { + name: "Description", + htmlPlugins() { + return [ + () => { + return async (tree: HTMLRoot, file) => { + let frontMatterDescription = file.data.frontmatter?.description + let text = escapeHTML(toString(tree)) + + if (opts.replaceExternalLinks) { + frontMatterDescription = frontMatterDescription?.replace( + urlRegex, + "$" + "$", + ) + text = text.replace(urlRegex, "$" + "$") + } + + const desc = frontMatterDescription ?? text + const sentences = desc.replace(/\s+/g, " ").split(/\.\s/) + const finalDesc: string[] = [] + const len = opts.descriptionLength + let sentenceIdx = 0 + let currentDescriptionLength = 0 + + if (sentences[0] !== undefined && sentences[0].length >= len) { + const firstSentence = sentences[0].split(" ") + while (currentDescriptionLength < len) { + const sentence = firstSentence[sentenceIdx] + if (!sentence) break + finalDesc.push(sentence) + currentDescriptionLength += sentence.length + sentenceIdx++ + } + finalDesc.push("...") + } else { + while (currentDescriptionLength < len) { + const sentence = sentences[sentenceIdx] + if (!sentence) break + const currentSentence = sentence.endsWith(".") ? sentence : sentence + "." + finalDesc.push(currentSentence) + currentDescriptionLength += currentSentence.length + sentenceIdx++ + } + } + + file.data.description = finalDesc.join(" ") + file.data.text = text + } + }, + ] + }, + } +} + +declare module "vfile" { + interface DataMap { + description: string + text: string + } +} diff --git a/quartz/plugins/transformers/frontmatter.ts b/quartz/plugins/transformers/frontmatter.ts new file mode 100644 index 0000000..5ab239a --- /dev/null +++ b/quartz/plugins/transformers/frontmatter.ts @@ -0,0 +1,98 @@ +import matter from "gray-matter" +import remarkFrontmatter from "remark-frontmatter" +import { QuartzTransformerPlugin } from "../types" +import yaml from "js-yaml" +import toml from "toml" +import { slugTag } from "../../util/path" +import { QuartzPluginData } from "../vfile" +import { i18n } from "../../i18n" + +export interface Options { + delimiters: string | [string, string] + language: "yaml" | "toml" +} + +const defaultOptions: Options = { + delimiters: "---", + language: "yaml", +} + +function coalesceAliases(data: { [key: string]: any }, aliases: string[]) { + for (const alias of aliases) { + if (data[alias] !== undefined && data[alias] !== null) return data[alias] + } +} + +function coerceToArray(input: string | string[]): string[] | undefined { + if (input === undefined || input === null) return undefined + + // coerce to array + if (!Array.isArray(input)) { + input = input + .toString() + .split(",") + .map((tag: string) => tag.trim()) + } + + // remove all non-strings + return input + .filter((tag: unknown) => typeof tag === "string" || typeof tag === "number") + .map((tag: string | number) => tag.toString()) +} + +export const FrontMatter: QuartzTransformerPlugin | undefined> = (userOpts) => { + const opts = { ...defaultOptions, ...userOpts } + return { + name: "FrontMatter", + markdownPlugins({ cfg }) { + return [ + [remarkFrontmatter, ["yaml", "toml"]], + () => { + return (_, file) => { + const { data } = matter(Buffer.from(file.value), { + ...opts, + engines: { + yaml: (s) => yaml.load(s, { schema: yaml.JSON_SCHEMA }) as object, + toml: (s) => toml.parse(s) as object, + }, + }) + + if (data.title != null && data.title.toString() !== "") { + data.title = data.title.toString() + } else { + data.title = file.stem ?? i18n(cfg.configuration.locale).propertyDefaults.title + } + + const tags = coerceToArray(coalesceAliases(data, ["tags", "tag"])) + if (tags) data.tags = [...new Set(tags.map((tag: string) => slugTag(tag)))] + + const aliases = coerceToArray(coalesceAliases(data, ["aliases", "alias"])) + if (aliases) data.aliases = aliases + const cssclasses = coerceToArray(coalesceAliases(data, ["cssclasses", "cssclass"])) + if (cssclasses) data.cssclasses = cssclasses + + // fill in frontmatter + file.data.frontmatter = data as QuartzPluginData["frontmatter"] + } + }, + ] + }, + } +} + +declare module "vfile" { + interface DataMap { + frontmatter: { [key: string]: unknown } & { + title: string + } & Partial<{ + tags: string[] + aliases: string[] + description: string + publish: boolean + draft: boolean + lang: string + enableToc: string + cssclasses: string[] + }> + } +} diff --git a/quartz/plugins/transformers/gfm.ts b/quartz/plugins/transformers/gfm.ts new file mode 100644 index 0000000..48681ff --- /dev/null +++ b/quartz/plugins/transformers/gfm.ts @@ -0,0 +1,80 @@ +import remarkGfm from "remark-gfm" +import smartypants from "remark-smartypants" +import { QuartzTransformerPlugin } from "../types" +import rehypeSlug from "rehype-slug" +import rehypeAutolinkHeadings from "rehype-autolink-headings" + +export interface Options { + enableSmartyPants: boolean + linkHeadings: boolean +} + +const defaultOptions: Options = { + enableSmartyPants: true, + linkHeadings: true, +} + +export const GitHubFlavoredMarkdown: QuartzTransformerPlugin | undefined> = ( + userOpts, +) => { + const opts = { ...defaultOptions, ...userOpts } + return { + name: "GitHubFlavoredMarkdown", + markdownPlugins() { + return opts.enableSmartyPants ? [remarkGfm, smartypants] : [remarkGfm] + }, + htmlPlugins() { + if (opts.linkHeadings) { + return [ + rehypeSlug, + [ + rehypeAutolinkHeadings, + { + behavior: "append", + properties: { + role: "anchor", + ariaHidden: true, + tabIndex: -1, + "data-no-popover": true, + }, + content: { + type: "element", + tagName: "svg", + properties: { + width: 18, + height: 18, + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round", + }, + children: [ + { + type: "element", + tagName: "path", + properties: { + d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71", + }, + children: [], + }, + { + type: "element", + tagName: "path", + properties: { + d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71", + }, + children: [], + }, + ], + }, + }, + ], + ] + } else { + return [] + } + }, + } +} diff --git a/quartz/plugins/transformers/index.ts b/quartz/plugins/transformers/index.ts new file mode 100644 index 0000000..7908c86 --- /dev/null +++ b/quartz/plugins/transformers/index.ts @@ -0,0 +1,12 @@ +export { FrontMatter } from "./frontmatter" +export { GitHubFlavoredMarkdown } from "./gfm" +export { Citations } from "./citations" +export { CreatedModifiedDate } from "./lastmod" +export { Latex } from "./latex" +export { Description } from "./description" +export { CrawlLinks } from "./links" +export { ObsidianFlavoredMarkdown } from "./ofm" +export { OxHugoFlavouredMarkdown } from "./oxhugofm" +export { SyntaxHighlighting } from "./syntax" +export { TableOfContents } from "./toc" +export { HardLineBreaks } from "./linebreaks" diff --git a/quartz/plugins/transformers/lastmod.ts b/quartz/plugins/transformers/lastmod.ts new file mode 100644 index 0000000..2c7b9ce --- /dev/null +++ b/quartz/plugins/transformers/lastmod.ts @@ -0,0 +1,99 @@ +import fs from "fs" +import path from "path" +import { Repository } from "@napi-rs/simple-git" +import { QuartzTransformerPlugin } from "../types" +import chalk from "chalk" + +export interface Options { + priority: ("frontmatter" | "git" | "filesystem")[] +} + +const defaultOptions: Options = { + priority: ["frontmatter", "git", "filesystem"], +} + +function coerceDate(fp: string, d: any): Date { + const dt = new Date(d) + const invalidDate = isNaN(dt.getTime()) || dt.getTime() === 0 + if (invalidDate && d !== undefined) { + console.log( + chalk.yellow( + `\nWarning: found invalid date "${d}" in \`${fp}\`. Supported formats: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format`, + ), + ) + } + + return invalidDate ? new Date() : dt +} + +type MaybeDate = undefined | string | number +export const CreatedModifiedDate: QuartzTransformerPlugin | undefined> = ( + userOpts, +) => { + const opts = { ...defaultOptions, ...userOpts } + return { + name: "CreatedModifiedDate", + markdownPlugins() { + return [ + () => { + let repo: Repository | undefined = undefined + return async (_tree, file) => { + let created: MaybeDate = undefined + let modified: MaybeDate = undefined + let published: MaybeDate = undefined + + const fp = file.data.filePath! + const fullFp = path.isAbsolute(fp) ? fp : path.posix.join(file.cwd, fp) + for (const source of opts.priority) { + if (source === "filesystem") { + const st = await fs.promises.stat(fullFp) + created ||= st.birthtimeMs + modified ||= st.mtimeMs + } else if (source === "frontmatter" && file.data.frontmatter) { + created ||= file.data.frontmatter.date as MaybeDate + modified ||= file.data.frontmatter.lastmod as MaybeDate + modified ||= file.data.frontmatter.updated as MaybeDate + modified ||= file.data.frontmatter["last-modified"] as MaybeDate + published ||= file.data.frontmatter.publishDate as MaybeDate + } else if (source === "git") { + if (!repo) { + // Get a reference to the main git repo. + // It's either the same as the workdir, + // or 1+ level higher in case of a submodule/subtree setup + repo = Repository.discover(file.cwd) + } + + try { + modified ||= await repo.getFileLatestModifiedDateAsync(file.data.filePath!) + } catch { + console.log( + chalk.yellow( + `\nWarning: ${file.data + .filePath!} isn't yet tracked by git, last modification date is not available for this file`, + ), + ) + } + } + } + + file.data.dates = { + created: coerceDate(fp, created), + modified: coerceDate(fp, modified), + published: coerceDate(fp, published), + } + } + }, + ] + }, + } +} + +declare module "vfile" { + interface DataMap { + dates: { + created: Date + modified: Date + published: Date + } + } +} diff --git a/quartz/plugins/transformers/latex.ts b/quartz/plugins/transformers/latex.ts new file mode 100644 index 0000000..c9f6bff --- /dev/null +++ b/quartz/plugins/transformers/latex.ts @@ -0,0 +1,45 @@ +import remarkMath from "remark-math" +import rehypeKatex from "rehype-katex" +import rehypeMathjax from "rehype-mathjax/svg" +import { QuartzTransformerPlugin } from "../types" + +interface Options { + renderEngine: "katex" | "mathjax" +} + +export const Latex: QuartzTransformerPlugin = (opts?: Options) => { + const engine = opts?.renderEngine ?? "katex" + return { + name: "Latex", + markdownPlugins() { + return [remarkMath] + }, + htmlPlugins() { + if (engine === "katex") { + return [[rehypeKatex, { output: "html" }]] + } else { + return [rehypeMathjax] + } + }, + externalResources() { + if (engine === "katex") { + return { + css: [ + // base css + "https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/katex.min.css", + ], + js: [ + { + // fix copy behaviour: https://github.com/KaTeX/KaTeX/blob/main/contrib/copy-tex/README.md + src: "https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/contrib/copy-tex.min.js", + loadTime: "afterDOMReady", + contentType: "external", + }, + ], + } + } else { + return {} + } + }, + } +} diff --git a/quartz/plugins/transformers/linebreaks.ts b/quartz/plugins/transformers/linebreaks.ts new file mode 100644 index 0000000..a8a066f --- /dev/null +++ b/quartz/plugins/transformers/linebreaks.ts @@ -0,0 +1,11 @@ +import { QuartzTransformerPlugin } from "../types" +import remarkBreaks from "remark-breaks" + +export const HardLineBreaks: QuartzTransformerPlugin = () => { + return { + name: "HardLineBreaks", + markdownPlugins() { + return [remarkBreaks] + }, + } +} diff --git a/quartz/plugins/transformers/links.ts b/quartz/plugins/transformers/links.ts new file mode 100644 index 0000000..f89d367 --- /dev/null +++ b/quartz/plugins/transformers/links.ts @@ -0,0 +1,171 @@ +import { QuartzTransformerPlugin } from "../types" +import { + FullSlug, + RelativeURL, + SimpleSlug, + TransformOptions, + stripSlashes, + simplifySlug, + splitAnchor, + transformLink, + joinSegments, +} from "../../util/path" +import path from "path" +import { visit } from "unist-util-visit" +import isAbsoluteUrl from "is-absolute-url" +import { Root } from "hast" + +interface Options { + /** How to resolve Markdown paths */ + markdownLinkResolution: TransformOptions["strategy"] + /** Strips folders from a link so that it looks nice */ + prettyLinks: boolean + openLinksInNewTab: boolean + lazyLoad: boolean + externalLinkIcon: boolean +} + +const defaultOptions: Options = { + markdownLinkResolution: "absolute", + prettyLinks: true, + openLinksInNewTab: false, + lazyLoad: false, + externalLinkIcon: true, +} + +export const CrawlLinks: QuartzTransformerPlugin | undefined> = (userOpts) => { + const opts = { ...defaultOptions, ...userOpts } + return { + name: "LinkProcessing", + htmlPlugins(ctx) { + return [ + () => { + return (tree: Root, file) => { + const curSlug = simplifySlug(file.data.slug!) + const outgoing: Set = new Set() + + const transformOptions: TransformOptions = { + strategy: opts.markdownLinkResolution, + allSlugs: ctx.allSlugs, + } + + visit(tree, "element", (node, _index, _parent) => { + // rewrite all links + if ( + node.tagName === "a" && + node.properties && + typeof node.properties.href === "string" + ) { + let dest = node.properties.href as RelativeURL + const classes = (node.properties.className ?? []) as string[] + const isExternal = isAbsoluteUrl(dest) + classes.push(isExternal ? "external" : "internal") + + if (isExternal && opts.externalLinkIcon) { + node.children.push({ + type: "element", + tagName: "svg", + properties: { + class: "external-icon", + viewBox: "0 0 512 512", + }, + children: [ + { + type: "element", + tagName: "path", + properties: { + d: "M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z", + }, + children: [], + }, + ], + }) + } + + // Check if the link has alias text + if ( + node.children.length === 1 && + node.children[0].type === "text" && + node.children[0].value !== dest + ) { + // Add the 'alias' class if the text content is not the same as the href + classes.push("alias") + } + node.properties.className = classes + + if (opts.openLinksInNewTab) { + node.properties.target = "_blank" + } + + // don't process external links or intra-document anchors + const isInternal = !(isAbsoluteUrl(dest) || dest.startsWith("#")) + if (isInternal) { + dest = node.properties.href = transformLink( + file.data.slug!, + dest, + transformOptions, + ) + + // url.resolve is considered legacy + // WHATWG equivalent https://nodejs.dev/en/api/v18/url/#urlresolvefrom-to + const url = new URL(dest, "https://base.com/" + stripSlashes(curSlug, true)) + const canonicalDest = url.pathname + let [destCanonical, _destAnchor] = splitAnchor(canonicalDest) + if (destCanonical.endsWith("/")) { + destCanonical += "index" + } + + // need to decodeURIComponent here as WHATWG URL percent-encodes everything + const full = decodeURIComponent(stripSlashes(destCanonical, true)) as FullSlug + const simple = simplifySlug(full) + outgoing.add(simple) + node.properties["data-slug"] = full + } + + // rewrite link internals if prettylinks is on + if ( + opts.prettyLinks && + isInternal && + node.children.length === 1 && + node.children[0].type === "text" && + !node.children[0].value.startsWith("#") + ) { + node.children[0].value = path.basename(node.children[0].value) + } + } + + // transform all other resources that may use links + if ( + ["img", "video", "audio", "iframe"].includes(node.tagName) && + node.properties && + typeof node.properties.src === "string" + ) { + if (opts.lazyLoad) { + node.properties.loading = "lazy" + } + + if (!isAbsoluteUrl(node.properties.src)) { + let dest = node.properties.src as RelativeURL + dest = node.properties.src = transformLink( + file.data.slug!, + dest, + transformOptions, + ) + node.properties.src = dest + } + } + }) + + file.data.links = [...outgoing] + } + }, + ] + }, + } +} + +declare module "vfile" { + interface DataMap { + links: SimpleSlug[] + } +} diff --git a/quartz/plugins/transformers/ofm.ts b/quartz/plugins/transformers/ofm.ts new file mode 100644 index 0000000..108f7f7 --- /dev/null +++ b/quartz/plugins/transformers/ofm.ts @@ -0,0 +1,712 @@ +import { QuartzTransformerPlugin } from "../types" +import { Root, Html, BlockContent, DefinitionContent, Paragraph, Code } from "mdast" +import { Element, Literal, Root as HtmlRoot } from "hast" +import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" +import { slug as slugAnchor } from "github-slugger" +import rehypeRaw from "rehype-raw" +import { SKIP, visit } from "unist-util-visit" +import path from "path" +import { JSResource } from "../../util/resources" +// @ts-ignore +import calloutScript from "../../components/scripts/callout.inline.ts" +// @ts-ignore +import checkboxScript from "../../components/scripts/checkbox.inline.ts" +import { FilePath, pathToRoot, slugTag, slugifyFilePath } from "../../util/path" +import { toHast } from "mdast-util-to-hast" +import { toHtml } from "hast-util-to-html" +import { PhrasingContent } from "mdast-util-find-and-replace/lib" +import { capitalize } from "../../util/lang" +import { PluggableList } from "unified" + +export interface Options { + comments: boolean + highlight: boolean + wikilinks: boolean + callouts: boolean + mermaid: boolean + parseTags: boolean + parseArrows: boolean + parseBlockReferences: boolean + enableInHtmlEmbed: boolean + enableYouTubeEmbed: boolean + enableVideoEmbed: boolean + enableCheckbox: boolean +} + +const defaultOptions: Options = { + comments: true, + highlight: true, + wikilinks: true, + callouts: true, + mermaid: true, + parseTags: true, + parseArrows: true, + parseBlockReferences: true, + enableInHtmlEmbed: false, + enableYouTubeEmbed: true, + enableVideoEmbed: true, + enableCheckbox: false, +} + +const calloutMapping = { + note: "note", + abstract: "abstract", + summary: "abstract", + tldr: "abstract", + info: "info", + todo: "todo", + tip: "tip", + hint: "tip", + important: "tip", + success: "success", + check: "success", + done: "success", + question: "question", + help: "question", + faq: "question", + warning: "warning", + attention: "warning", + caution: "warning", + failure: "failure", + missing: "failure", + fail: "failure", + danger: "danger", + error: "danger", + bug: "bug", + example: "example", + quote: "quote", + cite: "quote", +} as const + +const arrowMapping: Record = { + "->": "→", + "-->": "⇒", + "=>": "⇒", + "==>": "⇒", + "<-": "←", + "<--": "⇐", + "<=": "⇐", + "<==": "⇐", +} + +function canonicalizeCallout(calloutName: string): keyof typeof calloutMapping { + const normalizedCallout = calloutName.toLowerCase() as keyof typeof calloutMapping + // if callout is not recognized, make it a custom one + return calloutMapping[normalizedCallout] ?? calloutName +} + +export const externalLinkRegex = /^https?:\/\//i + +export const arrowRegex = new RegExp(/(-{1,2}>|={1,2}>|<-{1,2}|<={1,2})/, "g") + +// !? -> optional embedding +// \[\[ -> open brace +// ([^\[\]\|\#]+) -> one or more non-special characters ([,],|, or #) (name) +// (#[^\[\]\|\#]+)? -> # then one or more non-special characters (heading link) +// (\\?\|[^\[\]\#]+)? -> optional escape \ then | then one or more non-special characters (alias) +export const wikilinkRegex = new RegExp( + /!?\[\[([^\[\]\|\#\\]+)?(#+[^\[\]\|\#\\]+)?(\\?\|[^\[\]\#]+)?\]\]/, + "g", +) + +// ^\|([^\n])+\|\n(\|) -> matches the header row +// ( ?:?-{3,}:? ?\|)+ -> matches the header row separator +// (\|([^\n])+\|\n)+ -> matches the body rows +export const tableRegex = new RegExp( + /^\|([^\n])+\|\n(\|)( ?:?-{3,}:? ?\|)+\n(\|([^\n])+\|\n?)+/, + "gm", +) + +// matches any wikilink, only used for escaping wikilinks inside tables +export const tableWikilinkRegex = new RegExp(/(!?\[\[[^\]]*?\]\])/, "g") + +const highlightRegex = new RegExp(/==([^=]+)==/, "g") +const commentRegex = new RegExp(/%%[\s\S]*?%%/, "g") +// from https://github.com/escwxyz/remark-obsidian-callout/blob/main/src/index.ts +const calloutRegex = new RegExp(/^\[\!(\w+)\]([+-]?)/) +const calloutLineRegex = new RegExp(/^> *\[\!\w+\][+-]?.*$/, "gm") +// (?:^| ) -> non-capturing group, tag should start be separated by a space or be the start of the line +// #(...) -> capturing group, tag itself must start with # +// (?:[-_\p{L}\d\p{Z}])+ -> non-capturing group, non-empty string of (Unicode-aware) alpha-numeric characters and symbols, hyphens and/or underscores +// (?:\/[-_\p{L}\d\p{Z}]+)*) -> non-capturing group, matches an arbitrary number of tag strings separated by "/" +const tagRegex = new RegExp( + /(?:^| )#((?:[-_\p{L}\p{Emoji}\p{M}\d])+(?:\/[-_\p{L}\p{Emoji}\p{M}\d]+)*)/, + "gu", +) +const blockReferenceRegex = new RegExp(/\^([-_A-Za-z0-9]+)$/, "g") +const ytLinkRegex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/ +const ytPlaylistLinkRegex = /[?&]list=([^#?&]*)/ +const videoExtensionRegex = new RegExp(/\.(mp4|webm|ogg|avi|mov|flv|wmv|mkv|mpg|mpeg|3gp|m4v)$/) +const wikilinkImageEmbedRegex = new RegExp( + /^(?(?!^\d*x?\d*$).*?)?(\|?\s*?(?\d+)(x(?\d+))?)?$/, +) + +export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin | undefined> = ( + userOpts, +) => { + const opts = { ...defaultOptions, ...userOpts } + + const mdastToHtml = (ast: PhrasingContent | Paragraph) => { + const hast = toHast(ast, { allowDangerousHtml: true })! + return toHtml(hast, { allowDangerousHtml: true }) + } + + return { + name: "ObsidianFlavoredMarkdown", + textTransform(_ctx, src) { + // do comments at text level + if (opts.comments) { + if (src instanceof Buffer) { + src = src.toString() + } + + src = src.replace(commentRegex, "") + } + + // pre-transform blockquotes + if (opts.callouts) { + if (src instanceof Buffer) { + src = src.toString() + } + + src = src.replace(calloutLineRegex, (value) => { + // force newline after title of callout + return value + "\n> " + }) + } + + // 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.replace(tableRegex, (value) => { + // escape all aliases and headers in wikilinks inside a table + return value.replace(tableWikilinkRegex, (value, ...capture) => { + const [raw]: (string | undefined)[] = capture + let escaped = raw ?? "" + escaped = escaped.replace("#", "\\#") + // escape pipe characters if they are not already escaped + escaped = escaped.replace(/((^|[^\\])(\\\\)*)\|/g, "$1\\|") + + return escaped + }) + }) + + // replace all other wikilinks + src = src.replace(wikilinkRegex, (value, ...capture) => { + const [rawFp, rawHeader, rawAlias]: (string | undefined)[] = capture + + const fp = rawFp ?? "" + const anchor = rawHeader?.trim().replace(/^#+/, "") + const blockRef = Boolean(anchor?.startsWith("^")) ? "^" : "" + const displayAnchor = anchor ? `#${blockRef}${slugAnchor(anchor)}` : "" + const displayAlias = rawAlias ?? rawHeader?.replace("#", "|") ?? "" + const embedDisplay = value.startsWith("!") ? "!" : "" + + if (rawFp?.match(externalLinkRegex)) { + return `${embedDisplay}[${displayAlias.replace(/^\|/, "")}](${rawFp})` + } + + return `${embedDisplay}[[${fp}${displayAnchor}${displayAlias}]]` + }) + } + + return src + }, + markdownPlugins(_ctx) { + const plugins: PluggableList = [] + + // regex replacements + plugins.push(() => { + return (tree: Root, file) => { + const replacements: [RegExp, string | ReplaceFunction][] = [] + const base = pathToRoot(file.data.slug!) + + if (opts.wikilinks) { + replacements.push([ + wikilinkRegex, + (value: string, ...capture: string[]) => { + let [rawFp, rawHeader, rawAlias] = capture + const fp = rawFp?.trim() ?? "" + const anchor = rawHeader?.trim() ?? "" + const alias = rawAlias?.slice(1).trim() + + // embed cases + if (value.startsWith("!")) { + const ext: string = path.extname(fp).toLowerCase() + const url = slugifyFilePath(fp as FilePath) + if ([".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg", ".webp"].includes(ext)) { + const match = wikilinkImageEmbedRegex.exec(alias ?? "") + const alt = match?.groups?.alt ?? "" + const width = match?.groups?.width ?? "auto" + const height = match?.groups?.height ?? "auto" + return { + type: "image", + url, + data: { + hProperties: { + width, + height, + alt, + }, + }, + } + } else if ([".mp4", ".webm", ".ogv", ".mov", ".mkv"].includes(ext)) { + return { + type: "html", + value: ``, + } + } else if ( + [".mp3", ".webm", ".wav", ".m4a", ".ogg", ".3gp", ".flac"].includes(ext) + ) { + return { + type: "html", + value: ``, + } + } else if ([".pdf"].includes(ext)) { + return { + type: "html", + value: ``, + } + } else { + const block = anchor + return { + type: "html", + data: { hProperties: { transclude: true } }, + value: `
    Transclude of ${url}${block}
    `, + } + } + + // otherwise, fall through to regular link + } + + // internal link + const url = fp + anchor + return { + type: "link", + url, + children: [ + { + type: "text", + value: alias ?? fp, + }, + ], + } + }, + ]) + } + + if (opts.highlight) { + replacements.push([ + highlightRegex, + (_value: string, ...capture: string[]) => { + const [inner] = capture + return { + type: "html", + value: `${inner}`, + } + }, + ]) + } + + if (opts.parseArrows) { + replacements.push([ + arrowRegex, + (value: string, ..._capture: string[]) => { + const maybeArrow = arrowMapping[value] + if (maybeArrow === undefined) return SKIP + return { + type: "html", + value: `${maybeArrow}`, + } + }, + ]) + } + + if (opts.parseTags) { + replacements.push([ + tagRegex, + (_value: string, tag: string) => { + // Check if the tag only includes numbers + if (/^\d+$/.test(tag)) { + return false + } + + tag = slugTag(tag) + if (file.data.frontmatter) { + const noteTags = file.data.frontmatter.tags ?? [] + file.data.frontmatter.tags = [...new Set([...noteTags, tag])] + } + + return { + type: "link", + url: base + `/tags/${tag}`, + data: { + hProperties: { + className: ["tag-link"], + }, + }, + children: [ + { + type: "text", + value: tag, + }, + ], + } + }, + ]) + } + + if (opts.enableInHtmlEmbed) { + visit(tree, "html", (node: Html) => { + for (const [regex, replace] of replacements) { + if (typeof replace === "string") { + node.value = node.value.replace(regex, replace) + } else { + node.value = node.value.replace(regex, (substring: string, ...args) => { + const replaceValue = replace(substring, ...args) + if (typeof replaceValue === "string") { + return replaceValue + } else if (Array.isArray(replaceValue)) { + return replaceValue.map(mdastToHtml).join("") + } else if (typeof replaceValue === "object" && replaceValue !== null) { + return mdastToHtml(replaceValue) + } else { + return substring + } + }) + } + } + }) + } + mdastFindReplace(tree, replacements) + } + }) + + if (opts.enableVideoEmbed) { + plugins.push(() => { + return (tree: Root, _file) => { + visit(tree, "image", (node, index, parent) => { + if (parent && index != undefined && videoExtensionRegex.test(node.url)) { + const newNode: Html = { + type: "html", + value: ``, + } + + parent.children.splice(index, 1, newNode) + return SKIP + } + }) + } + }) + } + + if (opts.callouts) { + plugins.push(() => { + return (tree: Root, _file) => { + visit(tree, "blockquote", (node) => { + if (node.children.length === 0) { + return + } + + // find first line + const firstChild = node.children[0] + if (firstChild.type !== "paragraph" || firstChild.children[0]?.type !== "text") { + return + } + + const text = firstChild.children[0].value + const restOfTitle = firstChild.children.slice(1) + const [firstLine, ...remainingLines] = text.split("\n") + const remainingText = remainingLines.join("\n") + + const match = firstLine.match(calloutRegex) + if (match && match.input) { + const [calloutDirective, typeString, collapseChar] = match + const calloutType = canonicalizeCallout(typeString.toLowerCase()) + const collapse = collapseChar === "+" || collapseChar === "-" + const defaultState = collapseChar === "-" ? "collapsed" : "expanded" + const titleContent = match.input.slice(calloutDirective.length).trim() + const useDefaultTitle = titleContent === "" && restOfTitle.length === 0 + const titleNode: Paragraph = { + type: "paragraph", + children: [ + { + type: "text", + value: useDefaultTitle ? capitalize(typeString) : titleContent + " ", + }, + ...restOfTitle, + ], + } + const title = mdastToHtml(titleNode) + + const toggleIcon = `
    ` + + const titleHtml: Html = { + type: "html", + value: `
    +
    +
    ${title}
    + ${collapse ? toggleIcon : ""} +
    `, + } + + const blockquoteContent: (BlockContent | DefinitionContent)[] = [titleHtml] + if (remainingText.length > 0) { + blockquoteContent.push({ + type: "paragraph", + children: [ + { + type: "text", + value: remainingText, + }, + ], + }) + } + + // replace first line of blockquote with title and rest of the paragraph text + node.children.splice(0, 1, ...blockquoteContent) + + const classNames = ["callout", calloutType] + if (collapse) { + classNames.push("is-collapsible") + } + if (defaultState === "collapsed") { + classNames.push("is-collapsed") + } + + // add properties to base blockquote + node.data = { + hProperties: { + ...(node.data?.hProperties ?? {}), + className: classNames.join(" "), + "data-callout": calloutType, + "data-callout-fold": collapse, + }, + } + } + }) + } + }) + } + + if (opts.mermaid) { + plugins.push(() => { + return (tree: Root, _file) => { + visit(tree, "code", (node: Code) => { + if (node.lang === "mermaid") { + node.data = { + hProperties: { + className: ["mermaid"], + }, + } + } + }) + } + }) + } + + return plugins + }, + htmlPlugins() { + const plugins: PluggableList = [rehypeRaw] + + if (opts.parseBlockReferences) { + plugins.push(() => { + const inlineTagTypes = new Set(["p", "li"]) + const blockTagTypes = new Set(["blockquote"]) + return (tree: HtmlRoot, file) => { + file.data.blocks = {} + + visit(tree, "element", (node, index, parent) => { + if (blockTagTypes.has(node.tagName)) { + const nextChild = parent?.children.at(index! + 2) as Element + if (nextChild && nextChild.tagName === "p") { + const text = nextChild.children.at(0) as Literal + if (text && text.value && text.type === "text") { + const matches = text.value.match(blockReferenceRegex) + if (matches && matches.length >= 1) { + parent!.children.splice(index! + 2, 1) + const block = matches[0].slice(1) + + if (!Object.keys(file.data.blocks!).includes(block)) { + node.properties = { + ...node.properties, + id: block, + } + file.data.blocks![block] = node + } + } + } + } + } else if (inlineTagTypes.has(node.tagName)) { + const last = node.children.at(-1) as Literal + if (last && last.value && typeof last.value === "string") { + const matches = last.value.match(blockReferenceRegex) + if (matches && matches.length >= 1) { + last.value = last.value.slice(0, -matches[0].length) + const block = matches[0].slice(1) + + if (last.value === "") { + // this is an inline block ref but the actual block + // is the previous element above it + let idx = (index ?? 1) - 1 + while (idx >= 0) { + const element = parent?.children.at(idx) + if (!element) break + if (element.type !== "element") { + idx -= 1 + } else { + if (!Object.keys(file.data.blocks!).includes(block)) { + element.properties = { + ...element.properties, + id: block, + } + file.data.blocks![block] = element + } + return + } + } + } else { + // normal paragraph transclude + if (!Object.keys(file.data.blocks!).includes(block)) { + node.properties = { + ...node.properties, + id: block, + } + file.data.blocks![block] = node + } + } + } + } + } + }) + + file.data.htmlAst = tree + } + }) + } + + if (opts.enableYouTubeEmbed) { + plugins.push(() => { + return (tree: HtmlRoot) => { + visit(tree, "element", (node) => { + if (node.tagName === "img" && typeof node.properties.src === "string") { + const match = node.properties.src.match(ytLinkRegex) + const videoId = match && match[2].length == 11 ? match[2] : null + const playlistId = node.properties.src.match(ytPlaylistLinkRegex)?.[1] + if (videoId) { + // YouTube video (with optional playlist) + node.tagName = "iframe" + node.properties = { + class: "external-embed", + allow: "fullscreen", + frameborder: 0, + width: "600px", + height: "350px", + src: playlistId + ? `https://www.youtube.com/embed/${videoId}?list=${playlistId}` + : `https://www.youtube.com/embed/${videoId}`, + } + } else if (playlistId) { + // YouTube playlist only. + node.tagName = "iframe" + node.properties = { + class: "external-embed", + allow: "fullscreen", + frameborder: 0, + width: "600px", + height: "350px", + src: `https://www.youtube.com/embed/videoseries?list=${playlistId}`, + } + } + } + }) + } + }) + } + + if (opts.enableCheckbox) { + plugins.push(() => { + return (tree: HtmlRoot, _file) => { + visit(tree, "element", (node) => { + if (node.tagName === "input" && node.properties.type === "checkbox") { + const isChecked = node.properties?.checked ?? false + node.properties = { + type: "checkbox", + disabled: false, + checked: isChecked, + class: "checkbox-toggle", + } + } + }) + } + }) + } + + return plugins + }, + externalResources() { + const js: JSResource[] = [] + + if (opts.enableCheckbox) { + js.push({ + script: checkboxScript, + loadTime: "afterDOMReady", + contentType: "inline", + }) + } + + if (opts.callouts) { + js.push({ + script: calloutScript, + loadTime: "afterDOMReady", + contentType: "inline", + }) + } + + if (opts.mermaid) { + js.push({ + script: ` + let mermaidImport = undefined + document.addEventListener('nav', async () => { + if (document.querySelector("code.mermaid")) { + mermaidImport ||= await import('https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.7.0/mermaid.esm.min.mjs') + const mermaid = mermaidImport.default + const darkMode = document.documentElement.getAttribute('saved-theme') === 'dark' + mermaid.initialize({ + startOnLoad: false, + securityLevel: 'loose', + theme: darkMode ? 'dark' : 'default' + }) + + await mermaid.run({ + querySelector: '.mermaid' + }) + } + }); + `, + loadTime: "afterDOMReady", + moduleType: "module", + contentType: "inline", + }) + } + + return { js } + }, + } +} + +declare module "vfile" { + interface DataMap { + blocks: Record + htmlAst: HtmlRoot + } +} diff --git a/quartz/plugins/transformers/oxhugofm.ts b/quartz/plugins/transformers/oxhugofm.ts new file mode 100644 index 0000000..6e70bb1 --- /dev/null +++ b/quartz/plugins/transformers/oxhugofm.ts @@ -0,0 +1,108 @@ +import { QuartzTransformerPlugin } from "../types" + +export interface Options { + /** Replace {{ relref }} with quartz wikilinks []() */ + wikilinks: boolean + /** Remove pre-defined anchor (see https://ox-hugo.scripter.co/doc/anchors/) */ + removePredefinedAnchor: boolean + /** Remove hugo shortcode syntax */ + removeHugoShortcode: boolean + /** Replace
    with ![]() */ + replaceFigureWithMdImg: boolean + + /** Replace org latex fragments with $ and $$ */ + replaceOrgLatex: boolean +} + +const defaultOptions: Options = { + wikilinks: true, + removePredefinedAnchor: true, + removeHugoShortcode: true, + replaceFigureWithMdImg: true, + replaceOrgLatex: true, +} + +const relrefRegex = new RegExp(/\[([^\]]+)\]\(\{\{< relref "([^"]+)" >\}\}\)/, "g") +const predefinedHeadingIdRegex = new RegExp(/(.*) {#(?:.*)}/, "g") +const hugoShortcodeRegex = new RegExp(/{{(.*)}}/, "g") +const figureTagRegex = new RegExp(/< ?figure src="(.*)" ?>/, "g") +// \\\\\( -> matches \\( +// (.+?) -> Lazy match for capturing the equation +// \\\\\) -> matches \\) +const inlineLatexRegex = new RegExp(/\\\\\((.+?)\\\\\)/, "g") +// (?:\\begin{equation}|\\\\\(|\\\\\[) -> start of equation +// ([\s\S]*?) -> Matches the block equation +// (?:\\\\\]|\\\\\)|\\end{equation}) -> end of equation +const blockLatexRegex = new RegExp( + /(?:\\begin{equation}|\\\\\(|\\\\\[)([\s\S]*?)(?:\\\\\]|\\\\\)|\\end{equation})/, + "g", +) +// \$\$[\s\S]*?\$\$ -> Matches block equations +// \$.*?\$ -> Matches inline equations +const quartzLatexRegex = new RegExp(/\$\$[\s\S]*?\$\$|\$.*?\$/, "g") + +/** + * ox-hugo is an org exporter backend that exports org files to hugo-compatible + * markdown in an opinionated way. This plugin adds some tweaks to the generated + * markdown to make it compatible with quartz but the list of changes applied it + * is not exhaustive. + * */ +export const OxHugoFlavouredMarkdown: QuartzTransformerPlugin | undefined> = ( + userOpts, +) => { + const opts = { ...defaultOptions, ...userOpts } + return { + name: "OxHugoFlavouredMarkdown", + textTransform(_ctx, src) { + if (opts.wikilinks) { + src = src.toString() + src = src.replaceAll(relrefRegex, (value, ...capture) => { + const [text, link] = capture + return `[${text}](${link})` + }) + } + + if (opts.removePredefinedAnchor) { + src = src.toString() + src = src.replaceAll(predefinedHeadingIdRegex, (value, ...capture) => { + const [headingText] = capture + return headingText + }) + } + + if (opts.removeHugoShortcode) { + src = src.toString() + src = src.replaceAll(hugoShortcodeRegex, (value, ...capture) => { + const [scContent] = capture + return scContent + }) + } + + if (opts.replaceFigureWithMdImg) { + src = src.toString() + src = src.replaceAll(figureTagRegex, (value, ...capture) => { + const [src] = capture + return `![](${src})` + }) + } + + if (opts.replaceOrgLatex) { + src = src.toString() + src = src.replaceAll(inlineLatexRegex, (value, ...capture) => { + const [eqn] = capture + return `$${eqn}$` + }) + src = src.replaceAll(blockLatexRegex, (value, ...capture) => { + const [eqn] = capture + return `$$${eqn}$$` + }) + + // ox-hugo escapes _ as \_ + src = src.replaceAll(quartzLatexRegex, (value) => { + return value.replaceAll("\\_", "_") + }) + } + return src + }, + } +} diff --git a/quartz/plugins/transformers/syntax.ts b/quartz/plugins/transformers/syntax.ts new file mode 100644 index 0000000..f11734e --- /dev/null +++ b/quartz/plugins/transformers/syntax.ts @@ -0,0 +1,33 @@ +import { QuartzTransformerPlugin } from "../types" +import rehypePrettyCode, { Options as CodeOptions, Theme as CodeTheme } from "rehype-pretty-code" + +interface Theme extends Record { + light: CodeTheme + dark: CodeTheme +} + +interface Options { + theme?: Theme + keepBackground?: boolean +} + +const defaultOptions: Options = { + theme: { + light: "github-light", + dark: "github-dark", + }, + keepBackground: false, +} + +export const SyntaxHighlighting: QuartzTransformerPlugin = ( + userOpts?: Partial, +) => { + const opts: Partial = { ...defaultOptions, ...userOpts } + + return { + name: "SyntaxHighlighting", + htmlPlugins() { + return [[rehypePrettyCode, opts]] + }, + } +} diff --git a/quartz/plugins/transformers/toc.ts b/quartz/plugins/transformers/toc.ts new file mode 100644 index 0000000..bfc2f98 --- /dev/null +++ b/quartz/plugins/transformers/toc.ts @@ -0,0 +1,75 @@ +import { QuartzTransformerPlugin } from "../types" +import { Root } from "mdast" +import { visit } from "unist-util-visit" +import { toString } from "mdast-util-to-string" +import Slugger from "github-slugger" + +export interface Options { + maxDepth: 1 | 2 | 3 | 4 | 5 | 6 + minEntries: number + showByDefault: boolean + collapseByDefault: boolean +} + +const defaultOptions: Options = { + maxDepth: 3, + minEntries: 1, + showByDefault: true, + collapseByDefault: false, +} + +interface TocEntry { + depth: number + text: string + slug: string // this is just the anchor (#some-slug), not the canonical slug +} + +const slugAnchor = new Slugger() +export const TableOfContents: QuartzTransformerPlugin | undefined> = ( + userOpts, +) => { + const opts = { ...defaultOptions, ...userOpts } + return { + name: "TableOfContents", + markdownPlugins() { + return [ + () => { + return async (tree: Root, file) => { + const display = file.data.frontmatter?.enableToc ?? opts.showByDefault + if (display) { + slugAnchor.reset() + const toc: TocEntry[] = [] + let highestDepth: number = opts.maxDepth + visit(tree, "heading", (node) => { + if (node.depth <= opts.maxDepth) { + const text = toString(node) + highestDepth = Math.min(highestDepth, node.depth) + toc.push({ + depth: node.depth, + text, + slug: slugAnchor.slug(text), + }) + } + }) + + if (toc.length > 0 && toc.length > opts.minEntries) { + file.data.toc = toc.map((entry) => ({ + ...entry, + depth: entry.depth - highestDepth, + })) + file.data.collapseToc = opts.collapseByDefault + } + } + } + }, + ] + }, + } +} + +declare module "vfile" { + interface DataMap { + toc: TocEntry[] + collapseToc: boolean + } +} diff --git a/quartz/plugins/types.ts b/quartz/plugins/types.ts new file mode 100644 index 0000000..a23f5d6 --- /dev/null +++ b/quartz/plugins/types.ts @@ -0,0 +1,47 @@ +import { PluggableList } from "unified" +import { StaticResources } from "../util/resources" +import { ProcessedContent } from "./vfile" +import { QuartzComponent } from "../components/types" +import { FilePath } from "../util/path" +import { BuildCtx } from "../util/ctx" +import DepGraph from "../depgraph" + +export interface PluginTypes { + transformers: QuartzTransformerPluginInstance[] + filters: QuartzFilterPluginInstance[] + emitters: QuartzEmitterPluginInstance[] +} + +type OptionType = object | undefined +export type QuartzTransformerPlugin = ( + opts?: Options, +) => QuartzTransformerPluginInstance +export type QuartzTransformerPluginInstance = { + name: string + textTransform?: (ctx: BuildCtx, src: string | Buffer) => string | Buffer + markdownPlugins?: (ctx: BuildCtx) => PluggableList + htmlPlugins?: (ctx: BuildCtx) => PluggableList + externalResources?: (ctx: BuildCtx) => Partial +} + +export type QuartzFilterPlugin = ( + opts?: Options, +) => QuartzFilterPluginInstance +export type QuartzFilterPluginInstance = { + name: string + shouldPublish(ctx: BuildCtx, content: ProcessedContent): boolean +} + +export type QuartzEmitterPlugin = ( + opts?: Options, +) => QuartzEmitterPluginInstance +export type QuartzEmitterPluginInstance = { + name: string + emit(ctx: BuildCtx, content: ProcessedContent[], resources: StaticResources): Promise + getQuartzComponents(ctx: BuildCtx): QuartzComponent[] + getDependencyGraph?( + ctx: BuildCtx, + content: ProcessedContent[], + resources: StaticResources, + ): Promise> +} diff --git a/quartz/plugins/vfile.ts b/quartz/plugins/vfile.ts new file mode 100644 index 0000000..5be2105 --- /dev/null +++ b/quartz/plugins/vfile.ts @@ -0,0 +1,12 @@ +import { Node, Parent } from "hast" +import { Data, VFile } from "vfile" + +export type QuartzPluginData = Data +export type ProcessedContent = [Node, VFile] + +export function defaultProcessedContent(vfileData: Partial): ProcessedContent { + const root: Parent = { type: "root", children: [] } + const vfile = new VFile("") + vfile.data = vfileData + return [root, vfile] +} diff --git a/quartz/processors/emit.ts b/quartz/processors/emit.ts new file mode 100644 index 0000000..c68e0ed --- /dev/null +++ b/quartz/processors/emit.ts @@ -0,0 +1,33 @@ +import { PerfTimer } from "../util/perf" +import { getStaticResourcesFromPlugins } from "../plugins" +import { ProcessedContent } from "../plugins/vfile" +import { QuartzLogger } from "../util/log" +import { trace } from "../util/trace" +import { BuildCtx } from "../util/ctx" + +export async function emitContent(ctx: BuildCtx, content: ProcessedContent[]) { + const { argv, cfg } = ctx + const perf = new PerfTimer() + const log = new QuartzLogger(ctx.argv.verbose) + + log.start(`Emitting output files`) + + let emittedFiles = 0 + const staticResources = getStaticResourcesFromPlugins(ctx) + for (const emitter of cfg.plugins.emitters) { + try { + const emitted = await emitter.emit(ctx, content, staticResources) + emittedFiles += emitted.length + + if (ctx.argv.verbose) { + for (const file of emitted) { + console.log(`[emit:${emitter.name}] ${file}`) + } + } + } catch (err) { + trace(`Failed to emit from plugin \`${emitter.name}\``, err as Error) + } + } + + log.end(`Emitted ${emittedFiles} files to \`${argv.output}\` in ${perf.timeSince()}`) +} diff --git a/quartz/processors/filter.ts b/quartz/processors/filter.ts new file mode 100644 index 0000000..b269fb3 --- /dev/null +++ b/quartz/processors/filter.ts @@ -0,0 +1,24 @@ +import { BuildCtx } from "../util/ctx" +import { PerfTimer } from "../util/perf" +import { ProcessedContent } from "../plugins/vfile" + +export function filterContent(ctx: BuildCtx, content: ProcessedContent[]): ProcessedContent[] { + const { cfg, argv } = ctx + const perf = new PerfTimer() + const initialLength = content.length + for (const plugin of cfg.plugins.filters) { + const updatedContent = content.filter((item) => plugin.shouldPublish(ctx, item)) + + if (argv.verbose) { + const diff = content.filter((x) => !updatedContent.includes(x)) + for (const file of diff) { + console.log(`[filter:${plugin.name}] ${file[1].data.slug}`) + } + } + + content = updatedContent + } + + console.log(`Filtered out ${initialLength - content.length} files in ${perf.timeSince()}`) + return content +} diff --git a/quartz/processors/parse.ts b/quartz/processors/parse.ts new file mode 100644 index 0000000..3950fee --- /dev/null +++ b/quartz/processors/parse.ts @@ -0,0 +1,160 @@ +import esbuild from "esbuild" +import remarkParse from "remark-parse" +import remarkRehype from "remark-rehype" +import { Processor, unified } from "unified" +import { Root as MDRoot } from "remark-parse/lib" +import { Root as HTMLRoot } from "hast" +import { ProcessedContent } from "../plugins/vfile" +import { PerfTimer } from "../util/perf" +import { read } from "to-vfile" +import { FilePath, QUARTZ, slugifyFilePath } from "../util/path" +import path from "path" +import workerpool, { Promise as WorkerPromise } from "workerpool" +import { QuartzLogger } from "../util/log" +import { trace } from "../util/trace" +import { BuildCtx } from "../util/ctx" + +export type QuartzProcessor = Processor +export function createProcessor(ctx: BuildCtx): QuartzProcessor { + const transformers = ctx.cfg.plugins.transformers + + return ( + unified() + // base Markdown -> MD AST + .use(remarkParse) + // MD AST -> MD AST transforms + .use( + transformers + .filter((p) => p.markdownPlugins) + .flatMap((plugin) => plugin.markdownPlugins!(ctx)), + ) + // MD AST -> HTML AST + .use(remarkRehype, { allowDangerousHtml: true }) + // HTML AST -> HTML AST transforms + .use(transformers.filter((p) => p.htmlPlugins).flatMap((plugin) => plugin.htmlPlugins!(ctx))) + ) +} + +function* chunks(arr: T[], n: number) { + for (let i = 0; i < arr.length; i += n) { + yield arr.slice(i, i + n) + } +} + +async function transpileWorkerScript() { + // transpile worker script + const cacheFile = "./.quartz-cache/transpiled-worker.mjs" + const fp = "./quartz/worker.ts" + return esbuild.build({ + entryPoints: [fp], + outfile: path.join(QUARTZ, cacheFile), + bundle: true, + keepNames: true, + platform: "node", + format: "esm", + packages: "external", + sourcemap: true, + sourcesContent: false, + plugins: [ + { + name: "css-and-scripts-as-text", + setup(build) { + build.onLoad({ filter: /\.scss$/ }, (_) => ({ + contents: "", + loader: "text", + })) + build.onLoad({ filter: /\.inline\.(ts|js)$/ }, (_) => ({ + contents: "", + loader: "text", + })) + }, + }, + ], + }) +} + +export function createFileParser(ctx: BuildCtx, fps: FilePath[]) { + const { argv, cfg } = ctx + return async (processor: QuartzProcessor) => { + const res: ProcessedContent[] = [] + for (const fp of fps) { + try { + const perf = new PerfTimer() + const file = await read(fp) + + // strip leading and trailing whitespace + file.value = file.value.toString().trim() + + // Text -> Text transforms + for (const plugin of cfg.plugins.transformers.filter((p) => p.textTransform)) { + file.value = plugin.textTransform!(ctx, file.value.toString()) + } + + // base data properties that plugins may use + file.data.filePath = file.path as FilePath + file.data.relativePath = path.posix.relative(argv.directory, file.path) as FilePath + file.data.slug = slugifyFilePath(file.data.relativePath) + + const ast = processor.parse(file) + const newAst = await processor.run(ast, file) + res.push([newAst, file]) + + if (argv.verbose) { + console.log(`[process] ${fp} -> ${file.data.slug} (${perf.timeSince()})`) + } + } catch (err) { + trace(`\nFailed to process \`${fp}\``, err as Error) + } + } + + return res + } +} + +const clamp = (num: number, min: number, max: number) => + Math.min(Math.max(Math.round(num), min), max) +export async function parseMarkdown(ctx: BuildCtx, fps: FilePath[]): Promise { + const { argv } = ctx + const perf = new PerfTimer() + const log = new QuartzLogger(argv.verbose) + + // rough heuristics: 128 gives enough time for v8 to JIT and optimize parsing code paths + const CHUNK_SIZE = 128 + const concurrency = ctx.argv.concurrency ?? clamp(fps.length / CHUNK_SIZE, 1, 4) + + let res: ProcessedContent[] = [] + log.start(`Parsing input files using ${concurrency} threads`) + if (concurrency === 1) { + try { + const processor = createProcessor(ctx) + const parse = createFileParser(ctx, fps) + res = await parse(processor) + } catch (error) { + log.end() + throw error + } + } else { + await transpileWorkerScript() + const pool = workerpool.pool("./quartz/bootstrap-worker.mjs", { + minWorkers: "max", + maxWorkers: concurrency, + workerType: "thread", + }) + + const childPromises: WorkerPromise[] = [] + for (const chunk of chunks(fps, CHUNK_SIZE)) { + childPromises.push(pool.exec("parseFiles", [argv, chunk, ctx.allSlugs])) + } + + const results: ProcessedContent[][] = await WorkerPromise.all(childPromises).catch((err) => { + const errString = err.toString().slice("Error:".length) + console.error(errString) + process.exit(1) + }) + res = results.flat() + await pool.terminate() + } + + log.end(`Parsed ${res.length} Markdown files in ${perf.timeSince()}`) + return res +} diff --git a/quartz/static/icon.png b/quartz/static/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b6656a7a819cf41ba6502b9eddf4e580617bbaba GIT binary patch literal 17368 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 P00000NkvXXu0mjf;?EWX literal 0 HcmV?d00001 diff --git a/quartz/static/og-image.png b/quartz/static/og-image.png new file mode 100644 index 0000000000000000000000000000000000000000..f1321455b370d6ac9c89fa26b678a84972092c05 GIT binary patch literal 39281 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 literal 0 HcmV?d00001 diff --git a/quartz/styles/base.scss b/quartz/styles/base.scss new file mode 100644 index 0000000..859bb43 --- /dev/null +++ b/quartz/styles/base.scss @@ -0,0 +1,520 @@ +@use "./variables.scss" as *; +@use "./syntax.scss"; +@use "./callouts.scss"; + +html { + scroll-behavior: smooth; + text-size-adjust: none; + overflow-x: hidden; + width: 100vw; +} + +body, +section { + margin: 0; + max-width: 100%; + box-sizing: border-box; + background-color: var(--light); + font-family: var(--bodyFont); + color: var(--darkgray); +} + +.text-highlight { + background-color: #fff23688; + padding: 0 0.1rem; + border-radius: 5px; +} + +::selection { + background: color-mix(in srgb, var(--tertiary) 60%, rgba(255, 255, 255, 0)); + color: var(--darkgray); +} + +p, +ul, +text, +a, +tr, +td, +li, +ol, +ul, +.katex, +.math { + color: var(--darkgray); + fill: var(--darkgray); + overflow-wrap: anywhere; + hyphens: auto; +} + +.math { + &.math-display { + text-align: center; + } +} + +strong { + font-weight: $semiBoldWeight; +} + +a { + font-weight: $semiBoldWeight; + text-decoration: none; + transition: color 0.2s ease; + color: var(--secondary); + + &:hover { + color: var(--tertiary) !important; + } + + &.internal { + text-decoration: none; + background-color: var(--highlight); + padding: 0 0.1rem; + border-radius: 5px; + line-height: 1.4rem; + + &:has(> img) { + background-color: none; + border-radius: 0; + padding: 0; + } + &.tag-link { + &::before { + content: "#"; + } + } + } + + &.external .external-icon { + height: 1ex; + margin: 0 0.15em; + + > path { + fill: var(--dark); + } + } +} + +.desktop-only { + display: initial; + @media all and (max-width: $fullPageWidth) { + display: none; + } +} + +.mobile-only { + display: none; + @media all and (max-width: $fullPageWidth) { + display: initial; + } +} + +.page { + @media all and (max-width: $fullPageWidth) { + margin: 0 auto; + padding: 0 1rem; + max-width: $pageWidth; + } + + & article { + & > h1 { + font-size: 2rem; + } + + & li:has(> input[type="checkbox"]) { + list-style-type: none; + padding-left: 0; + } + + & li:has(> input[type="checkbox"]:checked) { + text-decoration: line-through; + text-decoration-color: var(--gray); + color: var(--gray); + } + + & li > * { + margin-top: 0; + margin-bottom: 0; + } + + p > strong { + color: var(--dark); + } + } + + & > #quartz-body { + width: 100%; + display: flex; + @media all and (max-width: $fullPageWidth) { + flex-direction: column; + } + + & .sidebar { + flex: 1; + display: flex; + flex-direction: column; + gap: 2rem; + top: 0; + width: $sidePanelWidth; + margin-top: $topSpacing; + box-sizing: border-box; + padding: 0 4rem; + position: fixed; + @media all and (max-width: $fullPageWidth) { + position: initial; + flex-direction: row; + padding: 0; + width: initial; + margin-top: 2rem; + } + } + + & .sidebar.left { + left: calc(calc(100vw - $pageWidth) / 2 - $sidePanelWidth); + @media all and (max-width: $fullPageWidth) { + gap: 0; + align-items: center; + } + } + + & .sidebar.right { + right: calc(calc(100vw - $pageWidth) / 2 - $sidePanelWidth); + flex-wrap: wrap; + & > * { + @media all and (max-width: $fullPageWidth) { + flex: 1; + min-width: 140px; + } + } + } + } + + & .page-header { + width: $pageWidth; + margin: $topSpacing auto 0 auto; + @media all and (max-width: $fullPageWidth) { + width: initial; + margin-top: 2rem; + } + } + + & .center, + & footer { + margin-left: auto; + margin-right: auto; + width: $pageWidth; + @media all and (max-width: $fullPageWidth) { + width: initial; + margin-left: 0; + margin-right: 0; + } + } +} + +.footnotes { + margin-top: 2rem; + border-top: 1px solid var(--lightgray); +} + +input[type="checkbox"] { + transform: translateY(2px); + color: var(--secondary); + border: 1px solid var(--lightgray); + border-radius: 3px; + background-color: var(--light); + position: relative; + margin-inline-end: 0.2rem; + margin-inline-start: -1.4rem; + appearance: none; + width: 16px; + height: 16px; + + &:checked { + border-color: var(--secondary); + background-color: var(--secondary); + + &::after { + content: ""; + position: absolute; + left: 4px; + top: 1px; + width: 4px; + height: 8px; + display: block; + border: solid var(--light); + border-width: 0 2px 2px 0; + transform: rotate(45deg); + } + } +} + +blockquote { + margin: 1rem 0; + border-left: 3px solid var(--secondary); + padding-left: 1rem; + transition: border-color 0.2s ease; +} + +h1, +h2, +h3, +h4, +h5, +h6, +thead { + font-family: var(--headerFont); + color: var(--dark); + font-weight: revert; + margin-bottom: 0; + + article > & > a[role="anchor"] { + color: var(--dark); + background-color: transparent; + } +} + +h1, +h2, +h3, +h4, +h5, +h6 { + &[id] > a[href^="#"] { + margin: 0 0.5rem; + opacity: 0; + transition: opacity 0.2s ease; + transform: translateY(-0.1rem); + font-family: var(--codeFont); + user-select: none; + } + + &[id]:hover > a { + opacity: 1; + } +} + +// typography improvements +h1 { + font-size: 1.75rem; + margin-top: 2.25rem; + margin-bottom: 1rem; +} + +h2 { + font-size: 1.4rem; + margin-top: 1.9rem; + margin-bottom: 1rem; +} + +h3 { + font-size: 1.12rem; + margin-top: 1.62rem; + margin-bottom: 1rem; +} + +h4, +h5, +h6 { + font-size: 1rem; + margin-top: 1.5rem; + margin-bottom: 1rem; +} + +figure[data-rehype-pretty-code-figure] { + margin: 0; + position: relative; + line-height: 1.6rem; + position: relative; + + & > [data-rehype-pretty-code-title] { + font-family: var(--codeFont); + font-size: 0.9rem; + padding: 0.1rem 0.5rem; + border: 1px solid var(--lightgray); + width: max-content; + border-radius: 5px; + margin-bottom: -0.5rem; + color: var(--darkgray); + } + + & > pre { + padding: 0; + } +} + +pre { + font-family: var(--codeFont); + padding: 0 0.5rem; + border-radius: 5px; + overflow-x: auto; + border: 1px solid var(--lightgray); + position: relative; + + &:has(> code.mermaid) { + border: none; + } + + & > code { + background: none; + padding: 0; + font-size: 0.85rem; + counter-reset: line; + counter-increment: line 0; + display: grid; + padding: 0.5rem 0; + overflow-x: scroll; + + & [data-highlighted-chars] { + background-color: var(--highlight); + border-radius: 5px; + } + + & > [data-line] { + padding: 0 0.25rem; + box-sizing: border-box; + border-left: 3px solid transparent; + + &[data-highlighted-line] { + background-color: var(--highlight); + border-left: 3px solid var(--secondary); + } + + &::before { + content: counter(line); + counter-increment: line; + width: 1rem; + margin-right: 1rem; + display: inline-block; + text-align: right; + color: rgba(115, 138, 148, 0.6); + } + } + + &[data-line-numbers-max-digits="2"] > [data-line]::before { + width: 2rem; + } + + &[data-line-numbers-max-digits="3"] > [data-line]::before { + width: 3rem; + } + } +} + +code { + font-size: 0.9em; + color: var(--dark); + font-family: var(--codeFont); + border-radius: 5px; + padding: 0.1rem 0.2rem; + background: var(--lightgray); +} + +tbody, +li, +p { + line-height: 1.6rem; +} + +.table-container { + overflow-x: auto; + + & > table { + margin: 1rem; + padding: 1.5rem; + border-collapse: collapse; + + th, + td { + min-width: 75px; + } + + & > * { + line-height: 2rem; + } + } +} + +th { + text-align: left; + padding: 0.4rem 0.7rem; + border-bottom: 2px solid var(--gray); +} + +td { + padding: 0.2rem 0.7rem; +} + +tr { + border-bottom: 1px solid var(--lightgray); + &:last-child { + border-bottom: none; + } +} + +img { + max-width: 100%; + border-radius: 5px; + margin: 1rem 0; +} + +p > img + em { + display: block; + transform: translateY(-1rem); +} + +hr { + width: 100%; + margin: 2rem auto; + height: 1px; + border: none; + background-color: var(--lightgray); +} + +audio, +video { + width: 100%; + border-radius: 5px; +} + +.spacer { + flex: 1 1 auto; +} + +ul.overflow, +ol.overflow { + max-height: 400; + overflow-y: auto; + + // clearfix + content: ""; + clear: both; + + & > li:last-of-type { + margin-bottom: 30px; + } + + &:after { + pointer-events: none; + content: ""; + width: 100%; + height: 50px; + position: absolute; + left: 0; + bottom: 0; + opacity: 1; + transition: opacity 0.3s ease; + background: linear-gradient(transparent 0px, var(--light)); + } +} + +.transclude { + ul { + padding-left: 1rem; + } +} + +.katex-display { + overflow-x: auto; + overflow-y: hidden; +} diff --git a/quartz/styles/callouts.scss b/quartz/styles/callouts.scss new file mode 100644 index 0000000..b1fd180 --- /dev/null +++ b/quartz/styles/callouts.scss @@ -0,0 +1,162 @@ +@use "./variables.scss" as *; +@use "sass:color"; + +.callout { + border: 1px solid var(--border); + background-color: var(--bg); + border-radius: 5px; + padding: 0 1rem; + overflow-y: hidden; + transition: max-height 0.3s ease; + box-sizing: border-box; + + & > *:nth-child(2) { + margin-top: 0; + } + + --callout-icon-note: url('data:image/svg+xml; utf8, '); + --callout-icon-abstract: url('data:image/svg+xml; utf8, '); + --callout-icon-info: url('data:image/svg+xml; utf8, '); + --callout-icon-todo: url('data:image/svg+xml; utf8, '); + --callout-icon-tip: url('data:image/svg+xml; utf8, '); + --callout-icon-success: url('data:image/svg+xml; utf8, '); + --callout-icon-question: url('data:image/svg+xml; utf8, '); + --callout-icon-warning: url('data:image/svg+xml; utf8, '); + --callout-icon-failure: url('data:image/svg+xml; utf8, '); + --callout-icon-danger: url('data:image/svg+xml; utf8, '); + --callout-icon-bug: url('data:image/svg+xml; utf8, '); + --callout-icon-example: url('data:image/svg+xml; utf8, '); + --callout-icon-quote: url('data:image/svg+xml; utf8, '); + --callout-icon-fold: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"%3E%3Cpolyline points="6 9 12 15 18 9"%3E%3C/polyline%3E%3C/svg%3E'); + + &[data-callout] { + --color: #448aff; + --border: #448aff44; + --bg: #448aff10; + --callout-icon: var(--callout-icon-note); + } + + &[data-callout="abstract"] { + --color: #00b0ff; + --border: #00b0ff44; + --bg: #00b0ff10; + --callout-icon: var(--callout-icon-abstract); + } + + &[data-callout="info"], + &[data-callout="todo"] { + --color: #00b8d4; + --border: #00b8d444; + --bg: #00b8d410; + --callout-icon: var(--callout-icon-info); + } + + &[data-callout="todo"] { + --callout-icon: var(--callout-icon-todo); + } + + &[data-callout="tip"] { + --color: #00bfa5; + --border: #00bfa544; + --bg: #00bfa510; + --callout-icon: var(--callout-icon-tip); + } + + &[data-callout="success"] { + --color: #09ad7a; + --border: #09ad7144; + --bg: #09ad7110; + --callout-icon: var(--callout-icon-success); + } + + &[data-callout="question"] { + --color: #dba642; + --border: #dba64244; + --bg: #dba64210; + --callout-icon: var(--callout-icon-question); + } + + &[data-callout="warning"] { + --color: #db8942; + --border: #db894244; + --bg: #db894210; + --callout-icon: var(--callout-icon-warning); + } + + &[data-callout="failure"], + &[data-callout="danger"], + &[data-callout="bug"] { + --color: #db4242; + --border: #db424244; + --bg: #db424210; + --callout-icon: var(--callout-icon-failure); + } + + &[data-callout="bug"] { + --callout-icon: var(--callout-icon-bug); + } + + &[data-callout="danger"] { + --callout-icon: var(--callout-icon-danger); + } + + &[data-callout="example"] { + --color: #7a43b5; + --border: #7a43b544; + --bg: #7a43b510; + --callout-icon: var(--callout-icon-example); + } + + &[data-callout="quote"] { + --color: var(--secondary); + --border: var(--lightgray); + --callout-icon: var(--callout-icon-quote); + } + + &.is-collapsed > .callout-title > .fold-callout-icon { + transform: rotateZ(-90deg); + } +} + +.callout-title { + display: flex; + align-items: flex-start; + gap: 5px; + padding: 1rem 0; + color: var(--color); + + --icon-size: 18px; + + & .fold-callout-icon { + transition: transform 0.15s ease; + opacity: 0.8; + cursor: pointer; + --callout-icon: var(--callout-icon-fold); + } + + & > .callout-title-inner > p { + color: var(--color); + margin: 0; + } + + .callout-icon, + & .fold-callout-icon { + width: var(--icon-size); + height: var(--icon-size); + flex: 0 0 var(--icon-size); + + // icon support + background-size: var(--icon-size) var(--icon-size); + background-position: center; + background-color: var(--color); + mask-image: var(--callout-icon); + mask-size: var(--icon-size) var(--icon-size); + mask-position: center; + mask-repeat: no-repeat; + padding: 0.2rem 0; + } + + .callout-title-inner { + font-weight: $semiBoldWeight; + } +} diff --git a/quartz/styles/custom.scss b/quartz/styles/custom.scss new file mode 100644 index 0000000..b0c09dc --- /dev/null +++ b/quartz/styles/custom.scss @@ -0,0 +1,3 @@ +@use "./base.scss"; + +// put your custom CSS here! diff --git a/quartz/styles/syntax.scss b/quartz/styles/syntax.scss new file mode 100644 index 0000000..ba20563 --- /dev/null +++ b/quartz/styles/syntax.scss @@ -0,0 +1,17 @@ +code[data-theme*=" "] { + color: var(--shiki-light); + background-color: var(--shiki-light-bg); +} + +code[data-theme*=" "] span { + color: var(--shiki-light); +} + +[saved-theme="dark"] code[data-theme*=" "] { + color: var(--shiki-dark); + background-color: var(--shiki-dark-bg); +} + +[saved-theme="dark"] code[data-theme*=" "] span { + color: var(--shiki-dark); +} diff --git a/quartz/styles/variables.scss b/quartz/styles/variables.scss new file mode 100644 index 0000000..e45cc91 --- /dev/null +++ b/quartz/styles/variables.scss @@ -0,0 +1,9 @@ +$pageWidth: 750px; +$mobileBreakpoint: 600px; +$tabletBreakpoint: 1000px; +$sidePanelWidth: 380px; +$topSpacing: 6rem; +$fullPageWidth: $pageWidth + 2 * $sidePanelWidth; +$boldWeight: 700; +$semiBoldWeight: 600; +$normalWeight: 400; diff --git a/quartz/util/ctx.ts b/quartz/util/ctx.ts new file mode 100644 index 0000000..e056114 --- /dev/null +++ b/quartz/util/ctx.ts @@ -0,0 +1,20 @@ +import { QuartzConfig } from "../cfg" +import { FullSlug } from "./path" + +export interface Argv { + directory: string + verbose: boolean + output: string + serve: boolean + fastRebuild: boolean + port: number + wsPort: number + remoteDevHost?: string + concurrency?: number +} + +export interface BuildCtx { + argv: Argv + cfg: QuartzConfig + allSlugs: FullSlug[] +} diff --git a/quartz/util/escape.ts b/quartz/util/escape.ts new file mode 100644 index 0000000..197558c --- /dev/null +++ b/quartz/util/escape.ts @@ -0,0 +1,8 @@ +export const escapeHTML = (unsafe: string) => { + return unsafe + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'") +} diff --git a/quartz/util/glob.ts b/quartz/util/glob.ts new file mode 100644 index 0000000..7a71160 --- /dev/null +++ b/quartz/util/glob.ts @@ -0,0 +1,22 @@ +import path from "path" +import { FilePath } from "./path" +import { globby } from "globby" + +export function toPosixPath(fp: string): string { + return fp.split(path.sep).join("/") +} + +export async function glob( + pattern: string, + cwd: string, + ignorePatterns: string[], +): Promise { + const fps = ( + await globby(pattern, { + cwd, + ignore: ignorePatterns, + gitignore: true, + }) + ).map(toPosixPath) + return fps as FilePath[] +} diff --git a/quartz/util/jsx.tsx b/quartz/util/jsx.tsx new file mode 100644 index 0000000..b525423 --- /dev/null +++ b/quartz/util/jsx.tsx @@ -0,0 +1,27 @@ +import { Components, Jsx, toJsxRuntime } from "hast-util-to-jsx-runtime" +import { Node, Root } from "hast" +import { Fragment, jsx, jsxs } from "preact/jsx-runtime" +import { trace } from "./trace" +import { type FilePath } from "./path" + +const customComponents: Components = { + table: (props) => ( +
    + + + ), +} + +export function htmlToJsx(fp: FilePath, tree: Node) { + try { + return toJsxRuntime(tree as Root, { + Fragment, + jsx: jsx as Jsx, + jsxs: jsxs as Jsx, + elementAttributeNameCase: "html", + components: customComponents, + }) + } catch (e) { + trace(`Failed to parse Markdown in \`${fp}\` into JSX`, e as Error) + } +} diff --git a/quartz/util/lang.ts b/quartz/util/lang.ts new file mode 100644 index 0000000..6fb0469 --- /dev/null +++ b/quartz/util/lang.ts @@ -0,0 +1,13 @@ +export function capitalize(s: string): string { + return s.substring(0, 1).toUpperCase() + s.substring(1) +} + +export function classNames( + displayClass?: "mobile-only" | "desktop-only", + ...classes: string[] +): string { + if (displayClass) { + classes.push(displayClass) + } + return classes.join(" ") +} diff --git a/quartz/util/log.ts b/quartz/util/log.ts new file mode 100644 index 0000000..773945c --- /dev/null +++ b/quartz/util/log.ts @@ -0,0 +1,28 @@ +import { Spinner } from "cli-spinner" + +export class QuartzLogger { + verbose: boolean + spinner: Spinner | undefined + constructor(verbose: boolean) { + this.verbose = verbose + } + + start(text: string) { + if (this.verbose) { + console.log(text) + } else { + this.spinner = new Spinner(`%s ${text}`) + this.spinner.setSpinnerString(18) + this.spinner.start() + } + } + + end(text?: string) { + if (!this.verbose) { + this.spinner!.stop(true) + } + if (text) { + console.log(text) + } + } +} diff --git a/quartz/util/path.test.ts b/quartz/util/path.test.ts new file mode 100644 index 0000000..7e9c4c8 --- /dev/null +++ b/quartz/util/path.test.ts @@ -0,0 +1,282 @@ +import test, { describe } from "node:test" +import * as path from "./path" +import assert from "node:assert" +import { FullSlug, TransformOptions } from "./path" + +describe("typeguards", () => { + test("isSimpleSlug", () => { + assert(path.isSimpleSlug("")) + assert(path.isSimpleSlug("abc")) + assert(path.isSimpleSlug("abc/")) + assert(path.isSimpleSlug("notindex")) + assert(path.isSimpleSlug("notindex/def")) + + assert(!path.isSimpleSlug("//")) + assert(!path.isSimpleSlug("index")) + assert(!path.isSimpleSlug("https://example.com")) + assert(!path.isSimpleSlug("/abc")) + assert(!path.isSimpleSlug("abc/index")) + assert(!path.isSimpleSlug("abc#anchor")) + assert(!path.isSimpleSlug("abc?query=1")) + assert(!path.isSimpleSlug("index.md")) + assert(!path.isSimpleSlug("index.html")) + }) + + test("isRelativeURL", () => { + assert(path.isRelativeURL(".")) + assert(path.isRelativeURL("..")) + assert(path.isRelativeURL("./abc/def")) + assert(path.isRelativeURL("./abc/def#an-anchor")) + assert(path.isRelativeURL("./abc/def?query=1#an-anchor")) + assert(path.isRelativeURL("../abc/def")) + assert(path.isRelativeURL("./abc/def.pdf")) + + assert(!path.isRelativeURL("abc")) + assert(!path.isRelativeURL("/abc/def")) + assert(!path.isRelativeURL("")) + assert(!path.isRelativeURL("./abc/def.html")) + assert(!path.isRelativeURL("./abc/def.md")) + }) + + test("isFullSlug", () => { + assert(path.isFullSlug("index")) + assert(path.isFullSlug("abc/def")) + assert(path.isFullSlug("html.energy")) + assert(path.isFullSlug("test.pdf")) + + assert(!path.isFullSlug(".")) + assert(!path.isFullSlug("./abc/def")) + assert(!path.isFullSlug("../abc/def")) + assert(!path.isFullSlug("abc/def#anchor")) + assert(!path.isFullSlug("abc/def?query=1")) + assert(!path.isFullSlug("note with spaces")) + }) + + test("isFilePath", () => { + assert(path.isFilePath("content/index.md")) + assert(path.isFilePath("content/test.png")) + assert(!path.isFilePath("../test.pdf")) + assert(!path.isFilePath("content/test")) + assert(!path.isFilePath("./content/test")) + }) +}) + +describe("transforms", () => { + function asserts( + pairs: [string, string][], + transform: (inp: Inp) => Out, + checkPre: (x: any) => x is Inp, + checkPost: (x: any) => x is Out, + ) { + for (const [inp, expected] of pairs) { + assert(checkPre(inp), `${inp} wasn't the expected input type`) + const actual = transform(inp) + assert.strictEqual( + actual, + expected, + `after transforming ${inp}, '${actual}' was not '${expected}'`, + ) + assert(checkPost(actual), `${actual} wasn't the expected output type`) + } + } + + test("simplifySlug", () => { + asserts( + [ + ["index", "/"], + ["abc", "abc"], + ["abc/index", "abc/"], + ["abc/def", "abc/def"], + ], + path.simplifySlug, + path.isFullSlug, + path.isSimpleSlug, + ) + }) + + test("slugifyFilePath", () => { + asserts( + [ + ["content/index.md", "content/index"], + ["content/index.html", "content/index"], + ["content/_index.md", "content/index"], + ["/content/index.md", "content/index"], + ["content/cool.png", "content/cool.png"], + ["index.md", "index"], + ["test.mp4", "test.mp4"], + ["note with spaces.md", "note-with-spaces"], + ["notes.with.dots.md", "notes.with.dots"], + ["test/special chars?.md", "test/special-chars"], + ["test/special chars #3.md", "test/special-chars-3"], + ["cool/what about r&d?.md", "cool/what-about-r-and-d"], + ], + path.slugifyFilePath, + path.isFilePath, + path.isFullSlug, + ) + }) + + test("transformInternalLink", () => { + asserts( + [ + ["", "."], + [".", "."], + ["./", "./"], + ["./index", "./"], + ["./index#abc", "./#abc"], + ["./index.html", "./"], + ["./index.md", "./"], + ["./index.css", "./index.css"], + ["content", "./content"], + ["content/test.md", "./content/test"], + ["content/test.pdf", "./content/test.pdf"], + ["./content/test.md", "./content/test"], + ["../content/test.md", "../content/test"], + ["tags/", "./tags/"], + ["/tags/", "./tags/"], + ["content/with spaces", "./content/with-spaces"], + ["content/with spaces/index", "./content/with-spaces/"], + ["content/with spaces#and Anchor!", "./content/with-spaces#and-anchor"], + ], + path.transformInternalLink, + (_x: string): _x is string => true, + path.isRelativeURL, + ) + }) + + test("pathToRoot", () => { + asserts( + [ + ["index", "."], + ["abc", "."], + ["abc/def", ".."], + ["abc/def/ghi", "../.."], + ["abc/def/index", "../.."], + ], + path.pathToRoot, + path.isFullSlug, + path.isRelativeURL, + ) + }) +}) + +describe("link strategies", () => { + const allSlugs = [ + "a/b/c", + "a/b/d", + "a/b/index", + "e/f", + "e/g/h", + "index", + "a/test.png", + ] as FullSlug[] + + describe("absolute", () => { + const opts: TransformOptions = { + strategy: "absolute", + allSlugs, + } + + test("from a/b/c", () => { + const cur = "a/b/c" as FullSlug + assert.strictEqual(path.transformLink(cur, "a/b/d", opts), "../../a/b/d") + assert.strictEqual(path.transformLink(cur, "a/b/index", opts), "../../a/b/") + assert.strictEqual(path.transformLink(cur, "e/f", opts), "../../e/f") + assert.strictEqual(path.transformLink(cur, "e/g/h", opts), "../../e/g/h") + assert.strictEqual(path.transformLink(cur, "index", opts), "../../") + assert.strictEqual(path.transformLink(cur, "index.png", opts), "../../index.png") + assert.strictEqual(path.transformLink(cur, "index#abc", opts), "../../#abc") + assert.strictEqual(path.transformLink(cur, "tag/test", opts), "../../tag/test") + assert.strictEqual(path.transformLink(cur, "a/b/c#test", opts), "../../a/b/c#test") + assert.strictEqual(path.transformLink(cur, "a/test.png", opts), "../../a/test.png") + }) + + test("from a/b/index", () => { + const cur = "a/b/index" as FullSlug + assert.strictEqual(path.transformLink(cur, "a/b/d", opts), "../../a/b/d") + assert.strictEqual(path.transformLink(cur, "a/b", opts), "../../a/b") + assert.strictEqual(path.transformLink(cur, "index", opts), "../../") + }) + + test("from index", () => { + const cur = "index" as FullSlug + assert.strictEqual(path.transformLink(cur, "index", opts), "./") + assert.strictEqual(path.transformLink(cur, "a/b/c", opts), "./a/b/c") + assert.strictEqual(path.transformLink(cur, "a/b/index", opts), "./a/b/") + }) + }) + + describe("shortest", () => { + const opts: TransformOptions = { + strategy: "shortest", + allSlugs, + } + + test("from a/b/c", () => { + const cur = "a/b/c" as FullSlug + assert.strictEqual(path.transformLink(cur, "d", opts), "../../a/b/d") + assert.strictEqual(path.transformLink(cur, "h", opts), "../../e/g/h") + assert.strictEqual(path.transformLink(cur, "a/b/index", opts), "../../a/b/") + assert.strictEqual(path.transformLink(cur, "a/b/index.png", opts), "../../a/b/index.png") + assert.strictEqual(path.transformLink(cur, "a/b/index#abc", opts), "../../a/b/#abc") + assert.strictEqual(path.transformLink(cur, "index", opts), "../../") + assert.strictEqual(path.transformLink(cur, "index.png", opts), "../../index.png") + assert.strictEqual(path.transformLink(cur, "test.png", opts), "../../a/test.png") + assert.strictEqual(path.transformLink(cur, "index#abc", opts), "../../#abc") + }) + + test("from a/b/index", () => { + const cur = "a/b/index" as FullSlug + assert.strictEqual(path.transformLink(cur, "d", opts), "../../a/b/d") + assert.strictEqual(path.transformLink(cur, "h", opts), "../../e/g/h") + assert.strictEqual(path.transformLink(cur, "a/b/index", opts), "../../a/b/") + assert.strictEqual(path.transformLink(cur, "index", opts), "../../") + }) + + test("from index", () => { + const cur = "index" as FullSlug + assert.strictEqual(path.transformLink(cur, "d", opts), "./a/b/d") + assert.strictEqual(path.transformLink(cur, "h", opts), "./e/g/h") + assert.strictEqual(path.transformLink(cur, "a/b/index", opts), "./a/b/") + assert.strictEqual(path.transformLink(cur, "index", opts), "./") + }) + }) + + describe("relative", () => { + const opts: TransformOptions = { + strategy: "relative", + allSlugs, + } + + test("from a/b/c", () => { + const cur = "a/b/c" as FullSlug + assert.strictEqual(path.transformLink(cur, "d", opts), "./d") + assert.strictEqual(path.transformLink(cur, "index", opts), "./") + assert.strictEqual(path.transformLink(cur, "../../../index", opts), "../../../") + assert.strictEqual(path.transformLink(cur, "../../../index.png", opts), "../../../index.png") + assert.strictEqual(path.transformLink(cur, "../../../index#abc", opts), "../../../#abc") + assert.strictEqual(path.transformLink(cur, "../../../", opts), "../../../") + assert.strictEqual( + path.transformLink(cur, "../../../a/test.png", opts), + "../../../a/test.png", + ) + assert.strictEqual(path.transformLink(cur, "../../../e/g/h", opts), "../../../e/g/h") + assert.strictEqual(path.transformLink(cur, "../../../e/g/h", opts), "../../../e/g/h") + assert.strictEqual(path.transformLink(cur, "../../../e/g/h#abc", opts), "../../../e/g/h#abc") + }) + + test("from a/b/index", () => { + const cur = "a/b/index" as FullSlug + assert.strictEqual(path.transformLink(cur, "../../index", opts), "../../") + assert.strictEqual(path.transformLink(cur, "../../", opts), "../../") + assert.strictEqual(path.transformLink(cur, "../../e/g/h", opts), "../../e/g/h") + assert.strictEqual(path.transformLink(cur, "c", opts), "./c") + }) + + test("from index", () => { + const cur = "index" as FullSlug + assert.strictEqual(path.transformLink(cur, "e/g/h", opts), "./e/g/h") + assert.strictEqual(path.transformLink(cur, "a/b/index", opts), "./a/b/") + }) + }) +}) diff --git a/quartz/util/path.ts b/quartz/util/path.ts new file mode 100644 index 0000000..c02bfb1 --- /dev/null +++ b/quartz/util/path.ts @@ -0,0 +1,295 @@ +import { slug as slugAnchor } from "github-slugger" +import type { Element as HastElement } from "hast" +import rfdc from "rfdc" + +export const clone = rfdc() + +// this file must be isomorphic so it can't use node libs (e.g. path) + +export const QUARTZ = "quartz" + +/// Utility type to simulate nominal types in TypeScript +type SlugLike = string & { __brand: T } + +/** Cannot be relative and must have a file extension. */ +export type FilePath = SlugLike<"filepath"> +export function isFilePath(s: string): s is FilePath { + const validStart = !s.startsWith(".") + return validStart && _hasFileExtension(s) +} + +/** Cannot be relative and may not have leading or trailing slashes. It can have `index` as it's last segment. Use this wherever possible is it's the most 'general' interpretation of a slug. */ +export type FullSlug = SlugLike<"full"> +export function isFullSlug(s: string): s is FullSlug { + const validStart = !(s.startsWith(".") || s.startsWith("/")) + const validEnding = !s.endsWith("/") + return validStart && validEnding && !containsForbiddenCharacters(s) +} + +/** Shouldn't be a relative path and shouldn't have `/index` as an ending or a file extension. It _can_ however have a trailing slash to indicate a folder path. */ +export type SimpleSlug = SlugLike<"simple"> +export function isSimpleSlug(s: string): s is SimpleSlug { + const validStart = !(s.startsWith(".") || (s.length > 1 && s.startsWith("/"))) + const validEnding = !endsWith(s, "index") + return validStart && !containsForbiddenCharacters(s) && validEnding && !_hasFileExtension(s) +} + +/** Can be found on `href`s but can also be constructed for client-side navigation (e.g. search and graph) */ +export type RelativeURL = SlugLike<"relative"> +export function isRelativeURL(s: string): s is RelativeURL { + const validStart = /^\.{1,2}/.test(s) + const validEnding = !endsWith(s, "index") + return validStart && validEnding && ![".md", ".html"].includes(_getFileExtension(s) ?? "") +} + +export function getFullSlug(window: Window): FullSlug { + const res = window.document.body.dataset.slug! as FullSlug + return res +} + +function sluggify(s: string): string { + return s + .split("/") + .map((segment) => + segment + .replace(/\s/g, "-") + .replace(/&/g, "-and-") + .replace(/%/g, "-percent") + .replace(/\?/g, "") + .replace(/#/g, ""), + ) + .join("/") // always use / as sep + .replace(/\/$/, "") +} + +export function slugifyFilePath(fp: FilePath, excludeExt?: boolean): FullSlug { + fp = stripSlashes(fp) as FilePath + let ext = _getFileExtension(fp) + const withoutFileExt = fp.replace(new RegExp(ext + "$"), "") + if (excludeExt || [".md", ".html", undefined].includes(ext)) { + ext = "" + } + + let slug = sluggify(withoutFileExt) + + // treat _index as index + if (endsWith(slug, "_index")) { + slug = slug.replace(/_index$/, "index") + } + + return (slug + ext) as FullSlug +} + +export function simplifySlug(fp: FullSlug): SimpleSlug { + const res = stripSlashes(trimSuffix(fp, "index"), true) + return (res.length === 0 ? "/" : res) as SimpleSlug +} + +export function transformInternalLink(link: string): RelativeURL { + let [fplike, anchor] = splitAnchor(decodeURI(link)) + + const folderPath = isFolderPath(fplike) + let segments = fplike.split("/").filter((x) => x.length > 0) + let prefix = segments.filter(isRelativeSegment).join("/") + let fp = segments.filter((seg) => !isRelativeSegment(seg) && seg !== "").join("/") + + // manually add ext here as we want to not strip 'index' if it has an extension + const simpleSlug = simplifySlug(slugifyFilePath(fp as FilePath)) + const joined = joinSegments(stripSlashes(prefix), stripSlashes(simpleSlug)) + const trail = folderPath ? "/" : "" + const res = (_addRelativeToStart(joined) + trail + anchor) as RelativeURL + return res +} + +// from micromorph/src/utils.ts +// https://github.com/natemoo-re/micromorph/blob/main/src/utils.ts#L5 +const _rebaseHtmlElement = (el: Element, attr: string, newBase: string | URL) => { + const rebased = new URL(el.getAttribute(attr)!, newBase) + el.setAttribute(attr, rebased.pathname + rebased.hash) +} +export function normalizeRelativeURLs(el: Element | Document, destination: string | URL) { + el.querySelectorAll('[href^="./"], [href^="../"]').forEach((item) => + _rebaseHtmlElement(item, "href", destination), + ) + el.querySelectorAll('[src^="./"], [src^="../"]').forEach((item) => + _rebaseHtmlElement(item, "src", destination), + ) +} + +const _rebaseHastElement = ( + el: HastElement, + attr: string, + curBase: FullSlug, + newBase: FullSlug, +) => { + if (el.properties?.[attr]) { + if (!isRelativeURL(String(el.properties[attr]))) { + return + } + + const rel = joinSegments(resolveRelative(curBase, newBase), "..", el.properties[attr] as string) + el.properties[attr] = rel + } +} + +export function normalizeHastElement(rawEl: HastElement, curBase: FullSlug, newBase: FullSlug) { + const el = clone(rawEl) // clone so we dont modify the original page + _rebaseHastElement(el, "src", curBase, newBase) + _rebaseHastElement(el, "href", curBase, newBase) + if (el.children) { + el.children = el.children.map((child) => + normalizeHastElement(child as HastElement, curBase, newBase), + ) + } + + return el +} + +// resolve /a/b/c to ../.. +export function pathToRoot(slug: FullSlug): RelativeURL { + let rootPath = slug + .split("/") + .filter((x) => x !== "") + .slice(0, -1) + .map((_) => "..") + .join("/") + + if (rootPath.length === 0) { + rootPath = "." + } + + return rootPath as RelativeURL +} + +export function resolveRelative(current: FullSlug, target: FullSlug | SimpleSlug): RelativeURL { + const res = joinSegments(pathToRoot(current), simplifySlug(target as FullSlug)) as RelativeURL + return res +} + +export function splitAnchor(link: string): [string, string] { + let [fp, anchor] = link.split("#", 2) + if (fp.endsWith(".pdf")) { + return [fp, anchor === undefined ? "" : `#${anchor}`] + } + anchor = anchor === undefined ? "" : "#" + slugAnchor(anchor) + return [fp, anchor] +} + +export function slugTag(tag: string) { + return tag + .split("/") + .map((tagSegment) => sluggify(tagSegment)) + .join("/") +} + +export function joinSegments(...args: string[]): string { + return args + .filter((segment) => segment !== "") + .join("/") + .replace(/\/\/+/g, "/") +} + +export function getAllSegmentPrefixes(tags: string): string[] { + const segments = tags.split("/") + const results: string[] = [] + for (let i = 0; i < segments.length; i++) { + results.push(segments.slice(0, i + 1).join("/")) + } + return results +} + +export interface TransformOptions { + strategy: "absolute" | "relative" | "shortest" + allSlugs: FullSlug[] +} + +export function transformLink(src: FullSlug, target: string, opts: TransformOptions): RelativeURL { + let targetSlug = transformInternalLink(target) + + if (opts.strategy === "relative") { + return targetSlug as RelativeURL + } else { + const folderTail = isFolderPath(targetSlug) ? "/" : "" + const canonicalSlug = stripSlashes(targetSlug.slice(".".length)) + let [targetCanonical, targetAnchor] = splitAnchor(canonicalSlug) + + if (opts.strategy === "shortest") { + // if the file name is unique, then it's just the filename + const matchingFileNames = opts.allSlugs.filter((slug) => { + const parts = slug.split("/") + const fileName = parts.at(-1) + return targetCanonical === fileName + }) + + // only match, just use it + if (matchingFileNames.length === 1) { + const targetSlug = matchingFileNames[0] + return (resolveRelative(src, targetSlug) + targetAnchor) as RelativeURL + } + } + + // if it's not unique, then it's the absolute path from the vault root + return (joinSegments(pathToRoot(src), canonicalSlug) + folderTail) as RelativeURL + } +} + +// path helpers +function isFolderPath(fplike: string): boolean { + return ( + fplike.endsWith("/") || + endsWith(fplike, "index") || + endsWith(fplike, "index.md") || + endsWith(fplike, "index.html") + ) +} + +export function endsWith(s: string, suffix: string): boolean { + return s === suffix || s.endsWith("/" + suffix) +} + +function trimSuffix(s: string, suffix: string): string { + if (endsWith(s, suffix)) { + s = s.slice(0, -suffix.length) + } + return s +} + +function containsForbiddenCharacters(s: string): boolean { + return s.includes(" ") || s.includes("#") || s.includes("?") || s.includes("&") +} + +function _hasFileExtension(s: string): boolean { + return _getFileExtension(s) !== undefined +} + +function _getFileExtension(s: string): string | undefined { + return s.match(/\.[A-Za-z0-9]+$/)?.[0] +} + +function isRelativeSegment(s: string): boolean { + return /^\.{0,2}$/.test(s) +} + +export function stripSlashes(s: string, onlyStripPrefix?: boolean): string { + if (s.startsWith("/")) { + s = s.substring(1) + } + + if (!onlyStripPrefix && s.endsWith("/")) { + s = s.slice(0, -1) + } + + return s +} + +function _addRelativeToStart(s: string): string { + if (s === "") { + s = "." + } + + if (!s.startsWith(".")) { + s = joinSegments(".", s) + } + + return s +} diff --git a/quartz/util/perf.ts b/quartz/util/perf.ts new file mode 100644 index 0000000..ba34ddb --- /dev/null +++ b/quartz/util/perf.ts @@ -0,0 +1,19 @@ +import chalk from "chalk" +import pretty from "pretty-time" + +export class PerfTimer { + evts: { [key: string]: [number, number] } + + constructor() { + this.evts = {} + this.addEvent("start") + } + + addEvent(evtName: string) { + this.evts[evtName] = process.hrtime() + } + + timeSince(evtName?: string): string { + return chalk.yellow(pretty(process.hrtime(this.evts[evtName ?? "start"]))) + } +} diff --git a/quartz/util/resources.tsx b/quartz/util/resources.tsx new file mode 100644 index 0000000..a572d89 --- /dev/null +++ b/quartz/util/resources.tsx @@ -0,0 +1,42 @@ +import { randomUUID } from "crypto" +import { JSX } from "preact/jsx-runtime" + +export type JSResource = { + loadTime: "beforeDOMReady" | "afterDOMReady" + moduleType?: "module" + spaPreserve?: boolean +} & ( + | { + src: string + contentType: "external" + } + | { + script: string + contentType: "inline" + } +) + +export function JSResourceToScriptElement(resource: JSResource, preserve?: boolean): JSX.Element { + const scriptType = resource.moduleType ?? "application/javascript" + const spaPreserve = preserve ?? resource.spaPreserve + if (resource.contentType === "external") { + return ( + + ) + } +} + +export interface StaticResources { + css: string[] + js: JSResource[] +} diff --git a/quartz/util/sourcemap.ts b/quartz/util/sourcemap.ts new file mode 100644 index 0000000..d3b9cf7 --- /dev/null +++ b/quartz/util/sourcemap.ts @@ -0,0 +1,18 @@ +import fs from "fs" +import sourceMapSupport from "source-map-support" +import { fileURLToPath } from "url" + +export const options: sourceMapSupport.Options = { + // source map hack to get around query param + // import cache busting + retrieveSourceMap(source) { + if (source.includes(".quartz-cache")) { + let realSource = fileURLToPath(source.split("?", 2)[0] + ".map") + return { + map: fs.readFileSync(realSource, "utf8"), + } + } else { + return null + } + }, +} diff --git a/quartz/util/theme.ts b/quartz/util/theme.ts new file mode 100644 index 0000000..d3bfb9a --- /dev/null +++ b/quartz/util/theme.ts @@ -0,0 +1,69 @@ +export interface ColorScheme { + light: string + lightgray: string + gray: string + darkgray: string + dark: string + secondary: string + tertiary: string + highlight: string +} + +interface Colors { + lightMode: ColorScheme + darkMode: ColorScheme +} + +export interface Theme { + typography: { + header: string + body: string + code: string + } + cdnCaching: boolean + colors: Colors + fontOrigin: "googleFonts" | "local" +} + +export type ThemeKey = keyof Colors + +const DEFAULT_SANS_SERIF = + '-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif' +const DEFAULT_MONO = "ui-monospace, SFMono-Regular, SF Mono, Menlo, monospace" + +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` +} + +export function joinStyles(theme: Theme, ...stylesheet: string[]) { + return ` +${stylesheet.join("\n\n")} + +:root { + --light: ${theme.colors.lightMode.light}; + --lightgray: ${theme.colors.lightMode.lightgray}; + --gray: ${theme.colors.lightMode.gray}; + --darkgray: ${theme.colors.lightMode.darkgray}; + --dark: ${theme.colors.lightMode.dark}; + --secondary: ${theme.colors.lightMode.secondary}; + --tertiary: ${theme.colors.lightMode.tertiary}; + --highlight: ${theme.colors.lightMode.highlight}; + + --headerFont: "${theme.typography.header}", ${DEFAULT_SANS_SERIF}; + --bodyFont: "${theme.typography.body}", ${DEFAULT_SANS_SERIF}; + --codeFont: "${theme.typography.code}", ${DEFAULT_MONO}; +} + +:root[saved-theme="dark"] { + --light: ${theme.colors.darkMode.light}; + --lightgray: ${theme.colors.darkMode.lightgray}; + --gray: ${theme.colors.darkMode.gray}; + --darkgray: ${theme.colors.darkMode.darkgray}; + --dark: ${theme.colors.darkMode.dark}; + --secondary: ${theme.colors.darkMode.secondary}; + --tertiary: ${theme.colors.darkMode.tertiary}; + --highlight: ${theme.colors.darkMode.highlight}; +} +` +} diff --git a/quartz/util/trace.ts b/quartz/util/trace.ts new file mode 100644 index 0000000..a33135d --- /dev/null +++ b/quartz/util/trace.ts @@ -0,0 +1,43 @@ +import chalk from "chalk" +import process from "process" +import { isMainThread } from "workerpool" + +const rootFile = /.*at file:/ +export function trace(msg: string, err: Error) { + let stack = err.stack ?? "" + + const lines: string[] = [] + + lines.push("") + lines.push( + "\n" + + chalk.bgRed.black.bold(" ERROR ") + + "\n\n" + + chalk.red(` ${msg}`) + + (err.message.length > 0 ? `: ${err.message}` : ""), + ) + + let reachedEndOfLegibleTrace = false + for (const line of stack.split("\n").slice(1)) { + if (reachedEndOfLegibleTrace) { + break + } + + if (!line.includes("node_modules")) { + lines.push(` ${line}`) + if (rootFile.test(line)) { + reachedEndOfLegibleTrace = true + } + } + } + + const traceMsg = lines.join("\n") + if (!isMainThread) { + // gather lines and throw + throw new Error(traceMsg) + } else { + // print and exit + console.error(traceMsg) + process.exit(1) + } +} diff --git a/quartz/worker.ts b/quartz/worker.ts new file mode 100644 index 0000000..b92bdac --- /dev/null +++ b/quartz/worker.ts @@ -0,0 +1,19 @@ +import sourceMapSupport from "source-map-support" +sourceMapSupport.install(options) +import cfg from "../quartz.config" +import { Argv, BuildCtx } from "./util/ctx" +import { FilePath, FullSlug } from "./util/path" +import { createFileParser, createProcessor } from "./processors/parse" +import { options } from "./util/sourcemap" + +// only called from worker thread +export async function parseFiles(argv: Argv, fps: FilePath[], allSlugs: FullSlug[]) { + const ctx: BuildCtx = { + cfg, + argv, + allSlugs, + } + const processor = createProcessor(ctx) + const parse = createFileParser(ctx, fps) + return parse(processor) +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..306204b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "lib": ["esnext", "DOM", "DOM.Iterable"], + "experimentalDecorators": true, + "module": "esnext", + "target": "esnext", + "moduleResolution": "node", + "strict": true, + "incremental": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + }, + "include": ["**/*.ts", "**/*.tsx", "./package.json"], + "exclude": ["build/**/*.d.ts"], +}