mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-12-19 19:04:06 -06:00
Merge branch 'v4' into v4
This commit is contained in:
commit
73829a2506
@ -31,7 +31,7 @@ If you prefer instructions in a video format you can try following Nicole van de
|
|||||||
|
|
||||||
## 🔧 Features
|
## 🔧 Features
|
||||||
|
|
||||||
- [[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
|
- [[Obsidian compatibility]], [[full-text search]], [[graph view]], [[wikilinks|wikilinks, transclusions]], [[backlinks]], [[features/Latex|Latex]], [[syntax highlighting]], [[popover previews]], [[Docker Support]], [[i18n|internationalization]], [[comments]] and [many more](./features/) right out of the box
|
||||||
- Hot-reload on configuration edits and incremental rebuilds for content edits
|
- Hot-reload on configuration edits and incremental rebuilds for content edits
|
||||||
- Simple JSX layouts and [[creating components|page components]]
|
- Simple JSX layouts and [[creating components|page components]]
|
||||||
- [[SPA Routing|Ridiculously fast page loads]] and tiny bundle sizes
|
- [[SPA Routing|Ridiculously fast page loads]] and tiny bundle sizes
|
||||||
|
|||||||
@ -60,3 +60,25 @@ The `DesktopOnly` component is the counterpart to `MobileOnly`. It makes its chi
|
|||||||
```typescript
|
```typescript
|
||||||
Component.DesktopOnly(Component.TableOfContents())
|
Component.DesktopOnly(Component.TableOfContents())
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `ConditionalRender` Component
|
||||||
|
|
||||||
|
The `ConditionalRender` component is a wrapper that conditionally renders its child component based on a provided condition function. This is useful for creating dynamic layouts where components should only appear under certain conditions.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type ConditionalRenderConfig = {
|
||||||
|
component: QuartzComponent
|
||||||
|
condition: (props: QuartzComponentProps) => boolean
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example Usage
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
Component.ConditionalRender({
|
||||||
|
component: Component.Search(),
|
||||||
|
condition: (props) => props.displayClass !== "fullpage",
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
This example would only render the Search component when the page is not in fullpage mode.
|
||||||
|
|||||||
@ -62,7 +62,7 @@ The following properties can be used to customize your link previews:
|
|||||||
| `socialDescription` | `description` | Description to be used for preview. |
|
| `socialDescription` | `description` | Description to be used for preview. |
|
||||||
| `socialImage` | `image`, `cover` | Link to preview image. |
|
| `socialImage` | `image`, `cover` | Link to preview image. |
|
||||||
|
|
||||||
The `socialImage` property should contain a link to an image relative to `quartz/static`. If you have a folder for all your images in `quartz/static/my-images`, an example for `socialImage` could be `"my-images/cover.png"`.
|
The `socialImage` property should contain a link to an image either relative to `quartz/static`, or a full URL. If you have a folder for all your images in `quartz/static/my-images`, an example for `socialImage` could be `"my-images/cover.png"`. Alternatively, you can use a fully qualified URL like `"https://example.com/cover.png"`.
|
||||||
|
|
||||||
> [!info] Info
|
> [!info] Info
|
||||||
>
|
>
|
||||||
|
|||||||
115
package-lock.json
generated
115
package-lock.json
generated
@ -30,13 +30,13 @@
|
|||||||
"hast-util-to-string": "^3.0.1",
|
"hast-util-to-string": "^3.0.1",
|
||||||
"is-absolute-url": "^4.0.1",
|
"is-absolute-url": "^4.0.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"lightningcss": "^1.29.2",
|
"lightningcss": "^1.29.3",
|
||||||
"mdast-util-find-and-replace": "^3.0.2",
|
"mdast-util-find-and-replace": "^3.0.2",
|
||||||
"mdast-util-to-hast": "^13.2.0",
|
"mdast-util-to-hast": "^13.2.0",
|
||||||
"mdast-util-to-string": "^4.0.0",
|
"mdast-util-to-string": "^4.0.0",
|
||||||
"micromorph": "^0.4.5",
|
"micromorph": "^0.4.5",
|
||||||
"minimatch": "^10.0.1",
|
"minimatch": "^10.0.1",
|
||||||
"pixi.js": "^8.8.1",
|
"pixi.js": "^8.9.0",
|
||||||
"preact": "^10.26.4",
|
"preact": "^10.26.4",
|
||||||
"preact-render-to-string": "^6.5.13",
|
"preact-render-to-string": "^6.5.13",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
@ -46,7 +46,7 @@
|
|||||||
"rehype-citation": "^2.2.2",
|
"rehype-citation": "^2.2.2",
|
||||||
"rehype-katex": "^7.0.1",
|
"rehype-katex": "^7.0.1",
|
||||||
"rehype-mathjax": "^7.1.0",
|
"rehype-mathjax": "^7.1.0",
|
||||||
"rehype-pretty-code": "^0.14.0",
|
"rehype-pretty-code": "^0.14.1",
|
||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
"rehype-slug": "^6.0.0",
|
"rehype-slug": "^6.0.0",
|
||||||
"remark": "^15.0.1",
|
"remark": "^15.0.1",
|
||||||
@ -80,7 +80,7 @@
|
|||||||
"@types/d3": "^7.4.3",
|
"@types/d3": "^7.4.3",
|
||||||
"@types/hast": "^3.0.4",
|
"@types/hast": "^3.0.4",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/node": "^22.13.10",
|
"@types/node": "^22.13.11",
|
||||||
"@types/pretty-time": "^1.1.5",
|
"@types/pretty-time": "^1.1.5",
|
||||||
"@types/source-map-support": "^0.5.10",
|
"@types/source-map-support": "^0.5.10",
|
||||||
"@types/ws": "^8.18.0",
|
"@types/ws": "^8.18.0",
|
||||||
@ -1924,9 +1924,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.13.10",
|
"version": "22.13.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.11.tgz",
|
||||||
"integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==",
|
"integrity": "sha512-iEUCUJoU0i3VnrCmgoWCXttklWcvoCIx4jzcP22fioIVSdTmjgoEvmAO/QPw6TcS9k5FrNgn4w7q5lGOd1CT5g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -3994,9 +3994,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss": {
|
"node_modules/lightningcss": {
|
||||||
"version": "1.29.2",
|
"version": "1.29.3",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.3.tgz",
|
||||||
"integrity": "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==",
|
"integrity": "sha512-GlOJwTIP6TMIlrTFsxTerwC0W6OpQpCGuX1ECRLBUVRh6fpJH3xTqjCjRgQHTb4ZXexH9rtHou1Lf03GKzmhhQ==",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"detect-libc": "^2.0.3"
|
"detect-libc": "^2.0.3"
|
||||||
@ -4009,22 +4009,22 @@
|
|||||||
"url": "https://opencollective.com/parcel"
|
"url": "https://opencollective.com/parcel"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"lightningcss-darwin-arm64": "1.29.2",
|
"lightningcss-darwin-arm64": "1.29.3",
|
||||||
"lightningcss-darwin-x64": "1.29.2",
|
"lightningcss-darwin-x64": "1.29.3",
|
||||||
"lightningcss-freebsd-x64": "1.29.2",
|
"lightningcss-freebsd-x64": "1.29.3",
|
||||||
"lightningcss-linux-arm-gnueabihf": "1.29.2",
|
"lightningcss-linux-arm-gnueabihf": "1.29.3",
|
||||||
"lightningcss-linux-arm64-gnu": "1.29.2",
|
"lightningcss-linux-arm64-gnu": "1.29.3",
|
||||||
"lightningcss-linux-arm64-musl": "1.29.2",
|
"lightningcss-linux-arm64-musl": "1.29.3",
|
||||||
"lightningcss-linux-x64-gnu": "1.29.2",
|
"lightningcss-linux-x64-gnu": "1.29.3",
|
||||||
"lightningcss-linux-x64-musl": "1.29.2",
|
"lightningcss-linux-x64-musl": "1.29.3",
|
||||||
"lightningcss-win32-arm64-msvc": "1.29.2",
|
"lightningcss-win32-arm64-msvc": "1.29.3",
|
||||||
"lightningcss-win32-x64-msvc": "1.29.2"
|
"lightningcss-win32-x64-msvc": "1.29.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-darwin-arm64": {
|
"node_modules/lightningcss-darwin-arm64": {
|
||||||
"version": "1.29.2",
|
"version": "1.29.3",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.2.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.3.tgz",
|
||||||
"integrity": "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==",
|
"integrity": "sha512-fb7raKO3pXtlNbQbiMeEu8RbBVHnpyqAoxTyTRMEWFQWmscGC2wZxoHzZ+YKAepUuKT9uIW5vL2QbFivTgprZg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -4042,9 +4042,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-darwin-x64": {
|
"node_modules/lightningcss-darwin-x64": {
|
||||||
"version": "1.29.2",
|
"version": "1.29.3",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.2.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.3.tgz",
|
||||||
"integrity": "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==",
|
"integrity": "sha512-KF2XZ4ZdmDGGtEYmx5wpzn6u8vg7AdBHaEOvDKu8GOs7xDL/vcU2vMKtTeNe1d4dogkDdi3B9zC77jkatWBwEQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -4062,9 +4062,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-freebsd-x64": {
|
"node_modules/lightningcss-freebsd-x64": {
|
||||||
"version": "1.29.2",
|
"version": "1.29.3",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.2.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.3.tgz",
|
||||||
"integrity": "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==",
|
"integrity": "sha512-VUWeVf+V1UM54jv9M4wen9vMlIAyT69Krl9XjI8SsRxz4tdNV/7QEPlW6JASev/pYdiynUCW0pwaFquDRYdxMw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -4082,9 +4082,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
||||||
"version": "1.29.2",
|
"version": "1.29.3",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.2.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.3.tgz",
|
||||||
"integrity": "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==",
|
"integrity": "sha512-UhgZ/XVNfXQVEJrMIWeK1Laj8KbhjbIz7F4znUk7G4zeGw7TRoJxhb66uWrEsonn1+O45w//0i0Fu0wIovYdYg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@ -4102,9 +4102,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-linux-arm64-gnu": {
|
"node_modules/lightningcss-linux-arm64-gnu": {
|
||||||
"version": "1.29.2",
|
"version": "1.29.3",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.2.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.3.tgz",
|
||||||
"integrity": "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==",
|
"integrity": "sha512-Pqau7jtgJNmQ/esugfmAT1aCFy/Gxc92FOxI+3n+LbMHBheBnk41xHDhc0HeYlx9G0xP5tK4t0Koy3QGGNqypw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -4122,9 +4122,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-linux-arm64-musl": {
|
"node_modules/lightningcss-linux-arm64-musl": {
|
||||||
"version": "1.29.2",
|
"version": "1.29.3",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.2.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.3.tgz",
|
||||||
"integrity": "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==",
|
"integrity": "sha512-dxakOk66pf7KLS7VRYFO7B8WOJLecE5OPL2YOk52eriFd/yeyxt2Km5H0BjLfElokIaR+qWi33gB8MQLrdAY3A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -4142,9 +4142,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-linux-x64-gnu": {
|
"node_modules/lightningcss-linux-x64-gnu": {
|
||||||
"version": "1.29.2",
|
"version": "1.29.3",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.2.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.3.tgz",
|
||||||
"integrity": "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==",
|
"integrity": "sha512-ySZTNCpbfbK8rqpKJeJR2S0g/8UqqV3QnzcuWvpI60LWxnFN91nxpSSwCbzfOXkzKfar9j5eOuOplf+klKtINg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -4162,9 +4162,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-linux-x64-musl": {
|
"node_modules/lightningcss-linux-x64-musl": {
|
||||||
"version": "1.29.2",
|
"version": "1.29.3",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.2.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.3.tgz",
|
||||||
"integrity": "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==",
|
"integrity": "sha512-3pVZhIzW09nzi10usAXfIGTTSTYQ141dk88vGFNCgawIzayiIzZQxEcxVtIkdvlEq2YuFsL9Wcj/h61JHHzuFQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -4182,9 +4182,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-win32-arm64-msvc": {
|
"node_modules/lightningcss-win32-arm64-msvc": {
|
||||||
"version": "1.29.2",
|
"version": "1.29.3",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.2.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.3.tgz",
|
||||||
"integrity": "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==",
|
"integrity": "sha512-VRnkAvtIkeWuoBJeGOTrZxsNp4HogXtcaaLm8agmbYtLDOhQdpgxW6NjZZjDXbvGF+eOehGulXZ3C1TiwHY4QQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -4202,9 +4202,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-win32-x64-msvc": {
|
"node_modules/lightningcss-win32-x64-msvc": {
|
||||||
"version": "1.29.2",
|
"version": "1.29.3",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.2.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.3.tgz",
|
||||||
"integrity": "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==",
|
"integrity": "sha512-IszwRPu2cPnDQsZpd7/EAr0x2W7jkaWqQ1SwCVIZ/tSbZVXPLt6k8s6FkcyBjViCzvB5CW0We0QbbP7zp2aBjQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -5502,9 +5502,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pixi.js": {
|
"node_modules/pixi.js": {
|
||||||
"version": "8.8.1",
|
"version": "8.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.9.0.tgz",
|
||||||
"integrity": "sha512-Zmox3Vy52Kl6X/uxknKlxJWPVEFiP63nsX8soqB4butTkIOK3y7c9C204wcDfAgkwO1OlwYxscWtHv+ef4gqgA==",
|
"integrity": "sha512-uhXZKbus1C4nHu2ZHDCHE7m9BSsGOAuR+rj31VPyN6O8L8TEFs/q5+/43sMBC89EjPahIhvYOSNtY9nnrrY7BA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@pixi/colord": "^2.9.6",
|
"@pixi/colord": "^2.9.6",
|
||||||
@ -5762,9 +5762,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rehype-pretty-code": {
|
"node_modules/rehype-pretty-code": {
|
||||||
"version": "0.14.0",
|
"version": "0.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/rehype-pretty-code/-/rehype-pretty-code-0.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/rehype-pretty-code/-/rehype-pretty-code-0.14.1.tgz",
|
||||||
"integrity": "sha512-hBeKF/Wkkf3zyUS8lal9RCUuhypDWLQc+h9UrP9Pav25FUm/AQAVh4m5gdvJxh4Oz+U+xKvdsV01p1LdvsZTiQ==",
|
"integrity": "sha512-IpG4OL0iYlbx78muVldsK86hdfNoht0z63AP7sekQNW2QOTmjxB7RbTO+rhIYNGRljgHxgVZoPwUl6bIC9SbjA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/hast": "^3.0.4",
|
"@types/hast": "^3.0.4",
|
||||||
"hast-util-to-string": "^3.0.0",
|
"hast-util-to-string": "^3.0.0",
|
||||||
@ -5777,7 +5778,7 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"shiki": "^1.3.0"
|
"shiki": "^1.0.0 || ^2.0.0 || ^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rehype-raw": {
|
"node_modules/rehype-raw": {
|
||||||
|
|||||||
@ -56,13 +56,13 @@
|
|||||||
"hast-util-to-string": "^3.0.1",
|
"hast-util-to-string": "^3.0.1",
|
||||||
"is-absolute-url": "^4.0.1",
|
"is-absolute-url": "^4.0.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"lightningcss": "^1.29.2",
|
"lightningcss": "^1.29.3",
|
||||||
"mdast-util-find-and-replace": "^3.0.2",
|
"mdast-util-find-and-replace": "^3.0.2",
|
||||||
"mdast-util-to-hast": "^13.2.0",
|
"mdast-util-to-hast": "^13.2.0",
|
||||||
"mdast-util-to-string": "^4.0.0",
|
"mdast-util-to-string": "^4.0.0",
|
||||||
"micromorph": "^0.4.5",
|
"micromorph": "^0.4.5",
|
||||||
"minimatch": "^10.0.1",
|
"minimatch": "^10.0.1",
|
||||||
"pixi.js": "^8.8.1",
|
"pixi.js": "^8.9.0",
|
||||||
"preact": "^10.26.4",
|
"preact": "^10.26.4",
|
||||||
"preact-render-to-string": "^6.5.13",
|
"preact-render-to-string": "^6.5.13",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
@ -72,7 +72,7 @@
|
|||||||
"rehype-citation": "^2.2.2",
|
"rehype-citation": "^2.2.2",
|
||||||
"rehype-katex": "^7.0.1",
|
"rehype-katex": "^7.0.1",
|
||||||
"rehype-mathjax": "^7.1.0",
|
"rehype-mathjax": "^7.1.0",
|
||||||
"rehype-pretty-code": "^0.14.0",
|
"rehype-pretty-code": "^0.14.1",
|
||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
"rehype-slug": "^6.0.0",
|
"rehype-slug": "^6.0.0",
|
||||||
"remark": "^15.0.1",
|
"remark": "^15.0.1",
|
||||||
@ -103,7 +103,7 @@
|
|||||||
"@types/d3": "^7.4.3",
|
"@types/d3": "^7.4.3",
|
||||||
"@types/hast": "^3.0.4",
|
"@types/hast": "^3.0.4",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/node": "^22.13.10",
|
"@types/node": "^22.13.11",
|
||||||
"@types/pretty-time": "^1.1.5",
|
"@types/pretty-time": "^1.1.5",
|
||||||
"@types/source-map-support": "^0.5.10",
|
"@types/source-map-support": "^0.5.10",
|
||||||
"@types/ws": "^8.18.0",
|
"@types/ws": "^8.18.0",
|
||||||
|
|||||||
@ -18,7 +18,10 @@ export const sharedPageComponents: SharedLayout = {
|
|||||||
// components for pages that display a single page (e.g. a single note)
|
// components for pages that display a single page (e.g. a single note)
|
||||||
export const defaultContentPageLayout: PageLayout = {
|
export const defaultContentPageLayout: PageLayout = {
|
||||||
beforeBody: [
|
beforeBody: [
|
||||||
// Component.Breadcrumbs(),
|
Component.ConditionalRender({
|
||||||
|
component: Component.Breadcrumbs(),
|
||||||
|
condition: (page) => page.fileData.slug !== "index",
|
||||||
|
}),
|
||||||
Component.ArticleTitle(),
|
Component.ArticleTitle(),
|
||||||
Component.ContentMeta(),
|
Component.ContentMeta(),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
import breadcrumbsStyle from "./styles/breadcrumbs.scss"
|
import breadcrumbsStyle from "./styles/breadcrumbs.scss"
|
||||||
import { FullSlug, SimpleSlug, joinSegments, resolveRelative } from "../util/path"
|
import { FullSlug, SimpleSlug, resolveRelative, simplifySlug } from "../util/path"
|
||||||
import { QuartzPluginData } from "../plugins/vfile"
|
|
||||||
import { classNames } from "../util/lang"
|
import { classNames } from "../util/lang"
|
||||||
|
import { trieFromAllFiles } from "../util/ctx"
|
||||||
|
|
||||||
type CrumbData = {
|
type CrumbData = {
|
||||||
displayName: string
|
displayName: string
|
||||||
@ -22,10 +22,6 @@ interface BreadcrumbOptions {
|
|||||||
* Whether to look up frontmatter title for folders (could cause performance problems with big vaults)
|
* Whether to look up frontmatter title for folders (could cause performance problems with big vaults)
|
||||||
*/
|
*/
|
||||||
resolveFrontmatterTitle: boolean
|
resolveFrontmatterTitle: boolean
|
||||||
/**
|
|
||||||
* Whether to display breadcrumbs on root `index.md`
|
|
||||||
*/
|
|
||||||
hideOnRoot: boolean
|
|
||||||
/**
|
/**
|
||||||
* Whether to display the current page in the breadcrumbs.
|
* Whether to display the current page in the breadcrumbs.
|
||||||
*/
|
*/
|
||||||
@ -36,7 +32,6 @@ const defaultOptions: BreadcrumbOptions = {
|
|||||||
spacerSymbol: "❯",
|
spacerSymbol: "❯",
|
||||||
rootName: "Home",
|
rootName: "Home",
|
||||||
resolveFrontmatterTitle: true,
|
resolveFrontmatterTitle: true,
|
||||||
hideOnRoot: true,
|
|
||||||
showCurrentPage: true,
|
showCurrentPage: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,78 +43,37 @@ function formatCrumb(displayName: string, baseSlug: FullSlug, currentSlug: Simpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default ((opts?: Partial<BreadcrumbOptions>) => {
|
export default ((opts?: Partial<BreadcrumbOptions>) => {
|
||||||
// Merge options with defaults
|
|
||||||
const options: BreadcrumbOptions = { ...defaultOptions, ...opts }
|
const options: BreadcrumbOptions = { ...defaultOptions, ...opts }
|
||||||
|
|
||||||
// computed index of folder name to its associated file data
|
|
||||||
let folderIndex: Map<string, QuartzPluginData> | undefined
|
|
||||||
|
|
||||||
const Breadcrumbs: QuartzComponent = ({
|
const Breadcrumbs: QuartzComponent = ({
|
||||||
fileData,
|
fileData,
|
||||||
allFiles,
|
allFiles,
|
||||||
displayClass,
|
displayClass,
|
||||||
|
ctx,
|
||||||
}: QuartzComponentProps) => {
|
}: QuartzComponentProps) => {
|
||||||
// Hide crumbs on root if enabled
|
const trie = (ctx.trie ??= trieFromAllFiles(allFiles))
|
||||||
if (options.hideOnRoot && fileData.slug === "index") {
|
const slugParts = fileData.slug!.split("/")
|
||||||
return <></>
|
const pathNodes = trie.ancestryChain(slugParts)
|
||||||
|
|
||||||
|
if (!pathNodes) {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format entry for root element
|
const crumbs: CrumbData[] = pathNodes.map((node, idx) => {
|
||||||
const firstEntry = formatCrumb(options.rootName, fileData.slug!, "/" as SimpleSlug)
|
const crumb = formatCrumb(node.displayName, fileData.slug!, simplifySlug(node.slug))
|
||||||
const crumbs: CrumbData[] = [firstEntry]
|
if (idx === 0) {
|
||||||
|
crumb.displayName = options.rootName
|
||||||
if (!folderIndex && options.resolveFrontmatterTitle) {
|
|
||||||
folderIndex = new Map()
|
|
||||||
// construct the index for the first time
|
|
||||||
for (const file of allFiles) {
|
|
||||||
const folderParts = file.slug?.split("/")
|
|
||||||
if (folderParts?.at(-1) === "index") {
|
|
||||||
folderIndex.set(folderParts.slice(0, -1).join("/"), file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split slug into hierarchy/parts
|
// For last node (current page), set empty path
|
||||||
const slugParts = fileData.slug?.split("/")
|
if (idx === pathNodes.length - 1) {
|
||||||
if (slugParts) {
|
crumb.path = ""
|
||||||
// is tag breadcrumb?
|
|
||||||
const isTagPath = slugParts[0] === "tags"
|
|
||||||
|
|
||||||
// full path until current part
|
|
||||||
let currentPath = ""
|
|
||||||
|
|
||||||
for (let i = 0; i < slugParts.length - 1; i++) {
|
|
||||||
let curPathSegment = slugParts[i]
|
|
||||||
|
|
||||||
// Try to resolve frontmatter folder title
|
|
||||||
const currentFile = folderIndex?.get(slugParts.slice(0, i + 1).join("/"))
|
|
||||||
if (currentFile) {
|
|
||||||
const title = currentFile.frontmatter!.title
|
|
||||||
if (title !== "index") {
|
|
||||||
curPathSegment = title
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add current slug to full path
|
return crumb
|
||||||
currentPath = joinSegments(currentPath, slugParts[i])
|
|
||||||
const includeTrailingSlash = !isTagPath || i < slugParts.length - 1
|
|
||||||
|
|
||||||
// Format and add current crumb
|
|
||||||
const crumb = formatCrumb(
|
|
||||||
curPathSegment,
|
|
||||||
fileData.slug!,
|
|
||||||
(currentPath + (includeTrailingSlash ? "/" : "")) as SimpleSlug,
|
|
||||||
)
|
|
||||||
crumbs.push(crumb)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add current file to crumb (can directly use frontmatter title)
|
|
||||||
if (options.showCurrentPage && slugParts.at(-1) !== "index") {
|
|
||||||
crumbs.push({
|
|
||||||
displayName: fileData.frontmatter!.title,
|
|
||||||
path: "",
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
if (!options.showCurrentPage) {
|
||||||
|
crumbs.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
22
quartz/components/ConditionalRender.tsx
Normal file
22
quartz/components/ConditionalRender.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
|
|
||||||
|
type ConditionalRenderConfig = {
|
||||||
|
component: QuartzComponent
|
||||||
|
condition: (props: QuartzComponentProps) => boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ((config: ConditionalRenderConfig) => {
|
||||||
|
const ConditionalRender: QuartzComponent = (props: QuartzComponentProps) => {
|
||||||
|
if (config.condition(props)) {
|
||||||
|
return <config.component {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
ConditionalRender.afterDOMLoaded = config.component.afterDOMLoaded
|
||||||
|
ConditionalRender.beforeDOMLoaded = config.component.beforeDOMLoaded
|
||||||
|
ConditionalRender.css = config.component.css
|
||||||
|
|
||||||
|
return ConditionalRender
|
||||||
|
}) satisfies QuartzComponentConstructor<ConditionalRenderConfig>
|
||||||
@ -1,15 +1,14 @@
|
|||||||
import { pathToRoot, slugTag } from "../util/path"
|
import { FullSlug, resolveRelative } from "../util/path"
|
||||||
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
import { classNames } from "../util/lang"
|
import { classNames } from "../util/lang"
|
||||||
|
|
||||||
const TagList: QuartzComponent = ({ fileData, displayClass }: QuartzComponentProps) => {
|
const TagList: QuartzComponent = ({ fileData, displayClass }: QuartzComponentProps) => {
|
||||||
const tags = fileData.frontmatter?.tags
|
const tags = fileData.frontmatter?.tags
|
||||||
const baseDir = pathToRoot(fileData.slug!)
|
|
||||||
if (tags && tags.length > 0) {
|
if (tags && tags.length > 0) {
|
||||||
return (
|
return (
|
||||||
<ul class={classNames(displayClass, "tags")}>
|
<ul class={classNames(displayClass, "tags")}>
|
||||||
{tags.map((tag) => {
|
{tags.map((tag) => {
|
||||||
const linkDest = baseDir + `/tags/${slugTag(tag)}`
|
const linkDest = resolveRelative(fileData.slug!, `tags/${tag}` as FullSlug)
|
||||||
return (
|
return (
|
||||||
<li>
|
<li>
|
||||||
<a href={linkDest} class="internal tag-link">
|
<a href={linkDest} class="internal tag-link">
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import RecentNotes from "./RecentNotes"
|
|||||||
import Breadcrumbs from "./Breadcrumbs"
|
import Breadcrumbs from "./Breadcrumbs"
|
||||||
import Comments from "./Comments"
|
import Comments from "./Comments"
|
||||||
import Flex from "./Flex"
|
import Flex from "./Flex"
|
||||||
|
import ConditionalRender from "./ConditionalRender"
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ArticleTitle,
|
ArticleTitle,
|
||||||
@ -46,4 +47,5 @@ export {
|
|||||||
Breadcrumbs,
|
Breadcrumbs,
|
||||||
Comments,
|
Comments,
|
||||||
Flex,
|
Flex,
|
||||||
|
ConditionalRender,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,8 @@ import { i18n } from "../../i18n"
|
|||||||
import { QuartzPluginData } from "../../plugins/vfile"
|
import { QuartzPluginData } from "../../plugins/vfile"
|
||||||
import { ComponentChildren } from "preact"
|
import { ComponentChildren } from "preact"
|
||||||
import { concatenateResources } from "../../util/resources"
|
import { concatenateResources } from "../../util/resources"
|
||||||
import { FileTrieNode } from "../../util/fileTrie"
|
import { trieFromAllFiles } from "../../util/ctx"
|
||||||
|
|
||||||
interface FolderContentOptions {
|
interface FolderContentOptions {
|
||||||
/**
|
/**
|
||||||
* Whether to display number of folders
|
* Whether to display number of folders
|
||||||
@ -25,31 +26,11 @@ const defaultOptions: FolderContentOptions = {
|
|||||||
|
|
||||||
export default ((opts?: Partial<FolderContentOptions>) => {
|
export default ((opts?: Partial<FolderContentOptions>) => {
|
||||||
const options: FolderContentOptions = { ...defaultOptions, ...opts }
|
const options: FolderContentOptions = { ...defaultOptions, ...opts }
|
||||||
let trie: FileTrieNode<
|
|
||||||
QuartzPluginData & {
|
|
||||||
slug: string
|
|
||||||
title: string
|
|
||||||
filePath: string
|
|
||||||
}
|
|
||||||
>
|
|
||||||
|
|
||||||
const FolderContent: QuartzComponent = (props: QuartzComponentProps) => {
|
const FolderContent: QuartzComponent = (props: QuartzComponentProps) => {
|
||||||
const { tree, fileData, allFiles, cfg } = props
|
const { tree, fileData, allFiles, cfg } = props
|
||||||
|
|
||||||
if (!trie) {
|
const trie = (props.ctx.trie ??= trieFromAllFiles(allFiles))
|
||||||
trie = new FileTrieNode([])
|
|
||||||
allFiles.forEach((file) => {
|
|
||||||
if (file.frontmatter) {
|
|
||||||
trie.add({
|
|
||||||
...file,
|
|
||||||
slug: file.slug!,
|
|
||||||
title: file.frontmatter.title,
|
|
||||||
filePath: file.filePath!,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const folder = trie.findNode(fileData.slug!.split("/"))
|
const folder = trie.findNode(fileData.slug!.split("/"))
|
||||||
if (!folder) {
|
if (!folder) {
|
||||||
return null
|
return null
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
||||||
import style from "../styles/listPage.scss"
|
import style from "../styles/listPage.scss"
|
||||||
import { PageList, SortFn } from "../PageList"
|
import { PageList, SortFn } from "../PageList"
|
||||||
import { FullSlug, getAllSegmentPrefixes, simplifySlug } from "../../util/path"
|
import { FullSlug, getAllSegmentPrefixes, resolveRelative, simplifySlug } from "../../util/path"
|
||||||
import { QuartzPluginData } from "../../plugins/vfile"
|
import { QuartzPluginData } from "../../plugins/vfile"
|
||||||
import { Root } from "hast"
|
import { Root } from "hast"
|
||||||
import { htmlToJsx } from "../../util/jsx"
|
import { htmlToJsx } from "../../util/jsx"
|
||||||
@ -74,10 +74,13 @@ export default ((opts?: Partial<TagContentOptions>) => {
|
|||||||
? contentPage?.description
|
? contentPage?.description
|
||||||
: htmlToJsx(contentPage.filePath!, root)
|
: htmlToJsx(contentPage.filePath!, root)
|
||||||
|
|
||||||
|
const tagListingPage = `/tags/${tag}` as FullSlug
|
||||||
|
const href = resolveRelative(fileData.slug!, tagListingPage)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2>
|
<h2>
|
||||||
<a class="internal tag-link" href={`../tags/${tag}`}>
|
<a class="internal tag-link" href={href}>
|
||||||
{tag}
|
{tag}
|
||||||
</a>
|
</a>
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@ -36,6 +36,8 @@
|
|||||||
box-shadow: 6px 6px 36px 0 rgba(0, 0, 0, 0.25);
|
box-shadow: 6px 6px 36px 0 rgba(0, 0, 0, 0.25);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
|
user-select: none;
|
||||||
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > .popover-inner[data-content-type] {
|
& > .popover-inner[data-content-type] {
|
||||||
|
|||||||
@ -1,14 +1,21 @@
|
|||||||
import { resolveRelative, simplifySlug } from "../../util/path"
|
import { FullSlug, isRelativeURL, resolveRelative, simplifySlug } from "../../util/path"
|
||||||
import { QuartzEmitterPlugin } from "../types"
|
import { QuartzEmitterPlugin } from "../types"
|
||||||
import { write } from "./helpers"
|
import { write } from "./helpers"
|
||||||
import { BuildCtx } from "../../util/ctx"
|
import { BuildCtx } from "../../util/ctx"
|
||||||
import { VFile } from "vfile"
|
import { VFile } from "vfile"
|
||||||
|
import path from "path"
|
||||||
|
|
||||||
async function* processFile(ctx: BuildCtx, file: VFile) {
|
async function* processFile(ctx: BuildCtx, file: VFile) {
|
||||||
const ogSlug = simplifySlug(file.data.slug!)
|
const ogSlug = simplifySlug(file.data.slug!)
|
||||||
|
|
||||||
for (const slug of file.data.aliases ?? []) {
|
for (const aliasTarget of file.data.aliases ?? []) {
|
||||||
const redirUrl = resolveRelative(slug, file.data.slug!)
|
const aliasTargetSlug = (
|
||||||
|
isRelativeURL(aliasTarget)
|
||||||
|
? path.normalize(path.join(ogSlug, "..", aliasTarget))
|
||||||
|
: aliasTarget
|
||||||
|
) as FullSlug
|
||||||
|
|
||||||
|
const redirUrl = resolveRelative(aliasTargetSlug, ogSlug)
|
||||||
yield write({
|
yield write({
|
||||||
ctx,
|
ctx,
|
||||||
content: `
|
content: `
|
||||||
@ -23,7 +30,7 @@ async function* processFile(ctx: BuildCtx, file: VFile) {
|
|||||||
</head>
|
</head>
|
||||||
</html>
|
</html>
|
||||||
`,
|
`,
|
||||||
slug,
|
slug: aliasTargetSlug,
|
||||||
ext: ".html",
|
ext: ".html",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -88,89 +88,108 @@ function addGlobalPageResources(ctx: BuildCtx, componentResources: ComponentReso
|
|||||||
if (cfg.analytics?.provider === "google") {
|
if (cfg.analytics?.provider === "google") {
|
||||||
const tagId = cfg.analytics.tagId
|
const tagId = cfg.analytics.tagId
|
||||||
componentResources.afterDOMLoaded.push(`
|
componentResources.afterDOMLoaded.push(`
|
||||||
const gtagScript = document.createElement("script")
|
const gtagScript = document.createElement('script');
|
||||||
gtagScript.src = "https://www.googletagmanager.com/gtag/js?id=${tagId}"
|
gtagScript.src = 'https://www.googletagmanager.com/gtag/js?id=${tagId}';
|
||||||
gtagScript.defer = true
|
gtagScript.defer = true;
|
||||||
document.head.appendChild(gtagScript)
|
gtagScript.onload = () => {
|
||||||
|
|
||||||
window.dataLayer = window.dataLayer || [];
|
window.dataLayer = window.dataLayer || [];
|
||||||
function gtag() { dataLayer.push(arguments); }
|
function gtag() {
|
||||||
gtag("js", new Date());
|
dataLayer.push(arguments);
|
||||||
gtag("config", "${tagId}", { send_page_view: false });
|
}
|
||||||
|
gtag('js', new Date());
|
||||||
document.addEventListener("nav", () => {
|
gtag('config', '${tagId}', { send_page_view: false });
|
||||||
gtag("event", "page_view", {
|
gtag('event', 'page_view', { page_title: document.title, page_location: location.href });
|
||||||
page_title: document.title,
|
document.addEventListener('nav', () => {
|
||||||
page_location: location.href,
|
gtag('event', 'page_view', { page_title: document.title, page_location: location.href });
|
||||||
});
|
});
|
||||||
});`)
|
};
|
||||||
|
|
||||||
|
document.head.appendChild(gtagScript);
|
||||||
|
`)
|
||||||
} else if (cfg.analytics?.provider === "plausible") {
|
} else if (cfg.analytics?.provider === "plausible") {
|
||||||
const plausibleHost = cfg.analytics.host ?? "https://plausible.io"
|
const plausibleHost = cfg.analytics.host ?? "https://plausible.io"
|
||||||
componentResources.afterDOMLoaded.push(`
|
componentResources.afterDOMLoaded.push(`
|
||||||
const plausibleScript = document.createElement("script")
|
const plausibleScript = document.createElement('script');
|
||||||
plausibleScript.src = "${plausibleHost}/js/script.manual.js"
|
plausibleScript.src = '${plausibleHost}/js/script.manual.js';
|
||||||
plausibleScript.setAttribute("data-domain", location.hostname)
|
plausibleScript.setAttribute('data-domain', location.hostname);
|
||||||
plausibleScript.defer = true
|
plausibleScript.defer = true;
|
||||||
document.head.appendChild(plausibleScript)
|
plausibleScript.onload = () => {
|
||||||
|
window.plausible = window.plausible || function () { (window.plausible.q = window.plausible.q || []).push(arguments); };
|
||||||
|
plausible('pageview');
|
||||||
|
document.addEventListener('nav', () => {
|
||||||
|
plausible('pageview');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }
|
document.head.appendChild(plausibleScript);
|
||||||
|
|
||||||
document.addEventListener("nav", () => {
|
|
||||||
plausible("pageview")
|
|
||||||
})
|
|
||||||
`)
|
`)
|
||||||
} else if (cfg.analytics?.provider === "umami") {
|
} else if (cfg.analytics?.provider === "umami") {
|
||||||
componentResources.afterDOMLoaded.push(`
|
componentResources.afterDOMLoaded.push(`
|
||||||
const umamiScript = document.createElement("script")
|
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.setAttribute("data-website-id", "${cfg.analytics.websiteId}");
|
||||||
umamiScript.setAttribute("data-auto-track", "false")
|
umamiScript.setAttribute("data-auto-track", "false");
|
||||||
umamiScript.defer = true
|
umamiScript.defer = true;
|
||||||
document.head.appendChild(umamiScript)
|
umamiScript.onload = () => {
|
||||||
|
umami.track();
|
||||||
document.addEventListener("nav", () => {
|
document.addEventListener("nav", () => {
|
||||||
umami.track();
|
umami.track();
|
||||||
})
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
document.head.appendChild(umamiScript);
|
||||||
`)
|
`)
|
||||||
} else if (cfg.analytics?.provider === "goatcounter") {
|
} else if (cfg.analytics?.provider === "goatcounter") {
|
||||||
componentResources.afterDOMLoaded.push(`
|
componentResources.afterDOMLoaded.push(`
|
||||||
const goatcounterScript = document.createElement("script")
|
const goatcounterScript = document.createElement('script');
|
||||||
goatcounterScript.src = "${cfg.analytics.scriptSrc ?? "https://gc.zgo.at/count.js"}"
|
goatcounterScript.src = "${cfg.analytics.scriptSrc ?? "https://gc.zgo.at/count.js"}";
|
||||||
goatcounterScript.defer = true
|
goatcounterScript.defer = true;
|
||||||
goatcounterScript.setAttribute("data-goatcounter",
|
goatcounterScript.setAttribute(
|
||||||
"https://${cfg.analytics.websiteId}.${cfg.analytics.host ?? "goatcounter.com"}/count")
|
'data-goatcounter',
|
||||||
document.head.appendChild(goatcounterScript)
|
"https://${cfg.analytics.websiteId}.${cfg.analytics.host ?? "goatcounter.com"}/count"
|
||||||
|
);
|
||||||
|
goatcounterScript.onload = () => {
|
||||||
|
window.goatcounter = { no_onload: true };
|
||||||
|
goatcounter.count({ path: location.pathname });
|
||||||
|
document.addEventListener('nav', () => {
|
||||||
|
goatcounter.count({ path: location.pathname });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
window.goatcounter = { no_onload: true }
|
document.head.appendChild(goatcounterScript);
|
||||||
document.addEventListener("nav", () => {
|
|
||||||
goatcounter.count({ path: location.pathname })
|
|
||||||
})
|
|
||||||
`)
|
`)
|
||||||
} else if (cfg.analytics?.provider === "posthog") {
|
} else if (cfg.analytics?.provider === "posthog") {
|
||||||
componentResources.afterDOMLoaded.push(`
|
componentResources.afterDOMLoaded.push(`
|
||||||
const posthogScript = document.createElement("script")
|
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<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
|
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<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
|
||||||
posthog.init('${cfg.analytics.apiKey}', {
|
posthog.init('${cfg.analytics.apiKey}', {
|
||||||
api_host: '${cfg.analytics.host ?? "https://app.posthog.com"}',
|
api_host: '${cfg.analytics.host ?? "https://app.posthog.com"}',
|
||||||
capture_pageview: false,
|
capture_pageview: false,
|
||||||
})\`
|
})\`
|
||||||
document.head.appendChild(posthogScript)
|
posthogScript.onload = () => {
|
||||||
|
posthog.capture('$pageview', { path: location.pathname });
|
||||||
|
|
||||||
document.addEventListener("nav", () => {
|
document.addEventListener('nav', () => {
|
||||||
posthog.capture('$pageview', { path: location.pathname })
|
posthog.capture('$pageview', { path: location.pathname });
|
||||||
})
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
document.head.appendChild(posthogScript);
|
||||||
`)
|
`)
|
||||||
} else if (cfg.analytics?.provider === "tinylytics") {
|
} else if (cfg.analytics?.provider === "tinylytics") {
|
||||||
const siteId = cfg.analytics.siteId
|
const siteId = cfg.analytics.siteId
|
||||||
componentResources.afterDOMLoaded.push(`
|
componentResources.afterDOMLoaded.push(`
|
||||||
const tinylyticsScript = document.createElement("script")
|
const tinylyticsScript = document.createElement('script');
|
||||||
tinylyticsScript.src = "https://tinylytics.app/embed/${siteId}.js?spa"
|
tinylyticsScript.src = 'https://tinylytics.app/embed/${siteId}.js?spa';
|
||||||
tinylyticsScript.defer = true
|
tinylyticsScript.defer = true;
|
||||||
document.head.appendChild(tinylyticsScript)
|
tinylyticsScript.onload = () => {
|
||||||
|
window.tinylytics.triggerUpdate();
|
||||||
|
document.addEventListener('nav', () => {
|
||||||
|
window.tinylytics.triggerUpdate();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
document.addEventListener("nav", () => {
|
document.head.appendChild(tinylyticsScript);
|
||||||
window.tinylytics.triggerUpdate()
|
|
||||||
})
|
|
||||||
`)
|
`)
|
||||||
} else if (cfg.analytics?.provider === "cabin") {
|
} else if (cfg.analytics?.provider === "cabin") {
|
||||||
componentResources.afterDOMLoaded.push(`
|
componentResources.afterDOMLoaded.push(`
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { QuartzEmitterPlugin } from "../types"
|
import { QuartzEmitterPlugin } from "../types"
|
||||||
import { i18n } from "../../i18n"
|
import { i18n } from "../../i18n"
|
||||||
import { unescapeHTML } from "../../util/escape"
|
import { unescapeHTML } from "../../util/escape"
|
||||||
import { FullSlug, getFileExtension, joinSegments, QUARTZ } from "../../util/path"
|
import { FullSlug, getFileExtension, isAbsoluteURL, joinSegments, QUARTZ } from "../../util/path"
|
||||||
import { ImageOptions, SocialImageOptions, defaultImage, getSatoriFonts } from "../../util/og"
|
import { ImageOptions, SocialImageOptions, defaultImage, getSatoriFonts } from "../../util/og"
|
||||||
import sharp from "sharp"
|
import sharp from "sharp"
|
||||||
import satori, { SatoriOptions } from "satori"
|
import satori, { SatoriOptions } from "satori"
|
||||||
@ -144,13 +144,19 @@ export const CustomOgImages: QuartzEmitterPlugin<Partial<SocialImageOptions>> =
|
|||||||
additionalHead: [
|
additionalHead: [
|
||||||
(pageData) => {
|
(pageData) => {
|
||||||
const isRealFile = pageData.filePath !== undefined
|
const isRealFile = pageData.filePath !== undefined
|
||||||
const userDefinedOgImagePath = pageData.frontmatter?.socialImage
|
let userDefinedOgImagePath = pageData.frontmatter?.socialImage
|
||||||
|
|
||||||
|
if (userDefinedOgImagePath) {
|
||||||
|
userDefinedOgImagePath = isAbsoluteURL(userDefinedOgImagePath)
|
||||||
|
? userDefinedOgImagePath
|
||||||
|
: `https://${baseUrl}/static/${userDefinedOgImagePath}`
|
||||||
|
}
|
||||||
|
|
||||||
const generatedOgImagePath = isRealFile
|
const generatedOgImagePath = isRealFile
|
||||||
? `https://${baseUrl}/${pageData.slug!}-og-image.webp`
|
? `https://${baseUrl}/${pageData.slug!}-og-image.webp`
|
||||||
: undefined
|
: undefined
|
||||||
const defaultOgImagePath = `https://${baseUrl}/static/og-image.png`
|
const defaultOgImagePath = `https://${baseUrl}/static/og-image.png`
|
||||||
const ogImagePath = userDefinedOgImagePath ?? generatedOgImagePath ?? defaultOgImagePath
|
const ogImagePath = userDefinedOgImagePath ?? generatedOgImagePath ?? defaultOgImagePath
|
||||||
|
|
||||||
const ogImageMimeType = `image/${getFileExtension(ogImagePath) ?? "png"}`
|
const ogImageMimeType = `image/${getFileExtension(ogImagePath) ?? "png"}`
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -114,6 +114,10 @@ export const FrontMatter: QuartzTransformerPlugin<Partial<Options>> = (userOpts)
|
|||||||
|
|
||||||
if (socialImage) data.socialImage = socialImage
|
if (socialImage) data.socialImage = socialImage
|
||||||
|
|
||||||
|
// Remove duplicate slugs
|
||||||
|
const uniqueSlugs = [...new Set(allSlugs)]
|
||||||
|
allSlugs.splice(0, allSlugs.length, ...uniqueSlugs)
|
||||||
|
|
||||||
// fill in frontmatter
|
// fill in frontmatter
|
||||||
file.data.frontmatter = data as QuartzPluginData["frontmatter"]
|
file.data.frontmatter = data as QuartzPluginData["frontmatter"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
import { QuartzConfig } from "../cfg"
|
import { QuartzConfig } from "../cfg"
|
||||||
|
import { QuartzPluginData } from "../plugins/vfile"
|
||||||
|
import { FileTrieNode } from "./fileTrie"
|
||||||
import { FilePath, FullSlug } from "./path"
|
import { FilePath, FullSlug } from "./path"
|
||||||
|
|
||||||
export interface Argv {
|
export interface Argv {
|
||||||
@ -13,13 +15,36 @@ export interface Argv {
|
|||||||
concurrency?: number
|
concurrency?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type BuildTimeTrieData = QuartzPluginData & {
|
||||||
|
slug: string
|
||||||
|
title: string
|
||||||
|
filePath: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface BuildCtx {
|
export interface BuildCtx {
|
||||||
buildId: string
|
buildId: string
|
||||||
argv: Argv
|
argv: Argv
|
||||||
cfg: QuartzConfig
|
cfg: QuartzConfig
|
||||||
allSlugs: FullSlug[]
|
allSlugs: FullSlug[]
|
||||||
allFiles: FilePath[]
|
allFiles: FilePath[]
|
||||||
|
trie?: FileTrieNode<BuildTimeTrieData>
|
||||||
incremental: boolean
|
incremental: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WorkerSerializableBuildCtx = Omit<BuildCtx, "cfg">
|
export function trieFromAllFiles(allFiles: QuartzPluginData[]): FileTrieNode<BuildTimeTrieData> {
|
||||||
|
const trie = new FileTrieNode<BuildTimeTrieData>([])
|
||||||
|
allFiles.forEach((file) => {
|
||||||
|
if (file.frontmatter) {
|
||||||
|
trie.add({
|
||||||
|
...file,
|
||||||
|
slug: file.slug!,
|
||||||
|
title: file.frontmatter.title,
|
||||||
|
filePath: file.filePath!,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return trie
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WorkerSerializableBuildCtx = Omit<BuildCtx, "cfg" | "trie">
|
||||||
|
|||||||
@ -330,4 +330,86 @@ describe("FileTrie", () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("pathToNode", () => {
|
||||||
|
test("should return root node for empty path", () => {
|
||||||
|
const data = { title: "Root", slug: "index", filePath: "index.md" }
|
||||||
|
trie.add(data)
|
||||||
|
const path = trie.ancestryChain([])
|
||||||
|
assert.deepStrictEqual(path, [trie])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should return root node for index path", () => {
|
||||||
|
const data = { title: "Root", slug: "index", filePath: "index.md" }
|
||||||
|
trie.add(data)
|
||||||
|
const path = trie.ancestryChain(["index"])
|
||||||
|
assert.deepStrictEqual(path, [trie])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should return path to first level node", () => {
|
||||||
|
const data = { title: "Test", slug: "test", filePath: "test.md" }
|
||||||
|
trie.add(data)
|
||||||
|
const path = trie.ancestryChain(["test"])
|
||||||
|
assert.deepStrictEqual(path, [trie, trie.children[0]])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should return path to nested node", () => {
|
||||||
|
const data = {
|
||||||
|
title: "Nested",
|
||||||
|
slug: "folder/subfolder/test",
|
||||||
|
filePath: "folder/subfolder/test.md",
|
||||||
|
}
|
||||||
|
trie.add(data)
|
||||||
|
const path = trie.ancestryChain(["folder", "subfolder", "test"])
|
||||||
|
assert.deepStrictEqual(path, [
|
||||||
|
trie,
|
||||||
|
trie.children[0],
|
||||||
|
trie.children[0].children[0],
|
||||||
|
trie.children[0].children[0].children[0],
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should return undefined for non-existent path", () => {
|
||||||
|
const data = { title: "Test", slug: "test", filePath: "test.md" }
|
||||||
|
trie.add(data)
|
||||||
|
const path = trie.ancestryChain(["nonexistent"])
|
||||||
|
assert.strictEqual(path, undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should return file data for intermediate folders", () => {
|
||||||
|
const data1 = {
|
||||||
|
title: "Root",
|
||||||
|
slug: "index",
|
||||||
|
filePath: "index.md",
|
||||||
|
}
|
||||||
|
const data2 = {
|
||||||
|
title: "Test",
|
||||||
|
slug: "folder/subfolder/test",
|
||||||
|
filePath: "folder/subfolder/test.md",
|
||||||
|
}
|
||||||
|
const data3 = {
|
||||||
|
title: "Folder Index",
|
||||||
|
slug: "folder/index",
|
||||||
|
filePath: "folder/index.md",
|
||||||
|
}
|
||||||
|
|
||||||
|
trie.add(data1)
|
||||||
|
trie.add(data2)
|
||||||
|
trie.add(data3)
|
||||||
|
const path = trie.ancestryChain(["folder", "subfolder"])
|
||||||
|
assert.deepStrictEqual(path, [trie, trie.children[0], trie.children[0].children[0]])
|
||||||
|
assert.strictEqual(path[1].data, data3)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should return path for partial path", () => {
|
||||||
|
const data = {
|
||||||
|
title: "Nested",
|
||||||
|
slug: "folder/subfolder/test",
|
||||||
|
filePath: "folder/subfolder/test.md",
|
||||||
|
}
|
||||||
|
trie.add(data)
|
||||||
|
const path = trie.ancestryChain(["folder"])
|
||||||
|
assert.deepStrictEqual(path, [trie, trie.children[0]])
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -97,6 +97,24 @@ export class FileTrieNode<T extends FileTrieData = ContentDetails> {
|
|||||||
return this.children.find((c) => c.slugSegment === path[0])?.findNode(path.slice(1))
|
return this.children.find((c) => c.slugSegment === path[0])?.findNode(path.slice(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ancestryChain(path: string[]): Array<FileTrieNode<T>> | undefined {
|
||||||
|
if (path.length === 0 || (path.length === 1 && path[0] === "index")) {
|
||||||
|
return [this]
|
||||||
|
}
|
||||||
|
|
||||||
|
const child = this.children.find((c) => c.slugSegment === path[0])
|
||||||
|
if (!child) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const childPath = child.ancestryChain(path.slice(1))
|
||||||
|
if (!childPath) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return [this, ...childPath]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter trie nodes. Behaves similar to `Array.prototype.filter()`, but modifies tree in place
|
* Filter trie nodes. Behaves similar to `Array.prototype.filter()`, but modifies tree in place
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import test, { describe } from "node:test"
|
import test, { describe } from "node:test"
|
||||||
import * as path from "./path"
|
import * as path from "./path"
|
||||||
import assert from "node:assert"
|
import assert from "node:assert"
|
||||||
import { FullSlug, TransformOptions } from "./path"
|
import { FullSlug, TransformOptions, SimpleSlug } from "./path"
|
||||||
|
|
||||||
describe("typeguards", () => {
|
describe("typeguards", () => {
|
||||||
test("isSimpleSlug", () => {
|
test("isSimpleSlug", () => {
|
||||||
@ -38,6 +38,17 @@ describe("typeguards", () => {
|
|||||||
assert(!path.isRelativeURL("./abc/def.md"))
|
assert(!path.isRelativeURL("./abc/def.md"))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("isAbsoluteURL", () => {
|
||||||
|
assert(path.isAbsoluteURL("https://example.com"))
|
||||||
|
assert(path.isAbsoluteURL("http://example.com"))
|
||||||
|
assert(path.isAbsoluteURL("ftp://example.com/a/b/c"))
|
||||||
|
assert(path.isAbsoluteURL("http://host/%25"))
|
||||||
|
assert(path.isAbsoluteURL("file://host/twoslashes?more//slashes"))
|
||||||
|
|
||||||
|
assert(!path.isAbsoluteURL("example.com/abc/def"))
|
||||||
|
assert(!path.isAbsoluteURL("abc"))
|
||||||
|
})
|
||||||
|
|
||||||
test("isFullSlug", () => {
|
test("isFullSlug", () => {
|
||||||
assert(path.isFullSlug("index"))
|
assert(path.isFullSlug("index"))
|
||||||
assert(path.isFullSlug("abc/def"))
|
assert(path.isFullSlug("abc/def"))
|
||||||
@ -303,3 +314,50 @@ describe("link strategies", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("resolveRelative", () => {
|
||||||
|
test("from index", () => {
|
||||||
|
assert.strictEqual(path.resolveRelative("index" as FullSlug, "index" as FullSlug), "./")
|
||||||
|
assert.strictEqual(path.resolveRelative("index" as FullSlug, "abc" as FullSlug), "./abc")
|
||||||
|
assert.strictEqual(
|
||||||
|
path.resolveRelative("index" as FullSlug, "abc/def" as FullSlug),
|
||||||
|
"./abc/def",
|
||||||
|
)
|
||||||
|
assert.strictEqual(
|
||||||
|
path.resolveRelative("index" as FullSlug, "abc/def/ghi" as FullSlug),
|
||||||
|
"./abc/def/ghi",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("from nested page", () => {
|
||||||
|
assert.strictEqual(path.resolveRelative("abc/def" as FullSlug, "index" as FullSlug), "../")
|
||||||
|
assert.strictEqual(path.resolveRelative("abc/def" as FullSlug, "abc" as FullSlug), "../abc")
|
||||||
|
assert.strictEqual(
|
||||||
|
path.resolveRelative("abc/def" as FullSlug, "abc/def" as FullSlug),
|
||||||
|
"../abc/def",
|
||||||
|
)
|
||||||
|
assert.strictEqual(
|
||||||
|
path.resolveRelative("abc/def" as FullSlug, "ghi/jkl" as FullSlug),
|
||||||
|
"../ghi/jkl",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("with index paths", () => {
|
||||||
|
assert.strictEqual(path.resolveRelative("abc/index" as FullSlug, "index" as FullSlug), "../")
|
||||||
|
assert.strictEqual(
|
||||||
|
path.resolveRelative("abc/def/index" as FullSlug, "index" as FullSlug),
|
||||||
|
"../../",
|
||||||
|
)
|
||||||
|
assert.strictEqual(path.resolveRelative("index" as FullSlug, "abc/index" as FullSlug), "./abc/")
|
||||||
|
assert.strictEqual(
|
||||||
|
path.resolveRelative("abc/def" as FullSlug, "abc/index" as FullSlug),
|
||||||
|
"../abc/",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("with simple slugs", () => {
|
||||||
|
assert.strictEqual(path.resolveRelative("abc/def" as FullSlug, "" as SimpleSlug), "../")
|
||||||
|
assert.strictEqual(path.resolveRelative("abc/def" as FullSlug, "ghi" as SimpleSlug), "../ghi")
|
||||||
|
assert.strictEqual(path.resolveRelative("abc/def" as FullSlug, "ghi/" as SimpleSlug), "../ghi/")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { slug as slugAnchor } from "github-slugger"
|
import { slug as slugAnchor } from "github-slugger"
|
||||||
import type { Element as HastElement } from "hast"
|
import type { Element as HastElement } from "hast"
|
||||||
import { clone } from "./clone"
|
import { clone } from "./clone"
|
||||||
|
|
||||||
// this file must be isomorphic so it can't use node libs (e.g. path)
|
// this file must be isomorphic so it can't use node libs (e.g. path)
|
||||||
|
|
||||||
export const QUARTZ = "quartz"
|
export const QUARTZ = "quartz"
|
||||||
@ -39,6 +40,15 @@ export function isRelativeURL(s: string): s is RelativeURL {
|
|||||||
return validStart && validEnding && ![".md", ".html"].includes(getFileExtension(s) ?? "")
|
return validStart && validEnding && ![".md", ".html"].includes(getFileExtension(s) ?? "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isAbsoluteURL(s: string): boolean {
|
||||||
|
try {
|
||||||
|
new URL(s)
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
export function getFullSlug(window: Window): FullSlug {
|
export function getFullSlug(window: Window): FullSlug {
|
||||||
const res = window.document.body.dataset.slug! as FullSlug
|
const res = window.document.body.dataset.slug! as FullSlug
|
||||||
return res
|
return res
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user