mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-12-27 23:04:05 -06:00
增加图注功能 (#9)
* Added plugin to support image captions * Added plugin to support image lightbox * Fixed style issues to resolve build failure * GLightbox shows close button by default * Fixed GLightbox not reloaded in SPA mode * chore(styles): omit sass deprecation warnings (#1737) update to newer API * chore(deps): bump the production-dependencies group across 1 directory with 3 updates (#1744) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * feat(graph): enable radial mode (#1738) * feat(explorer): collapsible mobile explorer (#1471) Co-authored-by: Aaron Pham <Aaronpham0103@gmail.com> * 测试效果 --------- Co-authored-by: Stephen Tse <Stephen-X@users.noreply.github.com> Co-authored-by: Anton Bulakh <him@necauq.ua> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Aaron Pham <contact@aarnphm.xyz> Co-authored-by: Emile Bangma <github@emilebangma.com> Co-authored-by: Aaron Pham <Aaronpham0103@gmail.com>
This commit is contained in:
parent
acb15b6e86
commit
405c5b8d3c
@ -6,3 +6,6 @@ comments: false
|
|||||||
_中英文混合斜体 Chinese & English_
|
_中英文混合斜体 Chinese & English_
|
||||||
**粗体 _嵌套斜体_**
|
**粗体 _嵌套斜体_**
|
||||||
`代码块不受影响`
|
`代码块不受影响`
|
||||||
|
|
||||||
|
![[Pic_20250204.png]]
|
||||||
|
*This is a caption.*
|
||||||
@ -36,6 +36,7 @@ Component.Graph({
|
|||||||
opacityScale: 1, // how quickly do we fade out the labels when zooming out?
|
opacityScale: 1, // how quickly do we fade out the labels when zooming out?
|
||||||
removeTags: [], // what tags to remove from the graph
|
removeTags: [], // what tags to remove from the graph
|
||||||
showTags: true, // whether to show tags in the graph
|
showTags: true, // whether to show tags in the graph
|
||||||
|
enableRadial: false, // whether to constrain the graph, similar to Obsidian
|
||||||
},
|
},
|
||||||
globalGraph: {
|
globalGraph: {
|
||||||
drag: true,
|
drag: true,
|
||||||
@ -49,6 +50,7 @@ Component.Graph({
|
|||||||
opacityScale: 1,
|
opacityScale: 1,
|
||||||
removeTags: [], // what tags to remove from the graph
|
removeTags: [], // what tags to remove from the graph
|
||||||
showTags: true, // whether to show tags in the graph
|
showTags: true, // whether to show tags in the graph
|
||||||
|
enableRadial: true, // whether to constrain the graph, similar to Obsidian
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|||||||
81
package-lock.json
generated
81
package-lock.json
generated
@ -34,7 +34,7 @@
|
|||||||
"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",
|
||||||
"pixi.js": "^8.6.6",
|
"pixi.js": "^8.7.3",
|
||||||
"preact": "^10.25.4",
|
"preact": "^10.25.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",
|
||||||
@ -42,6 +42,7 @@
|
|||||||
"reading-time": "^1.5.0",
|
"reading-time": "^1.5.0",
|
||||||
"rehype-autolink-headings": "^7.1.0",
|
"rehype-autolink-headings": "^7.1.0",
|
||||||
"rehype-citation": "^2.2.2",
|
"rehype-citation": "^2.2.2",
|
||||||
|
"rehype-image-caption": "^2.0.10",
|
||||||
"rehype-katex": "^7.0.1",
|
"rehype-katex": "^7.0.1",
|
||||||
"rehype-mathjax": "^6.0.0",
|
"rehype-mathjax": "^6.0.0",
|
||||||
"rehype-pretty-code": "^0.14.0",
|
"rehype-pretty-code": "^0.14.0",
|
||||||
@ -79,10 +80,10 @@
|
|||||||
"@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.10.6",
|
"@types/node": "^22.12.0",
|
||||||
"@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.5.13",
|
"@types/ws": "^8.5.14",
|
||||||
"@types/yargs": "^17.0.33",
|
"@types/yargs": "^17.0.33",
|
||||||
"esbuild": "^0.24.2",
|
"esbuild": "^0.24.2",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
@ -1914,10 +1915,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.10.6",
|
"version": "22.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.12.0.tgz",
|
||||||
"integrity": "sha512-qNiuwC4ZDAUNcY47xgaSuS92cjf8JbSUoaKS77bmLG1rU7MlATVSiw/IlrjtIyyskXBZ8KkNfjK/P5na7rgXbQ==",
|
"integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.20.0"
|
"undici-types": "~6.20.0"
|
||||||
}
|
}
|
||||||
@ -1943,10 +1945,11 @@
|
|||||||
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
|
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
|
||||||
},
|
},
|
||||||
"node_modules/@types/ws": {
|
"node_modules/@types/ws": {
|
||||||
"version": "8.5.13",
|
"version": "8.5.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.14.tgz",
|
||||||
"integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==",
|
"integrity": "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
@ -4561,9 +4564,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mdast-util-mdx-jsx": {
|
"node_modules/mdast-util-mdx-jsx": {
|
||||||
"version": "3.0.0",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.3.tgz",
|
||||||
"integrity": "sha512-XZuPPzQNBPAlaqsTTgRrcJnyFbSOBovSadFgbFu8SnuNgm+6Bdx1K+IWoitsmj6Lq6MNtI+ytOqwN70n//NaBA==",
|
"integrity": "sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree-jsx": "^1.0.0",
|
"@types/estree-jsx": "^1.0.0",
|
||||||
"@types/hast": "^3.0.0",
|
"@types/hast": "^3.0.0",
|
||||||
@ -4575,7 +4579,6 @@
|
|||||||
"mdast-util-to-markdown": "^2.0.0",
|
"mdast-util-to-markdown": "^2.0.0",
|
||||||
"parse-entities": "^4.0.0",
|
"parse-entities": "^4.0.0",
|
||||||
"stringify-entities": "^4.0.0",
|
"stringify-entities": "^4.0.0",
|
||||||
"unist-util-remove-position": "^5.0.0",
|
|
||||||
"unist-util-stringify-position": "^4.0.0",
|
"unist-util-stringify-position": "^4.0.0",
|
||||||
"vfile-message": "^4.0.0"
|
"vfile-message": "^4.0.0"
|
||||||
},
|
},
|
||||||
@ -5583,9 +5586,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pixi.js": {
|
"node_modules/pixi.js": {
|
||||||
"version": "8.6.6",
|
"version": "8.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.6.6.tgz",
|
"resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.7.3.tgz",
|
||||||
"integrity": "sha512-o5pw7G2yuIrnBx0G4npBlmFp+XGNcapI/Ufs62rRj/4XKxc1Zo74YJr/BtEXcXTraTKd+pQvYOLvnfxRjxBMvQ==",
|
"integrity": "sha512-wfWlhJYnGx1s4f2yoouevQjaeacbJ12LTkJGa+n9AIYNIjOnmJylBtZ2mARX7iFk3mr2xv0wuo//XPe2hk5OBw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@pixi/colord": "^2.9.6",
|
"@pixi/colord": "^2.9.6",
|
||||||
"@types/css-font-loading-module": "^0.0.12",
|
"@types/css-font-loading-module": "^0.0.12",
|
||||||
@ -5626,6 +5630,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
|
||||||
"integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
|
"integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin/prettier.cjs"
|
"prettier": "bin/prettier.cjs"
|
||||||
},
|
},
|
||||||
@ -5740,6 +5745,22 @@
|
|||||||
"resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz",
|
||||||
"integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="
|
"integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="
|
||||||
},
|
},
|
||||||
|
"node_modules/rehype": {
|
||||||
|
"version": "13.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.2.tgz",
|
||||||
|
"integrity": "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0",
|
||||||
|
"rehype-parse": "^9.0.0",
|
||||||
|
"rehype-stringify": "^10.0.0",
|
||||||
|
"unified": "^11.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/rehype-autolink-headings": {
|
"node_modules/rehype-autolink-headings": {
|
||||||
"version": "7.1.0",
|
"version": "7.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/rehype-autolink-headings/-/rehype-autolink-headings-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/rehype-autolink-headings/-/rehype-autolink-headings-7.1.0.tgz",
|
||||||
@ -5778,6 +5799,19 @@
|
|||||||
"unist-util-visit": "^5.0.0"
|
"unist-util-visit": "^5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rehype-image-caption": {
|
||||||
|
"version": "2.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/rehype-image-caption/-/rehype-image-caption-2.0.10.tgz",
|
||||||
|
"integrity": "sha512-iwm0gnR6Z3svaM8i34mS8GCHFCNnV9Ot3RCv1zteAPb8m3s0oqcwcZYdyZPLXrfQ3PdMH7eo6k7RJyuPIQrgHA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"hast-util-is-element": "^3.0.0",
|
||||||
|
"mdast-util-mdx-jsx": "^3.1.2",
|
||||||
|
"rehype": "^13.0.1",
|
||||||
|
"unified": "^11.0.4",
|
||||||
|
"unist-util-visit": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/rehype-katex": {
|
"node_modules/rehype-katex": {
|
||||||
"version": "7.0.1",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.1.tgz",
|
||||||
@ -5878,6 +5912,21 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rehype-stringify": {
|
||||||
|
"version": "10.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz",
|
||||||
|
"integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0",
|
||||||
|
"hast-util-to-html": "^9.0.0",
|
||||||
|
"unified": "^11.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/remark": {
|
"node_modules/remark": {
|
||||||
"version": "15.0.1",
|
"version": "15.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/remark/-/remark-15.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/remark/-/remark-15.0.1.tgz",
|
||||||
|
|||||||
@ -60,7 +60,7 @@
|
|||||||
"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",
|
||||||
"pixi.js": "^8.6.6",
|
"pixi.js": "^8.7.3",
|
||||||
"preact": "^10.25.4",
|
"preact": "^10.25.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",
|
||||||
@ -68,6 +68,7 @@
|
|||||||
"reading-time": "^1.5.0",
|
"reading-time": "^1.5.0",
|
||||||
"rehype-autolink-headings": "^7.1.0",
|
"rehype-autolink-headings": "^7.1.0",
|
||||||
"rehype-citation": "^2.2.2",
|
"rehype-citation": "^2.2.2",
|
||||||
|
"rehype-image-caption": "^2.0.10",
|
||||||
"rehype-katex": "^7.0.1",
|
"rehype-katex": "^7.0.1",
|
||||||
"rehype-mathjax": "^6.0.0",
|
"rehype-mathjax": "^6.0.0",
|
||||||
"rehype-pretty-code": "^0.14.0",
|
"rehype-pretty-code": "^0.14.0",
|
||||||
@ -102,10 +103,10 @@
|
|||||||
"@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.10.6",
|
"@types/node": "^22.12.0",
|
||||||
"@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.5.13",
|
"@types/ws": "^8.5.14",
|
||||||
"@types/yargs": "^17.0.33",
|
"@types/yargs": "^17.0.33",
|
||||||
"esbuild": "^0.24.2",
|
"esbuild": "^0.24.2",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
|
|||||||
@ -71,6 +71,14 @@ const config: QuartzConfig = {
|
|||||||
Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }),
|
Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }),
|
||||||
Plugin.Description(),
|
Plugin.Description(),
|
||||||
Plugin.Latex({ renderEngine: "katex" }),
|
Plugin.Latex({ renderEngine: "katex" }),
|
||||||
|
// Adds image caption support. Syntax:
|
||||||
|
// ```md
|
||||||
|
// 
|
||||||
|
// *caption text*
|
||||||
|
// ```
|
||||||
|
Plugin.FigureCaptions(),
|
||||||
|
// Adds image lightbox support
|
||||||
|
Plugin.Lightbox(),
|
||||||
],
|
],
|
||||||
filters: [Plugin.RemoveDrafts()],
|
filters: [Plugin.RemoveDrafts()],
|
||||||
emitters: [
|
emitters: [
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
import explorerStyle from "./styles/explorer.scss"
|
import style from "./styles/explorer.scss"
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import script from "./scripts/explorer.inline"
|
import script from "./scripts/explorer.inline"
|
||||||
@ -83,18 +83,46 @@ export default ((userOpts?: Partial<Options>) => {
|
|||||||
lastBuildId = ctx.buildId
|
lastBuildId = ctx.buildId
|
||||||
constructFileTree(allFiles)
|
constructFileTree(allFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={classNames(displayClass, "explorer")}>
|
<div class={classNames(displayClass, "explorer")}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
id="explorer"
|
id="mobile-explorer"
|
||||||
|
class="collapsed hide-until-loaded"
|
||||||
data-behavior={opts.folderClickBehavior}
|
data-behavior={opts.folderClickBehavior}
|
||||||
data-collapsed={opts.folderDefaultState}
|
data-collapsed={opts.folderDefaultState}
|
||||||
data-savestate={opts.useSavedState}
|
data-savestate={opts.useSavedState}
|
||||||
data-tree={jsonTree}
|
data-tree={jsonTree}
|
||||||
|
data-mobile={true}
|
||||||
aria-controls="explorer-content"
|
aria-controls="explorer-content"
|
||||||
aria-expanded={opts.folderDefaultState === "open"}
|
aria-expanded={false}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class="lucide lucide-menu"
|
||||||
|
>
|
||||||
|
<line x1="4" x2="20" y1="12" y2="12" />
|
||||||
|
<line x1="4" x2="20" y1="6" y2="6" />
|
||||||
|
<line x1="4" x2="20" y1="18" y2="18" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
id="desktop-explorer"
|
||||||
|
class="title-button"
|
||||||
|
data-behavior={opts.folderClickBehavior}
|
||||||
|
data-collapsed={opts.folderDefaultState}
|
||||||
|
data-savestate={opts.useSavedState}
|
||||||
|
data-tree={jsonTree}
|
||||||
|
data-mobile={false}
|
||||||
|
aria-controls="explorer-content"
|
||||||
|
aria-expanded={true}
|
||||||
>
|
>
|
||||||
<h2>{opts.title ?? i18n(cfg.locale).components.explorer.title}</h2>
|
<h2>{opts.title ?? i18n(cfg.locale).components.explorer.title}</h2>
|
||||||
<svg
|
<svg
|
||||||
@ -122,7 +150,7 @@ export default ((userOpts?: Partial<Options>) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Explorer.css = explorerStyle
|
Explorer.css = style
|
||||||
Explorer.afterDOMLoaded = script
|
Explorer.afterDOMLoaded = script
|
||||||
return Explorer
|
return Explorer
|
||||||
}) satisfies QuartzComponentConstructor
|
}) satisfies QuartzComponentConstructor
|
||||||
|
|||||||
@ -18,6 +18,7 @@ export interface D3Config {
|
|||||||
removeTags: string[]
|
removeTags: string[]
|
||||||
showTags: boolean
|
showTags: boolean
|
||||||
focusOnHover?: boolean
|
focusOnHover?: boolean
|
||||||
|
enableRadial?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GraphOptions {
|
interface GraphOptions {
|
||||||
@ -39,6 +40,7 @@ const defaultOptions: GraphOptions = {
|
|||||||
showTags: true,
|
showTags: true,
|
||||||
removeTags: [],
|
removeTags: [],
|
||||||
focusOnHover: false,
|
focusOnHover: false,
|
||||||
|
enableRadial: false,
|
||||||
},
|
},
|
||||||
globalGraph: {
|
globalGraph: {
|
||||||
drag: true,
|
drag: true,
|
||||||
@ -53,10 +55,11 @@ const defaultOptions: GraphOptions = {
|
|||||||
showTags: true,
|
showTags: true,
|
||||||
removeTags: [],
|
removeTags: [],
|
||||||
focusOnHover: true,
|
focusOnHover: true,
|
||||||
|
enableRadial: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ((opts?: GraphOptions) => {
|
export default ((opts?: Partial<GraphOptions>) => {
|
||||||
const Graph: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => {
|
const Graph: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => {
|
||||||
const localGraph = { ...defaultOptions.localGraph, ...opts?.localGraph }
|
const localGraph = { ...defaultOptions.localGraph, ...opts?.localGraph }
|
||||||
const globalGraph = { ...defaultOptions.globalGraph, ...opts?.globalGraph }
|
const globalGraph = { ...defaultOptions.globalGraph, ...opts?.globalGraph }
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import { FolderState } from "../ExplorerNode"
|
import { FolderState } from "../ExplorerNode"
|
||||||
|
|
||||||
|
// Current state of folders
|
||||||
type MaybeHTMLElement = HTMLElement | undefined
|
type MaybeHTMLElement = HTMLElement | undefined
|
||||||
let currentExplorerState: FolderState[]
|
let currentExplorerState: FolderState[]
|
||||||
|
|
||||||
const observer = new IntersectionObserver((entries) => {
|
const observer = new IntersectionObserver((entries) => {
|
||||||
// If last element is observed, remove gradient of "overflow" class so element is visible
|
// If last element is observed, remove gradient of "overflow" class so element is visible
|
||||||
const explorerUl = document.getElementById("explorer-ul")
|
const explorerUl = document.getElementById("explorer-ul")
|
||||||
@ -16,23 +18,43 @@ const observer = new IntersectionObserver((entries) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function toggleExplorer(this: HTMLElement) {
|
function toggleExplorer(this: HTMLElement) {
|
||||||
|
// Toggle collapsed state of entire explorer
|
||||||
this.classList.toggle("collapsed")
|
this.classList.toggle("collapsed")
|
||||||
|
|
||||||
|
// Toggle collapsed aria state of entire explorer
|
||||||
this.setAttribute(
|
this.setAttribute(
|
||||||
"aria-expanded",
|
"aria-expanded",
|
||||||
this.getAttribute("aria-expanded") === "true" ? "false" : "true",
|
this.getAttribute("aria-expanded") === "true" ? "false" : "true",
|
||||||
)
|
)
|
||||||
const content = this.nextElementSibling as MaybeHTMLElement
|
|
||||||
if (!content) return
|
|
||||||
|
|
||||||
|
const content = (
|
||||||
|
this.nextElementSibling?.nextElementSibling
|
||||||
|
? this.nextElementSibling.nextElementSibling
|
||||||
|
: this.nextElementSibling
|
||||||
|
) as MaybeHTMLElement
|
||||||
|
if (!content) return
|
||||||
content.classList.toggle("collapsed")
|
content.classList.toggle("collapsed")
|
||||||
|
content.classList.toggle("explorer-viewmode")
|
||||||
|
|
||||||
|
// Prevent scroll under
|
||||||
|
if (document.querySelector("#mobile-explorer")) {
|
||||||
|
// Disable scrolling on the page when the explorer is opened on mobile
|
||||||
|
const bodySelector = document.querySelector("#quartz-body")
|
||||||
|
if (bodySelector) bodySelector.classList.toggle("lock-scroll")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleFolder(evt: MouseEvent) {
|
function toggleFolder(evt: MouseEvent) {
|
||||||
evt.stopPropagation()
|
evt.stopPropagation()
|
||||||
|
|
||||||
|
// Element that was clicked
|
||||||
const target = evt.target as MaybeHTMLElement
|
const target = evt.target as MaybeHTMLElement
|
||||||
if (!target) return
|
if (!target) return
|
||||||
|
|
||||||
|
// Check if target was svg icon or button
|
||||||
const isSvg = target.nodeName === "svg"
|
const isSvg = target.nodeName === "svg"
|
||||||
|
|
||||||
|
// corresponding <ul> element relative to clicked button/folder
|
||||||
const childFolderContainer = (
|
const childFolderContainer = (
|
||||||
isSvg
|
isSvg
|
||||||
? target.parentElement?.nextSibling
|
? target.parentElement?.nextSibling
|
||||||
@ -42,10 +64,14 @@ function toggleFolder(evt: MouseEvent) {
|
|||||||
isSvg ? target.nextElementSibling : target.parentElement
|
isSvg ? target.nextElementSibling : target.parentElement
|
||||||
) as MaybeHTMLElement
|
) as MaybeHTMLElement
|
||||||
if (!(childFolderContainer && currentFolderParent)) return
|
if (!(childFolderContainer && currentFolderParent)) return
|
||||||
|
// <li> element of folder (stores folder-path dataset)
|
||||||
childFolderContainer.classList.toggle("open")
|
childFolderContainer.classList.toggle("open")
|
||||||
|
|
||||||
|
// Collapse folder container
|
||||||
const isCollapsed = childFolderContainer.classList.contains("open")
|
const isCollapsed = childFolderContainer.classList.contains("open")
|
||||||
setFolderState(childFolderContainer, !isCollapsed)
|
setFolderState(childFolderContainer, !isCollapsed)
|
||||||
|
|
||||||
|
// Save folder state to localStorage
|
||||||
const fullFolderPath = currentFolderParent.dataset.folderpath as string
|
const fullFolderPath = currentFolderParent.dataset.folderpath as string
|
||||||
toggleCollapsedByPath(currentExplorerState, fullFolderPath)
|
toggleCollapsedByPath(currentExplorerState, fullFolderPath)
|
||||||
const stringifiedFileTree = JSON.stringify(currentExplorerState)
|
const stringifiedFileTree = JSON.stringify(currentExplorerState)
|
||||||
@ -53,57 +79,106 @@ function toggleFolder(evt: MouseEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setupExplorer() {
|
function setupExplorer() {
|
||||||
const explorer = document.getElementById("explorer")
|
// Set click handler for collapsing entire explorer
|
||||||
if (!explorer) return
|
const allExplorers = document.querySelectorAll(".explorer > button") as NodeListOf<HTMLElement>
|
||||||
|
|
||||||
if (explorer.dataset.behavior === "collapse") {
|
for (const explorer of allExplorers) {
|
||||||
|
// Get folder state from local storage
|
||||||
|
const storageTree = localStorage.getItem("fileTree")
|
||||||
|
|
||||||
|
// Convert to bool
|
||||||
|
const useSavedFolderState = explorer?.dataset.savestate === "true"
|
||||||
|
|
||||||
|
if (explorer) {
|
||||||
|
// Get config
|
||||||
|
const collapseBehavior = explorer.dataset.behavior
|
||||||
|
|
||||||
|
// Add click handlers for all folders (click handler on folder "label")
|
||||||
|
if (collapseBehavior === "collapse") {
|
||||||
|
for (const item of document.getElementsByClassName(
|
||||||
|
"folder-button",
|
||||||
|
) as HTMLCollectionOf<HTMLElement>) {
|
||||||
|
window.addCleanup(() => explorer.removeEventListener("click", toggleExplorer))
|
||||||
|
item.addEventListener("click", toggleFolder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add click handler to main explorer
|
||||||
|
window.addCleanup(() => explorer.removeEventListener("click", toggleExplorer))
|
||||||
|
explorer.addEventListener("click", toggleExplorer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up click handlers for each folder (click handler on folder "icon")
|
||||||
for (const item of document.getElementsByClassName(
|
for (const item of document.getElementsByClassName(
|
||||||
"folder-button",
|
"folder-icon",
|
||||||
) as HTMLCollectionOf<HTMLElement>) {
|
) as HTMLCollectionOf<HTMLElement>) {
|
||||||
item.addEventListener("click", toggleFolder)
|
item.addEventListener("click", toggleFolder)
|
||||||
window.addCleanup(() => item.removeEventListener("click", toggleFolder))
|
window.addCleanup(() => item.removeEventListener("click", toggleFolder))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get folder state from local storage
|
||||||
|
const oldExplorerState: FolderState[] =
|
||||||
|
storageTree && useSavedFolderState ? JSON.parse(storageTree) : []
|
||||||
|
const oldIndex = new Map(oldExplorerState.map((entry) => [entry.path, entry.collapsed]))
|
||||||
|
const newExplorerState: FolderState[] = explorer.dataset.tree
|
||||||
|
? JSON.parse(explorer.dataset.tree)
|
||||||
|
: []
|
||||||
|
currentExplorerState = []
|
||||||
|
|
||||||
|
for (const { path, collapsed } of newExplorerState) {
|
||||||
|
currentExplorerState.push({
|
||||||
|
path,
|
||||||
|
collapsed: oldIndex.get(path) ?? collapsed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
currentExplorerState.map((folderState) => {
|
||||||
|
const folderLi = document.querySelector(
|
||||||
|
`[data-folderpath='${folderState.path.replace("'", "-")}']`,
|
||||||
|
) as MaybeHTMLElement
|
||||||
|
const folderUl = folderLi?.parentElement?.nextElementSibling as MaybeHTMLElement
|
||||||
|
if (folderUl) {
|
||||||
|
setFolderState(folderUl, folderState.collapsed)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
explorer.addEventListener("click", toggleExplorer)
|
function toggleExplorerFolders() {
|
||||||
window.addCleanup(() => explorer.removeEventListener("click", toggleExplorer))
|
const currentFile = (document.querySelector("body")?.getAttribute("data-slug") ?? "").replace(
|
||||||
|
/\/index$/g,
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
const allFolders = document.querySelectorAll(".folder-outer")
|
||||||
|
|
||||||
// Set up click handlers for each folder (click handler on folder "icon")
|
allFolders.forEach((element) => {
|
||||||
for (const item of document.getElementsByClassName(
|
const folderUl = Array.from(element.children).find((child) =>
|
||||||
"folder-icon",
|
child.matches("ul[data-folderul]"),
|
||||||
) as HTMLCollectionOf<HTMLElement>) {
|
)
|
||||||
item.addEventListener("click", toggleFolder)
|
|
||||||
window.addCleanup(() => item.removeEventListener("click", toggleFolder))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get folder state from local storage
|
|
||||||
const storageTree = localStorage.getItem("fileTree")
|
|
||||||
const useSavedFolderState = explorer?.dataset.savestate === "true"
|
|
||||||
const oldExplorerState: FolderState[] =
|
|
||||||
storageTree && useSavedFolderState ? JSON.parse(storageTree) : []
|
|
||||||
const oldIndex = new Map(oldExplorerState.map((entry) => [entry.path, entry.collapsed]))
|
|
||||||
const newExplorerState: FolderState[] = explorer.dataset.tree
|
|
||||||
? JSON.parse(explorer.dataset.tree)
|
|
||||||
: []
|
|
||||||
currentExplorerState = []
|
|
||||||
for (const { path, collapsed } of newExplorerState) {
|
|
||||||
currentExplorerState.push({ path, collapsed: oldIndex.get(path) ?? collapsed })
|
|
||||||
}
|
|
||||||
|
|
||||||
currentExplorerState.map((folderState) => {
|
|
||||||
const folderLi = document.querySelector(
|
|
||||||
`[data-folderpath='${folderState.path}']`,
|
|
||||||
) as MaybeHTMLElement
|
|
||||||
const folderUl = folderLi?.parentElement?.nextElementSibling as MaybeHTMLElement
|
|
||||||
if (folderUl) {
|
if (folderUl) {
|
||||||
setFolderState(folderUl, folderState.collapsed)
|
if (currentFile.includes(folderUl.getAttribute("data-folderul") ?? "")) {
|
||||||
|
if (!element.classList.contains("open")) {
|
||||||
|
element.classList.add("open")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("resize", setupExplorer)
|
window.addEventListener("resize", setupExplorer)
|
||||||
|
|
||||||
document.addEventListener("nav", () => {
|
document.addEventListener("nav", () => {
|
||||||
|
const explorer = document.querySelector("#mobile-explorer")
|
||||||
|
if (explorer) {
|
||||||
|
explorer.classList.add("collapsed")
|
||||||
|
const content = explorer.nextElementSibling?.nextElementSibling as HTMLElement
|
||||||
|
if (content) {
|
||||||
|
content.classList.add("collapsed")
|
||||||
|
content.classList.toggle("explorer-viewmode")
|
||||||
|
}
|
||||||
|
}
|
||||||
setupExplorer()
|
setupExplorer()
|
||||||
|
|
||||||
observer.disconnect()
|
observer.disconnect()
|
||||||
|
|
||||||
// select pseudo element at end of list
|
// select pseudo element at end of list
|
||||||
@ -111,6 +186,12 @@ document.addEventListener("nav", () => {
|
|||||||
if (lastItem) {
|
if (lastItem) {
|
||||||
observer.observe(lastItem)
|
observer.observe(lastItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide explorer on mobile until it is requested
|
||||||
|
const hiddenUntilDoneLoading = document.querySelector("#mobile-explorer")
|
||||||
|
hiddenUntilDoneLoading?.classList.remove("hide-until-loaded")
|
||||||
|
|
||||||
|
toggleExplorerFolders()
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import {
|
|||||||
forceCenter,
|
forceCenter,
|
||||||
forceLink,
|
forceLink,
|
||||||
forceCollide,
|
forceCollide,
|
||||||
|
forceRadial,
|
||||||
zoomIdentity,
|
zoomIdentity,
|
||||||
select,
|
select,
|
||||||
drag,
|
drag,
|
||||||
@ -87,6 +88,7 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
|
|||||||
removeTags,
|
removeTags,
|
||||||
showTags,
|
showTags,
|
||||||
focusOnHover,
|
focusOnHover,
|
||||||
|
enableRadial,
|
||||||
} = JSON.parse(graph.dataset["cfg"]!) as D3Config
|
} = JSON.parse(graph.dataset["cfg"]!) as D3Config
|
||||||
|
|
||||||
const data: Map<SimpleSlug, ContentDetails> = new Map(
|
const data: Map<SimpleSlug, ContentDetails> = new Map(
|
||||||
@ -161,15 +163,20 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
|
|||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const width = graph.offsetWidth
|
||||||
|
const height = Math.max(graph.offsetHeight, 250)
|
||||||
|
|
||||||
// we virtualize the simulation and use pixi to actually render it
|
// we virtualize the simulation and use pixi to actually render it
|
||||||
|
// Calculate the radius of the container circle
|
||||||
|
const radius = Math.min(width, height) / 2 - 40 // 40px padding
|
||||||
const simulation: Simulation<NodeData, LinkData> = forceSimulation<NodeData>(graphData.nodes)
|
const simulation: Simulation<NodeData, LinkData> = forceSimulation<NodeData>(graphData.nodes)
|
||||||
.force("charge", forceManyBody().strength(-100 * repelForce))
|
.force("charge", forceManyBody().strength(-100 * repelForce))
|
||||||
.force("center", forceCenter().strength(centerForce))
|
.force("center", forceCenter().strength(centerForce))
|
||||||
.force("link", forceLink(graphData.links).distance(linkDistance))
|
.force("link", forceLink(graphData.links).distance(linkDistance))
|
||||||
.force("collide", forceCollide<NodeData>((n) => nodeRadius(n)).iterations(3))
|
.force("collide", forceCollide<NodeData>((n) => nodeRadius(n)).iterations(3))
|
||||||
|
|
||||||
const width = graph.offsetWidth
|
if (enableRadial)
|
||||||
const height = Math.max(graph.offsetHeight, 250)
|
simulation.force("radial", forceRadial(radius * 0.8, width / 2, height / 2).strength(0.3))
|
||||||
|
|
||||||
// precompute style prop strings as pixi doesn't support css variables
|
// precompute style prop strings as pixi doesn't support css variables
|
||||||
const cssVars = [
|
const cssVars = [
|
||||||
|
|||||||
@ -1,14 +1,70 @@
|
|||||||
@use "../../styles/variables.scss" as *;
|
@use "../../styles/variables.scss" as *;
|
||||||
|
|
||||||
|
@media all and ($mobile) {
|
||||||
|
.page > #quartz-body {
|
||||||
|
// Shift page position when toggling Explorer on mobile.
|
||||||
|
& > :not(.sidebar.left:has(.explorer)) {
|
||||||
|
transform: translateX(0);
|
||||||
|
transition: transform 300ms ease-in-out;
|
||||||
|
}
|
||||||
|
&.lock-scroll > :not(.sidebar.left:has(.explorer)) {
|
||||||
|
transform: translateX(100dvw);
|
||||||
|
transition: transform 300ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sticky top bar (stays in place when scrolling down on mobile).
|
||||||
|
.sidebar.left:has(.explorer) {
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: sticky;
|
||||||
|
background-color: var(--light);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide Explorer on mobile until done loading.
|
||||||
|
// Prevents ugly animation on page load.
|
||||||
|
.hide-until-loaded ~ #explorer-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.explorer {
|
.explorer {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
|
|
||||||
|
@media all and ($mobile) {
|
||||||
|
order: -1;
|
||||||
|
height: initial;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
button#mobile-explorer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button#desktop-explorer {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and ($mobile) {
|
||||||
|
button#mobile-explorer {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
button#desktop-explorer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.desktop-only {
|
&.desktop-only {
|
||||||
@media all and not ($mobile) {
|
@media all and not ($mobile) {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*&:after {
|
/*&:after {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
content: "";
|
content: "";
|
||||||
@ -23,7 +79,8 @@
|
|||||||
}*/
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
button#explorer {
|
button#mobile-explorer,
|
||||||
|
button#desktop-explorer {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
@ -68,19 +125,19 @@ button#explorer {
|
|||||||
list-style: none;
|
list-style: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
max-height: 100%;
|
max-height: 0px;
|
||||||
transition:
|
transition:
|
||||||
max-height 0.35s ease,
|
max-height 0.35s ease,
|
||||||
visibility 0s linear 0s;
|
visibility 0s linear 0.35s;
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
visibility: visible;
|
visibility: hidden;
|
||||||
|
|
||||||
&.collapsed {
|
&.collapsed {
|
||||||
max-height: 0;
|
max-height: 100%;
|
||||||
transition:
|
transition:
|
||||||
max-height 0.35s ease,
|
max-height 0.35s ease,
|
||||||
visibility 0s linear 0.35s;
|
visibility 0s linear 0s;
|
||||||
visibility: hidden;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
& ul {
|
& ul {
|
||||||
@ -91,12 +148,14 @@ button#explorer {
|
|||||||
max-height 0.35s ease,
|
max-height 0.35s ease,
|
||||||
transform 0.35s ease,
|
transform 0.35s ease,
|
||||||
opacity 0.2s ease;
|
opacity 0.2s ease;
|
||||||
|
|
||||||
& li > a {
|
& li > a {
|
||||||
color: var(--dark);
|
color: var(--dark);
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> #explorer-ul {
|
> #explorer-ul {
|
||||||
max-height: none;
|
max-height: none;
|
||||||
}
|
}
|
||||||
@ -179,3 +238,80 @@ li:has(> .folder-outer:not(.open)) > .folder-container > svg {
|
|||||||
// remove default margin from li
|
// remove default margin from li
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.explorer {
|
||||||
|
@media all and ($mobile) {
|
||||||
|
#explorer-content {
|
||||||
|
box-sizing: border-box;
|
||||||
|
overscroll-behavior: none;
|
||||||
|
z-index: 100;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
background-color: var(--light);
|
||||||
|
max-width: 100dvw;
|
||||||
|
left: -100dvw;
|
||||||
|
width: 100%;
|
||||||
|
transition: transform 300ms ease-in-out;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: $topSpacing 2rem 2rem;
|
||||||
|
height: 100dvh;
|
||||||
|
max-height: 100dvh;
|
||||||
|
margin-top: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
|
||||||
|
&:not(.collapsed) {
|
||||||
|
transform: translateX(100dvw);
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.overflow {
|
||||||
|
max-height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.collapsed {
|
||||||
|
transform: translateX(0);
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#mobile-explorer {
|
||||||
|
margin: 5px;
|
||||||
|
z-index: 101;
|
||||||
|
|
||||||
|
&:not(.collapsed) .lucide-menu {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
transition: transform 200ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lucide-menu {
|
||||||
|
stroke: var(--darkgray);
|
||||||
|
transition: transform 200ms ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
stroke: var(--dark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-scroll {
|
||||||
|
opacity: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
html:has(.no-scroll) {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and not ($mobile) {
|
||||||
|
.no-scroll {
|
||||||
|
opacity: 1 !important;
|
||||||
|
overflow: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html:has(.no-scroll) {
|
||||||
|
overflow: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
11
quartz/plugins/transformers/figcaptions.ts
Normal file
11
quartz/plugins/transformers/figcaptions.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import rehypeImageCaption from "rehype-image-caption"
|
||||||
|
import { QuartzTransformerPlugin } from "../types"
|
||||||
|
|
||||||
|
export const FigureCaptions: QuartzTransformerPlugin = () => {
|
||||||
|
return {
|
||||||
|
name: "FigureCaptions",
|
||||||
|
htmlPlugins() {
|
||||||
|
return [[rehypeImageCaption]]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,3 +11,5 @@ export { SyntaxHighlighting } from "./syntax"
|
|||||||
export { TableOfContents } from "./toc"
|
export { TableOfContents } from "./toc"
|
||||||
export { HardLineBreaks } from "./linebreaks"
|
export { HardLineBreaks } from "./linebreaks"
|
||||||
export { RoamFlavoredMarkdown } from "./roam"
|
export { RoamFlavoredMarkdown } from "./roam"
|
||||||
|
export { FigureCaptions } from "./figcaptions"
|
||||||
|
export { Lightbox } from "./lightbox"
|
||||||
|
|||||||
75
quartz/plugins/transformers/lightbox.ts
Normal file
75
quartz/plugins/transformers/lightbox.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { QuartzTransformerPlugin } from "../types"
|
||||||
|
import { visit } from "unist-util-visit"
|
||||||
|
import { Root } from "hast"
|
||||||
|
|
||||||
|
// Options supported here should be in sync with what GLightbox supports:
|
||||||
|
// https://github.com/biati-digital/glightbox
|
||||||
|
interface Options {
|
||||||
|
/** Name of the effect on lightbox open. */
|
||||||
|
openEffect: "zoom" | "fade" | "none"
|
||||||
|
/** Name of the effect on lightbox close. */
|
||||||
|
closeEffect: "zoom" | "fade" | "none"
|
||||||
|
/** Name of the effect on slide change. */
|
||||||
|
slideEffect: "slide" | "zoom" | "fade" | "none"
|
||||||
|
/** Show or hide the close button. */
|
||||||
|
closeButton: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultOptions: Options = {
|
||||||
|
openEffect: "zoom",
|
||||||
|
closeEffect: "zoom",
|
||||||
|
slideEffect: "slide",
|
||||||
|
closeButton: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Lightbox: QuartzTransformerPlugin<Partial<Options>> = (userOpts) => {
|
||||||
|
const opts = { ...defaultOptions, ...userOpts }
|
||||||
|
return {
|
||||||
|
name: "Lightbox",
|
||||||
|
htmlPlugins(ctx) {
|
||||||
|
return [
|
||||||
|
() => {
|
||||||
|
return (tree: Root, file) => {
|
||||||
|
visit(tree, "element", (node, _index, _parent) => {
|
||||||
|
if (
|
||||||
|
node.tagName === "img" &&
|
||||||
|
node.properties &&
|
||||||
|
typeof node.properties.src === "string"
|
||||||
|
) {
|
||||||
|
// Add Image Lightbox support
|
||||||
|
const classes = (node.properties.className ?? []) as string[]
|
||||||
|
classes.push("glightbox")
|
||||||
|
|
||||||
|
node.properties.className = classes
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
externalResources() {
|
||||||
|
return {
|
||||||
|
css: ["https://cdnjs.cloudflare.com/ajax/libs/glightbox/3.3.0/css/glightbox.min.css"],
|
||||||
|
js: [
|
||||||
|
{
|
||||||
|
src: "https://cdnjs.cloudflare.com/ajax/libs/glightbox/3.3.0/js/glightbox.min.js",
|
||||||
|
loadTime: "afterDOMReady",
|
||||||
|
contentType: "external",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
contentType: "inline",
|
||||||
|
loadTime: "afterDOMReady",
|
||||||
|
// GLightbox needs to be reloaded whenever there's a page content change
|
||||||
|
// to make sure it loads all the images in the new page content.
|
||||||
|
// Ref: https://quartz.jzhao.xyz/advanced/creating-components#scripts-and-interactivity
|
||||||
|
script: `
|
||||||
|
document.addEventListener("nav", () => {
|
||||||
|
const lightbox = GLightbox(${JSON.stringify(opts)});
|
||||||
|
});
|
||||||
|
`.trim(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
@use "sass:map";
|
||||||
|
|
||||||
@use "./variables.scss" as *;
|
@use "./variables.scss" as *;
|
||||||
@use "./syntax.scss";
|
@use "./syntax.scss";
|
||||||
@use "./callouts.scss";
|
@use "./callouts.scss";
|
||||||
@ -121,7 +123,7 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.page {
|
.page {
|
||||||
max-width: calc(#{map-get($breakpoints, desktop)} + 300px);
|
max-width: calc(#{map.get($breakpoints, desktop)} + 300px);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
& article {
|
& article {
|
||||||
& > h1 {
|
& > h1 {
|
||||||
@ -151,24 +153,25 @@ a {
|
|||||||
|
|
||||||
& > #quartz-body {
|
& > #quartz-body {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: #{map-get($desktopGrid, templateColumns)};
|
grid-template-columns: #{map.get($desktopGrid, templateColumns)};
|
||||||
grid-template-rows: #{map-get($desktopGrid, templateRows)};
|
grid-template-rows: #{map.get($desktopGrid, templateRows)};
|
||||||
column-gap: #{map-get($desktopGrid, columnGap)};
|
column-gap: #{map.get($desktopGrid, columnGap)};
|
||||||
row-gap: #{map-get($desktopGrid, rowGap)};
|
row-gap: #{map.get($desktopGrid, rowGap)};
|
||||||
grid-template-areas: #{map-get($desktopGrid, templateAreas)};
|
grid-template-areas: #{map.get($desktopGrid, templateAreas)};
|
||||||
|
|
||||||
@media all and ($tablet) {
|
@media all and ($tablet) {
|
||||||
grid-template-columns: #{map-get($tabletGrid, templateColumns)};
|
grid-template-columns: #{map.get($tabletGrid, templateColumns)};
|
||||||
grid-template-rows: #{map-get($tabletGrid, templateRows)};
|
grid-template-rows: #{map.get($tabletGrid, templateRows)};
|
||||||
column-gap: #{map-get($tabletGrid, columnGap)};
|
column-gap: #{map.get($tabletGrid, columnGap)};
|
||||||
row-gap: #{map-get($tabletGrid, rowGap)};
|
row-gap: #{map.get($tabletGrid, rowGap)};
|
||||||
grid-template-areas: #{map-get($tabletGrid, templateAreas)};
|
grid-template-areas: #{map.get($tabletGrid, templateAreas)};
|
||||||
}
|
}
|
||||||
@media all and ($mobile) {
|
@media all and ($mobile) {
|
||||||
grid-template-columns: #{map-get($mobileGrid, templateColumns)};
|
grid-template-columns: #{map.get($mobileGrid, templateColumns)};
|
||||||
grid-template-rows: #{map-get($mobileGrid, templateRows)};
|
grid-template-rows: #{map.get($mobileGrid, templateRows)};
|
||||||
column-gap: #{map-get($mobileGrid, columnGap)};
|
column-gap: #{map.get($mobileGrid, columnGap)};
|
||||||
row-gap: #{map-get($mobileGrid, rowGap)};
|
row-gap: #{map.get($mobileGrid, rowGap)};
|
||||||
grid-template-areas: #{map-get($mobileGrid, templateAreas)};
|
grid-template-areas: #{map.get($mobileGrid, templateAreas)};
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all and not ($desktop) {
|
@media all and not ($desktop) {
|
||||||
@ -599,3 +602,24 @@ iframe.pdf {
|
|||||||
transition: width 0.2s ease;
|
transition: width 0.2s ease;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== Image captions ==========
|
||||||
|
|
||||||
|
figure {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
> img {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
figcaption {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
line-height: 1.3rem;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
@use "sass:map";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Layout breakpoints
|
* Layout breakpoints
|
||||||
* $mobile: screen width below this value will use mobile styles
|
* $mobile: screen width below this value will use mobile styles
|
||||||
@ -10,11 +12,11 @@ $breakpoints: (
|
|||||||
desktop: 1200px,
|
desktop: 1200px,
|
||||||
);
|
);
|
||||||
|
|
||||||
$mobile: "(max-width: #{map-get($breakpoints, mobile)})";
|
$mobile: "(max-width: #{map.get($breakpoints, mobile)})";
|
||||||
$tablet: "(min-width: #{map-get($breakpoints, mobile)}) and (max-width: #{map-get($breakpoints, desktop)})";
|
$tablet: "(min-width: #{map.get($breakpoints, mobile)}) and (max-width: #{map.get($breakpoints, desktop)})";
|
||||||
$desktop: "(min-width: #{map-get($breakpoints, desktop)})";
|
$desktop: "(min-width: #{map.get($breakpoints, desktop)})";
|
||||||
|
|
||||||
$pageWidth: #{map-get($breakpoints, mobile)};
|
$pageWidth: #{map.get($breakpoints, mobile)};
|
||||||
$sidePanelWidth: 320px; //380px;
|
$sidePanelWidth: 320px; //380px;
|
||||||
$topSpacing: 6rem;
|
$topSpacing: 6rem;
|
||||||
$boldWeight: 700;
|
$boldWeight: 700;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user