diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 56107cf..f0fc1fd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -7,6 +7,7 @@ on: push: branches: - v4 + workflow_dispatch: jobs: build-and-test: @@ -18,17 +19,17 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 - name: Cache dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} @@ -47,22 +48,22 @@ jobs: run: npx quartz build --bundleInfo publish-tag: - if: ${{ github.repository == 'jackyzha0/quartz' }} + if: ${{ github.repository == 'jackyzha0/quartz' && github.ref == 'refs/heads/v4' }} runs-on: ubuntu-latest permissions: contents: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 - name: Get package version run: node -p -e '`PACKAGE_VERSION=${require("./package.json").version}`' >> $GITHUB_ENV - name: Create release tag - uses: pkgdeps/git-tag-action@v2 + uses: pkgdeps/git-tag-action@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} github_repo: ${{ github.repository }} diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..805b5a4 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +v20.9.0 diff --git a/docs/advanced/creating components.md b/docs/advanced/creating components.md index 27369ab..628d5aa 100644 --- a/docs/advanced/creating components.md +++ b/docs/advanced/creating components.md @@ -129,11 +129,11 @@ export default (() => { return } - YourComponent.beforeDOM = ` + YourComponent.beforeDOMLoaded = ` console.log("hello from before the page loads!") ` - YourComponent.afterDOM = ` + YourComponent.afterDOMLoaded = ` document.getElementById('btn').onclick = () => { alert('button clicked!') } @@ -180,7 +180,7 @@ export default (() => { return } - YourComponent.afterDOM = script + YourComponent.afterDOMLoaded = script return YourComponent }) satisfies QuartzComponentConstructor ``` diff --git a/docs/advanced/making plugins.md b/docs/advanced/making plugins.md index b2bacf0..0ba2968 100644 --- a/docs/advanced/making plugins.md +++ b/docs/advanced/making plugins.md @@ -260,11 +260,11 @@ export const ContentPage: QuartzEmitterPlugin = () => { ...defaultContentPageLayout, pageBody: Content(), } - const { head, header, beforeBody, pageBody, left, right, footer } = layout + const { head, header, beforeBody, pageBody, afterBody, left, right, footer } = layout return { name: "ContentPage", getQuartzComponents() { - return [head, ...header, ...beforeBody, pageBody, ...left, ...right, footer] + return [head, ...header, ...beforeBody, pageBody, ...afterBody, ...left, ...right, footer] }, async emit(ctx, content, resources, emit): Promise { const cfg = ctx.cfg.configuration diff --git a/docs/advanced/paths.md b/docs/advanced/paths.md index 9455b98..16f6388 100644 --- a/docs/advanced/paths.md +++ b/docs/advanced/paths.md @@ -48,4 +48,4 @@ Here are the main types of slugs with a rough description of each type of path: - `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`. +To get a clearer picture of how these relate to each other, take a look at the path tests in `quartz/util/path.test.ts`. diff --git a/docs/authoring content.md b/docs/authoring content.md index d872a96..178f44a 100644 --- a/docs/authoring content.md +++ b/docs/authoring content.md @@ -29,6 +29,7 @@ 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. +- `permalink`: A custom URL for the page that will remain constant even if the path to the file changes. - `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. diff --git a/docs/configuration.md b/docs/configuration.md index 6c76654..1dc1148 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -28,6 +28,10 @@ This part of the configuration concerns anything that can affect the whole site. - `{ 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); + - `{ provider: 'posthog', apiKey: '', host: '' }`: use [Posthog](https://posthog.com/); + - `{ provider: 'tinylytics', siteId: '' }`: use [Tinylytics](https://tinylytics.app/); + - `{ provider: 'cabin' }` or `{ provider: 'cabin', host: 'https://cabin.example.com' }` (custom domain): use [Cabin](https://withcabin.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`. @@ -49,6 +53,7 @@ This part of the configuration concerns anything that can affect the whole site. - `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]] + - `textHighlight`: markdown highlighted text background ## Plugins @@ -73,10 +78,10 @@ You can customize the behaviour of Quartz by adding, removing and reordering plu > [!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/transformer|Transformer]], you would add the following line: +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" -transformers: [ +filters: [ ... Plugin.ExplicitPublish(), ... diff --git a/docs/features/Latex.md b/docs/features/Latex.md index b2bdb2d..fdc9d27 100644 --- a/docs/features/Latex.md +++ b/docs/features/Latex.md @@ -39,6 +39,17 @@ 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. diff --git a/docs/features/comments.md b/docs/features/comments.md new file mode 100644 index 0000000..92ea754 --- /dev/null +++ b/docs/features/comments.md @@ -0,0 +1,83 @@ +--- +title: Comments +tags: + - component +--- + +Quartz also has the ability to hook into various providers to enable readers to leave comments on your site. + +![[giscus-example.png]] + +As of today, only [Giscus](https://giscus.app/) is supported out of the box but PRs to support other providers are welcome! + +## Providers + +### Giscus + +First, make sure that the [[setting up your GitHub repository|GitHub]] repository you are using for your Quartz meets the following requirements: + +1. The **repository is [public](https://docs.github.com/en/github/administering-a-repository/managing-repository-settings/setting-repository-visibility#making-a-repository-public)**, otherwise visitors will not be able to view the discussion. +2. The **[giscus](https://github.com/apps/giscus) app is installed**, otherwise visitors will not be able to comment and react. +3. The **Discussions feature is turned on** by [enabling it for your repository](https://docs.github.com/en/github/administering-a-repository/managing-repository-settings/enabling-or-disabling-github-discussions-for-a-repository). + +Then, use the [Giscus site](https://giscus.app/#repository) to figure out what your `repoId` and `categoryId` should be. Make sure you select `Announcements` for the Discussion category. + +![[giscus-repo.png]] + +![[giscus-discussion.png]] + +After entering both your repository and selecting the discussion category, Giscus will compute some IDs that you'll need to provide back to Quartz. You won't need to manually add the script yourself as Quartz will handle that part for you but will need these values in the next step! + +![[giscus-results.png]] + +Finally, in `quartz.layout.ts`, edit the `afterBody` field of `sharedPageComponents` to include the following options but with the values you got from above: + +```ts title="quartz.layout.ts" +afterBody: [ + Component.Comments({ + provider: 'giscus', + options: { + // from data-repo + repo: 'jackyzha0/quartz', + // from data-repo-id + repoId: 'MDEwOlJlcG9zaXRvcnkzODcyMTMyMDg', + // from data-category + category: 'Announcements', + // from data-category-id + categoryId: 'DIC_kwDOFxRnmM4B-Xg6', + } + }), +], +``` + +### Customization + +Quartz also exposes a few of the other Giscus options as well and you can provide them the same way `repo`, `repoId`, `category`, and `categoryId` are provided. + +```ts +type Options = { + provider: "giscus" + options: { + repo: `${string}/${string}` + repoId: string + category: string + categoryId: string + + // how to map pages -> discussions + // defaults to 'url' + mapping?: "url" | "title" | "og:title" | "specific" | "number" | "pathname" + + // use strict title matching + // defaults to true + strict?: boolean + + // whether to enable reactions for the main post + // defaults to true + reactionsEnabled?: boolean + + // where to put the comment input box relative to the comments + // defaults to 'bottom' + inputPosition?: "top" | "bottom" + } +} +``` diff --git a/docs/features/folder and tag listings.md b/docs/features/folder and tag listings.md index d330f14..3190709 100644 --- a/docs/features/folder and tag listings.md +++ b/docs/features/folder and tag listings.md @@ -30,4 +30,4 @@ As with folder listings, you can also provide a description and title for a tag ## 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. +Quartz allows you to define a custom sort ordering for content on both page types. 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/recent notes.md b/docs/features/recent notes.md index 9236b7c..75406e5 100644 --- a/docs/features/recent notes.md +++ b/docs/features/recent notes.md @@ -9,6 +9,7 @@ Quartz can generate a list of recent notes based on some filtering and sorting c - 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 })` +- Display the note's tags (defaults to true): `Component.RecentNotes({ showTags: false })` - 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. diff --git a/docs/features/syntax highlighting.md b/docs/features/syntax highlighting.md index 16fef25..bf9baae 100644 --- a/docs/features/syntax highlighting.md +++ b/docs/features/syntax highlighting.md @@ -95,6 +95,16 @@ const [age, setAge] = useState(50) const [name, setName] = useState("Taylor") ``` +### Inline Highlighting + +Append {:lang} to the end of inline code to highlight it like a regular code block. + +``` +This is an array `[1, 2, 3]{:js}` of numbers 1 through 3. +``` + +This is an array `[1, 2, 3]{:js}` of numbers 1 through 3. + ### Line numbers Syntax highlighting has line numbers configured automatically. If you want to start line numbers at a specific number, use `showLineNumbers{number}`: diff --git a/docs/features/upcoming features.md b/docs/features/upcoming features.md index 76adda0..d45ebed 100644 --- a/docs/features/upcoming features.md +++ b/docs/features/upcoming features.md @@ -2,22 +2,11 @@ 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 +- static dead link detection - 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/hosting.md b/docs/hosting.md index eeb9308..eaeea19 100644 --- a/docs/hosting.md +++ b/docs/hosting.md @@ -57,18 +57,18 @@ jobs: build: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 # Fetch all history for git info - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: - node-version: 18.14 + node-version: 22 - name: Install Dependencies run: npm ci - name: Build Quartz run: npx quartz build - name: Upload artifact - uses: actions/upload-pages-artifact@v2 + uses: actions/upload-pages-artifact@v3 with: path: public @@ -81,7 +81,7 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v2 + uses: actions/deploy-pages@v4 ``` Then: @@ -182,37 +182,33 @@ Using `docs.example.com` is an example of a subdomain. They're a simple way of c ## GitLab Pages -In your local Quartz, create a new file `.gitlab-ci.yaml`. +In your local Quartz, create a new file `.gitlab-ci.yml`. -```yaml title=".gitlab-ci.yaml" +```yaml title=".gitlab-ci.yml" stages: - build - deploy -variables: - NODE_VERSION: "18.14" +image: node:20 +cache: # Cache modules in between jobs + key: $CI_COMMIT_REF_SLUG + paths: + - .npm/ 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 + - npm ci --cache .npm --prefer-offline script: - npx quartz build artifacts: paths: - public - cache: - paths: - - ~/.npm/ - key: "${CI_COMMIT_REF_SLUG}-node-${CI_COMMIT_REF_NAME}" tags: - - docker + - gitlab-org-docker pages: stage: deploy @@ -250,3 +246,21 @@ server { } } ``` + +### 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/giscus-discussion.png b/docs/images/giscus-discussion.png new file mode 100644 index 0000000..939af62 Binary files /dev/null and b/docs/images/giscus-discussion.png differ diff --git a/docs/images/giscus-example.png b/docs/images/giscus-example.png new file mode 100644 index 0000000..f59f52b Binary files /dev/null and b/docs/images/giscus-example.png differ diff --git a/docs/images/giscus-repo.png b/docs/images/giscus-repo.png new file mode 100644 index 0000000..bfabc56 Binary files /dev/null and b/docs/images/giscus-repo.png differ diff --git a/docs/images/giscus-results.png b/docs/images/giscus-results.png new file mode 100644 index 0000000..b25c751 Binary files /dev/null and b/docs/images/giscus-results.png differ diff --git a/docs/images/quartz layout.png b/docs/images/quartz layout.png index 03435f7..71ef3ac 100644 Binary files a/docs/images/quartz layout.png and b/docs/images/quartz layout.png differ diff --git a/docs/index.md b/docs/index.md index 87cf024..e41c171 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,7 +6,7 @@ Quartz is a fast, batteries-included static-site generator that transforms Markd ## 🪴 Get Started -Quartz requires **at least [Node](https://nodejs.org/) v18.14** and `npm` v9.3.1 to function correctly. Ensure you have this installed on your machine before continuing. +Quartz requires **at least [Node](https://nodejs.org/) v20** and `npm` v9.3.1 to function correctly. Ensure you have this installed on your machine before continuing. Then, in your terminal of choice, enter the following commands line by line: @@ -31,7 +31,7 @@ If you prefer instructions in a video format you can try following Nicole van de ## 🔧 Features -- [[Obsidian compatibility]], [[full-text search]], [[graph view]], note transclusion, [[wikilinks]], [[backlinks]], [[features/Latex|Latex]], [[syntax highlighting]], [[popover previews]], [[Docker Support]], [[i18n|internationalization]] and [many more](./features) right out of the box +- [[Obsidian compatibility]], [[full-text search]], [[graph view]], note transclusion, [[wikilinks]], [[backlinks]], [[features/Latex|Latex]], [[syntax highlighting]], [[popover previews]], [[Docker Support]], [[i18n|internationalization]], [[comments]] and [many more](./features) right out of the box - Hot-reload for both configuration and content - Simple JSX layouts and [[creating components|page components]] - [[SPA Routing|Ridiculously fast page loads]] and tiny bundle sizes diff --git a/docs/layout.md b/docs/layout.md index 3fabeb7..686b2f2 100644 --- a/docs/layout.md +++ b/docs/layout.md @@ -12,6 +12,7 @@ export interface FullPageLayout { header: QuartzComponent[] // laid out horizontally beforeBody: QuartzComponent[] // laid out vertically pageBody: QuartzComponent // single component + afterBody: QuartzComponent[] // laid out vertically left: QuartzComponent[] // vertical on desktop, horizontal on mobile right: QuartzComponent[] // vertical on desktop, horizontal on mobile footer: QuartzComponent // single component diff --git a/docs/plugins/AliasRedirects.md b/docs/plugins/AliasRedirects.md index 3a33ce6..8c03653 100644 --- a/docs/plugins/AliasRedirects.md +++ b/docs/plugins/AliasRedirects.md @@ -26,7 +26,7 @@ The emitter supports the following aliases: - `alias` > [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. This plugin has no configuration options. diff --git a/docs/plugins/Assets.md b/docs/plugins/Assets.md index eb03994..47589b2 100644 --- a/docs/plugins/Assets.md +++ b/docs/plugins/Assets.md @@ -9,7 +9,7 @@ This plugin emits all non-Markdown static assets in your content folder (like im 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. +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. This plugin has no configuration options. diff --git a/docs/plugins/CNAME.md b/docs/plugins/CNAME.md index 1cc0b5c..bc12b5a 100644 --- a/docs/plugins/CNAME.md +++ b/docs/plugins/CNAME.md @@ -8,10 +8,10 @@ This plugin emits a `CNAME` record that points your subdomain to the default dom 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. +See [[hosting|Hosting]] for more information. > [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. This plugin has no configuration options. diff --git a/docs/plugins/ComponentResources.md b/docs/plugins/ComponentResources.md index 739c170..6e8c82e 100644 --- a/docs/plugins/ComponentResources.md +++ b/docs/plugins/ComponentResources.md @@ -7,7 +7,7 @@ tags: 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. +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. This plugin has no configuration options. diff --git a/docs/plugins/ContentIndex.md b/docs/plugins/ContentIndex.md index af235b0..eb7265d 100644 --- a/docs/plugins/ContentIndex.md +++ b/docs/plugins/ContentIndex.md @@ -9,7 +9,7 @@ This plugin emits both RSS and an XML sitemap for your site. The [[RSS Feed]] al 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. +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. This plugin accepts the following configuration options: diff --git a/docs/plugins/ContentPage.md b/docs/plugins/ContentPage.md index 5d9016f..bd33e4e 100644 --- a/docs/plugins/ContentPage.md +++ b/docs/plugins/ContentPage.md @@ -7,7 +7,7 @@ tags: 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. +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. This plugin has no configuration options. diff --git a/docs/plugins/CrawlLinks.md b/docs/plugins/CrawlLinks.md index 15a0d66..47b7bdd 100644 --- a/docs/plugins/CrawlLinks.md +++ b/docs/plugins/CrawlLinks.md @@ -7,7 +7,7 @@ tags: 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. +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. This plugin accepts the following configuration options: diff --git a/docs/plugins/CreatedModifiedDate.md b/docs/plugins/CreatedModifiedDate.md index 6a0f137..f4134c4 100644 --- a/docs/plugins/CreatedModifiedDate.md +++ b/docs/plugins/CreatedModifiedDate.md @@ -7,11 +7,11 @@ tags: 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. +> 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"]`. +- `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`. diff --git a/docs/plugins/Description.md b/docs/plugins/Description.md index 6849b3c..af1c8b7 100644 --- a/docs/plugins/Description.md +++ b/docs/plugins/Description.md @@ -9,11 +9,12 @@ This plugin generates descriptions that are used as metadata for the HTML `head` 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. +> 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 diff --git a/docs/plugins/ExplicitPublish.md b/docs/plugins/ExplicitPublish.md index 7ef2dd4..2fd929b 100644 --- a/docs/plugins/ExplicitPublish.md +++ b/docs/plugins/ExplicitPublish.md @@ -7,12 +7,12 @@ tags: 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. +> 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 +- 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 index 92a700a..45cfa15 100644 --- a/docs/plugins/FolderPage.md +++ b/docs/plugins/FolderPage.md @@ -9,12 +9,14 @@ This plugin generates index pages for folders, creating a listing page for each 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. +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. 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`). +This plugin accepts the following configuration options: + +- `sort`: A function of type `(f1: QuartzPluginData, f2: QuartzPluginData) => number{:ts}` used to sort entries. Defaults to sorting by date and tie-breaking on lexographical order. + ## API - Category: Emitter diff --git a/docs/plugins/Frontmatter.md b/docs/plugins/Frontmatter.md index 960417a..879d087 100644 --- a/docs/plugins/Frontmatter.md +++ b/docs/plugins/Frontmatter.md @@ -7,7 +7,7 @@ tags: 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. +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. This plugin accepts the following configuration options: diff --git a/docs/plugins/GitHubFlavoredMarkdown.md b/docs/plugins/GitHubFlavoredMarkdown.md index 2ea8f8d..41fab6b 100644 --- a/docs/plugins/GitHubFlavoredMarkdown.md +++ b/docs/plugins/GitHubFlavoredMarkdown.md @@ -9,7 +9,7 @@ This plugin enhances Markdown processing to support GitHub Flavored Markdown (GF 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. +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. This plugin accepts the following configuration options: diff --git a/docs/plugins/HardLineBreaks.md b/docs/plugins/HardLineBreaks.md index d35d74c..e24f7e1 100644 --- a/docs/plugins/HardLineBreaks.md +++ b/docs/plugins/HardLineBreaks.md @@ -7,7 +7,7 @@ tags: 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. +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. This plugin has no configuration options. diff --git a/docs/plugins/Latex.md b/docs/plugins/Latex.md index 8a8999e..236cbec 100644 --- a/docs/plugins/Latex.md +++ b/docs/plugins/Latex.md @@ -7,11 +7,12 @@ tags: 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. +> 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. +- `customMacros`: custom macros for all LaTeX blocks. It takes the form of a key-value pair where the key is a new command name and the value is the expansion of the macro. For example: `{"\\R": "\\mathbb{R}"}` ## API diff --git a/docs/plugins/NotFoundPage.md b/docs/plugins/NotFoundPage.md index 082f864..b679943 100644 --- a/docs/plugins/NotFoundPage.md +++ b/docs/plugins/NotFoundPage.md @@ -7,7 +7,7 @@ tags: 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. +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. This plugin has no configuration options. diff --git a/docs/plugins/ObsidianFlavoredMarkdown.md b/docs/plugins/ObsidianFlavoredMarkdown.md index 7700a5c..30d1f71 100644 --- a/docs/plugins/ObsidianFlavoredMarkdown.md +++ b/docs/plugins/ObsidianFlavoredMarkdown.md @@ -7,7 +7,7 @@ tags: This plugin provides support for [[Obsidian compatibility]]. > [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. This plugin accepts the following configuration options: @@ -20,7 +20,7 @@ This plugin accepts the following configuration options: - `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 using external image Markdown syntax. +- `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`. diff --git a/docs/plugins/OxHugoFlavoredMarkdown.md b/docs/plugins/OxHugoFlavoredMarkdown.md index 523a215..5c2afee 100644 --- a/docs/plugins/OxHugoFlavoredMarkdown.md +++ b/docs/plugins/OxHugoFlavoredMarkdown.md @@ -7,7 +7,7 @@ tags: 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. +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. This plugin accepts the following configuration options: diff --git a/docs/plugins/RemoveDrafts.md b/docs/plugins/RemoveDrafts.md index 729ac86..07fb4d0 100644 --- a/docs/plugins/RemoveDrafts.md +++ b/docs/plugins/RemoveDrafts.md @@ -7,7 +7,7 @@ tags: 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. +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. This plugin has no configuration options. diff --git a/docs/plugins/Static.md b/docs/plugins/Static.md index c660eab..80bf5a1 100644 --- a/docs/plugins/Static.md +++ b/docs/plugins/Static.md @@ -10,7 +10,7 @@ This plugin emits all static resources needed by Quartz. This is used, for examp > 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. +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. This plugin has no configuration options. diff --git a/docs/plugins/SyntaxHighlighting.md b/docs/plugins/SyntaxHighlighting.md index 8bf581e..6fb67db 100644 --- a/docs/plugins/SyntaxHighlighting.md +++ b/docs/plugins/SyntaxHighlighting.md @@ -7,7 +7,7 @@ tags: 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. +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. This plugin accepts the following configuration options: diff --git a/docs/plugins/TableOfContents.md b/docs/plugins/TableOfContents.md index d443d0e..0e9e4ea 100644 --- a/docs/plugins/TableOfContents.md +++ b/docs/plugins/TableOfContents.md @@ -7,7 +7,7 @@ tags: 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. +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. This plugin accepts the following configuration options: diff --git a/docs/plugins/TagPage.md b/docs/plugins/TagPage.md index cd7dee6..9556363 100644 --- a/docs/plugins/TagPage.md +++ b/docs/plugins/TagPage.md @@ -7,12 +7,14 @@ tags: 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. +> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page. 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`). +This plugin accepts the following configuration options: + +- `sort`: A function of type `(f1: QuartzPluginData, f2: QuartzPluginData) => number{:ts}` used to sort entries. Defaults to sorting by date and tie-breaking on lexographical order. + ## API - Category: Emitter diff --git a/docs/showcase.md b/docs/showcase.md index 0509e93..2f92431 100644 --- a/docs/showcase.md +++ b/docs/showcase.md @@ -7,24 +7,25 @@ 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/) +- [Morrowind Modding Wiki](https://morrowind-modding.github.io/) - [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/) +- [Pelayo Arbues' Notes](https://pelayoarbues.com/) +- [Stanford CME 302 Numerical Linear Algebra](https://ericdarve.github.io/NLA/) +- [A Pattern Language - Christopher Alexander (Architecture)](https://patternlanguage.cc/) +- [oldwinter の数字花园](https://garden.oldwinter.top/) +- [Eilleen's Everything Notebook](https://quartz.eilleeenz.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) +- [Gatekeeper Wiki](https://www.gatekeeper.wiki) +- [Ellie's Notes](https://ellie.wtf) +- [🥷🏻🌳🍃 Computer Science & Thinkering Garden](https://notes.yxy.ninja) +- [Eledah's Crystalline](https://blog.eledah.ir/) If you want to see your own on here, submit a [Pull Request adding yourself to this file](https://github.com/jackyzha0/quartz/blob/v4/docs/showcase.md)! diff --git a/globals.d.ts b/globals.d.ts index ee13005..6cf30f8 100644 --- a/globals.d.ts +++ b/globals.d.ts @@ -4,6 +4,10 @@ export declare global { type: K, listener: (this: Document, ev: CustomEventMap[K]) => void, ): void + removeEventListener( + type: K, + listener: (this: Document, ev: CustomEventMap[K]) => void, + ): void dispatchEvent(ev: CustomEventMap[K] | UIEvent): void } interface Window { diff --git a/package-lock.json b/package-lock.json index 7e71a1d..d104192 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,46 +1,49 @@ { "name": "@jackyzha0/quartz", - "version": "4.2.3", + "version": "4.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@jackyzha0/quartz", - "version": "4.2.3", + "version": "4.3.1", "license": "MIT", "dependencies": { "@clack/prompts": "^0.7.0", - "@floating-ui/dom": "^1.6.3", - "@napi-rs/simple-git": "0.1.16", - "async-mutex": "^0.4.1", + "@floating-ui/dom": "^1.6.10", + "@napi-rs/simple-git": "0.1.17", + "@tweenjs/tween.js": "^25.0.0", + "async-mutex": "^0.5.0", "chalk": "^5.3.0", "chokidar": "^3.6.0", "cli-spinner": "^0.2.10", - "d3": "^7.8.5", + "d3": "^7.9.0", "esbuild-sass-plugin": "^2.16.1", "flexsearch": "0.7.43", "github-slugger": "^2.0.0", - "globby": "^14.0.1", + "globby": "^14.0.2", "gray-matter": "^4.0.3", - "hast-util-to-html": "^9.0.0", + "hast-util-to-html": "^9.0.1", "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.23.0", + "lightningcss": "^1.26.0", "mdast-util-find-and-replace": "^3.0.1", - "mdast-util-to-hast": "^13.1.0", + "mdast-util-to-hast": "^13.2.0", "mdast-util-to-string": "^4.0.0", "micromorph": "^0.4.5", - "preact": "^10.19.6", - "preact-render-to-string": "^6.3.1", + "pixi.js": "^8.3.3", + "preact": "^10.23.2", + "preact-render-to-string": "^6.5.9", "pretty-bytes": "^6.1.1", "pretty-time": "^1.1.0", "reading-time": "^1.5.0", "rehype-autolink-headings": "^7.1.0", - "rehype-katex": "^7.0.0", + "rehype-citation": "^2.1.1", + "rehype-katex": "^7.0.1", "rehype-mathjax": "^6.0.0", - "rehype-pretty-code": "^0.13.0", + "rehype-pretty-code": "^0.13.2", "rehype-raw": "^7.0.0", "rehype-slug": "^6.0.0", "remark": "^15.0.1", @@ -50,19 +53,19 @@ "remark-math": "^6.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.0", - "remark-smartypants": "^2.0.0", - "rfdc": "^1.3.1", - "rimraf": "^5.0.5", + "remark-smartypants": "^3.0.2", + "rfdc": "^1.4.1", + "rimraf": "^6.0.1", "serve-handler": "^6.1.5", - "shiki": "^1.1.6", + "shiki": "^1.12.1", "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", + "vfile": "^6.0.2", + "workerpool": "^9.1.3", + "ws": "^8.18.0", "yargs": "^17.7.2" }, "bin": { @@ -73,18 +76,18 @@ "@types/d3": "^7.4.3", "@types/hast": "^3.0.4", "@types/js-yaml": "^4.0.9", - "@types/node": "^20.11.19", + "@types/node": "^22.1.0", "@types/pretty-time": "^1.1.5", "@types/source-map-support": "^0.5.10", - "@types/ws": "^8.5.10", + "@types/ws": "^8.5.12", "@types/yargs": "^17.0.32", "esbuild": "^0.19.9", - "prettier": "^3.2.4", - "tsx": "^4.7.1", - "typescript": "^5.3.3" + "prettier": "^3.3.3", + "tsx": "^4.17.0", + "typescript": "^5.5.4" }, "engines": { - "node": ">=18.14", + "node": "20 || >=22", "npm": ">=9.3.1" } }, @@ -98,6 +101,82 @@ "is-potential-custom-element-name": "^1.0.1" } }, + "node_modules/@citation-js/core": { + "version": "0.7.14", + "resolved": "https://registry.npmjs.org/@citation-js/core/-/core-0.7.14.tgz", + "integrity": "sha512-dgeGqYDSQmn2MtnWZkwPGpJQPh43yr1lAAr9jl1NJ9pIY1RXUQxtlAUZVur0V9PHdbfQC+kkvB1KC3VpgVV3MA==", + "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.14", + "resolved": "https://registry.npmjs.org/@citation-js/plugin-bibjson/-/plugin-bibjson-0.7.14.tgz", + "integrity": "sha512-Hcmk01KrpHwcl5uVoLE6TRaJRFg7/qUvpJDcKqx3LLLCsNbaBlISfRDeFETrjjipTetkX70RvtS7FfGUN58gCQ==", + "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.14", + "resolved": "https://registry.npmjs.org/@citation-js/plugin-bibtex/-/plugin-bibtex-0.7.14.tgz", + "integrity": "sha512-xHOHqhF6dthLRv46N9U+mQgYLiiWQHLvQWK9+mcBKz+/3NWge62Xb1oBouNWwLEPd5FV/8gp9fp7SOp93T0dUg==", + "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.14", + "resolved": "https://registry.npmjs.org/@citation-js/plugin-csl/-/plugin-csl-0.7.14.tgz", + "integrity": "sha512-7AKB8lMz1IqdtoE33NnWIpteLYMuSl3xqT+Cax7sQKwAIJEoq2HBmb43Ja8xQQ36nREAupQJv1V6XksIAmYnCg==", + "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", @@ -402,6 +481,22 @@ "node": ">=12" } }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz", + "integrity": "sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/openbsd-x64": { "version": "0.19.12", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", @@ -486,18 +581,18 @@ } }, "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==", + "version": "1.6.10", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.10.tgz", + "integrity": "sha512-fskgCFv8J8OamCmyun8MfjB1Olfn+uZKjOKZ0vhYF3gRmEUXcGOjxWL8bBr7i4kIuPZ2KD2S3EUIOxnjC8kl2A==", "dependencies": { - "@floating-ui/core": "^1.0.0", - "@floating-ui/utils": "^0.2.0" + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.7" } }, "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==" + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.7.tgz", + "integrity": "sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==" }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -516,30 +611,33 @@ } }, "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==", + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git/-/simple-git-0.1.17.tgz", + "integrity": "sha512-lH8bYk2kqfbKsht/Gejd8K+y069ZXPHBfrlcj1ptS6xlJbHhncHxpFyy57W+PTuCcN+MPGVjs+3CiufG8EUrCQ==", "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" + "@napi-rs/simple-git-android-arm-eabi": "0.1.17", + "@napi-rs/simple-git-android-arm64": "0.1.17", + "@napi-rs/simple-git-darwin-arm64": "0.1.17", + "@napi-rs/simple-git-darwin-x64": "0.1.17", + "@napi-rs/simple-git-freebsd-x64": "0.1.17", + "@napi-rs/simple-git-linux-arm-gnueabihf": "0.1.17", + "@napi-rs/simple-git-linux-arm64-gnu": "0.1.17", + "@napi-rs/simple-git-linux-arm64-musl": "0.1.17", + "@napi-rs/simple-git-linux-powerpc64le-gnu": "0.1.17", + "@napi-rs/simple-git-linux-s390x-gnu": "0.1.17", + "@napi-rs/simple-git-linux-x64-gnu": "0.1.17", + "@napi-rs/simple-git-linux-x64-musl": "0.1.17", + "@napi-rs/simple-git-win32-arm64-msvc": "0.1.17", + "@napi-rs/simple-git-win32-x64-msvc": "0.1.17" } }, "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==", + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-android-arm-eabi/-/simple-git-android-arm-eabi-0.1.17.tgz", + "integrity": "sha512-P+B95PKy46Dq9q1sr18wCn+Uj/WShMIyBBA+ezVHWJge6JSeGh4hLhKEpv3+Rk6S7ITCXxrr7Pn7U4o20nVqhQ==", "cpu": [ "arm" ], @@ -552,9 +650,9 @@ } }, "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==", + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-android-arm64/-/simple-git-android-arm64-0.1.17.tgz", + "integrity": "sha512-qggMcxfNKiQsAa1pupFuC8fajvAz6QQcZirHxTPWUxQSEwUvliL8cyKM4QdJwSac0VEITTmHaegDSXsn43EvGg==", "cpu": [ "arm64" ], @@ -567,9 +665,9 @@ } }, "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==", + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-darwin-arm64/-/simple-git-darwin-arm64-0.1.17.tgz", + "integrity": "sha512-LYgvP3Rw1lCkBW0Ud4xZFUZ2SI+Y2vvy9X/OEzlmqee5VPC1wiez2kZ62lD3ABU0Ta4Khv7W+eJsaXiTuvcq+Q==", "cpu": [ "arm64" ], @@ -582,9 +680,9 @@ } }, "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==", + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-darwin-x64/-/simple-git-darwin-x64-0.1.17.tgz", + "integrity": "sha512-CyLbxyLILT47jdNDTCREdO0LELKWqfkbw9EV4gaFrLZVD1Dej+NnZogR4oDrg7N12pcgVWnleaK1hcBDs7SeLQ==", "cpu": [ "x64" ], @@ -596,10 +694,25 @@ "node": ">= 10" } }, + "node_modules/@napi-rs/simple-git-freebsd-x64": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-freebsd-x64/-/simple-git-freebsd-x64-0.1.17.tgz", + "integrity": "sha512-SHWa3o5EZWYh7UoLi2sO4uLjZd58UFHaMttw4O9PZPvFcdjz5LjC6CQclwZbLyPDPMGefalrkUeYTs+/VJ+XEA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "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==", + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-arm-gnueabihf/-/simple-git-linux-arm-gnueabihf-0.1.17.tgz", + "integrity": "sha512-nQpwitNfSN4qGmDpWOlS3XqeE7NARxCvL+lxO0CtKih2iBuWIoU0wViVKdf9fb/Rm3xsQHcblMkliMnjcAOupg==", "cpu": [ "arm" ], @@ -612,9 +725,9 @@ } }, "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==", + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-arm64-gnu/-/simple-git-linux-arm64-gnu-0.1.17.tgz", + "integrity": "sha512-JD8nSLa9WY1kAppMufYqcqFYYjZKjZZFdZtlpz6Kn0kk4Qmm3Rvt1etnuQBwax9R2wG4n9YPYfpidDxic8rlNw==", "cpu": [ "arm64" ], @@ -627,9 +740,9 @@ } }, "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==", + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-arm64-musl/-/simple-git-linux-arm64-musl-0.1.17.tgz", + "integrity": "sha512-PRdVIEvgdIuJhDvdneO3X7XfZwujU7MOyymwK3kR1RMJPlbwzxdQBA86am/jEkBP7d8Cx8RbREzJ6y/2hAHKOQ==", "cpu": [ "arm64" ], @@ -641,10 +754,40 @@ "node": ">= 10" } }, + "node_modules/@napi-rs/simple-git-linux-powerpc64le-gnu": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-powerpc64le-gnu/-/simple-git-linux-powerpc64le-gnu-0.1.17.tgz", + "integrity": "sha512-afbfsJMpQjtdLP3BRGj/hKpRqymxw2Lt+dmyoRej0zKxZnuPrws3Fi85RyYsT/6Tq0hSUAMeh5UtxGAOH3q8gA==", + "cpu": [ + "powerpc64le" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-linux-s390x-gnu": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-s390x-gnu/-/simple-git-linux-s390x-gnu-0.1.17.tgz", + "integrity": "sha512-qTgRIUsU+b7RMls+Ji4xlDYq0rsUuNBpzVgb991UPnzrhFWFFkCtyk6I6tJqMtRfg7Vgn1stCghFEQiHmpqkew==", + "cpu": [ + "s390x" + ], + "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==", + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-x64-gnu/-/simple-git-linux-x64-gnu-0.1.17.tgz", + "integrity": "sha512-xHlyUDJhjPUCR07JGrvMfLg5XSRVDsxgpo6B6zYQOSMcVgM7fjvyWNMBe508r4eD5YZKZyBPfSJUc5Ls9ToJNQ==", "cpu": [ "x64" ], @@ -657,9 +800,9 @@ } }, "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==", + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-x64-musl/-/simple-git-linux-x64-musl-0.1.17.tgz", + "integrity": "sha512-eaTr+WPeiuEegduE3O7VzHhHftGXmX1pzzILoOTbbdmeEuH1BHnGAr35XTu+1lUHUqE2JHef3d3PgBHeh844hA==", "cpu": [ "x64" ], @@ -672,9 +815,9 @@ } }, "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==", + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-win32-arm64-msvc/-/simple-git-win32-arm64-msvc-0.1.17.tgz", + "integrity": "sha512-v1F72stOCjapCd0Ha928m8X8i/IPhPQIXbYEGX0MEmaaAzbAJ3PTSSFpb0rFLShXaDFA2Wuw/jzlkPLESPdKVQ==", "cpu": [ "arm64" ], @@ -687,9 +830,9 @@ } }, "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==", + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-win32-x64-msvc/-/simple-git-win32-x64-msvc-0.1.17.tgz", + "integrity": "sha512-ziSqhCGE2eTUqpQKEutGobU2fH1t9fXwGF58dMFaPgTJIISaENvdnKu5FDJfA94vPbe3BMN64JoTmjBSglGFhQ==", "cpu": [ "x64" ], @@ -733,6 +876,12 @@ "node": ">= 8" } }, + "node_modules/@pixi/colord": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@pixi/colord/-/colord-2.9.6.tgz", + "integrity": "sha512-nezytU2pw587fQstUu1AsJZDVEynjskwOL+kibwcdxsMBFqPsFFNA7xl0ii/gXuDi6M0xj3mfRJj8pBSc2jCfA==", + "license": "MIT" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -743,9 +892,12 @@ } }, "node_modules/@shikijs/core": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.1.6.tgz", - "integrity": "sha512-kt9hhvrWTm0EPtRDIsoAZnSsFlIDBVBBI5CQewpA/NZCPin+MOKRXg+JiWc4y+8fZ/v0HzfDhu/UC+OTZGMt7A==" + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.12.1.tgz", + "integrity": "sha512-biCz/mnkMktImI6hMfMX3H9kOeqsInxWEyCHbSlL8C/2TR1FqfmGxTLRNwYCKsyCyxWLbB8rEqXRVZuyxuLFmA==", + "dependencies": { + "@types/hast": "^3.0.4" + } }, "node_modules/@sindresorhus/merge-streams": { "version": "2.3.0", @@ -758,6 +910,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@tweenjs/tween.js": { + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-25.0.0.tgz", + "integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==", + "license": "MIT" + }, "node_modules/@types/cli-spinner": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@types/cli-spinner/-/cli-spinner-0.2.3.tgz", @@ -767,6 +925,12 @@ "@types/node": "*" } }, + "node_modules/@types/css-font-loading-module": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.12.tgz", + "integrity": "sha512-x2tZZYkSxXqWvTDgveSynfjq/T2HyiZHXb00j/+gy19yp70PHCizM48XFdjBCWH7eHBD0R5i/pw9yMBP/BH5uA==", + "license": "MIT" + }, "node_modules/@types/d3": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", @@ -1028,6 +1192,12 @@ "@types/ms": "*" } }, + "node_modules/@types/earcut": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.4.tgz", + "integrity": "sha512-qp3m9PPz4gULB9MhjGID7wpo3gJ4bTGXm7ltNDsmOvsPduTeHp8wSW9YckBj3mljeOh4F0m2z/0JKAALRKbmLQ==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -1085,20 +1255,20 @@ "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==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", + "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", "dependencies": { "@types/unist": "*" } }, "node_modules/@types/node": { - "version": "20.11.19", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz", - "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==", + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.1.0.tgz", + "integrity": "sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==", "dev": true, "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.13.0" } }, "node_modules/@types/pretty-time": { @@ -1122,9 +1292,9 @@ "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==", + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", "dev": true, "dependencies": { "@types/node": "*" @@ -1150,6 +1320,21 @@ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, + "node_modules/@webgpu/types": { + "version": "0.1.44", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.44.tgz", + "integrity": "sha512-JDpYJN5E/asw84LTYhKyvPpxGnD+bAKPtpW9Ilurf7cZpxaTbxkQcGwOd7jgB9BPBrTYQ+32ufo4HiuomTjHNQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/agent-base": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", @@ -1213,9 +1398,9 @@ } }, "node_modules/async-mutex": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.1.tgz", - "integrity": "sha512-WfoBo4E/TbCX1G95XTjbWTE3X2XLG0m1Xbv2cwOtuPdyH9CZvnaA5nCt1ucjaKEgW2A5IF71hxrRhr83Je5xjA==", + "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" } @@ -1239,6 +1424,25 @@ "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", @@ -1274,6 +1478,29 @@ "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", @@ -1366,6 +1593,11 @@ "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", @@ -1497,6 +1729,14 @@ "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", @@ -1534,9 +1774,9 @@ } }, "node_modules/d3": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz", - "integrity": "sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", "dependencies": { "d3-array": "3", "d3-axis": "3", @@ -1995,6 +2235,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/earcut": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", + "license": "ISC" + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -2113,6 +2359,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -2172,6 +2424,52 @@ "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", @@ -2189,9 +2487,9 @@ "integrity": "sha512-c5o/+Um8aqCSOXGcZoqZOm+NqtVwNsvVpWv6lfmSclU954O3wvQKxxK8zj74fPaSJbXpSLTs4PRhh+wnoCXnKg==" }, "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -2254,9 +2552,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", - "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", + "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==", "dev": true, "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -2271,21 +2569,22 @@ "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" }, "node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -2303,9 +2602,9 @@ } }, "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==", + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", + "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", "dependencies": { "@sindresorhus/merge-streams": "^2.1.0", "fast-glob": "^3.3.2", @@ -2532,9 +2831,9 @@ "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==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.1.tgz", + "integrity": "sha512-hZOofyZANbyWo+9RP75xIDV/gq+OUKx+T46IlwERnKmfpwp81XBFbT9mi26ws+SJchA4RVUQwIBJpqEOBhMzEQ==", "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", @@ -2748,6 +3047,25 @@ "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", @@ -2818,28 +3136,6 @@ "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", @@ -2933,15 +3229,21 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, + "node_modules/ismobilejs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ismobilejs/-/ismobilejs-1.1.1.tgz", + "integrity": "sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw==", + "license": "MIT" + }, "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", + "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", "dependencies": { "@isaacs/cliui": "^8.0.2" }, "engines": { - "node": ">=14" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -3032,9 +3334,9 @@ } }, "node_modules/lightningcss": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.23.0.tgz", - "integrity": "sha512-SEArWKMHhqn/0QzOtclIwH5pXIYQOUEkF8DgICd/105O+GCgd7jxjNod/QPnBCSWvpRHQBGVz5fQ9uScby03zA==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.26.0.tgz", + "integrity": "sha512-a/XZ5hdgifrofQJUArr5AiJjx26SwMam3SJUSMjgebZbESZ96i+6Qsl8tLi0kaUsdMzBWXh9sN1Oe6hp2/dkQw==", "dependencies": { "detect-libc": "^1.0.3" }, @@ -3046,21 +3348,22 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-darwin-arm64": "1.23.0", - "lightningcss-darwin-x64": "1.23.0", - "lightningcss-freebsd-x64": "1.23.0", - "lightningcss-linux-arm-gnueabihf": "1.23.0", - "lightningcss-linux-arm64-gnu": "1.23.0", - "lightningcss-linux-arm64-musl": "1.23.0", - "lightningcss-linux-x64-gnu": "1.23.0", - "lightningcss-linux-x64-musl": "1.23.0", - "lightningcss-win32-x64-msvc": "1.23.0" + "lightningcss-darwin-arm64": "1.26.0", + "lightningcss-darwin-x64": "1.26.0", + "lightningcss-freebsd-x64": "1.26.0", + "lightningcss-linux-arm-gnueabihf": "1.26.0", + "lightningcss-linux-arm64-gnu": "1.26.0", + "lightningcss-linux-arm64-musl": "1.26.0", + "lightningcss-linux-x64-gnu": "1.26.0", + "lightningcss-linux-x64-musl": "1.26.0", + "lightningcss-win32-arm64-msvc": "1.26.0", + "lightningcss-win32-x64-msvc": "1.26.0" } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.23.0.tgz", - "integrity": "sha512-kl4Pk3Q2lnE6AJ7Qaij47KNEfY2/UXRZBT/zqGA24B8qwkgllr/j7rclKOf1axcslNXvvUdztjo4Xqh39Yq1aA==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.26.0.tgz", + "integrity": "sha512-n4TIvHO1NY1ondKFYpL2ZX0bcC2y6yjXMD6JfyizgR8BCFNEeArINDzEaeqlfX9bXz73Bpz/Ow0nu+1qiDrBKg==", "cpu": [ "arm64" ], @@ -3077,9 +3380,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.23.0.tgz", - "integrity": "sha512-KeRFCNoYfDdcolcFXvokVw+PXCapd2yHS1Diko1z1BhRz/nQuD5XyZmxjWdhmhN/zj5sH8YvWsp0/lPLVzqKpg==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.26.0.tgz", + "integrity": "sha512-Rf9HuHIDi1R6/zgBkJh25SiJHF+dm9axUZW/0UoYCW1/8HV0gMI0blARhH4z+REmWiU1yYT/KyNF3h7tHyRXUg==", "cpu": [ "x64" ], @@ -3096,9 +3399,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.23.0.tgz", - "integrity": "sha512-xhnhf0bWPuZxcqknvMDRFFo2TInrmQRWZGB0f6YoAsZX8Y+epfjHeeOIGCfAmgF0DgZxHwYc8mIR5tQU9/+ROA==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.26.0.tgz", + "integrity": "sha512-C/io7POAxp6sZxFSVGezjajMlCKQ8KSwISLLGRq8xLQpQMokYrUoqYEwmIX8mLmF6C/CZPk0gFmRSzd8biWM0g==", "cpu": [ "x64" ], @@ -3115,9 +3418,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.23.0.tgz", - "integrity": "sha512-fBamf/bULvmWft9uuX+bZske236pUZEoUlaHNBjnueaCTJ/xd8eXgb0cEc7S5o0Nn6kxlauMBnqJpF70Bgq3zg==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.26.0.tgz", + "integrity": "sha512-Aag9kqXqkyPSW+dXMgyWk66C984Nay2pY8Nws+67gHlDzV3cWh7TvFlzuaTaVFMVqdDTzN484LSK3u39zFBnzg==", "cpu": [ "arm" ], @@ -3134,9 +3437,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.23.0.tgz", - "integrity": "sha512-RS7sY77yVLOmZD6xW2uEHByYHhQi5JYWmgVumYY85BfNoVI3DupXSlzbw+b45A9NnVKq45+oXkiN6ouMMtTwfg==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.26.0.tgz", + "integrity": "sha512-iJmZM7fUyVjH+POtdiCtExG+67TtPUTer7K/5A8DIfmPfrmeGvzfRyBltGhQz13Wi15K1lf2cPYoRaRh6vcwNA==", "cpu": [ "arm64" ], @@ -3153,9 +3456,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.23.0.tgz", - "integrity": "sha512-cU00LGb6GUXCwof6ACgSMKo3q7XYbsyTj0WsKHLi1nw7pV0NCq8nFTn6ZRBYLoKiV8t+jWl0Hv8KkgymmK5L5g==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.26.0.tgz", + "integrity": "sha512-XxoEL++tTkyuvu+wq/QS8bwyTXZv2y5XYCMcWL45b8XwkiS8eEEEej9BkMGSRwxa5J4K+LDeIhLrS23CpQyfig==", "cpu": [ "arm64" ], @@ -3172,9 +3475,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.23.0.tgz", - "integrity": "sha512-q4jdx5+5NfB0/qMbXbOmuC6oo7caPnFghJbIAV90cXZqgV8Am3miZhC4p+sQVdacqxfd+3nrle4C8icR3p1AYw==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.26.0.tgz", + "integrity": "sha512-1dkTfZQAYLj8MUSkd6L/+TWTG8V6Kfrzfa0T1fSlXCXQHrt1HC1/UepXHtKHDt/9yFwyoeayivxXAsApVxn6zA==", "cpu": [ "x64" ], @@ -3191,9 +3494,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.23.0.tgz", - "integrity": "sha512-G9Ri3qpmF4qef2CV/80dADHKXRAQeQXpQTLx7AiQrBYQHqBjB75oxqj06FCIe5g4hNCqLPnM9fsO4CyiT1sFSQ==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.26.0.tgz", + "integrity": "sha512-yX3Rk9m00JGCUzuUhFEojY+jf/6zHs3XU8S8Vk+FRbnr4St7cjyMXdNjuA2LjiT8e7j8xHRCH8hyZ4H/btRE4A==", "cpu": [ "x64" ], @@ -3209,10 +3512,29 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.26.0.tgz", + "integrity": "sha512-X/597/cFnCogy9VItj/+7Tgu5VLbAtDF7KZDPdSw0MaL6FL940th1y3HiOzFIlziVvAtbo0RB3NAae1Oofr+Tw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.23.0.tgz", - "integrity": "sha512-1rcBDJLU+obPPJM6qR5fgBUiCdZwZLafZM5f9kwjFLkb/UBNIzmae39uCSmh71nzPCTXZqHbvwu23OWnWEz+eg==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.26.0.tgz", + "integrity": "sha512-pYS3EyGP3JRhfqEFYmfFDiZ9/pVNfy8jVIYtrx9TVNusVyDK3gpW1w/rbvroQ4bDJi7grdUtyrYU6V2xkY/bBw==", "cpu": [ "x64" ], @@ -3238,11 +3560,11 @@ } }, "node_modules/lru-cache": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", - "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", + "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==", "engines": { - "node": "14 || >=16.14" + "node": "20 || >=22" } }, "node_modules/markdown-table": { @@ -3612,9 +3934,9 @@ } }, "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==", + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", @@ -4290,23 +4612,23 @@ } }, "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "engines": { "node": ">=16 || 14 >=14.17" } @@ -4316,23 +4638,66 @@ "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==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", + "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==", "dependencies": { - "@types/nlcst": "^1.0.0" + "@types/nlcst": "^2.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", @@ -4341,6 +4706,11 @@ "node": ">=0.10.0" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" + }, "node_modules/parse-entities": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", @@ -4361,24 +4731,38 @@ } }, "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==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz", + "integrity": "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==", "dependencies": { - "nlcst-to-string": "^3.0.0", - "unist-util-modify-children": "^3.0.0", - "unist-util-visit-children": "^2.0.0" + "@types/nlcst": "^2.0.0", + "@types/unist": "^3.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-modify-children": "^4.0.0", + "unist-util-visit-children": "^3.0.0", + "vfile": "^6.0.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/parse-latin/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/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/parse-svg-path": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz", + "integrity": "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==", + "license": "MIT" + }, "node_modules/parse5": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", @@ -4409,15 +4793,15 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -4455,30 +4839,44 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pixi.js": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.3.3.tgz", + "integrity": "sha512-dpucBKAqEm0K51MQKlXvyIJ40bcxniP82uz4ZPEQejGtPp0P+vueuG5DyArHCkC48mkVE2FEDvyYvBa45/JlQg==", + "license": "MIT", + "dependencies": { + "@pixi/colord": "^2.9.6", + "@types/css-font-loading-module": "^0.0.12", + "@types/earcut": "^2.1.4", + "@webgpu/types": "^0.1.40", + "@xmldom/xmldom": "^0.8.10", + "earcut": "^2.2.4", + "eventemitter3": "^5.0.1", + "ismobilejs": "^1.1.1", + "parse-svg-path": "^0.1.2" + } + }, "node_modules/preact": { - "version": "10.19.6", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.6.tgz", - "integrity": "sha512-gympg+T2Z1fG1unB8NH29yHJwnEaCH37Z32diPDku316OTnRPeMbiRV9kTrfZpocXjdfnWuFUl/Mj4BHaf6gnw==", + "version": "10.23.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.23.2.tgz", + "integrity": "sha512-kKYfePf9rzKnxOAKDpsWhg/ysrHPqT+yQ7UW4JjdnqjFIeNUnNcEJvhuA8fDenxAGWzUqtd51DfVg7xp/8T9NA==", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" } }, "node_modules/preact-render-to-string": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.3.1.tgz", - "integrity": "sha512-NQ28WrjLtWY6lKDlTxnFpKHZdpjfF+oE6V4tZ0rTrunHrtZp6Dm0oFrcJalt/5PNeqJz4j1DuZDS0Y6rCBoqDA==", - "dependencies": { - "pretty-format": "^3.8.0" - }, + "version": "6.5.9", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.9.tgz", + "integrity": "sha512-Fn9R89h6qrQeSRmsH2O2fWzqpVwsJgEL9UTly5nGEV2ldhVuG+9JhXdNJ6zreIkOZcBT20+AOMwlG1x72znJ+g==", "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==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -4501,11 +4899,6 @@ "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", @@ -4598,10 +4991,31 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/rehype-citation": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/rehype-citation/-/rehype-citation-2.1.1.tgz", + "integrity": "sha512-9aXDHR4pnR8Yyt6DFDXq2EKcxhwjf4HuF5kxvQDbhH8xfY5VjqJQNmIzpsXAn6YcmytOGWNf+82WnIroKAaLSw==", + "dependencies": { + "@citation-js/core": "^0.7.14", + "@citation-js/date": "^0.5.1", + "@citation-js/name": "^0.4.2", + "@citation-js/plugin-bibjson": "^0.7.14", + "@citation-js/plugin-bibtex": "^0.7.14", + "@citation-js/plugin-csl": "^0.7.14", + "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==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.1.tgz", + "integrity": "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==", "dependencies": { "@types/hast": "^3.0.0", "@types/katex": "^0.16.0", @@ -4710,9 +5124,9 @@ } }, "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==", + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/rehype-pretty-code/-/rehype-pretty-code-0.13.2.tgz", + "integrity": "sha512-F+PaFMscfJOcSHcR2b//+hk/0jT56hmGDqXcVD6VC9j0CUSGiqv8YxaWUyhR7qEIRRSbzAVxx+0uxzk+akXs+w==", "dependencies": { "@types/hast": "^3.0.4", "hast-util-to-string": "^3.0.0", @@ -4725,7 +5139,7 @@ "node": ">=18" }, "peerDependencies": { - "shiki": "^1.0.0" + "shiki": "^1.3.0" } }, "node_modules/rehype-raw": { @@ -4866,30 +5280,17 @@ } }, "node_modules/remark-smartypants": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-2.0.0.tgz", - "integrity": "sha512-Rc0VDmr/yhnMQIz8n2ACYXlfw/P/XZev884QU1I5u+5DgJls32o97Vc1RbK3pfumLsJomS2yy8eT4Fxj/2MDVA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.2.tgz", + "integrity": "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==", "dependencies": { - "retext": "^8.1.0", - "retext-smartypants": "^5.1.0", - "unist-util-visit": "^4.1.0" + "retext": "^9.0.0", + "retext-smartypants": "^6.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/remark-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": ">=16.0.0" } }, "node_modules/remark-stringify": { @@ -4953,14 +5354,14 @@ } }, "node_modules/retext": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/retext/-/retext-8.1.0.tgz", - "integrity": "sha512-N9/Kq7YTn6ZpzfiGW45WfEGJqFf1IM1q8OsRa1CGzIebCJBNCANDRmOrholiDRGKo/We7ofKR4SEvcGAWEMD3Q==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", + "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==", "dependencies": { - "@types/nlcst": "^1.0.0", - "retext-latin": "^3.0.0", - "retext-stringify": "^3.0.0", - "unified": "^10.0.0" + "@types/nlcst": "^2.0.0", + "retext-latin": "^4.0.0", + "retext-stringify": "^4.0.0", + "unified": "^11.0.0" }, "funding": { "type": "opencollective", @@ -4968,47 +5369,13 @@ } }, "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==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-4.0.0.tgz", + "integrity": "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==", "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" + "@types/nlcst": "^2.0.0", + "parse-latin": "^7.0.0", + "unified": "^11.0.0" }, "funding": { "type": "opencollective", @@ -5016,61 +5383,13 @@ } }, "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==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.1.0.tgz", + "integrity": "sha512-LDPXg95346bqFZnDMHo0S7Rq5p64+B+N8Vz733+wPMDtwb9rCOs9LIdIEhrUOU+TAywX9St+ocQWJt8wrzivcQ==", "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" + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-visit": "^5.0.0" }, "funding": { "type": "opencollective", @@ -5078,79 +5397,13 @@ } }, "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==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-4.0.0.tgz", + "integrity": "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==", "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" + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unified": "^11.0.0" }, "funding": { "type": "opencollective", @@ -5167,22 +5420,23 @@ } }, "node_modules/rfdc": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", - "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" }, "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==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", "dependencies": { - "glob": "^10.3.7" + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" }, "bin": { "rimraf": "dist/esm/bin.mjs" }, "engines": { - "node": ">=14" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -5324,11 +5578,12 @@ } }, "node_modules/shiki": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.1.6.tgz", - "integrity": "sha512-j4pcpvaQWHb42cHeV+W6P+X/VcK7Y2ctvEham6zB8wsuRQroT6cEMIkiUmBU2Nqg2qnHZDH6ZyRdVldcy0l6xw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.12.1.tgz", + "integrity": "sha512-nwmjbHKnOYYAe1aaQyEBHvQymJgfm86ZSS7fT8OaPRr4sbAcBNz7PbfAikMEFSDQ6se2j2zobkXvVKcBOm0ysg==", "dependencies": { - "@shikijs/core": "1.1.6" + "@shikijs/core": "1.12.1", + "@types/hast": "^3.0.4" } }, "node_modules/signal-exit": { @@ -5551,6 +5806,18 @@ "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", @@ -5644,13 +5911,13 @@ "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==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.17.0.tgz", + "integrity": "sha512-eN4mnDA5UMKDt4YZixo9tBioibaMBpoxBkD+rIPAjVmYERSG0/dWEY1CEFuV89CgASlKL499q8AhmkMnnjtOJg==", "dev": true, "dependencies": { - "esbuild": "~0.19.10", - "get-tsconfig": "^4.7.2" + "esbuild": "~0.23.0", + "get-tsconfig": "^4.7.5" }, "bin": { "tsx": "dist/cli.mjs" @@ -5662,10 +5929,417 @@ "fsevents": "~2.3.3" } }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz", + "integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.0.tgz", + "integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz", + "integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.0.tgz", + "integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz", + "integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz", + "integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz", + "integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz", + "integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz", + "integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz", + "integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz", + "integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz", + "integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz", + "integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz", + "integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz", + "integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz", + "integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz", + "integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz", + "integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz", + "integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz", + "integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz", + "integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz", + "integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz", + "integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz", + "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.0", + "@esbuild/android-arm": "0.23.0", + "@esbuild/android-arm64": "0.23.0", + "@esbuild/android-x64": "0.23.0", + "@esbuild/darwin-arm64": "0.23.0", + "@esbuild/darwin-x64": "0.23.0", + "@esbuild/freebsd-arm64": "0.23.0", + "@esbuild/freebsd-x64": "0.23.0", + "@esbuild/linux-arm": "0.23.0", + "@esbuild/linux-arm64": "0.23.0", + "@esbuild/linux-ia32": "0.23.0", + "@esbuild/linux-loong64": "0.23.0", + "@esbuild/linux-mips64el": "0.23.0", + "@esbuild/linux-ppc64": "0.23.0", + "@esbuild/linux-riscv64": "0.23.0", + "@esbuild/linux-s390x": "0.23.0", + "@esbuild/linux-x64": "0.23.0", + "@esbuild/netbsd-x64": "0.23.0", + "@esbuild/openbsd-arm64": "0.23.0", + "@esbuild/openbsd-x64": "0.23.0", + "@esbuild/sunos-x64": "0.23.0", + "@esbuild/win32-arm64": "0.23.0", + "@esbuild/win32-ia32": "0.23.0", + "@esbuild/win32-x64": "0.23.0" + } + }, "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -5676,20 +6350,11 @@ } }, "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==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz", + "integrity": "sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==", "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", @@ -5754,24 +6419,12 @@ "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==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz", + "integrity": "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==", "dependencies": { - "@types/unist": "^2.0.0", + "@types/unist": "^3.0.0", "array-iterate": "^2.0.0" }, "funding": { @@ -5779,6 +6432,11 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unist-util-modify-children/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-position": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", @@ -5814,18 +6472,6 @@ "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", @@ -5841,29 +6487,21 @@ } }, "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==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz", + "integrity": "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==", "dependencies": { - "@types/unist": "^2.0.0" + "@types/unist": "^3.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-children/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/@types/unist": { "version": "3.0.2", @@ -5913,9 +6551,9 @@ } }, "node_modules/vfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", - "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.2.tgz", + "integrity": "sha512-zND7NlS8rJYb/sPqkb13ZvbbUoExdbi4w3SfRrMq6R3FvnLQmmfpajJNITuuYm6AZ5uao9vy4BAos3EXBPf2rg==", "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0", @@ -5944,19 +6582,6 @@ "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", @@ -6066,9 +6691,9 @@ "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==" + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.1.3.tgz", + "integrity": "sha512-LhUrk4tbxJRDQmRrrFWA9EnboXI79fe0ZNTy3u8m+dqPN1EkVSIsQYAB8OF/fkyhG8Rtup+c/bzj/+bzbG8fqg==" }, "node_modules/wrap-ansi": { "version": "8.1.0", @@ -6152,9 +6777,9 @@ } }, "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index ef623a0..5823535 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@jackyzha0/quartz", "description": "🌱 publish your digital garden and notes as a website", "private": true, - "version": "4.2.3", + "version": "4.3.1", "type": "module", "author": "jackyzha0 ", "license": "MIT", @@ -12,6 +12,7 @@ "url": "https://github.com/jackyzha0/quartz.git" }, "scripts": { + "quartz": "./quartz/bootstrap-cli.mjs", "docs": "npx quartz build --serve -d docs", "check": "tsc --noEmit && npx prettier . --check", "format": "npx prettier . --write", @@ -20,7 +21,7 @@ }, "engines": { "npm": ">=9.3.1", - "node": ">=18.14" + "node": "20 || >=22" }, "keywords": [ "site generator", @@ -35,37 +36,40 @@ }, "dependencies": { "@clack/prompts": "^0.7.0", - "@floating-ui/dom": "^1.6.3", - "@napi-rs/simple-git": "0.1.16", - "async-mutex": "^0.4.1", + "@floating-ui/dom": "^1.6.10", + "@napi-rs/simple-git": "0.1.17", + "@tweenjs/tween.js": "^25.0.0", + "async-mutex": "^0.5.0", "chalk": "^5.3.0", "chokidar": "^3.6.0", "cli-spinner": "^0.2.10", - "d3": "^7.8.5", + "d3": "^7.9.0", "esbuild-sass-plugin": "^2.16.1", "flexsearch": "0.7.43", "github-slugger": "^2.0.0", - "globby": "^14.0.1", + "globby": "^14.0.2", "gray-matter": "^4.0.3", - "hast-util-to-html": "^9.0.0", + "hast-util-to-html": "^9.0.1", "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.23.0", + "lightningcss": "^1.26.0", "mdast-util-find-and-replace": "^3.0.1", - "mdast-util-to-hast": "^13.1.0", + "mdast-util-to-hast": "^13.2.0", "mdast-util-to-string": "^4.0.0", "micromorph": "^0.4.5", - "preact": "^10.19.6", - "preact-render-to-string": "^6.3.1", + "pixi.js": "^8.3.3", + "preact": "^10.23.2", + "preact-render-to-string": "^6.5.9", "pretty-bytes": "^6.1.1", "pretty-time": "^1.1.0", "reading-time": "^1.5.0", "rehype-autolink-headings": "^7.1.0", - "rehype-katex": "^7.0.0", + "rehype-citation": "^2.1.1", + "rehype-katex": "^7.0.1", "rehype-mathjax": "^6.0.0", - "rehype-pretty-code": "^0.13.0", + "rehype-pretty-code": "^0.13.2", "rehype-raw": "^7.0.0", "rehype-slug": "^6.0.0", "remark": "^15.0.1", @@ -75,19 +79,19 @@ "remark-math": "^6.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.0", - "remark-smartypants": "^2.0.0", - "rfdc": "^1.3.1", - "rimraf": "^5.0.5", + "remark-smartypants": "^3.0.2", + "rfdc": "^1.4.1", + "rimraf": "^6.0.1", "serve-handler": "^6.1.5", - "shiki": "^1.1.6", + "shiki": "^1.12.1", "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", + "vfile": "^6.0.2", + "workerpool": "^9.1.3", + "ws": "^8.18.0", "yargs": "^17.7.2" }, "devDependencies": { @@ -95,14 +99,14 @@ "@types/d3": "^7.4.3", "@types/hast": "^3.0.4", "@types/js-yaml": "^4.0.9", - "@types/node": "^20.11.19", + "@types/node": "^22.1.0", "@types/pretty-time": "^1.1.5", "@types/source-map-support": "^0.5.10", - "@types/ws": "^8.5.10", + "@types/ws": "^8.5.12", "@types/yargs": "^17.0.32", "esbuild": "^0.19.9", - "prettier": "^3.2.4", - "tsx": "^4.7.1", - "typescript": "^5.3.3" + "prettier": "^3.3.3", + "tsx": "^4.17.0", + "typescript": "^5.5.4" } } diff --git a/quartz.config.ts b/quartz.config.ts index ab9f542..b3befde 100644 --- a/quartz.config.ts +++ b/quartz.config.ts @@ -37,6 +37,7 @@ const config: QuartzConfig = { secondary:"#f5f5f7", tertiary: "#a1a1a1", highlight: "rgba(143, 159, 169, 0.15)", + textHighlight: "#fff23688", }, darkMode: { light: "#161618", @@ -47,6 +48,7 @@ const config: QuartzConfig = { secondary:"#ffffff", tertiary: "#ffffff", highlight: "rgba(143, 159, 169, 0.15)", + textHighlight: "#b3aa0288", }, }, }, @@ -69,11 +71,12 @@ const config: QuartzConfig = { Plugin.TableOfContents(), Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }), Plugin.Description(), + Plugin.Latex({ renderEngine: "katex" }), ], filters: [Plugin.RemoveDrafts()], emitters: [ Plugin.AliasRedirects(), - Plugin.ComponentResources({ fontOrigin: "googleFonts" }), + Plugin.ComponentResources(), Plugin.ContentPage(), Plugin.ContentIndex({ rssFullHtml: true diff --git a/quartz/build.ts b/quartz/build.ts index d72b8dd..342a27c 100644 --- a/quartz/build.ts +++ b/quartz/build.ts @@ -38,8 +38,13 @@ type BuildData = { type FileEvent = "add" | "change" | "delete" +function newBuildId() { + return new Date().toISOString() +} + async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) { const ctx: BuildCtx = { + buildId: newBuildId(), argv, cfg, allSlugs: [], @@ -167,6 +172,7 @@ async function partialRebuildFromEntrypoint( const perf = new PerfTimer() console.log(chalk.yellow("Detected change, rebuilding...")) + ctx.buildId = newBuildId() // UPDATE DEP GRAPH const fp = joinSegments(argv.directory, toPosixPath(filepath)) as FilePath @@ -309,6 +315,8 @@ async function partialRebuildFromEntrypoint( } await rimraf([...destinationsToDelete]) + console.log(chalk.green(`Done rebuilding in ${perf.timeSince()}`)) + toRemove.clear() release() clientRefresh() @@ -361,14 +369,10 @@ async function rebuildFromEntrypoint( const perf = new PerfTimer() console.log(chalk.yellow("Detected change, rebuilding...")) + ctx.buildId = newBuildId() + try { const filesToRebuild = [...toRebuild].filter((fp) => !toRemove.has(fp)) - - const trackedSlugs = [...new Set([...contentMap.keys(), ...toRebuild, ...trackedAssets])] - .filter((fp) => !toRemove.has(fp)) - .map((fp) => slugifyFilePath(path.posix.relative(argv.directory, fp) as FilePath)) - - ctx.allSlugs = [...new Set([...initialSlugs, ...trackedSlugs])] const parsedContent = await parseMarkdown(ctx, filesToRebuild) for (const content of parsedContent) { const [_tree, vfile] = content @@ -382,6 +386,13 @@ async function rebuildFromEntrypoint( const parsedFiles = [...contentMap.values()] const filteredContent = filterContent(ctx, parsedFiles) + // re-update slugs + const trackedSlugs = [...new Set([...contentMap.keys(), ...toRebuild, ...trackedAssets])] + .filter((fp) => !toRemove.has(fp)) + .map((fp) => slugifyFilePath(path.posix.relative(argv.directory, fp) as FilePath)) + + ctx.allSlugs = [...new Set([...initialSlugs, ...trackedSlugs])] + // TODO: we can probably traverse the link graph to figure out what's safe to delete here // instead of just deleting everything await rimraf(path.join(argv.output, ".*"), { glob: true }) diff --git a/quartz/cfg.ts b/quartz/cfg.ts index a477db0..0c344d3 100644 --- a/quartz/cfg.ts +++ b/quartz/cfg.ts @@ -19,6 +19,25 @@ export type Analytics = websiteId: string host?: string } + | { + provider: "goatcounter" + websiteId: string + host?: string + scriptSrc?: string + } + | { + provider: "posthog" + apiKey: string + host?: string + } + | { + provider: "tinylytics" + siteId: string + } + | { + provider: "cabin" + host?: string + } export interface GlobalConfiguration { pageTitle: string @@ -58,10 +77,11 @@ export interface FullPageLayout { header: QuartzComponent[] beforeBody: QuartzComponent[] pageBody: QuartzComponent + afterBody: QuartzComponent[] left: QuartzComponent[] right: QuartzComponent[] footer: QuartzComponent } export type PageLayout = Pick -export type SharedLayout = Pick +export type SharedLayout = Pick diff --git a/quartz/components/Comments.tsx b/quartz/components/Comments.tsx new file mode 100644 index 0000000..8e44940 --- /dev/null +++ b/quartz/components/Comments.tsx @@ -0,0 +1,44 @@ +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" +import { classNames } from "../util/lang" +// @ts-ignore +import script from "./scripts/comments.inline" + +type Options = { + provider: "giscus" + options: { + repo: `${string}/${string}` + repoId: string + category: string + categoryId: string + mapping?: "url" | "title" | "og:title" | "specific" | "number" | "pathname" + strict?: boolean + reactionsEnabled?: boolean + inputPosition?: "top" | "bottom" + } +} + +function boolToStringBool(b: boolean): string { + return b ? "1" : "0" +} + +export default ((opts: Options) => { + const Comments: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => { + return ( +
+ ) + } + + Comments.afterDOMLoaded = script + + return Comments +}) satisfies QuartzComponentConstructor diff --git a/quartz/components/ContentMeta.tsx b/quartz/components/ContentMeta.tsx index bcbe428..5dfec14 100644 --- a/quartz/components/ContentMeta.tsx +++ b/quartz/components/ContentMeta.tsx @@ -3,16 +3,20 @@ 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) => { @@ -23,7 +27,7 @@ export default ((opts?: Partial) => { const text = fileData.text if (text) { - const segments: string[] = [] + const segments: (string | JSX.Element)[] = [] if (fileData.dates) { segments.push(formatDate(getDate(cfg, fileData)!, cfg.locale)) @@ -38,17 +42,19 @@ export default ((opts?: Partial) => { segments.push(displayedTime) } - return

{segments.join(", ")}

+ const segmentsElements = segments.map((segment) => {segment}) + + return ( +

+ {segmentsElements} +

+ ) } else { return null } } - ContentMetadata.css = ` - .content-meta { - margin-top: 0; - color: var(--gray); - } - ` + ContentMetadata.css = style + return ContentMetadata }) satisfies QuartzComponentConstructor diff --git a/quartz/components/Darkmode.tsx b/quartz/components/Darkmode.tsx index 8ed7c99..f64aad6 100644 --- a/quartz/components/Darkmode.tsx +++ b/quartz/components/Darkmode.tsx @@ -9,41 +9,38 @@ import { classNames } from "../util/lang" const Darkmode: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => { return ( -
- - - -
+ ) } diff --git a/quartz/components/Explorer.tsx b/quartz/components/Explorer.tsx index cffc079..ec7c48e 100644 --- a/quartz/components/Explorer.tsx +++ b/quartz/components/Explorer.tsx @@ -44,12 +44,9 @@ export default ((userOpts?: Partial) => { // memoized let fileTree: FileNode let jsonTree: string + let lastBuildId: string = "" function constructFileTree(allFiles: QuartzPluginData[]) { - if (fileTree) { - return - } - // Construct tree from allFiles fileTree = new FileNode("") allFiles.forEach((file) => fileTree.add(file)) @@ -76,12 +73,17 @@ export default ((userOpts?: Partial) => { } const Explorer: QuartzComponent = ({ + ctx, cfg, allFiles, displayClass, fileData, }: QuartzComponentProps) => { - constructFileTree(allFiles) + if (ctx.buildId !== lastBuildId) { + lastBuildId = ctx.buildId + constructFileTree(allFiles) + } + return (
diff --git a/quartz/components/Head.tsx b/quartz/components/Head.tsx index 3cb6bea..46ba5e0 100644 --- a/quartz/components/Head.tsx +++ b/quartz/components/Head.tsx @@ -1,6 +1,7 @@ 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 (() => { @@ -21,10 +22,11 @@ export default (() => { {title} - {cfg.theme.cdnCaching && ( + {cfg.theme.cdnCaching && cfg.theme.fontOrigin === "googleFonts" && ( <> + )} diff --git a/quartz/components/PageList.tsx b/quartz/components/PageList.tsx index 62b77b1..cc0124a 100644 --- a/quartz/components/PageList.tsx +++ b/quartz/components/PageList.tsx @@ -4,9 +4,9 @@ import { Date, getDate } from "./Date" import { QuartzComponent, QuartzComponentProps } from "./types" import { GlobalConfiguration } from "../cfg" -export function byDateAndAlphabetical( - cfg: GlobalConfiguration, -): (f1: QuartzPluginData, f2: QuartzPluginData) => number { +export type SortFn = (f1: QuartzPluginData, f2: QuartzPluginData) => number + +export function byDateAndAlphabetical(cfg: GlobalConfiguration): SortFn { return (f1, f2) => { if (f1.dates && f2.dates) { // sort descending @@ -27,10 +27,12 @@ export function byDateAndAlphabetical( type Props = { limit?: number + sort?: SortFn } & QuartzComponentProps -export const PageList: QuartzComponent = ({ cfg, fileData, allFiles, limit }: Props) => { - let list = allFiles.sort(byDateAndAlphabetical(cfg)) +export const PageList: QuartzComponent = ({ cfg, fileData, allFiles, limit, sort }: Props) => { + const sorter = sort ?? byDateAndAlphabetical(cfg) + let list = allFiles.sort(sorter) if (limit) { list = list.slice(0, limit) } @@ -44,11 +46,13 @@ export const PageList: QuartzComponent = ({ cfg, fileData, allFiles, limit }: Pr return (
  • ))} diff --git a/quartz/components/PageTitle.tsx b/quartz/components/PageTitle.tsx index 2362f10..046dc52 100644 --- a/quartz/components/PageTitle.tsx +++ b/quartz/components/PageTitle.tsx @@ -7,14 +7,15 @@ const PageTitle: QuartzComponent = ({ fileData, cfg, displayClass }: QuartzCompo const title = cfg?.pageTitle ?? i18n(cfg.locale).propertyDefaults.title const baseDir = pathToRoot(fileData.slug!) return ( -

    +

    {title} -

    + ) } PageTitle.css = ` .page-title { + font-size: 1.75rem; margin: 0; } ` diff --git a/quartz/components/RecentNotes.tsx b/quartz/components/RecentNotes.tsx index 549b025..2c32fea 100644 --- a/quartz/components/RecentNotes.tsx +++ b/quartz/components/RecentNotes.tsx @@ -12,6 +12,7 @@ interface Options { title?: string limit: number linkToMore: SimpleSlug | false + showTags: boolean filter: (f: QuartzPluginData) => boolean sort: (f1: QuartzPluginData, f2: QuartzPluginData) => number } @@ -19,6 +20,7 @@ interface Options { const defaultOptions = (cfg: GlobalConfiguration): Options => ({ limit: 3, linkToMore: false, + showTags: true, filter: () => true, sort: byDateAndAlphabetical(cfg), }) @@ -56,18 +58,20 @@ export default ((userOpts?: Partial) => {

    )} - + {opts.showTags && ( + + )}
    ) diff --git a/quartz/components/Search.tsx b/quartz/components/Search.tsx index 01e5a35..8b97555 100644 --- a/quartz/components/Search.tsx +++ b/quartz/components/Search.tsx @@ -19,24 +19,16 @@ export default ((userOpts?: Partial) => { const searchPlaceholder = i18n(cfg.locale).components.search.searchBarPlaceholder return (
    -
    +
    +
    - -
    +
      {fileData.toc.map((tocEntry) => (
    • diff --git a/quartz/components/TagList.tsx b/quartz/components/TagList.tsx index 04a483b..ba48098 100644 --- a/quartz/components/TagList.tsx +++ b/quartz/components/TagList.tsx @@ -9,12 +9,11 @@ const TagList: QuartzComponent = ({ fileData, displayClass }: QuartzComponentPro return (
        {tags.map((tag) => { - const display = `#${tag}` const linkDest = baseDir + `/tags/${slugTag(tag)}` return (
      • - {display} + {tag}
      • ) diff --git a/quartz/components/index.ts b/quartz/components/index.ts index b3db76b..5b19794 100644 --- a/quartz/components/index.ts +++ b/quartz/components/index.ts @@ -19,6 +19,7 @@ import DesktopOnly from "./DesktopOnly" import MobileOnly from "./MobileOnly" import RecentNotes from "./RecentNotes" import Breadcrumbs from "./Breadcrumbs" +import Comments from "./Comments" export { ArticleTitle, @@ -42,4 +43,5 @@ export { RecentNotes, NotFound, Breadcrumbs, + Comments, } diff --git a/quartz/components/pages/404.tsx b/quartz/components/pages/404.tsx index 4ef1b91..63da2c8 100644 --- a/quartz/components/pages/404.tsx +++ b/quartz/components/pages/404.tsx @@ -2,10 +2,15 @@ import { i18n } from "../../i18n" import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../types" const NotFound: QuartzComponent = ({ cfg }: QuartzComponentProps) => { + // If baseUrl contains a pathname after the domain, use this as the home link + const url = new URL(`https://${cfg.baseUrl ?? "example.com"}`) + const baseDir = url.pathname + return ( ) } diff --git a/quartz/components/pages/FolderContent.tsx b/quartz/components/pages/FolderContent.tsx index 55f1e42..dc216cd 100644 --- a/quartz/components/pages/FolderContent.tsx +++ b/quartz/components/pages/FolderContent.tsx @@ -2,7 +2,7 @@ import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } fro import path from "path" import style from "../styles/listPage.scss" -import { PageList } from "../PageList" +import { PageList, SortFn } from "../PageList" import { stripSlashes, simplifySlug } from "../../util/path" import { Root } from "hast" import { htmlToJsx } from "../../util/jsx" @@ -13,6 +13,7 @@ interface FolderContentOptions { * Whether to display number of folders */ showFolderCount: boolean + sort?: SortFn } const defaultOptions: FolderContentOptions = { @@ -37,6 +38,7 @@ export default ((opts?: Partial) => { const classes = ["popover-hint", ...cssClasses].join(" ") const listProps = { ...props, + sort: options.sort, allFiles: allPagesInFolder, } @@ -47,9 +49,7 @@ export default ((opts?: Partial) => { return (
        -
        -

        {content}

        -
        +
        {content}
        {options.showFolderCount && (

        diff --git a/quartz/components/pages/TagContent.tsx b/quartz/components/pages/TagContent.tsx index 1dd9124..e41ab46 100644 --- a/quartz/components/pages/TagContent.tsx +++ b/quartz/components/pages/TagContent.tsx @@ -1,107 +1,127 @@ import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../types" import style from "../styles/listPage.scss" -import { PageList } from "../PageList" +import { PageList, SortFn } 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}`)[0] - const content = contentPage?.description - 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 })}

        -
        - -
        -
        -
        - ) - } +interface TagContentOptions { + sort?: SortFn + numPages: number } -TagContent.css = style + PageList.css -export default (() => TagContent) satisfies QuartzComponentConstructor +const defaultOptions: TagContentOptions = { + numPages: 10, +} + +export default ((opts?: Partial) => { + const options: TagContentOptions = { ...defaultOptions, ...opts } + + 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 > options.numPages && ( + <> + {" "} + + {i18n(cfg.locale).pages.tagContent.showingFirst({ + count: options.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 + return TagContent +}) satisfies QuartzComponentConstructor diff --git a/quartz/components/renderPage.tsx b/quartz/components/renderPage.tsx index 5394d6f..ec5124f 100644 --- a/quartz/components/renderPage.tsx +++ b/quartz/components/renderPage.tsx @@ -14,11 +14,13 @@ interface RenderComponents { header: QuartzComponent[] beforeBody: QuartzComponent[] pageBody: QuartzComponent + afterBody: QuartzComponent[] left: QuartzComponent[] right: QuartzComponent[] footer: QuartzComponent } +const headerRegex = new RegExp(/h[1-6]/) export function pageResources( baseDir: FullSlug | RelativeURL, staticResources: StaticResources, @@ -105,18 +107,24 @@ export function renderPage( // header transclude blockRef = blockRef.slice(1) let startIdx = undefined + let startDepth = undefined let endIdx = undefined for (const [i, el] of page.htmlAst.children.entries()) { - if (el.type === "element" && el.tagName.match(/h[1-6]/)) { - if (endIdx) { - break - } + // skip non-headers + if (!(el.type === "element" && el.tagName.match(headerRegex))) continue + const depth = Number(el.tagName.substring(1)) - if (startIdx !== undefined) { - endIdx = i - } else if (el.properties?.id === blockRef) { + // 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 } } @@ -180,6 +188,7 @@ export function renderPage( header, beforeBody, pageBody: Content, + afterBody, left, right, footer: Footer, @@ -203,7 +212,7 @@ export function renderPage(
        ) - const lang = componentData.frontmatter?.lang ?? cfg.locale?.split("-")[0] ?? "en" + const lang = componentData.fileData.frontmatter?.lang ?? cfg.locale?.split("-")[0] ?? "en" const doc = ( @@ -225,6 +234,12 @@ export function renderPage(
    +
    +
    {RightComponent} diff --git a/quartz/components/scripts/comments.inline.ts b/quartz/components/scripts/comments.inline.ts new file mode 100644 index 0000000..4ab29f0 --- /dev/null +++ b/quartz/components/scripts/comments.inline.ts @@ -0,0 +1,67 @@ +const changeTheme = (e: CustomEventMap["themechange"]) => { + const theme = e.detail.theme + const iframe = document.querySelector("iframe.giscus-frame") as HTMLIFrameElement + if (!iframe) { + return + } + + if (!iframe.contentWindow) { + return + } + + iframe.contentWindow.postMessage( + { + giscus: { + setConfig: { + theme: theme, + }, + }, + }, + "https://giscus.app", + ) +} + +type GiscusElement = Omit & { + dataset: DOMStringMap & { + repo: `${string}/${string}` + repoId: string + category: string + categoryId: string + mapping: "url" | "title" | "og:title" | "specific" | "number" | "pathname" + strict: string + reactionsEnabled: string + inputPosition: "top" | "bottom" + } +} + +document.addEventListener("nav", () => { + const giscusContainer = document.querySelector(".giscus") as GiscusElement + if (!giscusContainer) { + return + } + + const giscusScript = document.createElement("script") + giscusScript.src = "https://giscus.app/client.js" + giscusScript.async = true + giscusScript.crossOrigin = "anonymous" + giscusScript.setAttribute("data-loading", "lazy") + giscusScript.setAttribute("data-emit-metadata", "0") + giscusScript.setAttribute("data-repo", giscusContainer.dataset.repo) + giscusScript.setAttribute("data-repo-id", giscusContainer.dataset.repoId) + giscusScript.setAttribute("data-category", giscusContainer.dataset.category) + giscusScript.setAttribute("data-category-id", giscusContainer.dataset.categoryId) + giscusScript.setAttribute("data-mapping", giscusContainer.dataset.mapping) + giscusScript.setAttribute("data-strict", giscusContainer.dataset.strict) + giscusScript.setAttribute("data-reactions-enabled", giscusContainer.dataset.reactionsEnabled) + giscusScript.setAttribute("data-input-position", giscusContainer.dataset.inputPosition) + + const theme = document.documentElement.getAttribute("saved-theme") + if (theme) { + giscusScript.setAttribute("data-theme", theme) + } + + giscusContainer.appendChild(giscusScript) + + document.addEventListener("themechange", changeTheme) + window.addCleanup(() => document.removeEventListener("themechange", changeTheme)) +}) diff --git a/quartz/components/scripts/darkmode.inline.ts b/quartz/components/scripts/darkmode.inline.ts index 48e0aa1..038ae0f 100644 --- a/quartz/components/scripts/darkmode.inline.ts +++ b/quartz/components/scripts/darkmode.inline.ts @@ -11,7 +11,8 @@ const emitThemeChangeEvent = (theme: "light" | "dark") => { document.addEventListener("nav", () => { const switchTheme = (e: Event) => { - const newTheme = (e.target as HTMLInputElement)?.checked ? "dark" : "light" + const newTheme = + document.documentElement.getAttribute("saved-theme") === "dark" ? "light" : "dark" document.documentElement.setAttribute("saved-theme", newTheme) localStorage.setItem("theme", newTheme) emitThemeChangeEvent(newTheme) @@ -21,17 +22,13 @@ document.addEventListener("nav", () => { 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 - } + const themeButton = document.querySelector("#darkmode") as HTMLButtonElement + themeButton.addEventListener("click", switchTheme) + window.addCleanup(() => themeButton.removeEventListener("click", switchTheme)) // Listen for changes in prefers-color-scheme const colorSchemeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)") diff --git a/quartz/components/scripts/explorer.inline.ts b/quartz/components/scripts/explorer.inline.ts index 3eb25ea..584de6c 100644 --- a/quartz/components/scripts/explorer.inline.ts +++ b/quartz/components/scripts/explorer.inline.ts @@ -17,6 +17,10 @@ const observer = new IntersectionObserver((entries) => { function toggleExplorer(this: HTMLElement) { this.classList.toggle("collapsed") + this.setAttribute( + "aria-expanded", + this.getAttribute("aria-expanded") === "true" ? "false" : "true", + ) const content = this.nextElementSibling as MaybeHTMLElement if (!content) return diff --git a/quartz/components/scripts/graph.inline.ts b/quartz/components/scripts/graph.inline.ts index c991e16..6bf43aa 100644 --- a/quartz/components/scripts/graph.inline.ts +++ b/quartz/components/scripts/graph.inline.ts @@ -1,19 +1,56 @@ -import type { ContentDetails, ContentIndex } from "../../plugins/emitters/contentIndex" -import * as d3 from "d3" +import type { ContentDetails } from "../../plugins/emitters/contentIndex" +import { + SimulationNodeDatum, + SimulationLinkDatum, + Simulation, + forceSimulation, + forceManyBody, + forceCenter, + forceLink, + forceCollide, + zoomIdentity, + select, + drag, + zoom, +} from "d3" +import { Text, Graphics, Application, Container, Circle } from "pixi.js" +import { Group as TweenGroup, Tween as Tweened } from "@tweenjs/tween.js" import { registerEscapeHandler, removeAllChildren } from "./util" import { FullSlug, SimpleSlug, getFullSlug, resolveRelative, simplifySlug } from "../../util/path" +import { D3Config } from "../Graph" + +type GraphicsInfo = { + color: string + gfx: Graphics + alpha: number + active: boolean +} type NodeData = { id: SimpleSlug text: string tags: string[] -} & d3.SimulationNodeDatum +} & SimulationNodeDatum -type LinkData = { +type SimpleLinkData = { source: SimpleSlug target: SimpleSlug } +type LinkData = { + source: NodeData + target: NodeData +} & SimulationLinkDatum + +type LinkRenderData = GraphicsInfo & { + simulationData: LinkData +} + +type NodeRenderData = GraphicsInfo & { + simulationData: NodeData + label: Text +} + const localStorageKey = "graph-visited" function getVisited(): Set { return new Set(JSON.parse(localStorage.getItem(localStorageKey) ?? "[]")) @@ -25,6 +62,11 @@ function addToVisited(slug: SimpleSlug) { localStorage.setItem(localStorageKey, JSON.stringify([...visited])) } +type TweenNode = { + update: (time: number) => void + stop: () => void +} + async function renderGraph(container: string, fullSlug: FullSlug) { const slug = simplifySlug(fullSlug) const visited = getVisited() @@ -44,7 +86,8 @@ async function renderGraph(container: string, fullSlug: FullSlug) { opacityScale, removeTags, showTags, - } = JSON.parse(graph.dataset["cfg"]!) + focusOnHover, + } = JSON.parse(graph.dataset["cfg"]!) as D3Config const data: Map = new Map( Object.entries(await fetchData).map(([k, v]) => [ @@ -52,10 +95,11 @@ async function renderGraph(container: string, fullSlug: FullSlug) { v, ]), ) - const links: LinkData[] = [] + const links: SimpleLinkData[] = [] const tags: SimpleSlug[] = [] - const validLinks = new Set(data.keys()) + + const tweens = new Map() for (const [source, details] of data.entries()) { const outgoing = details.links ?? [] @@ -99,232 +143,446 @@ async function renderGraph(container: string, fullSlug: FullSlug) { if (showTags) tags.forEach((tag) => neighbourhood.add(tag)) } + const nodes = [...neighbourhood].map((url) => { + const text = url.startsWith("tags/") ? "#" + url.substring(5) : (data.get(url)?.title ?? url) + return { + id: url, + text, + tags: data.get(url)?.tags ?? [], + } + }) 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)), + nodes, + links: links + .filter((l) => neighbourhood.has(l.source) && neighbourhood.has(l.target)) + .map((l) => ({ + source: nodes.find((n) => n.id === l.source)!, + target: nodes.find((n) => n.id === 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)) + // we virtualize the simulation and use pixi to actually render it + const simulation: Simulation = forceSimulation(graphData.nodes) + .force("charge", forceManyBody().strength(-100 * repelForce)) + .force("center", forceCenter().strength(centerForce)) + .force("link", forceLink(graphData.links).distance(linkDistance)) + .force("collide", forceCollide((n) => nodeRadius(n)).iterations(3)) - const height = Math.max(graph.offsetHeight, 250) const width = graph.offsetWidth + const height = Math.max(graph.offsetHeight, 250) - 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") + // precompute style prop strings as pixi doesn't support css variables + const cssVars = [ + "--secondary", + "--tertiary", + "--gray", + "--light", + "--lightgray", + "--dark", + "--darkgray", + "--bodyFont", + ] as const + const computedStyleMap = cssVars.reduce( + (acc, key) => { + acc[key] = getComputedStyle(document.documentElement).getPropertyValue(key) + return acc + }, + {} as Record<(typeof cssVars)[number], string>, + ) // calculate color const color = (d: NodeData) => { const isCurrent = d.id === slug if (isCurrent) { - return "var(--secondary)" + return computedStyleMap["--secondary"] } else if (visited.has(d.id) || d.id.startsWith("tags/")) { - return "var(--tertiary)" + return computedStyleMap["--tertiary"] } else { - return "var(--gray)" + return computedStyleMap["--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 + const numLinks = graphData.links.filter( + (l) => l.source.id === d.id || l.target.id === d.id, + ).length return 2 + Math.sqrt(numLinks) } - // 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())) + let hoveredNodeId: string | null = null + let hoveredNeighbours: Set = new Set() + const linkRenderData: LinkRenderData[] = [] + const nodeRenderData: NodeRenderData[] = [] + function updateHoverInfo(newHoveredId: string | null) { + hoveredNodeId = newHoveredId + + if (newHoveredId === null) { + hoveredNeighbours = new Set() + for (const n of nodeRenderData) { + n.active = false + } + + for (const l of linkRenderData) { + l.active = false + } + } else { + hoveredNeighbours = new Set() + for (const l of linkRenderData) { + const linkData = l.simulationData + if (linkData.source.id === newHoveredId || linkData.target.id === newHoveredId) { + hoveredNeighbours.add(linkData.source.id) + hoveredNeighbours.add(linkData.target.id) + } + + l.active = linkData.source.id === newHoveredId || linkData.target.id === newHoveredId + } + + for (const n of nodeRenderData) { + n.active = hoveredNeighbours.has(n.simulationData.id) + } + } + } + + let dragStartTime = 0 + let dragging = false + + function renderLinks() { + tweens.get("link")?.stop() + const tweenGroup = new TweenGroup() + + for (const l of linkRenderData) { + let alpha = 1 + + // if we are hovering over a node, we want to highlight the immediate neighbours + // with full alpha and the rest with default alpha + if (hoveredNodeId) { + alpha = l.active ? 1 : 0.2 + } + + l.color = l.active ? computedStyleMap["--gray"] : computedStyleMap["--lightgray"] + tweenGroup.add(new Tweened(l).to({ alpha }, 200)) + } + + tweenGroup.getAll().forEach((tw) => tw.start()) + tweens.set("link", { + update: tweenGroup.update.bind(tweenGroup), + stop() { + tweenGroup.getAll().forEach((tw) => tw.stop()) + }, }) - .on("mouseover", function (_, d) { - const neighbours: SimpleSlug[] = data.get(slug)?.links ?? [] - const neighbourNodes = d3 - .selectAll(".node") - .filter((d) => neighbours.includes(d.id)) - const currentId = d.id - const linkNodes = d3 - .selectAll(".link") - .filter((d: any) => d.source.id === currentId || d.target.id === currentId) + } - // highlight neighbour nodes - neighbourNodes.transition().duration(200).attr("fill", color) + function renderLabels() { + tweens.get("label")?.stop() + const tweenGroup = new TweenGroup() - // highlight links - linkNodes.transition().duration(200).attr("stroke", "var(--gray)").attr("stroke-width", 1) + const defaultScale = 1 / scale + const activeScale = defaultScale * 1.1 + for (const n of nodeRenderData) { + const nodeId = n.simulationData.id - const bigFont = fontSize * 1.5 + if (hoveredNodeId === nodeId) { + tweenGroup.add( + new Tweened(n.label).to( + { + alpha: 1, + scale: { x: activeScale, y: activeScale }, + }, + 100, + ), + ) + } else { + tweenGroup.add( + new Tweened(n.label).to( + { + alpha: n.label.alpha, + scale: { x: defaultScale, y: defaultScale }, + }, + 100, + ), + ) + } + } - // 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") + tweenGroup.getAll().forEach((tw) => tw.start()) + tweens.set("label", { + update: tweenGroup.update.bind(tweenGroup), + stop() { + tweenGroup.getAll().forEach((tw) => tw.stop()) + }, }) - .on("mouseleave", function (_, d) { - 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)") + function renderNodes() { + tweens.get("hover")?.stop() - 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") + const tweenGroup = new TweenGroup() + for (const n of nodeRenderData) { + let alpha = 1 + + // if we are hovering over a node, we want to highlight the immediate neighbours + if (hoveredNodeId !== null && focusOnHover) { + alpha = n.active ? 1 : 0.2 + } + + tweenGroup.add(new Tweened(n.gfx, tweenGroup).to({ alpha }, 200)) + } + + tweenGroup.getAll().forEach((tw) => tw.start()) + tweens.set("hover", { + update: tweenGroup.update.bind(tweenGroup), + stop() { + tweenGroup.getAll().forEach((tw) => tw.stop()) + }, }) - // @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)) + function renderPixiFromD3() { + renderNodes() + renderLinks() + renderLabels() + } + + tweens.forEach((tween) => tween.stop()) + tweens.clear() + + const app = new Application() + await app.init({ + width, + height, + antialias: true, + autoStart: false, + autoDensity: true, + backgroundAlpha: 0, + preference: "webgpu", + resolution: window.devicePixelRatio, + eventMode: "static", + }) + graph.appendChild(app.canvas) + + const stage = app.stage + stage.interactive = false + + const labelsContainer = new Container({ zIndex: 3 }) + const nodesContainer = new Container({ zIndex: 2 }) + const linkContainer = new Container({ zIndex: 1 }) + stage.addChild(nodesContainer, labelsContainer, linkContainer) + + for (const n of graphData.nodes) { + const nodeId = n.id + + const label = new Text({ + interactive: false, + eventMode: "none", + text: n.text, + alpha: 0, + anchor: { x: 0.5, y: 1.2 }, + style: { + fontSize: fontSize * 15, + fill: computedStyleMap["--dark"], + fontFamily: computedStyleMap["--bodyFont"], + }, + resolution: window.devicePixelRatio * 4, + }) + label.scale.set(1 / scale) + + let oldLabelOpacity = 0 + const isTagNode = nodeId.startsWith("tags/") + const gfx = new Graphics({ + interactive: true, + label: nodeId, + eventMode: "static", + hitArea: new Circle(0, 0, nodeRadius(n)), + cursor: "pointer", + }) + .circle(0, 0, nodeRadius(n)) + .fill({ color: isTagNode ? computedStyleMap["--light"] : color(n) }) + .stroke({ width: isTagNode ? 2 : 0, color: color(n) }) + .on("pointerover", (e) => { + updateHoverInfo(e.target.label) + oldLabelOpacity = label.alpha + if (!dragging) { + renderPixiFromD3() + } + }) + .on("pointerleave", () => { + updateHoverInfo(null) + label.alpha = oldLabelOpacity + if (!dragging) { + renderPixiFromD3() + } + }) + + nodesContainer.addChild(gfx) + labelsContainer.addChild(label) + + const nodeRenderDatum: NodeRenderData = { + simulationData: n, + gfx, + label, + color: color(n), + alpha: 1, + active: false, + } + + nodeRenderData.push(nodeRenderDatum) + } + + for (const l of graphData.links) { + const gfx = new Graphics({ interactive: false, eventMode: "none" }) + linkContainer.addChild(gfx) + + const linkRenderDatum: LinkRenderData = { + simulationData: l, + gfx, + color: computedStyleMap["--lightgray"], + alpha: 1, + active: false, + } + + linkRenderData.push(linkRenderDatum) + } + + let currentTransform = zoomIdentity + if (enableDrag) { + select(app.canvas).call( + drag() + .container(() => app.canvas) + .subject(() => graphData.nodes.find((n) => n.id === hoveredNodeId)) + .on("start", function dragstarted(event) { + if (!event.active) simulation.alphaTarget(1).restart() + event.subject.fx = event.subject.x + event.subject.fy = event.subject.y + event.subject.__initialDragPos = { + x: event.subject.x, + y: event.subject.y, + fx: event.subject.fx, + fy: event.subject.fy, + } + dragStartTime = Date.now() + dragging = true + }) + .on("drag", function dragged(event) { + const initPos = event.subject.__initialDragPos + event.subject.fx = initPos.x + (event.x - initPos.x) / currentTransform.k + event.subject.fy = initPos.y + (event.y - initPos.y) / currentTransform.k + }) + .on("end", function dragended(event) { + if (!event.active) simulation.alphaTarget(0) + event.subject.fx = null + event.subject.fy = null + dragging = false + + // if the time between mousedown and mouseup is short, we consider it a click + if (Date.now() - dragStartTime < 500) { + const node = graphData.nodes.find((n) => n.id === event.subject.id) as NodeData + const targ = resolveRelative(fullSlug, node.id) + window.spaNavigate(new URL(targ, window.location.toString())) + } + }), + ) + } else { + for (const node of nodeRenderData) { + node.gfx.on("click", () => { + const targ = resolveRelative(fullSlug, node.simulationData.id) + window.spaNavigate(new URL(targ, window.location.toString())) + }) + } + } - // set panning if (enableZoom) { - svg.call( - d3 - .zoom() + select(app.canvas).call( + zoom() .extent([ [0, 0], [width, height], ]) .scaleExtent([0.25, 4]) .on("zoom", ({ transform }) => { - link.attr("transform", transform) - node.attr("transform", transform) + currentTransform = transform + stage.scale.set(transform.k, transform.k) + stage.position.set(transform.x, transform.y) + + // zoom adjusts opacity of labels too const scale = transform.k * opacityScale - const scaledOpacity = Math.max((scale - 1) / 3.75, 0) - labels.attr("transform", transform).style("opacity", scaledOpacity) + let scaleOpacity = Math.max((scale - 1) / 3.75, 0) + const activeNodes = nodeRenderData.filter((n) => n.active).flatMap((n) => n.label) + + for (const label of labelsContainer.children) { + if (!activeNodes.includes(label)) { + label.alpha = scaleOpacity + } + } }), ) } - // 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" + function animate(time: number) { + for (const n of nodeRenderData) { + const { x, y } = n.simulationData + if (!x || !y) continue + n.gfx.position.set(x + width / 2, y + height / 2) + if (n.label) { + n.label.position.set(x + width / 2, y + height / 2) + } } - if (!graph) return - removeAllChildren(graph) + + for (const l of linkRenderData) { + const linkData = l.simulationData + l.gfx.clear() + l.gfx.moveTo(linkData.source.x! + width / 2, linkData.source.y! + height / 2) + l.gfx + .lineTo(linkData.target.x! + width / 2, linkData.target.y! + height / 2) + .stroke({ alpha: l.alpha, width: 1, color: l.color }) + } + + tweens.forEach((t) => t.update(time)) + app.renderer.render(stage) + requestAnimationFrame(animate) } - registerEscapeHandler(container, hideGlobalGraph) + const graphAnimationFrameHandle = requestAnimationFrame(animate) + window.addCleanup(() => cancelAnimationFrame(graphAnimationFrameHandle)) } document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { const slug = e.detail.url - addToVisited(slug) + addToVisited(simplifySlug(slug)) await renderGraph("graph-container", slug) + const container = document.getElementById("global-graph-outer") + const sidebar = container?.closest(".sidebar") as HTMLElement + + function renderGlobalGraph() { + const slug = getFullSlug(window) + container?.classList.add("active") + if (sidebar) { + sidebar.style.zIndex = "1" + } + + renderGraph("global-graph-container", slug) + registerEscapeHandler(container, hideGlobalGraph) + } + + function hideGlobalGraph() { + container?.classList.remove("active") + if (sidebar) { + sidebar.style.zIndex = "unset" + } + } + + async function shortcutHandler(e: HTMLElementEventMap["keydown"]) { + if (e.key === "g" && (e.ctrlKey || e.metaKey) && !e.shiftKey) { + e.preventDefault() + const globalGraphOpen = container?.classList.contains("active") + globalGraphOpen ? hideGlobalGraph() : renderGlobalGraph() + } + } + const containerIcon = document.getElementById("global-graph-icon") containerIcon?.addEventListener("click", renderGlobalGraph) window.addCleanup(() => containerIcon?.removeEventListener("click", renderGlobalGraph)) + + document.addEventListener("keydown", shortcutHandler) + window.addCleanup(() => document.removeEventListener("keydown", shortcutHandler)) }) diff --git a/quartz/components/scripts/popover.inline.ts b/quartz/components/scripts/popover.inline.ts index 972d3c6..49f4382 100644 --- a/quartz/components/scripts/popover.inline.ts +++ b/quartz/components/scripts/popover.inline.ts @@ -3,7 +3,7 @@ import { normalizeRelativeURLs } from "../../util/path" const p = new DOMParser() async function mouseEnterHandler( - this: HTMLLinkElement, + this: HTMLAnchorElement, { clientX, clientY }: { clientX: number; clientY: number }, ) { const link = this @@ -33,7 +33,7 @@ async function mouseEnterHandler( thisUrl.hash = "" thisUrl.search = "" const targetUrl = new URL(link.href) - const hash = targetUrl.hash + const hash = decodeURIComponent(targetUrl.hash) targetUrl.hash = "" targetUrl.search = "" @@ -100,7 +100,7 @@ async function mouseEnterHandler( } document.addEventListener("nav", () => { - const links = [...document.getElementsByClassName("internal")] as HTMLLinkElement[] + const links = [...document.getElementsByClassName("internal")] as HTMLAnchorElement[] 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 index a75f4ff..73e64b3 100644 --- a/quartz/components/scripts/search.inline.ts +++ b/quartz/components/scripts/search.inline.ts @@ -21,6 +21,7 @@ let index = new FlexSearch.Document({ encode: encoder, document: { id: "id", + tag: "tags", index: [ { field: "title", @@ -147,7 +148,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { const data = await fetchData const container = document.getElementById("search-container") const sidebar = container?.closest(".sidebar") as HTMLElement - const searchIcon = document.getElementById("search-icon") + const searchButton = document.getElementById("search-button") const searchBar = document.getElementById("search-bar") as HTMLInputElement | null const searchLayout = document.getElementById("search-layout") const idDataMap = Object.keys(data) as FullSlug[] @@ -190,6 +191,8 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { } searchType = "basic" // reset search type after closing + + searchButton?.focus() } function showSearch(searchTypeNew: SearchType) { @@ -405,11 +408,33 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { let searchResults: FlexSearch.SimpleDocumentSearchResultSetUnit[] if (searchType === "tags") { - searchResults = await index.searchAsync({ - query: currentSearchTerm.substring(1), - limit: numSearchResults, - index: ["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, @@ -435,8 +460,8 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { document.addEventListener("keydown", shortcutHandler) window.addCleanup(() => document.removeEventListener("keydown", shortcutHandler)) - searchIcon?.addEventListener("click", () => showSearch("basic")) - window.addCleanup(() => searchIcon?.removeEventListener("click", () => showSearch("basic"))) + searchButton?.addEventListener("click", () => showSearch("basic")) + window.addCleanup(() => searchButton?.removeEventListener("click", () => showSearch("basic"))) searchBar?.addEventListener("input", onType) window.addCleanup(() => searchBar?.removeEventListener("input", onType)) diff --git a/quartz/components/scripts/toc.inline.ts b/quartz/components/scripts/toc.inline.ts index 546859e..acc81b2 100644 --- a/quartz/components/scripts/toc.inline.ts +++ b/quartz/components/scripts/toc.inline.ts @@ -16,6 +16,10 @@ const observer = new IntersectionObserver((entries) => { function toggleToc(this: HTMLElement) { this.classList.toggle("collapsed") + this.setAttribute( + "aria-expanded", + this.getAttribute("aria-expanded") === "true" ? "false" : "true", + ) const content = this.nextElementSibling as HTMLElement | undefined if (!content) return content.classList.toggle("collapsed") diff --git a/quartz/components/scripts/util.ts b/quartz/components/scripts/util.ts index 4ffff29..d0a16c6 100644 --- a/quartz/components/scripts/util.ts +++ b/quartz/components/scripts/util.ts @@ -3,6 +3,7 @@ export function registerEscapeHandler(outsideContainer: HTMLElement | null, cb: function click(this: HTMLElement, e: HTMLElementEventMap["click"]) { if (e.target !== this) return e.preventDefault() + e.stopPropagation() cb() } 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 index 348c6f7..edf4e61 100644 --- a/quartz/components/styles/darkmode.scss +++ b/quartz/components/styles/darkmode.scss @@ -1,17 +1,15 @@ .darkmode { + cursor: pointer; + padding: 0; position: relative; + background: none; + border: none; width: 20px; height: 20px; margin: 0 10px; - - & > .toggle { - display: none; - box-sizing: border-box; - } + text-align: inherit; & svg { - cursor: pointer; - opacity: 0; position: absolute; width: 20px; height: 20px; @@ -29,20 +27,20 @@ color-scheme: light; } -:root[saved-theme="dark"] .toggle ~ label { +:root[saved-theme="dark"] .darkmode { & > #dayIcon { - opacity: 0; + display: none; } & > #nightIcon { - opacity: 1; + display: inline; } } -:root .toggle ~ label { +:root .darkmode { & > #dayIcon { - opacity: 1; + display: inline; } & > #nightIcon { - opacity: 0; + display: none; } } diff --git a/quartz/components/styles/explorer.scss b/quartz/components/styles/explorer.scss index 55ea8aa..2f94b15 100644 --- a/quartz/components/styles/explorer.scss +++ b/quartz/components/styles/explorer.scss @@ -1,7 +1,6 @@ @use "../../styles/variables.scss" as *; button#explorer { - all: unset; background-color: transparent; border: none; text-align: left; @@ -11,7 +10,7 @@ button#explorer { display: flex; align-items: center; - & h1 { + & h2 { font-size: 1rem; display: inline-block; margin: 0; @@ -46,8 +45,18 @@ button#explorer { list-style: none; overflow: hidden; max-height: none; - transition: max-height 0.35s ease; + transition: + max-height 0.35s ease, + visibility 0s linear 0s; margin-top: 0.5rem; + visibility: visible; + + &.collapsed { + transition: + max-height 0.35s ease, + visibility 0s linear 0.35s; + visibility: hidden; + } &.collapsed > .overflow::after { opacity: 0; diff --git a/quartz/components/styles/graph.scss b/quartz/components/styles/graph.scss index 3deaa1f..188907d 100644 --- a/quartz/components/styles/graph.scss +++ b/quartz/components/styles/graph.scss @@ -16,10 +16,13 @@ overflow: hidden; & > #global-graph-icon { + cursor: pointer; + background: none; + border: none; color: var(--dark); opacity: 0.5; - width: 18px; - height: 18px; + width: 24px; + height: 24px; position: absolute; padding: 0.2rem; margin: 0.3rem; @@ -59,8 +62,8 @@ top: 50%; left: 50%; transform: translate(-50%, -50%); - height: 60vh; - width: 50vw; + height: 80vh; + width: 80vw; @media all and (max-width: $fullPageWidth) { width: 90%; diff --git a/quartz/components/styles/listPage.scss b/quartz/components/styles/listPage.scss index c8fc9e9..44de70a 100644 --- a/quartz/components/styles/listPage.scss +++ b/quartz/components/styles/listPage.scss @@ -11,7 +11,7 @@ li.section-li { & > .section { display: grid; - grid-template-columns: 6em 3fr 1fr; + grid-template-columns: fit-content(8em) 3fr 1fr; @media all and (max-width: $mobileBreakpoint) { & > .tags { @@ -23,9 +23,8 @@ li.section-li { background-color: transparent; } - & > .meta { - margin: 0; - flex-basis: 6em; + & .meta { + margin: 0 1em 0 0; opacity: 0.6; } } @@ -33,7 +32,8 @@ li.section-li { // modifications in popover context .popover .section { - grid-template-columns: 6em 1fr !important; + grid-template-columns: fit-content(8em) 1fr !important; + & > .tags { display: none; } diff --git a/quartz/components/styles/search.scss b/quartz/components/styles/search.scss index 8a9ec67..cc2daca 100644 --- a/quartz/components/styles/search.scss +++ b/quartz/components/styles/search.scss @@ -5,18 +5,21 @@ max-width: 14rem; flex-grow: 0.3; - & > #search-icon { + & > .search-button { background-color: var(--lightgray); + border: none; border-radius: 4px; + font-family: inherit; + font-size: inherit; height: 2rem; + padding: 0; display: flex; align-items: center; + text-align: inherit; cursor: pointer; white-space: nowrap; - - & > div { - flex-grow: 1; - } + width: 100%; + justify-content: space-between; & > p { display: inline; diff --git a/quartz/components/styles/toc.scss b/quartz/components/styles/toc.scss index 27ff62a..6845812 100644 --- a/quartz/components/styles/toc.scss +++ b/quartz/components/styles/toc.scss @@ -29,8 +29,18 @@ button#toc { list-style: none; overflow: hidden; max-height: none; - transition: max-height 0.5s ease; + transition: + max-height 0.5s ease, + visibility 0s linear 0s; position: relative; + visibility: visible; + + &.collapsed { + transition: + max-height 0.5s ease, + visibility 0s linear 0.5s; + visibility: hidden; + } &.collapsed > .overflow::after { opacity: 0; diff --git a/quartz/i18n/index.ts b/quartz/i18n/index.ts index 38d3562..edde307 100644 --- a/quartz/i18n/index.ts +++ b/quartz/i18n/index.ts @@ -1,20 +1,28 @@ import { Translation, CalloutTranslation } from "./locales/definition" -import en from "./locales/en-US" +import enUs from "./locales/en-US" +import enGb from "./locales/en-GB" 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 ca from "./locales/ca-ES" 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" +import fa from "./locales/fa-IR" +import pl from "./locales/pl-PL" export const TRANSLATIONS = { - "en-US": en, + "en-US": enUs, + "en-GB": enGb, "fr-FR": fr, "it-IT": it, "ja-JP": ja, @@ -23,6 +31,7 @@ export const TRANSLATIONS = { "nl-BE": nl, "ro-RO": ro, "ro-MD": ro, + "ca-ES": ca, "es-ES": es, "ar-SA": ar, "ar-AE": ar, @@ -48,6 +57,11 @@ export const TRANSLATIONS = { "ru-RU": ru, "ko-KR": ko, "zh-CN": zh, + "vi-VN": vi, + "pt-BR": pt, + "hu-HU": hu, + "fa-IR": fa, + "pl-PL": pl, } as const export const defaultTranslation = "en-US" diff --git a/quartz/i18n/locales/ar-SA.ts b/quartz/i18n/locales/ar-SA.ts index f704810..8463e2f 100644 --- a/quartz/i18n/locales/ar-SA.ts +++ b/quartz/i18n/locales/ar-SA.ts @@ -70,6 +70,7 @@ export default { error: { title: "غير موجود", notFound: "إما أن هذه الصفحة خاصة أو غير موجودة.", + home: "العوده للصفحة الرئيسية", }, folderContent: { folder: "مجلد", diff --git a/quartz/i18n/locales/ca-ES.ts b/quartz/i18n/locales/ca-ES.ts new file mode 100644 index 0000000..aadbd41 --- /dev/null +++ b/quartz/i18n/locales/ca-ES.ts @@ -0,0 +1,84 @@ +import { Translation } from "./definition" + +export default { + propertyDefaults: { + title: "Sense títol", + description: "Sense descripció", + }, + components: { + callout: { + note: "Nota", + abstract: "Resum", + info: "Informació", + todo: "Per fer", + tip: "Consell", + success: "Èxit", + question: "Pregunta", + warning: "Advertència", + failure: "Fall", + danger: "Perill", + bug: "Error", + example: "Exemple", + quote: "Cita", + }, + backlinks: { + title: "Retroenllaç", + noBacklinksFound: "No s'han trobat retroenllaços", + }, + themeToggle: { + lightMode: "Mode clar", + darkMode: "Mode fosc", + }, + explorer: { + title: "Explorador", + }, + footer: { + createdWith: "Creat amb", + }, + graph: { + title: "Vista Gràfica", + }, + recentNotes: { + title: "Notes Recents", + seeRemainingMore: ({ remaining }) => `Vegi ${remaining} més →`, + }, + transcludes: { + transcludeOf: ({ targetSlug }) => `Transcluit de ${targetSlug}`, + linkToOriginal: "Enllaç a l'original", + }, + search: { + title: "Cercar", + searchBarPlaceholder: "Cerca alguna cosa", + }, + tableOfContents: { + title: "Taula de Continguts", + }, + contentMeta: { + readingTime: ({ minutes }) => `Es llegeix en ${minutes} min`, + }, + }, + pages: { + rss: { + recentNotes: "Notes recents", + lastFewNotes: ({ count }) => `Últimes ${count} notes`, + }, + error: { + title: "No s'ha trobat.", + notFound: "Aquesta pàgina és privada o no existeix.", + home: "Torna a la pàgina principal", + }, + folderContent: { + folder: "Carpeta", + itemsUnderFolder: ({ count }) => + count === 1 ? "1 article en aquesta carpeta." : `${count} articles en esta carpeta.`, + }, + tagContent: { + tag: "Etiqueta", + tagIndex: "índex d'Etiquetes", + itemsUnderTag: ({ count }) => + count === 1 ? "1 article amb aquesta etiqueta." : `${count} article amb aquesta etiqueta.`, + showingFirst: ({ count }) => `Mostrant les primeres ${count} etiquetes.`, + totalTags: ({ count }) => `S'han trobat ${count} etiquetes en total.`, + }, + }, +} as const satisfies Translation diff --git a/quartz/i18n/locales/de-DE.ts b/quartz/i18n/locales/de-DE.ts index 64c9ba9..023d4be 100644 --- a/quartz/i18n/locales/de-DE.ts +++ b/quartz/i18n/locales/de-DE.ts @@ -65,6 +65,7 @@ export default { error: { title: "Nicht gefunden", notFound: "Diese Seite ist entweder nicht öffentlich oder existiert nicht.", + home: "Return to Homepage", }, folderContent: { folder: "Ordner", diff --git a/quartz/i18n/locales/definition.ts b/quartz/i18n/locales/definition.ts index 1d5d3dd..25a8cd7 100644 --- a/quartz/i18n/locales/definition.ts +++ b/quartz/i18n/locales/definition.ts @@ -67,6 +67,7 @@ export interface Translation { error: { title: string notFound: string + home: string } folderContent: { folder: string diff --git a/quartz/i18n/locales/en-GB.ts b/quartz/i18n/locales/en-GB.ts new file mode 100644 index 0000000..5388b03 --- /dev/null +++ b/quartz/i18n/locales/en-GB.ts @@ -0,0 +1,84 @@ +import { Translation } from "./definition" + +export default { + propertyDefaults: { + title: "Untitled", + description: "No description provided", + }, + components: { + callout: { + note: "Note", + abstract: "Abstract", + info: "Info", + todo: "To-Do", + 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.", + home: "Return to Homepage", + }, + 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/en-US.ts b/quartz/i18n/locales/en-US.ts index ac283fd..22cf31e 100644 --- a/quartz/i18n/locales/en-US.ts +++ b/quartz/i18n/locales/en-US.ts @@ -65,6 +65,7 @@ export default { error: { title: "Not Found", notFound: "Either this page is private or doesn't exist.", + home: "Return to Homepage", }, folderContent: { folder: "Folder", diff --git a/quartz/i18n/locales/es-ES.ts b/quartz/i18n/locales/es-ES.ts index 37a2a79..c4a57aa 100644 --- a/quartz/i18n/locales/es-ES.ts +++ b/quartz/i18n/locales/es-ES.ts @@ -22,8 +22,8 @@ export default { quote: "Cita", }, backlinks: { - title: "Enlaces de Retroceso", - noBacklinksFound: "No se han encontrado enlaces traseros", + title: "Retroenlaces", + noBacklinksFound: "No se han encontrado retroenlaces", }, themeToggle: { lightMode: "Modo claro", @@ -54,17 +54,18 @@ export default { title: "Tabla de Contenidos", }, contentMeta: { - readingTime: ({ minutes }) => `${minutes} min read`, + readingTime: ({ minutes }) => `Se lee en ${minutes} min`, }, }, pages: { rss: { recentNotes: "Notas recientes", - lastFewNotes: ({ count }) => `Últimás ${count} notas`, + lastFewNotes: ({ count }) => `Últimas ${count} notas`, }, error: { - title: "No se encontró.", + title: "No se ha encontrado.", notFound: "Esta página es privada o no existe.", + home: "Regresa a la página principal", }, folderContent: { folder: "Carpeta", @@ -77,7 +78,7 @@ export default { 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.`, + totalTags: ({ count }) => `Se han encontrado ${count} etiquetas en total.`, }, }, } as const satisfies Translation diff --git a/quartz/i18n/locales/fa-IR.ts b/quartz/i18n/locales/fa-IR.ts new file mode 100644 index 0000000..5bfef5a --- /dev/null +++ b/quartz/i18n/locales/fa-IR.ts @@ -0,0 +1,84 @@ +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} دقیقه`, + }, + }, + pages: { + rss: { + recentNotes: "یادداشت‌های اخیر", + lastFewNotes: ({ count }) => `${count} یادداشت اخیر`, + }, + error: { + title: "یافت نشد", + notFound: "این صفحه یا خصوصی است یا وجود ندارد", + home: "بازگشت به صفحه اصلی", + }, + 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/fr-FR.ts b/quartz/i18n/locales/fr-FR.ts index b485d2b..ef43fa8 100644 --- a/quartz/i18n/locales/fr-FR.ts +++ b/quartz/i18n/locales/fr-FR.ts @@ -54,7 +54,7 @@ export default { title: "Table des Matières", }, contentMeta: { - readingTime: ({ minutes }) => `${minutes} min read`, + readingTime: ({ minutes }) => `${minutes} min de lecture`, }, }, pages: { @@ -63,8 +63,9 @@ export default { lastFewNotes: ({ count }) => `Les dernières ${count} notes`, }, error: { - title: "Pas trouvé", + title: "Introuvable", notFound: "Cette page est soit privée, soit elle n'existe pas.", + home: "Retour à la page d'accueil", }, folderContent: { folder: "Dossier", diff --git a/quartz/i18n/locales/hu-HU.ts b/quartz/i18n/locales/hu-HU.ts new file mode 100644 index 0000000..066b777 --- /dev/null +++ b/quartz/i18n/locales/hu-HU.ts @@ -0,0 +1,82 @@ +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.", + home: "Vissza a kezdőlapra", + }, + 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 index ca8818a..c8c5973 100644 --- a/quartz/i18n/locales/it-IT.ts +++ b/quartz/i18n/locales/it-IT.ts @@ -65,6 +65,7 @@ export default { error: { title: "Non trovato", notFound: "Questa pagina è privata o non esiste.", + home: "Ritorna alla home page", }, folderContent: { folder: "Cartella", diff --git a/quartz/i18n/locales/ja-JP.ts b/quartz/i18n/locales/ja-JP.ts index d429db4..9581b5e 100644 --- a/quartz/i18n/locales/ja-JP.ts +++ b/quartz/i18n/locales/ja-JP.ts @@ -65,6 +65,7 @@ export default { error: { title: "Not Found", notFound: "ページが存在しないか、非公開設定になっています。", + home: "ホームページに戻る", }, folderContent: { folder: "フォルダ", diff --git a/quartz/i18n/locales/ko-KR.ts b/quartz/i18n/locales/ko-KR.ts index ea735b0..9be08d9 100644 --- a/quartz/i18n/locales/ko-KR.ts +++ b/quartz/i18n/locales/ko-KR.ts @@ -65,6 +65,7 @@ export default { error: { title: "Not Found", notFound: "페이지가 존재하지 않거나 비공개 설정이 되어 있습니다.", + home: "홈페이지로 돌아가기", }, folderContent: { folder: "폴더", diff --git a/quartz/i18n/locales/nl-NL.ts b/quartz/i18n/locales/nl-NL.ts index d075d58..ccbafa7 100644 --- a/quartz/i18n/locales/nl-NL.ts +++ b/quartz/i18n/locales/nl-NL.ts @@ -66,6 +66,7 @@ export default { error: { title: "Niet gevonden", notFound: "Deze pagina is niet zichtbaar of bestaat niet.", + home: "Keer terug naar de start pagina", }, folderContent: { folder: "Map", diff --git a/quartz/i18n/locales/pl-PL.ts b/quartz/i18n/locales/pl-PL.ts new file mode 100644 index 0000000..7fa0cd4 --- /dev/null +++ b/quartz/i18n/locales/pl-PL.ts @@ -0,0 +1,84 @@ +import { Translation } from "./definition" + +export default { + propertyDefaults: { + title: "Bez nazwy", + description: "Brak opisu", + }, + components: { + callout: { + note: "Notatka", + abstract: "Streszczenie", + info: "informacja", + todo: "Do zrobienia", + tip: "Wskazówka", + success: "Zrobione", + question: "Pytanie", + warning: "Ostrzeżenie", + failure: "Usterka", + danger: "Niebiezpieczeństwo", + bug: "Błąd w kodzie", + example: "Przykład", + quote: "Cytat", + }, + backlinks: { + title: "Odnośniki zwrotne", + noBacklinksFound: "Brak połączeń zwrotnych", + }, + themeToggle: { + lightMode: "Trzyb jasny", + darkMode: "Tryb ciemny", + }, + explorer: { + title: "Przeglądaj", + }, + footer: { + createdWith: "Stworzone z użyciem", + }, + graph: { + title: "Graf", + }, + recentNotes: { + title: "Najnowsze notatki", + seeRemainingMore: ({ remaining }) => `Zobacz ${remaining} nastepnych →`, + }, + transcludes: { + transcludeOf: ({ targetSlug }) => `Osadzone ${targetSlug}`, + linkToOriginal: "Łącze do oryginału", + }, + search: { + title: "Szukaj", + searchBarPlaceholder: "Search for something", + }, + tableOfContents: { + title: "Spis treści", + }, + contentMeta: { + readingTime: ({ minutes }) => `${minutes} min. czytania `, + }, + }, + pages: { + rss: { + recentNotes: "Najnowsze notatki", + lastFewNotes: ({ count }) => `Ostatnie ${count} notatek`, + }, + error: { + title: "Nie znaleziono", + notFound: "Ta strona jest prywatna lub nie istnieje.", + home: "Powrót do strony głównej", + }, + folderContent: { + folder: "Folder", + itemsUnderFolder: ({ count }) => + count === 1 ? "W tym folderze jest 1 element." : `Elementów w folderze: ${count}.`, + }, + tagContent: { + tag: "Znacznik", + tagIndex: "Spis znaczników", + itemsUnderTag: ({ count }) => + count === 1 ? "Oznaczony 1 element." : `Elementów z tym znacznikiem: ${count}.`, + showingFirst: ({ count }) => `Pokazuje ${count} pierwszych znaczników.`, + totalTags: ({ count }) => `Znalezionych wszystkich znaczników: ${count}.`, + }, + }, +} 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..c7b6bfb --- /dev/null +++ b/quartz/i18n/locales/pt-BR.ts @@ -0,0 +1,84 @@ +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.", + home: "Retornar a página inicial", + }, + folderContent: { + folder: "Arquivo", + itemsUnderFolder: ({ count }) => + count === 1 ? "1 item neste 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 index 556b189..2de1c8c 100644 --- a/quartz/i18n/locales/ro-RO.ts +++ b/quartz/i18n/locales/ro-RO.ts @@ -66,6 +66,7 @@ export default { error: { title: "Pagina nu a fost găsită", notFound: "Fie această pagină este privată, fie nu există.", + home: "Reveniți la pagina de pornire", }, folderContent: { folder: "Dosar", diff --git a/quartz/i18n/locales/ru-RU.ts b/quartz/i18n/locales/ru-RU.ts index 8ead3ca..18e0817 100644 --- a/quartz/i18n/locales/ru-RU.ts +++ b/quartz/i18n/locales/ru-RU.ts @@ -67,6 +67,7 @@ export default { error: { title: "Страница не найдена", notFound: "Эта страница приватная или не существует", + home: "Вернуться на главную страницу", }, folderContent: { folder: "Папка", diff --git a/quartz/i18n/locales/uk-UA.ts b/quartz/i18n/locales/uk-UA.ts index b636938..469de4f 100644 --- a/quartz/i18n/locales/uk-UA.ts +++ b/quartz/i18n/locales/uk-UA.ts @@ -54,7 +54,7 @@ export default { title: "Зміст", }, contentMeta: { - readingTime: ({ minutes }) => `${minutes} min read`, + readingTime: ({ minutes }) => `${minutes} хв читання`, }, }, pages: { @@ -65,19 +65,20 @@ export default { error: { title: "Не знайдено", notFound: "Ця сторінка або приватна, або не існує.", + home: "Повернутися на головну сторінку", }, folderContent: { - folder: "Папка", + folder: "Тека", itemsUnderFolder: ({ count }) => - count === 1 ? "У цій папці 1 елемент." : `Елементів у цій папці: ${count}.`, + count === 1 ? "У цій теці 1 елемент." : `Елементів у цій теці: ${count}.`, }, tagContent: { - tag: "Тег", - tagIndex: "Індекс тегу", + tag: "Мітка", + tagIndex: "Індекс мітки", itemsUnderTag: ({ count }) => - count === 1 ? "1 елемент з цим тегом." : `Елементів з цим тегом: ${count}.`, - showingFirst: ({ count }) => `Показ перших ${count} тегів.`, - totalTags: ({ count }) => `Всього знайдено тегів: ${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..39a8fbc --- /dev/null +++ b/quartz/i18n/locales/vi-VN.ts @@ -0,0 +1,84 @@ +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.", + home: "Trở về trang chủ", + }, + 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 index 43d0111..b710db5 100644 --- a/quartz/i18n/locales/zh-CN.ts +++ b/quartz/i18n/locales/zh-CN.ts @@ -65,6 +65,7 @@ export default { error: { title: "无法找到", notFound: "私有笔记或笔记不存在。", + home: "返回首页", }, folderContent: { folder: "文件夹", diff --git a/quartz/plugins/emitters/componentResources.ts b/quartz/plugins/emitters/componentResources.ts index f41293c..cb2475c 100644 --- a/quartz/plugins/emitters/componentResources.ts +++ b/quartz/plugins/emitters/componentResources.ts @@ -8,7 +8,6 @@ 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 { StaticResources } from "../../util/resources" import { QuartzComponent } from "../../components/types" import { googleFontHref, joinStyles } from "../../util/theme" import { Features, transform } from "lightningcss" @@ -69,13 +68,8 @@ async function joinScripts(scripts: string[]): Promise { return res.code } -function addGlobalPageResources( - ctx: BuildCtx, - staticResources: StaticResources, - componentResources: ComponentResources, -) { +function addGlobalPageResources(ctx: BuildCtx, componentResources: ComponentResources) { const cfg = ctx.cfg.configuration - const reloadScript = ctx.argv.serve // popovers if (cfg.enablePopovers) { @@ -85,12 +79,12 @@ function addGlobalPageResources( if (cfg.analytics?.provider === "google") { const tagId = cfg.analytics.tagId - staticResources.js.push({ - src: `https://www.googletagmanager.com/gtag/js?id=${tagId}`, - contentType: "external", - loadTime: "afterDOMReady", - }) 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()); @@ -126,6 +120,38 @@ function addGlobalPageResources( 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) + `) + } else if (cfg.analytics?.provider === "posthog") { + componentResources.afterDOMLoaded.push(` + const posthogScript = document.createElement("script") + posthogScript.innerHTML= \`!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.async=!0,p.src=s.api_host+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags getFeatureFlag getFeatureFlagPayload reloadFeatureFlags group updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures getActiveMatchingSurveys getSurveys onSessionId".split(" "),n=0;n document.location.reload(true)) - `, - }) - } } -interface Options { - fontOrigin: "googleFonts" | "local" -} - -const defaultOptions: Options = { - fontOrigin: "googleFonts", -} - -export const ComponentResources: QuartzEmitterPlugin = (opts?: Partial) => { - const { fontOrigin } = { ...defaultOptions, ...opts } +// 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) { - // This emitter adds static resources to the `resources` parameter. One - // important resource this emitter adds is the code to start a websocket - // connection and listen to rebuild messages, which triggers a page reload. - // The resources parameter with the reload logic is later used by the - // ContentPage emitter while creating the final html page. In order for - // the reload logic to be included, and so for partial rebuilds to work, - // we need to run this emitter for all markdown files. - 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) - } - - return graph + async getDependencyGraph(_ctx, _content, _resources) { + return new DepGraph() }, - async emit(ctx, _content, resources): Promise { + 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 (fontOrigin === "local") { + if (cfg.theme.fontOrigin === "local") { // let the user do it themselves in css - } else if (fontOrigin === "googleFonts") { - if (cfg.theme.cdnCaching) { - resources.css.push(googleFontHref(cfg.theme)) - } else { - let match + } 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 + const fontSourceRegex = /url\((https:\/\/fonts.gstatic.com\/s\/[^)]+\.(woff2|ttf))\)/g - googleFontsStyleSheet = await ( - await fetch(googleFontHref(ctx.cfg.configuration.theme)) - ).text() + 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(".") + 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, - `/static/fonts/${filename}.ttf`, - ) + 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), - }), - ), - ) - } + 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, resources, componentResources) + addGlobalPageResources(ctx, componentResources) const stylesheet = joinStyles( ctx.cfg.configuration.theme, diff --git a/quartz/plugins/emitters/contentPage.tsx b/quartz/plugins/emitters/contentPage.tsx index f493802..2ac1321 100644 --- a/quartz/plugins/emitters/contentPage.tsx +++ b/quartz/plugins/emitters/contentPage.tsx @@ -59,14 +59,25 @@ export const ContentPage: QuartzEmitterPlugin> = (userOp ...userOpts, } - const { head: Head, header, beforeBody, pageBody, left, right, footer: Footer } = opts + const { head: Head, header, beforeBody, pageBody, afterBody, 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] + return [ + Head, + Header, + Body, + ...header, + ...beforeBody, + pageBody, + ...afterBody, + ...left, + ...right, + Footer, + ] }, async getDependencyGraph(ctx, content, _resources) { const graph = new DepGraph() diff --git a/quartz/plugins/emitters/folderPage.tsx b/quartz/plugins/emitters/folderPage.tsx index d892b28..7eebb21 100644 --- a/quartz/plugins/emitters/folderPage.tsx +++ b/quartz/plugins/emitters/folderPage.tsx @@ -3,7 +3,7 @@ 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 { ProcessedContent, QuartzPluginData, defaultProcessedContent } from "../vfile" import { FullPageLayout } from "../../cfg" import path from "path" import { @@ -21,22 +21,37 @@ import { write } from "./helpers" import { i18n } from "../../i18n" import DepGraph from "../../depgraph" -export const FolderPage: QuartzEmitterPlugin> = (userOpts) => { +interface FolderPageOptions extends FullPageLayout { + sort?: (f1: QuartzPluginData, f2: QuartzPluginData) => number +} + +export const FolderPage: QuartzEmitterPlugin> = (userOpts) => { const opts: FullPageLayout = { ...sharedPageComponents, ...defaultListPageLayout, - pageBody: FolderContent(), + pageBody: FolderContent({ sort: userOpts?.sort }), ...userOpts, } - const { head: Head, header, beforeBody, pageBody, left, right, footer: Footer } = opts + const { head: Head, header, beforeBody, pageBody, afterBody, 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] + return [ + Head, + Header, + Body, + ...header, + ...beforeBody, + pageBody, + ...afterBody, + ...left, + ...right, + Footer, + ] }, async getDependencyGraph(_ctx, content, _resources) { // Example graph: diff --git a/quartz/plugins/emitters/tagPage.tsx b/quartz/plugins/emitters/tagPage.tsx index 6f65ae4..066d4ec 100644 --- a/quartz/plugins/emitters/tagPage.tsx +++ b/quartz/plugins/emitters/tagPage.tsx @@ -3,7 +3,7 @@ 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 { ProcessedContent, QuartzPluginData, defaultProcessedContent } from "../vfile" import { FullPageLayout } from "../../cfg" import { FilePath, @@ -18,22 +18,37 @@ import { write } from "./helpers" import { i18n } from "../../i18n" import DepGraph from "../../depgraph" -export const TagPage: QuartzEmitterPlugin> = (userOpts) => { +interface TagPageOptions extends FullPageLayout { + sort?: (f1: QuartzPluginData, f2: QuartzPluginData) => number +} + +export const TagPage: QuartzEmitterPlugin> = (userOpts) => { const opts: FullPageLayout = { ...sharedPageComponents, ...defaultListPageLayout, - pageBody: TagContent(), + pageBody: TagContent({ sort: userOpts?.sort }), ...userOpts, } - const { head: Head, header, beforeBody, pageBody, left, right, footer: Footer } = opts + const { head: Head, header, beforeBody, pageBody, afterBody, 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] + return [ + Head, + Header, + Body, + ...header, + ...beforeBody, + pageBody, + ...afterBody, + ...left, + ...right, + Footer, + ] }, async getDependencyGraph(ctx, content, _resources) { const graph = new DepGraph() @@ -73,7 +88,7 @@ export const TagPage: QuartzEmitterPlugin> = (userOpts) const title = tag === "index" ? i18n(cfg.locale).pages.tagContent.tagIndex - : `${i18n(cfg.locale).pages.tagContent.tag}: #${tag}` + : `${i18n(cfg.locale).pages.tagContent.tag}: ${tag}` return [ tag, defaultProcessedContent({ diff --git a/quartz/plugins/filters/draft.ts b/quartz/plugins/filters/draft.ts index 65e2d6b..5fd06b9 100644 --- a/quartz/plugins/filters/draft.ts +++ b/quartz/plugins/filters/draft.ts @@ -3,7 +3,7 @@ import { QuartzFilterPlugin } from "../types" export const RemoveDrafts: QuartzFilterPlugin<{}> = () => ({ name: "RemoveDrafts", shouldPublish(_ctx, [_tree, vfile]) { - const draftFlag: boolean = vfile.data?.frontmatter?.draft ?? false + const draftFlag: boolean = vfile.data?.frontmatter?.draft || false return !draftFlag }, }) diff --git a/quartz/plugins/index.ts b/quartz/plugins/index.ts index f35d053..df9fd1d 100644 --- a/quartz/plugins/index.ts +++ b/quartz/plugins/index.ts @@ -18,6 +18,23 @@ export function getStaticResourcesFromPlugins(ctx: BuildCtx) { } } + // 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 } diff --git a/quartz/plugins/transformers/citations.ts b/quartz/plugins/transformers/citations.ts new file mode 100644 index 0000000..5242f37 --- /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> = (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 index 884d5b1..c7e592e 100644 --- a/quartz/plugins/transformers/description.ts +++ b/quartz/plugins/transformers/description.ts @@ -5,13 +5,20 @@ import { escapeHTML } from "../../util/escape" export interface Options { descriptionLength: number + replaceExternalLinks: boolean } const defaultOptions: Options = { descriptionLength: 150, + replaceExternalLinks: true, } -export const Description: QuartzTransformerPlugin | undefined> = (userOpts) => { +const urlRegex = new RegExp( + /(https?:\/\/)?(?([\da-z\.-]+)\.([a-z\.]{2,6})(:\d+)?)(?[\/\w\.-]*)(\?[\/\w\.=&;-]*)?/, + "g", +) + +export const Description: QuartzTransformerPlugin> = (userOpts) => { const opts = { ...defaultOptions, ...userOpts } return { name: "Description", @@ -19,22 +26,46 @@ export const Description: QuartzTransformerPlugin | undefined> return [ () => { return async (tree: HTMLRoot, file) => { - const frontMatterDescription = file.data.frontmatter?.description - const text = escapeHTML(toString(tree)) + let frontMatterDescription = file.data.frontmatter?.description + let text = escapeHTML(toString(tree)) - const desc = frontMatterDescription ?? text - const sentences = desc.replace(/\s+/g, " ").split(".") - let finalDesc = "" - let sentenceIdx = 0 - const len = opts.descriptionLength - while (finalDesc.length < len) { - const sentence = sentences[sentenceIdx] - if (!sentence) break - finalDesc += sentence + "." - sentenceIdx++ + if (opts.replaceExternalLinks) { + frontMatterDescription = frontMatterDescription?.replace( + urlRegex, + "$" + "$", + ) + text = text.replace(urlRegex, "$" + "$") } - file.data.description = finalDesc + 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 } }, diff --git a/quartz/plugins/transformers/frontmatter.ts b/quartz/plugins/transformers/frontmatter.ts index 79aa5f3..70a60d8 100644 --- a/quartz/plugins/transformers/frontmatter.ts +++ b/quartz/plugins/transformers/frontmatter.ts @@ -40,7 +40,7 @@ function coerceToArray(input: string | string[]): string[] | undefined { .map((tag: string | number) => tag.toString()) } -export const FrontMatter: QuartzTransformerPlugin | undefined> = (userOpts) => { +export const FrontMatter: QuartzTransformerPlugin> = (userOpts) => { const opts = { ...defaultOptions, ...userOpts } return { name: "FrontMatter", @@ -90,6 +90,7 @@ declare module "vfile" { 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 index 48681ff..eec26f7 100644 --- a/quartz/plugins/transformers/gfm.ts +++ b/quartz/plugins/transformers/gfm.ts @@ -14,9 +14,7 @@ const defaultOptions: Options = { linkHeadings: true, } -export const GitHubFlavoredMarkdown: QuartzTransformerPlugin | undefined> = ( - userOpts, -) => { +export const GitHubFlavoredMarkdown: QuartzTransformerPlugin> = (userOpts) => { const opts = { ...defaultOptions, ...userOpts } return { name: "GitHubFlavoredMarkdown", diff --git a/quartz/plugins/transformers/index.ts b/quartz/plugins/transformers/index.ts index e340f10..7908c86 100644 --- a/quartz/plugins/transformers/index.ts +++ b/quartz/plugins/transformers/index.ts @@ -1,5 +1,6 @@ export { FrontMatter } from "./frontmatter" export { GitHubFlavoredMarkdown } from "./gfm" +export { Citations } from "./citations" export { CreatedModifiedDate } from "./lastmod" export { Latex } from "./latex" export { Description } from "./description" diff --git a/quartz/plugins/transformers/lastmod.ts b/quartz/plugins/transformers/lastmod.ts index 2c7b9ce..fe8c01b 100644 --- a/quartz/plugins/transformers/lastmod.ts +++ b/quartz/plugins/transformers/lastmod.ts @@ -27,9 +27,7 @@ function coerceDate(fp: string, d: any): Date { } type MaybeDate = undefined | string | number -export const CreatedModifiedDate: QuartzTransformerPlugin | undefined> = ( - userOpts, -) => { +export const CreatedModifiedDate: QuartzTransformerPlugin> = (userOpts) => { const opts = { ...defaultOptions, ...userOpts } return { name: "CreatedModifiedDate", diff --git a/quartz/plugins/transformers/latex.ts b/quartz/plugins/transformers/latex.ts index c9f6bff..28b4d50 100644 --- a/quartz/plugins/transformers/latex.ts +++ b/quartz/plugins/transformers/latex.ts @@ -5,10 +5,16 @@ import { QuartzTransformerPlugin } from "../types" interface Options { renderEngine: "katex" | "mathjax" + customMacros: MacroType } -export const Latex: QuartzTransformerPlugin = (opts?: Options) => { +interface MacroType { + [key: string]: string +} + +export const Latex: QuartzTransformerPlugin> = (opts) => { const engine = opts?.renderEngine ?? "katex" + const macros = opts?.customMacros ?? {} return { name: "Latex", markdownPlugins() { @@ -16,9 +22,9 @@ export const Latex: QuartzTransformerPlugin = (opts?: Options) => { }, htmlPlugins() { if (engine === "katex") { - return [[rehypeKatex, { output: "html" }]] + return [[rehypeKatex, { output: "html", macros }]] } else { - return [rehypeMathjax] + return [[rehypeMathjax, { macros }]] } }, externalResources() { diff --git a/quartz/plugins/transformers/links.ts b/quartz/plugins/transformers/links.ts index f89d367..38bc0dc 100644 --- a/quartz/plugins/transformers/links.ts +++ b/quartz/plugins/transformers/links.ts @@ -8,7 +8,6 @@ import { simplifySlug, splitAnchor, transformLink, - joinSegments, } from "../../util/path" import path from "path" import { visit } from "unist-util-visit" @@ -33,7 +32,7 @@ const defaultOptions: Options = { externalLinkIcon: true, } -export const CrawlLinks: QuartzTransformerPlugin | undefined> = (userOpts) => { +export const CrawlLinks: QuartzTransformerPlugin> = (userOpts) => { const opts = { ...defaultOptions, ...userOpts } return { name: "LinkProcessing", @@ -66,6 +65,7 @@ export const CrawlLinks: QuartzTransformerPlugin | undefined> = type: "element", tagName: "svg", properties: { + "aria-hidden": "true", class: "external-icon", viewBox: "0 0 512 512", }, @@ -93,7 +93,7 @@ export const CrawlLinks: QuartzTransformerPlugin | undefined> = } node.properties.className = classes - if (opts.openLinksInNewTab) { + if (isExternal && opts.openLinksInNewTab) { node.properties.target = "_blank" } diff --git a/quartz/plugins/transformers/ofm.ts b/quartz/plugins/transformers/ofm.ts index fab7cf8..0dea893 100644 --- a/quartz/plugins/transformers/ofm.ts +++ b/quartz/plugins/transformers/ofm.ts @@ -1,11 +1,11 @@ import { QuartzTransformerPlugin } from "../types" -import { Blockquote, Root, Html, BlockContent, DefinitionContent, Paragraph, Code } from "mdast" +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 { splitAnchor } from "../../util/path" import { JSResource } from "../../util/resources" // @ts-ignore import calloutScript from "../../components/scripts/callout.inline.ts" @@ -17,7 +17,6 @@ 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" -import { ValidCallout, i18n } from "../../i18n" export interface Options { comments: boolean @@ -98,40 +97,46 @@ function canonicalizeCallout(calloutName: string): keyof typeof calloutMapping { export const externalLinkRegex = /^https?:\/\//i -export const arrowRegex = new RegExp(/(-{1,2}>|={1,2}>|<-{1,2}|<={1,2})/, "g") +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) -// (\|[^\[\]\#]+)? -> | then one or more non-special characters (alias) +// !? -> 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", + /!?\[\[([^\[\]\|\#\\]+)?(#+[^\[\]\|\#\\]+)?(\\?\|[^\[\]\#]+)?\]\]/g, ) -const highlightRegex = new RegExp(/==([^=]+)==/, "g") -const commentRegex = new RegExp(/%%[\s\S]*?%%/, "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") +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", + /(?:^| )#((?:[-_\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 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, -) => { +export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin> = (userOpts) => { const opts = { ...defaultOptions, ...userOpts } const mdastToHtml = (ast: PhrasingContent | Paragraph) => { @@ -169,13 +174,27 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin 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, raw) => { + // 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 [fp, anchor] = splitAnchor(`${rawFp ?? ""}${rawHeader ?? ""}`) + const blockRef = Boolean(rawHeader?.match(/^#?\^/)) ? "^" : "" + const displayAnchor = anchor ? `#${blockRef}${anchor.trim().replace(/^#+/, "")}` : "" const displayAlias = rawAlias ?? rawHeader?.replace("#", "|") ?? "" const embedDisplay = value.startsWith("!") ? "!" : "" @@ -189,9 +208,8 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin return src }, - markdownPlugins(ctx) { + markdownPlugins(_ctx) { const plugins: PluggableList = [] - const cfg = ctx.cfg.configuration // regex replacements plugins.push(() => { @@ -243,14 +261,14 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin } else if ([".pdf"].includes(ext)) { return { type: "html", - value: ``, + value: ``, } } else { const block = anchor return { type: "html", data: { hProperties: { transclude: true } }, - value: `
    Transclude of ${url}${block}
    `, } @@ -328,7 +346,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin children: [ { type: "text", - value: `#${tag}`, + value: tag, }, ], } @@ -388,8 +406,8 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin return } - // find first line - const firstChild = node.children[0] + // find first line and callout content + const [firstChild, ...calloutContent] = node.children if (firstChild.type !== "paragraph" || firstChild.children[0]?.type !== "text") { return } @@ -401,7 +419,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin const match = firstLine.match(calloutRegex) if (match && match.input) { - const [calloutDirective, typeString, collapseChar] = match + const [calloutDirective, typeString, calloutMetaData, collapseChar] = match const calloutType = canonicalizeCallout(typeString.toLowerCase()) const collapse = collapseChar === "+" || collapseChar === "-" const defaultState = collapseChar === "-" ? "collapsed" : "expanded" @@ -463,8 +481,24 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin className: classNames.join(" "), "data-callout": calloutType, "data-callout-fold": collapse, + "data-callout-metadata": calloutMetaData, }, } + + // Add callout-content class to callout body if it has one. + if (calloutContent.length > 0) { + const contentData: BlockContent | DefinitionContent = { + data: { + hProperties: { + className: "callout-content", + }, + hName: "div", + }, + type: "blockquote", + children: [...calloutContent], + } + node.children = [node.children[0], contentData] + } } }) } @@ -528,12 +562,35 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin last.value = last.value.slice(0, -matches[0].length) const block = matches[0].slice(1) - if (!Object.keys(file.data.blocks!).includes(block)) { - node.properties = { - ...node.properties, - id: block, + 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.blocks![block] = node } } } @@ -552,15 +609,28 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin 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", + class: "external-embed youtube", allow: "fullscreen", frameborder: 0, width: "600px", - height: "350px", - src: `https://www.youtube.com/embed/${videoId}`, + 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 youtube", + allow: "fullscreen", + frameborder: 0, + width: "600px", + src: `https://www.youtube.com/embed/videoseries?list=${playlistId}`, } } } diff --git a/quartz/plugins/transformers/oxhugofm.ts b/quartz/plugins/transformers/oxhugofm.ts index 6e70bb1..cdbffcf 100644 --- a/quartz/plugins/transformers/oxhugofm.ts +++ b/quartz/plugins/transformers/oxhugofm.ts @@ -47,9 +47,7 @@ const quartzLatexRegex = new RegExp(/\$\$[\s\S]*?\$\$|\$.*?\$/, "g") * markdown to make it compatible with quartz but the list of changes applied it * is not exhaustive. * */ -export const OxHugoFlavouredMarkdown: QuartzTransformerPlugin | undefined> = ( - userOpts, -) => { +export const OxHugoFlavouredMarkdown: QuartzTransformerPlugin> = (userOpts) => { const opts = { ...defaultOptions, ...userOpts } return { name: "OxHugoFlavouredMarkdown", diff --git a/quartz/plugins/transformers/syntax.ts b/quartz/plugins/transformers/syntax.ts index f11734e..5d3aae0 100644 --- a/quartz/plugins/transformers/syntax.ts +++ b/quartz/plugins/transformers/syntax.ts @@ -19,10 +19,8 @@ const defaultOptions: Options = { keepBackground: false, } -export const SyntaxHighlighting: QuartzTransformerPlugin = ( - userOpts?: Partial, -) => { - const opts: Partial = { ...defaultOptions, ...userOpts } +export const SyntaxHighlighting: QuartzTransformerPlugin> = (userOpts) => { + const opts: CodeOptions = { ...defaultOptions, ...userOpts } return { name: "SyntaxHighlighting", diff --git a/quartz/plugins/transformers/toc.ts b/quartz/plugins/transformers/toc.ts index bfc2f98..791547b 100644 --- a/quartz/plugins/transformers/toc.ts +++ b/quartz/plugins/transformers/toc.ts @@ -25,9 +25,7 @@ interface TocEntry { } const slugAnchor = new Slugger() -export const TableOfContents: QuartzTransformerPlugin | undefined> = ( - userOpts, -) => { +export const TableOfContents: QuartzTransformerPlugin> = (userOpts) => { const opts = { ...defaultOptions, ...userOpts } return { name: "TableOfContents", diff --git a/quartz/processors/parse.ts b/quartz/processors/parse.ts index 3950fee..2bd530c 100644 --- a/quartz/processors/parse.ts +++ b/quartz/processors/parse.ts @@ -143,7 +143,7 @@ export async function parseMarkdown(ctx: BuildCtx, fps: FilePath[]): Promise[] = [] for (const chunk of chunks(fps, CHUNK_SIZE)) { - childPromises.push(pool.exec("parseFiles", [argv, chunk, ctx.allSlugs])) + childPromises.push(pool.exec("parseFiles", [ctx.buildId, argv, chunk, ctx.allSlugs])) } const results: ProcessedContent[][] = await WorkerPromise.all(childPromises).catch((err) => { diff --git a/quartz/styles/base.scss b/quartz/styles/base.scss index 92798a2..3b3ead7 100644 --- a/quartz/styles/base.scss +++ b/quartz/styles/base.scss @@ -20,11 +20,10 @@ section { } .text-highlight { - background-color: #fff23688; + background-color: var(--textHighlight); padding: 0 0.1rem; border-radius: 5px; } - ::selection { background: color-mix(in srgb, var(--tertiary) 60%, rgba(255, 255, 255, 0)); color: var(--darkgray); @@ -43,10 +42,22 @@ ul, .math { color: var(--darkgray); fill: var(--darkgray); - overflow-wrap: anywhere; hyphens: auto; } +p, +ul, +text, +a, +li, +ol, +ul, +.katex, +.math { + overflow-wrap: anywhere; + /* tr and td removed from list of selectors for overflow-wrap, allowing them to use default 'normal' property value */ +} + .math { &.math-display { text-align: center; @@ -79,6 +90,11 @@ a { border-radius: 0; padding: 0; } + &.tag-link { + &::before { + content: "#"; + } + } } &.external .external-icon { @@ -185,11 +201,19 @@ a { } } - & .page-header { + & .page-header, + & .page-footer { width: $pageWidth; - margin: $topSpacing auto 0 auto; + margin-top: 1rem; + @media all and (max-width: $fullPageWidth) { width: initial; + } + } + + & .page-header { + margin: $topSpacing auto 0 auto; + @media all and (max-width: $fullPageWidth) { margin-top: 2rem; } } @@ -476,6 +500,10 @@ video { flex: 1 1 auto; } +div:has(> .overflow) { + position: relative; +} + ul.overflow, ol.overflow { max-height: 400; @@ -508,3 +536,16 @@ ol.overflow { padding-left: 1rem; } } + +.katex-display { + overflow-x: auto; + overflow-y: hidden; +} + +.external-embed.youtube, +iframe.pdf { + aspect-ratio: 16 / 9; + height: 100%; + width: 100%; + border-radius: 5px; +} diff --git a/quartz/styles/callouts.scss b/quartz/styles/callouts.scss index b1fd180..d6f65aa 100644 --- a/quartz/styles/callouts.scss +++ b/quartz/styles/callouts.scss @@ -10,7 +10,7 @@ transition: max-height 0.3s ease; box-sizing: border-box; - & > *:nth-child(2) { + & > .callout-content > :first-child { margin-top: 0; } diff --git a/quartz/util/ctx.ts b/quartz/util/ctx.ts index e056114..044d21f 100644 --- a/quartz/util/ctx.ts +++ b/quartz/util/ctx.ts @@ -14,6 +14,7 @@ export interface Argv { } export interface BuildCtx { + buildId: string argv: Argv cfg: QuartzConfig allSlugs: FullSlug[] diff --git a/quartz/util/path.ts b/quartz/util/path.ts index dceb89b..c02bfb1 100644 --- a/quartz/util/path.ts +++ b/quartz/util/path.ts @@ -168,6 +168,9 @@ export function resolveRelative(current: FullSlug, target: FullSlug | SimpleSlug 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] } diff --git a/quartz/util/theme.ts b/quartz/util/theme.ts index 49cc9cc..9046cec 100644 --- a/quartz/util/theme.ts +++ b/quartz/util/theme.ts @@ -7,6 +7,7 @@ export interface ColorScheme { secondary: string tertiary: string highlight: string + textHighlight: string } interface Colors { @@ -22,6 +23,7 @@ export interface Theme { } cdnCaching: boolean colors: Colors + fontOrigin: "googleFonts" | "local" } export type ThemeKey = keyof Colors @@ -48,6 +50,7 @@ ${stylesheet.join("\n\n")} --secondary: ${theme.colors.lightMode.secondary}; --tertiary: ${theme.colors.lightMode.tertiary}; --highlight: ${theme.colors.lightMode.highlight}; + --textHighlight: ${theme.colors.lightMode.textHighlight}; --headerFont: "${theme.typography.header}", ${DEFAULT_SANS_SERIF}; --bodyFont: "${theme.typography.body}", ${DEFAULT_SANS_SERIF}; @@ -63,6 +66,7 @@ ${stylesheet.join("\n\n")} --secondary: ${theme.colors.darkMode.secondary}; --tertiary: ${theme.colors.darkMode.tertiary}; --highlight: ${theme.colors.darkMode.highlight}; + --textHighlight: ${theme.colors.darkMode.textHighlight}; } ` } diff --git a/quartz/worker.ts b/quartz/worker.ts index b92bdac..a209df9 100644 --- a/quartz/worker.ts +++ b/quartz/worker.ts @@ -7,8 +7,14 @@ 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[]) { +export async function parseFiles( + buildId: string, + argv: Argv, + fps: FilePath[], + allSlugs: FullSlug[], +) { const ctx: BuildCtx = { + buildId, cfg, argv, allSlugs, diff --git a/tsconfig.json b/tsconfig.json index 306204b..784ab23 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,8 +13,8 @@ "forceConsistentCasingInFileNames": true, "esModuleInterop": true, "jsx": "react-jsx", - "jsxImportSource": "preact", + "jsxImportSource": "preact" }, "include": ["**/*.ts", "**/*.tsx", "./package.json"], - "exclude": ["build/**/*.d.ts"], + "exclude": ["build/**/*.d.ts"] }