mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-12-28 07:14:05 -06:00
Merge branch 'jackyzha0:v4' into v4
This commit is contained in:
commit
3dcd659d8c
26
.github/workflows/ci.yaml
vendored
26
.github/workflows/ci.yaml
vendored
@ -46,8 +46,26 @@ jobs:
|
||||
- name: Ensure Quartz builds, check bundle info
|
||||
run: npx quartz build --bundleInfo
|
||||
|
||||
- name: Create release tag
|
||||
uses: Klemensas/action-autotag@stable
|
||||
publish-tag:
|
||||
if: ${{ github.repository == 'jackyzha0/quartz' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
tag_prefix: "v"
|
||||
fetch-depth: 0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- name: Get package version
|
||||
run: node -p -e '`PACKAGE_VERSION=${require("./package.json").version}`' >> $GITHUB_ENV
|
||||
- name: Create release tag
|
||||
uses: pkgdeps/git-tag-action@v2
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
github_repo: ${{ github.repository }}
|
||||
version: ${{ env.PACKAGE_VERSION }}
|
||||
git_commit_sha: ${{ github.sha }}
|
||||
git_tag_prefix: "v"
|
||||
|
||||
@ -28,6 +28,7 @@ This part of the configuration concerns anything that can affect the whole site.
|
||||
- `{ provider: 'google', tagId: '<your-google-tag>' }`: use Google Analytics;
|
||||
- `{ provider: 'plausible' }` (managed) or `{ provider: 'plausible', host: '<your-plausible-host>' }` (self-hosted): use [Plausible](https://plausible.io/);
|
||||
- `{ provider: 'umami', host: '<your-umami-host>', websiteId: '<your-umami-website-id>' }`: use [Umami](https://umami.is/);
|
||||
- `{ provider: 'goatcounter', websiteId: 'my-goatcounter-id' }` (managed) or `{ provider: 'goatcounter', websiteId: 'my-goatcounter-id', host: 'my-goatcounter-domain.com', scriptSrc: 'https://my-url.to/counter.js' }` (self-hosted) use [GoatCounter](https://goatcounter.com)
|
||||
- `locale`: used for [[i18n]] and date formatting
|
||||
- `baseUrl`: this is used for sitemaps and RSS feeds that require an absolute URL to know where the canonical 'home' of your site lives. This is normally the deployed URL of your site (e.g. `quartz.jzhao.xyz` for this site). Do not include the protocol (i.e. `https://`) or any leading or trailing slashes.
|
||||
- This should also include the subpath if you are [[hosting]] on GitHub pages without a custom domain. For example, if my repository is `jackyzha0/quartz`, GitHub pages would deploy to `https://jackyzha0.github.io/quartz` and the `baseUrl` would be `jackyzha0.github.io/quartz`.
|
||||
@ -73,10 +74,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(),
|
||||
...
|
||||
|
||||
@ -57,6 +57,15 @@ For example:
|
||||
- Incorrect: `I have $1 and you have $2` produces I have $1 and you have $2
|
||||
- Correct: `I have \$1 and you have \$2` produces I have \$1 and you have \$2
|
||||
|
||||
### Using mhchem
|
||||
|
||||
Add the following import to the top of `quartz/plugins/transformers/latex.ts` (before all the other
|
||||
imports):
|
||||
|
||||
```ts title="quartz/plugins/transformers/latex.ts"
|
||||
import "katex/contrib/mhchem"
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
||||
Latex parsing is a functionality of the [[plugins/Latex|Latex]] plugin. See the plugin page for customization options.
|
||||
|
||||
@ -61,7 +61,7 @@ export class FileNode {
|
||||
children: FileNode[] // children of current node
|
||||
name: string // last part of slug
|
||||
displayName: string // what actually should be displayed in the explorer
|
||||
file: QuartzPluginData | null // set if node is a file, see `QuartzPluginData` for more detail
|
||||
file: QuartzPluginData | null // if node is a file, this is the file's metadata. See `QuartzPluginData` for more detail
|
||||
depth: number // depth of current node
|
||||
|
||||
... // rest of implementation
|
||||
@ -167,6 +167,19 @@ Component.Explorer({
|
||||
|
||||
You can customize this by changing the entries of the `omit` set. Simply add all folder or file names you want to remove.
|
||||
|
||||
### Remove files by tag
|
||||
|
||||
You can access the frontmatter of a file by `node.file?.frontmatter?`. This allows you to filter out files based on their frontmatter, for example by their tags.
|
||||
|
||||
```ts title="quartz.layout.ts"
|
||||
Component.Explorer({
|
||||
filterFn: (node) => {
|
||||
// exclude files with the tag "explorerexclude"
|
||||
return node.file?.frontmatter?.tags?.includes("explorerexclude") !== true
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Show every element in explorer
|
||||
|
||||
To override the default filter function that removes the `tags` folder from the explorer, you can set the filter function to `undefined`.
|
||||
|
||||
@ -14,6 +14,7 @@ If the frontmatter contains a `description` property, it is used (see [[authorin
|
||||
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
|
||||
|
||||
|
||||
@ -13,6 +13,6 @@ 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).
|
||||
|
||||
@ -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`.
|
||||
|
||||
|
||||
@ -16,5 +16,5 @@ The pages are displayed using the `defaultListPageLayout` in `quartz.layouts.ts`
|
||||
## API
|
||||
|
||||
- Category: Emitter
|
||||
- Function name: `Plugin.AliasRedirects()`.
|
||||
- Function name: `Plugin.TagPage()`.
|
||||
- Source: [`quartz/plugins/emitters/tagPage.tsx`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/tagPage.tsx).
|
||||
|
||||
167
package-lock.json
generated
167
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@jackyzha0/quartz",
|
||||
"version": "4.2.2",
|
||||
"version": "4.2.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@jackyzha0/quartz",
|
||||
"version": "4.2.2",
|
||||
"version": "4.2.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^0.7.0",
|
||||
@ -14,7 +14,7 @@
|
||||
"@napi-rs/simple-git": "0.1.16",
|
||||
"async-mutex": "^0.4.1",
|
||||
"chalk": "^5.3.0",
|
||||
"chokidar": "^3.5.3",
|
||||
"chokidar": "^3.6.0",
|
||||
"cli-spinner": "^0.2.10",
|
||||
"d3": "^7.8.5",
|
||||
"esbuild-sass-plugin": "^2.16.1",
|
||||
@ -27,13 +27,13 @@
|
||||
"hast-util-to-string": "^3.0.0",
|
||||
"is-absolute-url": "^4.0.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lightningcss": "^1.23.0",
|
||||
"lightningcss": "^1.24.0",
|
||||
"mdast-util-find-and-replace": "^3.0.1",
|
||||
"mdast-util-to-hast": "^13.1.0",
|
||||
"mdast-util-to-string": "^4.0.0",
|
||||
"micromorph": "^0.4.5",
|
||||
"preact": "^10.19.5",
|
||||
"preact-render-to-string": "^6.3.1",
|
||||
"preact": "^10.19.6",
|
||||
"preact-render-to-string": "^6.4.0",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pretty-time": "^1.1.0",
|
||||
"reading-time": "^1.5.0",
|
||||
@ -50,11 +50,11 @@
|
||||
"remark-math": "^6.0.0",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-rehype": "^11.1.0",
|
||||
"remark-smartypants": "^2.0.0",
|
||||
"remark-smartypants": "^2.1.0",
|
||||
"rfdc": "^1.3.1",
|
||||
"rimraf": "^5.0.5",
|
||||
"serve-handler": "^6.1.5",
|
||||
"shiki": "^1.1.6",
|
||||
"shiki": "^1.1.7",
|
||||
"source-map-support": "^0.5.21",
|
||||
"to-vfile": "^8.0.0",
|
||||
"toml": "^3.0.0",
|
||||
@ -73,7 +73,7 @@
|
||||
"@types/d3": "^7.4.3",
|
||||
"@types/hast": "^3.0.4",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/node": "^20.11.19",
|
||||
"@types/node": "^20.11.24",
|
||||
"@types/pretty-time": "^1.1.5",
|
||||
"@types/source-map-support": "^0.5.10",
|
||||
"@types/ws": "^8.5.10",
|
||||
@ -743,9 +743,9 @@
|
||||
}
|
||||
},
|
||||
"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.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.1.7.tgz",
|
||||
"integrity": "sha512-gTYLUIuD1UbZp/11qozD3fWpUTuMqPSf3svDMMrL0UmlGU7D9dPw/V1FonwAorCUJBltaaESxq90jrSjQyGixg=="
|
||||
},
|
||||
"node_modules/@sindresorhus/merge-streams": {
|
||||
"version": "2.3.0",
|
||||
@ -1093,9 +1093,9 @@
|
||||
}
|
||||
},
|
||||
"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": "20.11.24",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.24.tgz",
|
||||
"integrity": "sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
@ -1344,15 +1344,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
||||
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
],
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
@ -1365,6 +1359,9 @@
|
||||
"engines": {
|
||||
"node": ">= 8.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
@ -3035,9 +3032,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.24.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.24.0.tgz",
|
||||
"integrity": "sha512-y36QEEDVx4IM7/yIZNsZJMRREIu26WzTsauIysf5s76YeCmlSbRZS7aC97IGPuoFRnyZ5Wx43OBsQBFB5Ne7ng==",
|
||||
"dependencies": {
|
||||
"detect-libc": "^1.0.3"
|
||||
},
|
||||
@ -3049,21 +3046,21 @@
|
||||
"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.24.0",
|
||||
"lightningcss-darwin-x64": "1.24.0",
|
||||
"lightningcss-freebsd-x64": "1.24.0",
|
||||
"lightningcss-linux-arm-gnueabihf": "1.24.0",
|
||||
"lightningcss-linux-arm64-gnu": "1.24.0",
|
||||
"lightningcss-linux-arm64-musl": "1.24.0",
|
||||
"lightningcss-linux-x64-gnu": "1.24.0",
|
||||
"lightningcss-linux-x64-musl": "1.24.0",
|
||||
"lightningcss-win32-x64-msvc": "1.24.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.24.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.24.0.tgz",
|
||||
"integrity": "sha512-rTNPkEiynOu4CfGdd5ZfVOQe2gd2idfQd4EfX1l2ZUUwd+2SwSdbb7cG4rlwfnZckbzCAygm85xkpekRE5/wFw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -3080,9 +3077,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.24.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.24.0.tgz",
|
||||
"integrity": "sha512-4KCeF2RJjzp9xdGY8zIH68CUtptEg8uz8PfkHvsIdrP4t9t5CIgfDBhiB8AmuO75N6SofdmZexDZIKdy9vA7Ww==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -3099,9 +3096,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.24.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.24.0.tgz",
|
||||
"integrity": "sha512-FJAYlek1wXuVTsncNU0C6YD41q126dXcIUm97KAccMn9C4s/JfLSqGWT2gIzAblavPFkyGG2gIADTWp3uWfN1g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -3118,9 +3115,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.24.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.24.0.tgz",
|
||||
"integrity": "sha512-N55K6JqzMx7C0hYUu1YmWqhkHwzEJlkQRMA6phY65noO0I1LOAvP4wBIoFWrzRE+O6zL0RmXJ2xppqyTbk3sYw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -3137,9 +3134,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.24.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.24.0.tgz",
|
||||
"integrity": "sha512-MqqUB2TpYtFWeBvvf5KExDdClU3YGLW5bHKs50uKKootcvG9KoS7wYwd5UichS+W3mYLc5yXUPGD1DNWbLiYKw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -3156,9 +3153,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.24.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.24.0.tgz",
|
||||
"integrity": "sha512-5wn4d9tFwa5bS1ao9mLexYVJdh3nn09HNIipsII6ZF7z9ZA5J4dOEhMgKoeCl891axTGTUYd8Kxn+Hn3XUSYRQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -3175,9 +3172,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.24.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.24.0.tgz",
|
||||
"integrity": "sha512-3j5MdTh+LSDF3o6uDwRjRUgw4J+IfDCVtdkUrJvKxL79qBLUujXY7CTe5X3IQDDLKEe/3wu49r8JKgxr0MfjbQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -3194,9 +3191,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.24.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.24.0.tgz",
|
||||
"integrity": "sha512-HI+rNnvaLz0o36z6Ki0gyG5igVGrJmzczxA5fznr6eFTj3cHORoR/j2q8ivMzNFR4UKJDkTWUH5LMhacwOHWBA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -3213,9 +3210,9 @@
|
||||
}
|
||||
},
|
||||
"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.24.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.24.0.tgz",
|
||||
"integrity": "sha512-oeije/t7OZ5N9vSs6amyW/34wIYoBCpE6HUlsSKcP2SR1CVgx9oKEM00GtQmtqNnYiMIfsSm7+ppMb4NLtD5vg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -4459,18 +4456,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/preact": {
|
||||
"version": "10.19.5",
|
||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.19.5.tgz",
|
||||
"integrity": "sha512-OPELkDmSVbKjbFqF9tgvOowiiQ9TmsJljIzXRyNE8nGiis94pwv1siF78rQkAP1Q1738Ce6pellRg/Ns/CtHqQ==",
|
||||
"version": "10.19.6",
|
||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.19.6.tgz",
|
||||
"integrity": "sha512-gympg+T2Z1fG1unB8NH29yHJwnEaCH37Z32diPDku316OTnRPeMbiRV9kTrfZpocXjdfnWuFUl/Mj4BHaf6gnw==",
|
||||
"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==",
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.4.0.tgz",
|
||||
"integrity": "sha512-pzDwezZaLbK371OiJjXDsZJwVOALzFX5M1wEh2Kr0pEApq5AV6bRH/DFbA/zNA7Lck/duyREPQLLvzu2G6hEQQ==",
|
||||
"dependencies": {
|
||||
"pretty-format": "^3.8.0"
|
||||
},
|
||||
@ -4869,32 +4866,18 @@
|
||||
}
|
||||
},
|
||||
"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": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-2.1.0.tgz",
|
||||
"integrity": "sha512-qoF6Vz3BjU2tP6OfZqHOvCU0ACmu/6jhGaINSQRI9mM7wCxNQTKB3JUAN4SVoN2ybElEDTxBIABRep7e569iJw==",
|
||||
"dependencies": {
|
||||
"retext": "^8.1.0",
|
||||
"retext-smartypants": "^5.1.0",
|
||||
"unist-util-visit": "^4.1.0"
|
||||
"retext-smartypants": "^5.2.0",
|
||||
"unist-util-visit": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/remark-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/remark-stringify": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz",
|
||||
@ -5327,11 +5310,11 @@
|
||||
}
|
||||
},
|
||||
"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.1.7",
|
||||
"resolved": "https://registry.npmjs.org/shiki/-/shiki-1.1.7.tgz",
|
||||
"integrity": "sha512-9kUTMjZtcPH3i7vHunA6EraTPpPOITYTdA5uMrvsJRexktqP0s7P3s9HVK80b4pP42FRVe03D7fT3NmJv2yYhw==",
|
||||
"dependencies": {
|
||||
"@shikijs/core": "1.1.6"
|
||||
"@shikijs/core": "1.1.7"
|
||||
}
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
|
||||
16
package.json
16
package.json
@ -2,7 +2,7 @@
|
||||
"name": "@jackyzha0/quartz",
|
||||
"description": "🌱 publish your digital garden and notes as a website",
|
||||
"private": true,
|
||||
"version": "4.2.2",
|
||||
"version": "4.2.3",
|
||||
"type": "module",
|
||||
"author": "jackyzha0 <j.zhao2k19@gmail.com>",
|
||||
"license": "MIT",
|
||||
@ -39,7 +39,7 @@
|
||||
"@napi-rs/simple-git": "0.1.16",
|
||||
"async-mutex": "^0.4.1",
|
||||
"chalk": "^5.3.0",
|
||||
"chokidar": "^3.5.3",
|
||||
"chokidar": "^3.6.0",
|
||||
"cli-spinner": "^0.2.10",
|
||||
"d3": "^7.8.5",
|
||||
"esbuild-sass-plugin": "^2.16.1",
|
||||
@ -52,13 +52,13 @@
|
||||
"hast-util-to-string": "^3.0.0",
|
||||
"is-absolute-url": "^4.0.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lightningcss": "^1.23.0",
|
||||
"lightningcss": "^1.24.0",
|
||||
"mdast-util-find-and-replace": "^3.0.1",
|
||||
"mdast-util-to-hast": "^13.1.0",
|
||||
"mdast-util-to-string": "^4.0.0",
|
||||
"micromorph": "^0.4.5",
|
||||
"preact": "^10.19.5",
|
||||
"preact-render-to-string": "^6.3.1",
|
||||
"preact": "^10.19.6",
|
||||
"preact-render-to-string": "^6.4.0",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pretty-time": "^1.1.0",
|
||||
"reading-time": "^1.5.0",
|
||||
@ -75,11 +75,11 @@
|
||||
"remark-math": "^6.0.0",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-rehype": "^11.1.0",
|
||||
"remark-smartypants": "^2.0.0",
|
||||
"remark-smartypants": "^2.1.0",
|
||||
"rfdc": "^1.3.1",
|
||||
"rimraf": "^5.0.5",
|
||||
"serve-handler": "^6.1.5",
|
||||
"shiki": "^1.1.6",
|
||||
"shiki": "^1.1.7",
|
||||
"source-map-support": "^0.5.21",
|
||||
"to-vfile": "^8.0.0",
|
||||
"toml": "^3.0.0",
|
||||
@ -95,7 +95,7 @@
|
||||
"@types/d3": "^7.4.3",
|
||||
"@types/hast": "^3.0.4",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/node": "^20.11.19",
|
||||
"@types/node": "^20.11.24",
|
||||
"@types/pretty-time": "^1.1.5",
|
||||
"@types/source-map-support": "^0.5.10",
|
||||
"@types/ws": "^8.5.10",
|
||||
|
||||
@ -185,9 +185,14 @@ async function partialRebuildFromEntrypoint(
|
||||
const emitterGraph =
|
||||
(await emitter.getDependencyGraph?.(ctx, processedFiles, staticResources)) ?? null
|
||||
|
||||
// emmiter may not define a dependency graph. nothing to update if so
|
||||
if (emitterGraph) {
|
||||
dependencies[emitter.name]?.updateIncomingEdgesForNode(emitterGraph, fp)
|
||||
const existingGraph = dependencies[emitter.name]
|
||||
if (existingGraph !== null) {
|
||||
existingGraph.mergeGraph(emitterGraph)
|
||||
} else {
|
||||
// might be the first time we're adding a mardown file
|
||||
dependencies[emitter.name] = emitterGraph
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
@ -224,7 +229,6 @@ async function partialRebuildFromEntrypoint(
|
||||
// EMIT
|
||||
perf.addEvent("rebuild")
|
||||
let emittedFiles = 0
|
||||
const destinationsToDelete = new Set<FilePath>()
|
||||
|
||||
for (const emitter of cfg.plugins.emitters) {
|
||||
const depGraph = dependencies[emitter.name]
|
||||
@ -264,11 +268,6 @@ async function partialRebuildFromEntrypoint(
|
||||
// and supply [a.md, b.md] to the emitter
|
||||
const upstreams = [...depGraph.getLeafNodeAncestors(fp)] as FilePath[]
|
||||
|
||||
if (action === "delete" && upstreams.length === 1) {
|
||||
// if there's only one upstream, the destination is solely dependent on this file
|
||||
destinationsToDelete.add(upstreams[0])
|
||||
}
|
||||
|
||||
const upstreamContent = upstreams
|
||||
// filter out non-markdown files
|
||||
.filter((file) => contentMap.has(file))
|
||||
@ -291,14 +290,24 @@ async function partialRebuildFromEntrypoint(
|
||||
console.log(`Emitted ${emittedFiles} files to \`${argv.output}\` in ${perf.timeSince("rebuild")}`)
|
||||
|
||||
// CLEANUP
|
||||
// delete files that are solely dependent on this file
|
||||
await rimraf([...destinationsToDelete])
|
||||
const destinationsToDelete = new Set<FilePath>()
|
||||
for (const file of toRemove) {
|
||||
// remove from cache
|
||||
contentMap.delete(file)
|
||||
// remove the node from dependency graphs
|
||||
Object.values(dependencies).forEach((depGraph) => depGraph?.removeNode(file))
|
||||
Object.values(dependencies).forEach((depGraph) => {
|
||||
// remove the node from dependency graphs
|
||||
depGraph?.removeNode(file)
|
||||
// remove any orphan nodes. eg if a.md is deleted, a.html is orphaned and should be removed
|
||||
const orphanNodes = depGraph?.removeOrphanNodes()
|
||||
orphanNodes?.forEach((node) => {
|
||||
// only delete files that are in the output directory
|
||||
if (node.startsWith(argv.output)) {
|
||||
destinationsToDelete.add(node)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
await rimraf([...destinationsToDelete])
|
||||
|
||||
toRemove.clear()
|
||||
release()
|
||||
|
||||
@ -19,6 +19,12 @@ export type Analytics =
|
||||
websiteId: string
|
||||
host?: string
|
||||
}
|
||||
| {
|
||||
provider: "goatcounter"
|
||||
websiteId: string
|
||||
host?: string
|
||||
scriptSrc?: string
|
||||
}
|
||||
|
||||
export interface GlobalConfiguration {
|
||||
pageTitle: string
|
||||
|
||||
@ -17,6 +17,7 @@ export interface D3Config {
|
||||
opacityScale: number
|
||||
removeTags: string[]
|
||||
showTags: boolean
|
||||
focusOnHover?: boolean
|
||||
}
|
||||
|
||||
interface GraphOptions {
|
||||
@ -37,6 +38,7 @@ const defaultOptions: GraphOptions = {
|
||||
opacityScale: 1,
|
||||
showTags: true,
|
||||
removeTags: [],
|
||||
focusOnHover: false,
|
||||
},
|
||||
globalGraph: {
|
||||
drag: true,
|
||||
@ -50,6 +52,7 @@ const defaultOptions: GraphOptions = {
|
||||
opacityScale: 1,
|
||||
showTags: true,
|
||||
removeTags: [],
|
||||
focusOnHover: true,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -63,7 +63,7 @@ export const PageList: QuartzComponent = ({ cfg, fileData, allFiles, limit }: Pr
|
||||
class="internal tag-link"
|
||||
href={resolveRelative(fileData.slug!, `tags/${tag}` as FullSlug)}
|
||||
>
|
||||
#{tag}
|
||||
{tag}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
|
||||
@ -63,7 +63,7 @@ export default ((userOpts?: Partial<Options>) => {
|
||||
class="internal tag-link"
|
||||
href={resolveRelative(fileData.slug!, `tags/${tag}` as FullSlug)}
|
||||
>
|
||||
#{tag}
|
||||
{tag}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
|
||||
@ -9,12 +9,11 @@ const TagList: QuartzComponent = ({ fileData, displayClass }: QuartzComponentPro
|
||||
return (
|
||||
<ul class={classNames(displayClass, "tags")}>
|
||||
{tags.map((tag) => {
|
||||
const display = `#${tag}`
|
||||
const linkDest = baseDir + `/tags/${slugTag(tag)}`
|
||||
return (
|
||||
<li>
|
||||
<a href={linkDest} class="internal tag-link">
|
||||
{display}
|
||||
{tag}
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { i18n } from "../../i18n"
|
||||
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
||||
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
||||
|
||||
function NotFound({ cfg }: QuartzComponentProps) {
|
||||
const NotFound: QuartzComponent = ({ cfg }: QuartzComponentProps) => {
|
||||
return (
|
||||
<article class="popover-hint">
|
||||
<h1>404</h1>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { htmlToJsx } from "../../util/jsx"
|
||||
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
||||
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
||||
|
||||
function Content({ fileData, tree }: QuartzComponentProps) {
|
||||
const Content: QuartzComponent = ({ fileData, tree }: QuartzComponentProps) => {
|
||||
const content = htmlToJsx(fileData.filePath!, tree)
|
||||
const classes: string[] = fileData.frontmatter?.cssclasses ?? []
|
||||
const classString = ["popover-hint", ...classes].join(" ")
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
||||
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
||||
import path from "path"
|
||||
|
||||
import style from "../styles/listPage.scss"
|
||||
@ -22,7 +22,7 @@ const defaultOptions: FolderContentOptions = {
|
||||
export default ((opts?: Partial<FolderContentOptions>) => {
|
||||
const options: FolderContentOptions = { ...defaultOptions, ...opts }
|
||||
|
||||
function FolderContent(props: QuartzComponentProps) {
|
||||
const FolderContent: QuartzComponent = (props: QuartzComponentProps) => {
|
||||
const { tree, fileData, allFiles, cfg } = props
|
||||
const folderSlug = stripSlashes(simplifySlug(fileData.slug!))
|
||||
const allPagesInFolder = allFiles.filter((file) => {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
||||
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
||||
import style from "../styles/listPage.scss"
|
||||
import { PageList } from "../PageList"
|
||||
import { FullSlug, getAllSegmentPrefixes, simplifySlug } from "../../util/path"
|
||||
@ -8,7 +8,7 @@ import { htmlToJsx } from "../../util/jsx"
|
||||
import { i18n } from "../../i18n"
|
||||
|
||||
const numPages = 10
|
||||
function TagContent(props: QuartzComponentProps) {
|
||||
const TagContent: QuartzComponent = (props: QuartzComponentProps) => {
|
||||
const { tree, fileData, allFiles, cfg } = props
|
||||
const slug = fileData.slug
|
||||
|
||||
@ -58,7 +58,7 @@ function TagContent(props: QuartzComponentProps) {
|
||||
<div>
|
||||
<h2>
|
||||
<a class="internal tag-link" href={`../tags/${tag}`}>
|
||||
#{tag}
|
||||
{tag}
|
||||
</a>
|
||||
</h2>
|
||||
{content && <p>{content}</p>}
|
||||
|
||||
@ -19,6 +19,7 @@ interface RenderComponents {
|
||||
footer: QuartzComponent
|
||||
}
|
||||
|
||||
const headerRegex = new RegExp(/h[1-6]/)
|
||||
export function pageResources(
|
||||
baseDir: FullSlug | RelativeURL,
|
||||
staticResources: StaticResources,
|
||||
@ -105,18 +106,23 @@ 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 = Number(el.tagName.substring(1))
|
||||
}
|
||||
} else if (depth <= startDepth) {
|
||||
// looking for new header that is same level or higher
|
||||
endIdx = i
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -44,6 +44,7 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
|
||||
opacityScale,
|
||||
removeTags,
|
||||
showTags,
|
||||
focusOnHover,
|
||||
} = JSON.parse(graph.dataset["cfg"]!)
|
||||
|
||||
const data: Map<SimpleSlug, ContentDetails> = new Map(
|
||||
@ -189,6 +190,8 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
|
||||
return 2 + Math.sqrt(numLinks)
|
||||
}
|
||||
|
||||
let connectedNodes: SimpleSlug[] = []
|
||||
|
||||
// draw individual nodes
|
||||
const node = graphNode
|
||||
.append("circle")
|
||||
@ -202,17 +205,25 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
|
||||
window.spaNavigate(new URL(targ, window.location.toString()))
|
||||
})
|
||||
.on("mouseover", function (_, d) {
|
||||
const neighbours: SimpleSlug[] = data.get(slug)?.links ?? []
|
||||
const neighbourNodes = d3
|
||||
.selectAll<HTMLElement, NodeData>(".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)
|
||||
if (focusOnHover) {
|
||||
// fade out non-neighbour nodes
|
||||
connectedNodes = linkNodes.data().flatMap((d: any) => [d.source.id, d.target.id])
|
||||
|
||||
d3.selectAll<HTMLElement, NodeData>(".link")
|
||||
.transition()
|
||||
.duration(200)
|
||||
.style("opacity", 0.2)
|
||||
d3.selectAll<HTMLElement, NodeData>(".node")
|
||||
.filter((d) => !connectedNodes.includes(d.id))
|
||||
.transition()
|
||||
.duration(200)
|
||||
.style("opacity", 0.2)
|
||||
}
|
||||
|
||||
// highlight links
|
||||
linkNodes.transition().duration(200).attr("stroke", "var(--gray)").attr("stroke-width", 1)
|
||||
@ -231,6 +242,10 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
|
||||
.style("font-size", bigFont + "em")
|
||||
})
|
||||
.on("mouseleave", function (_, d) {
|
||||
if (focusOnHover) {
|
||||
d3.selectAll<HTMLElement, NodeData>(".link").transition().duration(200).style("opacity", 1)
|
||||
d3.selectAll<HTMLElement, NodeData>(".node").transition().duration(200).style("opacity", 1)
|
||||
}
|
||||
const currentId = d.id
|
||||
const linkNodes = d3
|
||||
.selectAll(".link")
|
||||
|
||||
@ -39,6 +39,28 @@ describe("DepGraph", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("mergeGraph", () => {
|
||||
test("merges two graphs", () => {
|
||||
const graph = new DepGraph<string>()
|
||||
graph.addEdge("A.md", "A.html")
|
||||
|
||||
const other = new DepGraph<string>()
|
||||
other.addEdge("B.md", "B.html")
|
||||
|
||||
graph.mergeGraph(other)
|
||||
|
||||
const expected = {
|
||||
nodes: ["A.md", "A.html", "B.md", "B.html"],
|
||||
edges: [
|
||||
["A.md", "A.html"],
|
||||
["B.md", "B.html"],
|
||||
],
|
||||
}
|
||||
|
||||
assert.deepStrictEqual(graph.export(), expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe("updateIncomingEdgesForNode", () => {
|
||||
test("merges when node exists", () => {
|
||||
// A.md -> B.md -> B.html
|
||||
|
||||
@ -39,12 +39,26 @@ export default class DepGraph<T> {
|
||||
}
|
||||
}
|
||||
|
||||
// Remove node and all edges connected to it
|
||||
removeNode(node: T): void {
|
||||
if (this._graph.has(node)) {
|
||||
// first remove all edges so other nodes don't have references to this node
|
||||
for (const target of this._graph.get(node)!.outgoing) {
|
||||
this.removeEdge(node, target)
|
||||
}
|
||||
for (const source of this._graph.get(node)!.incoming) {
|
||||
this.removeEdge(source, node)
|
||||
}
|
||||
this._graph.delete(node)
|
||||
}
|
||||
}
|
||||
|
||||
forEachNode(callback: (node: T) => void): void {
|
||||
for (const node of this._graph.keys()) {
|
||||
callback(node)
|
||||
}
|
||||
}
|
||||
|
||||
hasEdge(from: T, to: T): boolean {
|
||||
return Boolean(this._graph.get(from)?.outgoing.has(to))
|
||||
}
|
||||
@ -92,6 +106,15 @@ export default class DepGraph<T> {
|
||||
|
||||
// DEPENDENCY ALGORITHMS
|
||||
|
||||
// Add all nodes and edges from other graph to this graph
|
||||
mergeGraph(other: DepGraph<T>): void {
|
||||
other.forEachEdge(([source, target]) => {
|
||||
this.addNode(source)
|
||||
this.addNode(target)
|
||||
this.addEdge(source, target)
|
||||
})
|
||||
}
|
||||
|
||||
// For the node provided:
|
||||
// If node does not exist, add it
|
||||
// If an incoming edge was added in other, it is added in this graph
|
||||
@ -112,6 +135,24 @@ export default class DepGraph<T> {
|
||||
})
|
||||
}
|
||||
|
||||
// Remove all nodes that do not have any incoming or outgoing edges
|
||||
// A node may be orphaned if the only node pointing to it was removed
|
||||
removeOrphanNodes(): Set<T> {
|
||||
let orphanNodes = new Set<T>()
|
||||
|
||||
this.forEachNode((node) => {
|
||||
if (this.inDegree(node) === 0 && this.outDegree(node) === 0) {
|
||||
orphanNodes.add(node)
|
||||
}
|
||||
})
|
||||
|
||||
orphanNodes.forEach((node) => {
|
||||
this.removeNode(node)
|
||||
})
|
||||
|
||||
return orphanNodes
|
||||
}
|
||||
|
||||
// Get all leaf nodes (i.e. destination paths) reachable from the node provided
|
||||
// Eg. if the graph is A -> B -> C
|
||||
// D ---^
|
||||
|
||||
@ -12,6 +12,7 @@ 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"
|
||||
|
||||
export const TRANSLATIONS = {
|
||||
"en-US": en,
|
||||
@ -48,6 +49,7 @@ export const TRANSLATIONS = {
|
||||
"ru-RU": ru,
|
||||
"ko-KR": ko,
|
||||
"zh-CN": zh,
|
||||
"vi-VN": vi,
|
||||
} as const
|
||||
|
||||
export const defaultTranslation = "en-US"
|
||||
|
||||
83
quartz/i18n/locales/vi-VN.ts
Normal file
83
quartz/i18n/locales/vi-VN.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { Translation } from "./definition"
|
||||
|
||||
export default {
|
||||
propertyDefaults: {
|
||||
title: "Không có tiêu đề",
|
||||
description: "Không có mô tả được cung cấp",
|
||||
},
|
||||
components: {
|
||||
callout: {
|
||||
note: "Ghi Chú",
|
||||
abstract: "Tóm Tắt",
|
||||
info: "Thông tin",
|
||||
todo: "Cần Làm",
|
||||
tip: "Gợi Ý",
|
||||
success: "Thành Công",
|
||||
question: "Nghi Vấn",
|
||||
warning: "Cảnh Báo",
|
||||
failure: "Thất Bại",
|
||||
danger: "Nguy Hiểm",
|
||||
bug: "Lỗi",
|
||||
example: "Ví Dụ",
|
||||
quote: "Trích Dẫn",
|
||||
},
|
||||
backlinks: {
|
||||
title: "Liên Kết Ngược",
|
||||
noBacklinksFound: "Không có liên kết ngược được tìm thấy",
|
||||
},
|
||||
themeToggle: {
|
||||
lightMode: "Sáng",
|
||||
darkMode: "Tối",
|
||||
},
|
||||
explorer: {
|
||||
title: "Trong bài này",
|
||||
},
|
||||
footer: {
|
||||
createdWith: "Được tạo bởi",
|
||||
},
|
||||
graph: {
|
||||
title: "Biểu Đồ",
|
||||
},
|
||||
recentNotes: {
|
||||
title: "Bài viết gần đây",
|
||||
seeRemainingMore: ({ remaining }) => `Xem ${remaining} thêm →`,
|
||||
},
|
||||
transcludes: {
|
||||
transcludeOf: ({ targetSlug }) => `Bao gồm ${targetSlug}`,
|
||||
linkToOriginal: "Liên Kết Gốc",
|
||||
},
|
||||
search: {
|
||||
title: "Tìm Kiếm",
|
||||
searchBarPlaceholder: "Tìm kiếm thông tin",
|
||||
},
|
||||
tableOfContents: {
|
||||
title: "Bảng Nội Dung",
|
||||
},
|
||||
contentMeta: {
|
||||
readingTime: ({ minutes }) => `đọc ${minutes} phút`,
|
||||
},
|
||||
},
|
||||
pages: {
|
||||
rss: {
|
||||
recentNotes: "Những bài gần đây",
|
||||
lastFewNotes: ({ count }) => `${count} Bài gần đây`,
|
||||
},
|
||||
error: {
|
||||
title: "Không Tìm Thấy",
|
||||
notFound: "Trang này được bảo mật hoặc không tồn tại.",
|
||||
},
|
||||
folderContent: {
|
||||
folder: "Thư Mục",
|
||||
itemsUnderFolder: ({ count }) =>
|
||||
count === 1 ? "1 mục trong thư mục này." : `${count} mục trong thư mục này.`,
|
||||
},
|
||||
tagContent: {
|
||||
tag: "Thẻ",
|
||||
tagIndex: "Thẻ Mục Lục",
|
||||
itemsUnderTag: ({ count }) =>
|
||||
count === 1 ? "1 mục gắn thẻ này." : `${count} mục gắn thẻ này.`,
|
||||
showingFirst: ({ count }) => `Hiển thị trước ${count} thẻ.`,
|
||||
totalTags: ({ count }) => `Tìm thấy ${count} thẻ tổng cộng.`,
|
||||
},
|
||||
},
|
||||
} as const satisfies Translation
|
||||
@ -120,12 +120,21 @@ function addGlobalPageResources(
|
||||
} else if (cfg.analytics?.provider === "umami") {
|
||||
componentResources.afterDOMLoaded.push(`
|
||||
const umamiScript = document.createElement("script")
|
||||
umamiScript.src = "${cfg.analytics.host}" ?? "https://analytics.umami.is/script.js"
|
||||
umamiScript.src = "${cfg.analytics.host ?? "https://analytics.umami.is"}/script.js"
|
||||
umamiScript.setAttribute("data-website-id", "${cfg.analytics.websiteId}")
|
||||
umamiScript.async = true
|
||||
|
||||
document.head.appendChild(umamiScript)
|
||||
`)
|
||||
} else if (cfg.analytics?.provider === "goatcounter") {
|
||||
componentResources.afterDOMLoaded.push(`
|
||||
const goatcounterScript = document.createElement("script")
|
||||
goatcounterScript.src = "${cfg.analytics.scriptSrc ?? "https://gc.zgo.at/count.js"}"
|
||||
goatcounterScript.async = true
|
||||
goatcounterScript.setAttribute("data-goatcounter",
|
||||
"https://${cfg.analytics.websiteId}.${cfg.analytics.host ?? "goatcounter.com"}/count")
|
||||
document.head.appendChild(goatcounterScript)
|
||||
`)
|
||||
}
|
||||
|
||||
if (cfg.enableSPA) {
|
||||
|
||||
@ -73,7 +73,7 @@ export const TagPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (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({
|
||||
|
||||
@ -5,12 +5,19 @@ import { escapeHTML } from "../../util/escape"
|
||||
|
||||
export interface Options {
|
||||
descriptionLength: number
|
||||
replaceExternalLinks: boolean
|
||||
}
|
||||
|
||||
const defaultOptions: Options = {
|
||||
descriptionLength: 150,
|
||||
replaceExternalLinks: true,
|
||||
}
|
||||
|
||||
const urlRegex = new RegExp(
|
||||
/(https?:\/\/)?(?<domain>([\da-z\.-]+)\.([a-z\.]{2,6})(:\d+)?)(?<path>[\/\w\.-]*)(\?[\/\w\.=&;-]*)?/,
|
||||
"g",
|
||||
)
|
||||
|
||||
export const Description: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
|
||||
const opts = { ...defaultOptions, ...userOpts }
|
||||
return {
|
||||
@ -19,22 +26,42 @@ export const Description: QuartzTransformerPlugin<Partial<Options> | 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,
|
||||
"$<domain>" + "$<path>",
|
||||
)
|
||||
text = text.replace(urlRegex, "$<domain>" + "$<path>")
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
if (sentences[0] !== undefined && sentences[0].length >= len) {
|
||||
const firstSentence = sentences[0].split(" ")
|
||||
while (finalDesc.length < len) {
|
||||
const sentence = firstSentence[sentenceIdx]
|
||||
if (!sentence) break
|
||||
finalDesc.push(sentence)
|
||||
sentenceIdx++
|
||||
}
|
||||
finalDesc.push("...")
|
||||
} else {
|
||||
while (finalDesc.length < len) {
|
||||
const sentence = sentences[sentenceIdx]
|
||||
if (!sentence) break
|
||||
finalDesc.push(sentence.endsWith(".") ? sentence : sentence + ".")
|
||||
sentenceIdx++
|
||||
}
|
||||
}
|
||||
|
||||
file.data.description = finalDesc.join(" ")
|
||||
file.data.text = text
|
||||
}
|
||||
},
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
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"
|
||||
@ -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
|
||||
@ -124,6 +123,7 @@ const tagRegex = new RegExp(
|
||||
)
|
||||
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(
|
||||
/^(?<alt>(?!^\d*x?\d*$).*?)?(\|?\s*?(?<width>\d+)(x(?<height>\d+))?)?$/,
|
||||
@ -328,7 +328,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
value: `#${tag}`,
|
||||
value: tag,
|
||||
},
|
||||
],
|
||||
}
|
||||
@ -528,12 +528,35 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
|
||||
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,7 +575,9 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
|
||||
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",
|
||||
@ -560,7 +585,20 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
|
||||
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",
|
||||
allow: "fullscreen",
|
||||
frameborder: 0,
|
||||
width: "600px",
|
||||
height: "350px",
|
||||
src: `https://www.youtube.com/embed/videoseries?list=${playlistId}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,6 +79,11 @@ a {
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
}
|
||||
&.tag-link {
|
||||
&::before {
|
||||
content: "#";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.external .external-icon {
|
||||
|
||||
@ -9,6 +9,11 @@ export interface ColorScheme {
|
||||
highlight: string
|
||||
}
|
||||
|
||||
interface Colors {
|
||||
lightMode: ColorScheme
|
||||
darkMode: ColorScheme
|
||||
}
|
||||
|
||||
export interface Theme {
|
||||
typography: {
|
||||
header: string
|
||||
@ -16,12 +21,11 @@ export interface Theme {
|
||||
code: string
|
||||
}
|
||||
cdnCaching: boolean
|
||||
colors: {
|
||||
lightMode: ColorScheme
|
||||
darkMode: ColorScheme
|
||||
}
|
||||
colors: Colors
|
||||
}
|
||||
|
||||
export type ThemeKey = keyof Colors
|
||||
|
||||
const DEFAULT_SANS_SERIF =
|
||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif'
|
||||
const DEFAULT_MONO = "ui-monospace, SFMono-Regular, SF Mono, Menlo, monospace"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user