Compare commits

..

1 Commits

Author SHA1 Message Date
Jacky Zhao
64c7851939 pkg: bump version to 4.1.3 2023-12-18 09:49:55 -08:00
26 changed files with 188 additions and 335 deletions

View File

@@ -20,19 +20,12 @@ Steps to reproduce the behavior:
**Expected behavior** **Expected behavior**
A clear and concise description of what you expected to happen. A clear and concise description of what you expected to happen.
**Screenshots and Source** **Screenshots**
If applicable, add screenshots to help explain your problem. If applicable, add screenshots to help explain your problem.
You can help speed up fixing the problem by either
1. providing a simple reproduction
2. linking to your Quartz repository where the problem can be observed
**Desktop (please complete the following information):** **Desktop (please complete the following information):**
- Quartz Version: [e.g. v4.1.2] - Device: [e.g. iPhone6]
- `node` Version: [e.g. v18.16]
- `npm` version: [e.g. v10.1.0]
- OS: [e.g. iOS] - OS: [e.g. iOS]
- Browser [e.g. chrome, safari] - Browser [e.g. chrome, safari]

View File

@@ -179,34 +179,6 @@ Component.Explorer({
## Advanced examples ## Advanced examples
> [!tip]
> When writing more complicated functions, the `layout` file can start to look very cramped.
> You can fix this by defining your functions in another file.
>
> ```ts title="functions.ts"
> import { Options } from "./quartz/components/ExplorerNode"
> export const mapFn: Options["mapFn"] = (node) => {
> // implement your function here
> }
> export const filterFn: Options["filterFn"] = (node) => {
> // implement your function here
> }
> export const sortFn: Options["sortFn"] = (a, b) => {
> // implement your function here
> }
> ```
>
> You can then import them like this:
>
> ```ts title="quartz.layout.ts"
> import { mapFn, filterFn, sortFn } from "./functions.ts"
> Component.Explorer({
> mapFn: mapFn,
> filterFn: filterFn,
> sortFn: sortFn,
> })
> ```
### Add emoji prefix ### Add emoji prefix
To add emoji prefixes (📁 for folders, 📄 for files), you could use a map function like this: To add emoji prefixes (📁 for folders, 📄 for files), you could use a map function like this:
@@ -244,63 +216,30 @@ Notice how we customized the `order` array here. This is done because the defaul
To fix this, we just changed around the order and apply the `sort` function before changing the display names in the `map` function. To fix this, we just changed around the order and apply the `sort` function before changing the display names in the `map` function.
### Use `sort` with pre-defined sort order > [!tip]
> When writing more complicated functions, the `layout` file can start to look very cramped.
Here's another example where a map containing file/folder names (as slugs) is used to define the sort order of the explorer in quartz. All files/folders that aren't listed inside of `nameOrderMap` will appear at the top of that folders hierarchy level. > You can fix this by defining your functions in another file.
>
It's also worth mentioning, that the smaller the number set in `nameOrderMap`, the higher up the entry will be in the explorer. Incrementing every folder/file by 100, makes ordering files in their folders a lot easier. Lastly, this example still allows you to use a `mapFn` or frontmatter titles to change display names, as it uses slugs for `nameOrderMap` (which is unaffected by display name changes). > ```ts title="functions.ts"
> import { Options } from "./quartz/components/ExplorerNode"
```ts title="quartz.layout.ts" > export const mapFn: Options["mapFn"] = (node) => {
Component.Explorer({ > // implement your function here
sortFn: (a, b) => { > }
const nameOrderMap: Record<string, number> = { > export const filterFn: Options["filterFn"] = (node) => {
"poetry-folder": 100, > // implement your function here
"essay-folder": 200, > }
"research-paper-file": 201, > export const sortFn: Options["sortFn"] = (a, b) => {
"dinosaur-fossils-file": 300, > // implement your function here
"other-folder": 400, > }
} > ```
>
let orderA = 0 > You can then import them like this:
let orderB = 0 >
> ```ts title="quartz.layout.ts"
if (a.file && a.file.slug) { > import { mapFn, filterFn, sortFn } from "./functions.ts"
orderA = nameOrderMap[a.file.slug] || 0 > Component.Explorer({
} else if (a.name) { > mapFn: mapFn,
orderA = nameOrderMap[a.name] || 0 > filterFn: filterFn,
} > sortFn: sortFn,
> })
if (b.file && b.file.slug) { > ```
orderB = nameOrderMap[b.file.slug] || 0
} else if (b.name) {
orderB = nameOrderMap[b.name] || 0
}
return orderA - orderB
},
})
```
For reference, this is how the quartz explorer window would look like with that example:
```
📖 Poetry Folder
📑 Essay Folder
⚗️ Research Paper File
🦴 Dinosaur Fossils File
🔮 Other Folder
```
And this is how the file structure would look like:
```
index.md
poetry-folder
index.md
essay-folder
index.md
research-paper-file.md
dinosaur-fossils-file.md
other-folder
index.md
```

View File

@@ -21,6 +21,5 @@ Want to see what Quartz can do? Here are some cool community gardens:
- [Pedro MC Fernandes's Topo da Mente](https://www.pmcf.xyz/topo-da-mente/) - [Pedro MC Fernandes's Topo da Mente](https://www.pmcf.xyz/topo-da-mente/)
- [Mau Camargo's Notkesto](https://notes.camargomau.com/) - [Mau Camargo's Notkesto](https://notes.camargomau.com/)
- [Caicai's Novels](https://imoko.cc/blog/caicai/) - [Caicai's Novels](https://imoko.cc/blog/caicai/)
- [🌊 Collapsed Wave](https://collapsedwave.com/)
If you want to see your own on here, submit a [Pull Request adding yourself to this file](https://github.com/jackyzha0/quartz/blob/v4/docs/showcase.md)! If you want to see your own on here, submit a [Pull Request adding yourself to this file](https://github.com/jackyzha0/quartz/blob/v4/docs/showcase.md)!

44
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "@jackyzha0/quartz", "name": "@jackyzha0/quartz",
"version": "4.1.4", "version": "4.1.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@jackyzha0/quartz", "name": "@jackyzha0/quartz",
"version": "4.1.4", "version": "4.1.2",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@clack/prompts": "^0.7.0", "@clack/prompts": "^0.7.0",
@@ -32,6 +32,7 @@
"mdast-util-to-hast": "^13.0.2", "mdast-util-to-hast": "^13.0.2",
"mdast-util-to-string": "^4.0.0", "mdast-util-to-string": "^4.0.0",
"micromorph": "^0.4.5", "micromorph": "^0.4.5",
"plausible-tracker": "^0.3.8",
"preact": "^10.19.3", "preact": "^10.19.3",
"preact-render-to-string": "^6.3.1", "preact-render-to-string": "^6.3.1",
"pretty-bytes": "^6.1.1", "pretty-bytes": "^6.1.1",
@@ -40,7 +41,7 @@
"rehype-autolink-headings": "^7.1.0", "rehype-autolink-headings": "^7.1.0",
"rehype-katex": "^7.0.0", "rehype-katex": "^7.0.0",
"rehype-mathjax": "^5.0.0", "rehype-mathjax": "^5.0.0",
"rehype-pretty-code": "^0.12.3", "rehype-pretty-code": "^0.12.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",
@@ -51,10 +52,9 @@
"remark-parse": "^11.0.0", "remark-parse": "^11.0.0",
"remark-rehype": "^11.0.0", "remark-rehype": "^11.0.0",
"remark-smartypants": "^2.0.0", "remark-smartypants": "^2.0.0",
"rfdc": "^1.3.0",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"serve-handler": "^6.1.5", "serve-handler": "^6.1.5",
"shikiji": "^0.9.9", "shikiji": "^0.8.7",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"to-vfile": "^8.0.0", "to-vfile": "^8.0.0",
"toml": "^3.0.0", "toml": "^3.0.0",
@@ -4450,6 +4450,14 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/plausible-tracker": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/plausible-tracker/-/plausible-tracker-0.3.8.tgz",
"integrity": "sha512-lmOWYQ7s9KOUJ1R+YTOR3HrjdbxIS2Z4de0P/Jx2dQPteznJl2eX3tXxKClpvbfyGP59B5bbhW8ftN59HbbFSg==",
"engines": {
"node": ">=10"
}
},
"node_modules/preact": { "node_modules/preact": {
"version": "10.19.3", "version": "10.19.3",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.19.3.tgz", "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.3.tgz",
@@ -4705,9 +4713,9 @@
} }
}, },
"node_modules/rehype-pretty-code": { "node_modules/rehype-pretty-code": {
"version": "0.12.3", "version": "0.12.1",
"resolved": "https://registry.npmjs.org/rehype-pretty-code/-/rehype-pretty-code-0.12.3.tgz", "resolved": "https://registry.npmjs.org/rehype-pretty-code/-/rehype-pretty-code-0.12.1.tgz",
"integrity": "sha512-6NbIit8A3hLrkKBEbNs862jVnTLeIOM2AmM0VZ/MtyHb+OuNMeCa6UZSx6UG4zrobm5tY9efTwhih1exsGYsiw==", "integrity": "sha512-QeGvAPTc31KwP52wBjXqdXXfBAhEs6wETNlOcZRkaPKZZo6Alsz0U6yY05cCKQK53xYNeDCClVYDCyVMl3SP1Q==",
"dependencies": { "dependencies": {
"@types/hast": "^3.0.3", "@types/hast": "^3.0.3",
"hast-util-to-string": "^3.0.0", "hast-util-to-string": "^3.0.0",
@@ -4720,7 +4728,7 @@
"node": ">=18" "node": ">=18"
}, },
"peerDependencies": { "peerDependencies": {
"shikiji": "^0.7.0 || ^0.8.0 || ^0.9.0" "shikiji": "^0.7.0 || ^0.8.0"
} }
}, },
"node_modules/rehype-raw": { "node_modules/rehype-raw": {
@@ -5153,11 +5161,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/rfdc": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz",
"integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA=="
},
"node_modules/rimraf": { "node_modules/rimraf": {
"version": "5.0.5", "version": "5.0.5",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz",
@@ -5311,18 +5314,13 @@
} }
}, },
"node_modules/shikiji": { "node_modules/shikiji": {
"version": "0.9.9", "version": "0.8.7",
"resolved": "https://registry.npmjs.org/shikiji/-/shikiji-0.9.9.tgz", "resolved": "https://registry.npmjs.org/shikiji/-/shikiji-0.8.7.tgz",
"integrity": "sha512-/S3unr/0mZTstNOuAmNDEufeimtqeQb8lXvPMLsYfDvqyfmG6334bO2xmDzD0kfxH2y8gnFgSWAJpdEzksmYXg==", "integrity": "sha512-j5usxwI0yHkDTHOuhuSJl9+wT5CNYeYO82dJMSJBlJ/NYT5SIebGcPoL6y9QOyH15wGrJC4LOP2nz5k8mUDGRQ==",
"dependencies": { "dependencies": {
"shikiji-core": "0.9.9" "hast-util-to-html": "^9.0.0"
} }
}, },
"node_modules/shikiji-core": {
"version": "0.9.9",
"resolved": "https://registry.npmjs.org/shikiji-core/-/shikiji-core-0.9.9.tgz",
"integrity": "sha512-qu5Qq7Co6JIMY312J9Ek6WYjXieeyJT/fIqmkcjF4MdnMNlUnhSqPo8/42g5UdPgdyTCwijS7Nhg8DfLSLodkg=="
},
"node_modules/signal-exit": { "node_modules/signal-exit": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",

View File

@@ -2,7 +2,7 @@
"name": "@jackyzha0/quartz", "name": "@jackyzha0/quartz",
"description": "🌱 publish your digital garden and notes as a website", "description": "🌱 publish your digital garden and notes as a website",
"private": true, "private": true,
"version": "4.1.4", "version": "4.1.3",
"type": "module", "type": "module",
"author": "jackyzha0 <j.zhao2k19@gmail.com>", "author": "jackyzha0 <j.zhao2k19@gmail.com>",
"license": "MIT", "license": "MIT",
@@ -57,6 +57,7 @@
"mdast-util-to-hast": "^13.0.2", "mdast-util-to-hast": "^13.0.2",
"mdast-util-to-string": "^4.0.0", "mdast-util-to-string": "^4.0.0",
"micromorph": "^0.4.5", "micromorph": "^0.4.5",
"plausible-tracker": "^0.3.8",
"preact": "^10.19.3", "preact": "^10.19.3",
"preact-render-to-string": "^6.3.1", "preact-render-to-string": "^6.3.1",
"pretty-bytes": "^6.1.1", "pretty-bytes": "^6.1.1",
@@ -65,7 +66,7 @@
"rehype-autolink-headings": "^7.1.0", "rehype-autolink-headings": "^7.1.0",
"rehype-katex": "^7.0.0", "rehype-katex": "^7.0.0",
"rehype-mathjax": "^5.0.0", "rehype-mathjax": "^5.0.0",
"rehype-pretty-code": "^0.12.3", "rehype-pretty-code": "^0.12.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",
@@ -76,10 +77,9 @@
"remark-parse": "^11.0.0", "remark-parse": "^11.0.0",
"remark-rehype": "^11.0.0", "remark-rehype": "^11.0.0",
"remark-smartypants": "^2.0.0", "remark-smartypants": "^2.0.0",
"rfdc": "^1.3.0",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"serve-handler": "^6.1.5", "serve-handler": "^6.1.5",
"shikiji": "^0.9.9", "shikiji": "^0.8.7",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"to-vfile": "^8.0.0", "to-vfile": "^8.0.0",
"toml": "^3.0.0", "toml": "^3.0.0",

View File

@@ -49,11 +49,11 @@ const config: QuartzConfig = {
Plugin.CreatedModifiedDate({ Plugin.CreatedModifiedDate({
priority: ["frontmatter", "filesystem"], // you can add 'git' here for last modified from Git but this makes the build slower priority: ["frontmatter", "filesystem"], // you can add 'git' here for last modified from Git but this makes the build slower
}), }),
Plugin.Latex({ renderEngine: "katex" }),
Plugin.SyntaxHighlighting(), Plugin.SyntaxHighlighting(),
Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }), Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }),
Plugin.GitHubFlavoredMarkdown(), Plugin.GitHubFlavoredMarkdown(),
Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }), Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }),
Plugin.Latex({ renderEngine: "katex" }),
Plugin.Description(), Plugin.Description(),
], ],
filters: [Plugin.RemoveDrafts()], filters: [Plugin.RemoveDrafts()],

View File

@@ -148,11 +148,8 @@ async function startServing(
await rimraf(argv.output) await rimraf(argv.output)
await emitContent(ctx, filteredContent) await emitContent(ctx, filteredContent)
console.log(chalk.green(`Done rebuilding in ${perf.timeSince()}`)) console.log(chalk.green(`Done rebuilding in ${perf.timeSince()}`))
} catch (err) { } catch {
console.log(chalk.yellow(`Rebuild failed. Waiting on a change to fix the error...`)) console.log(chalk.yellow(`Rebuild failed. Waiting on a change to fix the error...`))
if (argv.verbose) {
console.log(chalk.red(err))
}
} }
release() release()

View File

@@ -7,7 +7,6 @@ export type Analytics =
| null | null
| { | {
provider: "plausible" provider: "plausible"
host?: string
} }
| { | {
provider: "google" provider: "google"

View File

@@ -68,9 +68,8 @@ export default ((opts?: Partial<BreadcrumbOptions>) => {
// construct the index for the first time // construct the index for the first time
for (const file of allFiles) { for (const file of allFiles) {
if (file.slug?.endsWith("index")) { if (file.slug?.endsWith("index")) {
const folderParts = file.slug?.split("/") const folderParts = file.filePath?.split("/")
if (folderParts) { if (folderParts) {
// 2nd last to exclude the /index
const folderName = folderParts[folderParts?.length - 2] const folderName = folderParts[folderParts?.length - 2]
folderIndex.set(folderName, file) folderIndex.set(folderName, file)
} }
@@ -89,10 +88,7 @@ export default ((opts?: Partial<BreadcrumbOptions>) => {
// Try to resolve frontmatter folder title // Try to resolve frontmatter folder title
const currentFile = folderIndex?.get(curPathSegment) const currentFile = folderIndex?.get(curPathSegment)
if (currentFile) { if (currentFile) {
const title = currentFile.frontmatter!.title curPathSegment = currentFile.frontmatter!.title
if (title !== "index") {
curPathSegment = title
}
} }
// Add current slug to full path // Add current slug to full path

View File

@@ -18,7 +18,7 @@ function Darkmode({ displayClass }: QuartzComponentProps) {
x="0px" x="0px"
y="0px" y="0px"
viewBox="0 0 35 35" viewBox="0 0 35 35"
style="enable-background:new 0 0 35 35" style="enable-background:new 0 0 35 35;"
xmlSpace="preserve" xmlSpace="preserve"
> >
<title>Light mode</title> <title>Light mode</title>
@@ -34,7 +34,7 @@ function Darkmode({ displayClass }: QuartzComponentProps) {
x="0px" x="0px"
y="0px" y="0px"
viewBox="0 0 100 100" viewBox="0 0 100 100"
style="enable-background:new 0 0 100 100" style="enable-background='new 0 0 100 100'"
xmlSpace="preserve" xmlSpace="preserve"
> >
<title>Dark mode</title> <title>Dark mode</title>

View File

@@ -12,9 +12,6 @@ const defaultOptions = {
folderClickBehavior: "collapse", folderClickBehavior: "collapse",
folderDefaultState: "collapsed", folderDefaultState: "collapsed",
useSavedState: true, useSavedState: true,
mapFn: (node) => {
return node
},
sortFn: (a, b) => { sortFn: (a, b) => {
// Sort order: folders first, then files. Sort folders and files alphabetically // Sort order: folders first, then files. Sort folders and files alphabetically
if ((!a.file && !b.file) || (a.file && b.file)) { if ((!a.file && !b.file) || (a.file && b.file)) {
@@ -25,7 +22,6 @@ const defaultOptions = {
sensitivity: "base", sensitivity: "base",
}) })
} }
if (a.file && !b.file) { if (a.file && !b.file) {
return 1 return 1
} else { } else {
@@ -45,25 +41,36 @@ export default ((userOpts?: Partial<Options>) => {
let jsonTree: string let jsonTree: string
function constructFileTree(allFiles: QuartzPluginData[]) { function constructFileTree(allFiles: QuartzPluginData[]) {
if (fileTree) { if (!fileTree) {
return
}
// Construct tree from allFiles // Construct tree from allFiles
fileTree = new FileNode("") fileTree = new FileNode("")
allFiles.forEach((file) => fileTree.add(file)) allFiles.forEach((file) => fileTree.add(file, 1))
/**
* Keys of this object must match corresponding function name of `FileNode`,
* while values must be the argument that will be passed to the function.
*
* e.g. entry for FileNode.sort: `sort: opts.sortFn` (value is sort function from options)
*/
const functions = {
map: opts.mapFn,
sort: opts.sortFn,
filter: opts.filterFn,
}
// Execute all functions (sort, filter, map) that were provided (if none were provided, only default "sort" is applied) // Execute all functions (sort, filter, map) that were provided (if none were provided, only default "sort" is applied)
if (opts.order) { if (opts.order) {
// Order is important, use loop with index instead of order.map() // Order is important, use loop with index instead of order.map()
for (let i = 0; i < opts.order.length; i++) { for (let i = 0; i < opts.order.length; i++) {
const functionName = opts.order[i] const functionName = opts.order[i]
if (functionName === "map") { if (functions[functionName]) {
fileTree.map(opts.mapFn) // for every entry in order, call matching function in FileNode and pass matching argument
} else if (functionName === "sort") { // e.g. i = 0; functionName = "filter"
fileTree.sort(opts.sortFn) // converted to: (if opts.filterFn) => fileTree.filter(opts.filterFn)
} else if (functionName === "filter") {
fileTree.filter(opts.filterFn) // @ts-ignore
// typescript cant statically check these dynamic references, so manually make sure reference is valid and ignore warning
fileTree[functionName].call(fileTree, functions[functionName])
} }
} }
} }
@@ -74,6 +81,7 @@ export default ((userOpts?: Partial<Options>) => {
// Stringify to pass json tree as data attribute ([data-tree]) // Stringify to pass json tree as data attribute ([data-tree])
jsonTree = JSON.stringify(folders) jsonTree = JSON.stringify(folders)
} }
}
function Explorer({ allFiles, displayClass, fileData }: QuartzComponentProps) { function Explorer({ allFiles, displayClass, fileData }: QuartzComponentProps) {
constructFileTree(allFiles) constructFileTree(allFiles)
@@ -112,7 +120,6 @@ export default ((userOpts?: Partial<Options>) => {
</div> </div>
) )
} }
Explorer.css = explorerStyle Explorer.css = explorerStyle
Explorer.afterDOMLoaded = script Explorer.afterDOMLoaded = script
return Explorer return Explorer

View File

@@ -1,13 +1,6 @@
// @ts-ignore // @ts-ignore
import { QuartzPluginData } from "../plugins/vfile" import { QuartzPluginData } from "../plugins/vfile"
import { import { resolveRelative } from "../util/path"
joinSegments,
resolveRelative,
clone,
simplifySlug,
SimpleSlug,
FilePath,
} from "../util/path"
type OrderEntries = "sort" | "filter" | "map" type OrderEntries = "sort" | "filter" | "map"
@@ -17,9 +10,9 @@ export interface Options {
folderClickBehavior: "collapse" | "link" folderClickBehavior: "collapse" | "link"
useSavedState: boolean useSavedState: boolean
sortFn: (a: FileNode, b: FileNode) => number sortFn: (a: FileNode, b: FileNode) => number
filterFn: (node: FileNode) => boolean filterFn?: (node: FileNode) => boolean
mapFn: (node: FileNode) => void mapFn?: (node: FileNode) => void
order: OrderEntries[] order?: OrderEntries[]
} }
type DataWrapper = { type DataWrapper = {
@@ -32,74 +25,59 @@ export type FolderState = {
collapsed: boolean collapsed: boolean
} }
function getPathSegment(fp: FilePath | undefined, idx: number): string | undefined {
if (!fp) {
return undefined
}
return fp.split("/").at(idx)
}
// Structure to add all files into a tree // Structure to add all files into a tree
export class FileNode { export class FileNode {
children: Array<FileNode> children: FileNode[]
name: string // this is the slug segment name: string
displayName: string displayName: string
file: QuartzPluginData | null file: QuartzPluginData | null
depth: number depth: number
constructor(slugSegment: string, displayName?: string, file?: QuartzPluginData, depth?: number) { constructor(name: string, file?: QuartzPluginData, depth?: number) {
this.children = [] this.children = []
this.name = slugSegment this.name = name
this.displayName = displayName ?? file?.frontmatter?.title ?? slugSegment this.displayName = name
this.file = file ? clone(file) : null this.file = file ? structuredClone(file) : null
this.depth = depth ?? 0 this.depth = depth ?? 0
} }
private insert(fileData: DataWrapper) { private insert(file: DataWrapper) {
if (fileData.path.length === 0) { if (file.path.length === 1) {
return if (file.path[0] !== "index.md") {
} this.children.push(new FileNode(file.file.frontmatter!.title, file.file, this.depth + 1))
} else {
const nextSegment = fileData.path[0] const title = file.file.frontmatter?.title
if (title && title !== "index" && file.path[0] === "index.md") {
// base case, insert here
if (fileData.path.length === 1) {
if (nextSegment === "") {
// index case (we are the root and we just found index.md), set our data appropriately
const title = fileData.file.frontmatter?.title
if (title && title !== "index") {
this.displayName = title this.displayName = title
} }
}
} else { } else {
// direct child const next = file.path[0]
this.children.push(new FileNode(nextSegment, undefined, fileData.file, this.depth + 1)) file.path = file.path.splice(1)
} for (const child of this.children) {
if (child.name === next) {
child.insert(file)
return return
} }
// find the right child to insert into
fileData.path = fileData.path.splice(1)
const child = this.children.find((c) => c.name === nextSegment)
if (child) {
child.insert(fileData)
return
} }
const newChild = new FileNode( const newChild = new FileNode(next, undefined, this.depth + 1)
nextSegment, newChild.insert(file)
getPathSegment(fileData.file.relativePath, this.depth),
undefined,
this.depth + 1,
)
newChild.insert(fileData)
this.children.push(newChild) this.children.push(newChild)
} }
}
// Add new file to tree // Add new file to tree
add(file: QuartzPluginData) { add(file: QuartzPluginData, splice: number = 0) {
this.insert({ file: file, path: simplifySlug(file.slug!).split("/") }) this.insert({ file, path: file.filePath!.split("/").splice(splice) })
}
// Print tree structure (for debugging)
print(depth: number = 0) {
let folderChar = ""
if (!this.file) folderChar = "|"
console.log("-".repeat(depth), folderChar, this.name, this.depth)
this.children.forEach((e) => e.print(depth + 1))
} }
/** /**
@@ -117,6 +95,7 @@ export class FileNode {
*/ */
map(mapFn: (node: FileNode) => void) { map(mapFn: (node: FileNode) => void) {
mapFn(this) mapFn(this)
this.children.forEach((child) => child.map(mapFn)) this.children.forEach((child) => child.map(mapFn))
} }
@@ -131,16 +110,16 @@ export class FileNode {
const traverse = (node: FileNode, currentPath: string) => { const traverse = (node: FileNode, currentPath: string) => {
if (!node.file) { if (!node.file) {
const folderPath = joinSegments(currentPath, node.name) const folderPath = currentPath + (currentPath ? "/" : "") + node.name
if (folderPath !== "") { if (folderPath !== "") {
folderPaths.push({ path: folderPath, collapsed }) folderPaths.push({ path: folderPath, collapsed })
} }
node.children.forEach((child) => traverse(child, folderPath)) node.children.forEach((child) => traverse(child, folderPath))
} }
} }
traverse(this, "") traverse(this, "")
return folderPaths return folderPaths
} }
@@ -168,13 +147,14 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro
const isDefaultOpen = opts.folderDefaultState === "open" const isDefaultOpen = opts.folderDefaultState === "open"
// Calculate current folderPath // Calculate current folderPath
let pathOld = fullPath ? fullPath : ""
let folderPath = "" let folderPath = ""
if (node.name !== "") { if (node.name !== "") {
folderPath = joinSegments(fullPath ?? "", node.name) folderPath = `${pathOld}/${node.name}`
} }
return ( return (
<> <li>
{node.file ? ( {node.file ? (
// Single file node // Single file node
<li key={node.file.slug}> <li key={node.file.slug}>
@@ -183,7 +163,7 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro
</a> </a>
</li> </li>
) : ( ) : (
<li> <div>
{node.name !== "" && ( {node.name !== "" && (
// Node with entire folder // Node with entire folder
// Render svg button + folder name, then children // Render svg button + folder name, then children
@@ -205,16 +185,12 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro
{/* render <a> tag if folderBehavior is "link", otherwise render <button> with collapse click event */} {/* render <a> tag if folderBehavior is "link", otherwise render <button> with collapse click event */}
<div key={node.name} data-folderpath={folderPath}> <div key={node.name} data-folderpath={folderPath}>
{folderBehavior === "link" ? ( {folderBehavior === "link" ? (
<a <a href={`${folderPath}`} data-for={node.name} class="folder-title">
href={resolveRelative(fileData.slug!, folderPath as SimpleSlug)}
data-for={node.name}
class="folder-title"
>
{node.displayName} {node.displayName}
</a> </a>
) : ( ) : (
<button class="folder-button"> <button class="folder-button">
<span class="folder-title">{node.displayName}</span> <p class="folder-title">{node.displayName}</p>
</button> </button>
)} )}
</div> </div>
@@ -241,8 +217,8 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro
))} ))}
</ul> </ul>
</div> </div>
</li> </div>
)} )}
</> </li>
) )
} }

View File

@@ -23,7 +23,7 @@ export function pageResources(
staticResources: StaticResources, staticResources: StaticResources,
): StaticResources { ): StaticResources {
const contentIndexPath = joinSegments(baseDir, "static/contentIndex.json") const contentIndexPath = joinSegments(baseDir, "static/contentIndex.json")
const contentIndexScript = `const fetchData = fetch("${contentIndexPath}").then(data => data.json())` const contentIndexScript = `const fetchData = fetch(\`${contentIndexPath}\`).then(data => data.json())`
return { return {
css: [joinSegments(baseDir, "index.css"), ...staticResources.css], css: [joinSegments(baseDir, "index.css"), ...staticResources.css],

View File

@@ -59,7 +59,8 @@ function toggleFolder(evt: MouseEvent) {
// Save folder state to localStorage // Save folder state to localStorage
const clickFolderPath = currentFolderParent.dataset.folderpath as string const clickFolderPath = currentFolderParent.dataset.folderpath as string
const fullFolderPath = clickFolderPath // Remove leading "/"
const fullFolderPath = clickFolderPath.substring(1)
toggleCollapsedByPath(explorerState, fullFolderPath) toggleCollapsedByPath(explorerState, fullFolderPath)
const stringifiedFileTree = JSON.stringify(explorerState) const stringifiedFileTree = JSON.stringify(explorerState)
@@ -107,7 +108,9 @@ function setupExplorer() {
explorerState = JSON.parse(storageTree) explorerState = JSON.parse(storageTree)
explorerState.map((folderUl) => { explorerState.map((folderUl) => {
// grab <li> element for matching folder path // grab <li> element for matching folder path
const folderLi = document.querySelector(`[data-folderpath='${folderUl.path}']`) as HTMLElement const folderLi = document.querySelector(
`[data-folderpath='/${folderUl.path}']`,
) as HTMLElement
// Get corresponding content <ul> tag and set state // Get corresponding content <ul> tag and set state
if (folderLi) { if (folderLi) {

View File

@@ -0,0 +1,3 @@
import Plausible from "plausible-tracker"
const { trackPageview } = Plausible()
document.addEventListener("nav", () => trackPageview())

View File

@@ -106,7 +106,7 @@ svg {
align-items: center; align-items: center;
font-family: var(--headerFont); font-family: var(--headerFont);
& span { & p {
font-size: 0.95rem; font-size: 0.95rem;
display: inline-block; display: inline-block;
color: var(--secondary); color: var(--secondary);

View File

@@ -4,6 +4,8 @@ import { QuartzEmitterPlugin } from "../types"
// @ts-ignore // @ts-ignore
import spaRouterScript from "../../components/scripts/spa.inline" import spaRouterScript from "../../components/scripts/spa.inline"
// @ts-ignore // @ts-ignore
import plausibleScript from "../../components/scripts/plausible.inline"
// @ts-ignore
import popoverScript from "../../components/scripts/popover.inline" import popoverScript from "../../components/scripts/popover.inline"
import styles from "../../styles/custom.scss" import styles from "../../styles/custom.scss"
import popoverStyle from "../../components/styles/popover.scss" import popoverStyle from "../../components/styles/popover.scss"
@@ -12,7 +14,6 @@ import { StaticResources } from "../../util/resources"
import { QuartzComponent } from "../../components/types" import { QuartzComponent } from "../../components/types"
import { googleFontHref, joinStyles } from "../../util/theme" import { googleFontHref, joinStyles } from "../../util/theme"
import { Features, transform } from "lightningcss" import { Features, transform } from "lightningcss"
import { transform as transpile } from "esbuild"
type ComponentResources = { type ComponentResources = {
css: string[] css: string[]
@@ -55,16 +56,9 @@ function getComponentResources(ctx: BuildCtx): ComponentResources {
} }
} }
async function joinScripts(scripts: string[]): Promise<string> { function joinScripts(scripts: string[]): string {
// wrap with iife to prevent scope collision // wrap with iife to prevent scope collision
const script = scripts.map((script) => `(function () {${script}})();`).join("\n") return scripts.map((script) => `(function () {${script}})();`).join("\n")
// minify with esbuild
const res = await transpile(script, {
minify: true,
})
return res.code
} }
function addGlobalPageResources( function addGlobalPageResources(
@@ -91,30 +85,17 @@ function addGlobalPageResources(
componentResources.afterDOMLoaded.push(` componentResources.afterDOMLoaded.push(`
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); } function gtag() { dataLayer.push(arguments); }
gtag("js", new Date()); gtag(\`js\`, new Date());
gtag("config", "${tagId}", { send_page_view: false }); gtag(\`config\`, \`${tagId}\`, { send_page_view: false });
document.addEventListener("nav", () => { document.addEventListener(\`nav\`, () => {
gtag("event", "page_view", { gtag(\`event\`, \`page_view\`, {
page_title: document.title, page_title: document.title,
page_location: location.href, page_location: location.href,
}); });
});`) });`)
} else if (cfg.analytics?.provider === "plausible") { } else if (cfg.analytics?.provider === "plausible") {
const plausibleHost = cfg.analytics.host ?? "https://plausible.io" componentResources.afterDOMLoaded.push(plausibleScript)
componentResources.afterDOMLoaded.push(`
const plausibleScript = document.createElement("script")
plausibleScript.src = "${plausibleHost}/js/script.manual.js"
plausibleScript.setAttribute("data-domain", location.hostname)
plausibleScript.defer = true
document.head.appendChild(plausibleScript)
window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }
document.addEventListener("nav", () => {
plausible("pageview")
})
`)
} else if (cfg.analytics?.provider === "umami") { } else if (cfg.analytics?.provider === "umami") {
componentResources.afterDOMLoaded.push(` componentResources.afterDOMLoaded.push(`
const umamiScript = document.createElement("script") const umamiScript = document.createElement("script")
@@ -184,11 +165,8 @@ export const ComponentResources: QuartzEmitterPlugin<Options> = (opts?: Partial<
addGlobalPageResources(ctx, resources, componentResources) addGlobalPageResources(ctx, resources, componentResources)
const stylesheet = joinStyles(ctx.cfg.configuration.theme, ...componentResources.css, styles) const stylesheet = joinStyles(ctx.cfg.configuration.theme, ...componentResources.css, styles)
const [prescript, postscript] = await Promise.all([ const prescript = joinScripts(componentResources.beforeDOMLoaded)
joinScripts(componentResources.beforeDOMLoaded), const postscript = joinScripts(componentResources.afterDOMLoaded)
joinScripts(componentResources.afterDOMLoaded),
])
const fps = await Promise.all([ const fps = await Promise.all([
emit({ emit({
slug: "index" as FullSlug, slug: "index" as FullSlug,

View File

@@ -2,7 +2,7 @@ import { Root } from "hast"
import { GlobalConfiguration } from "../../cfg" import { GlobalConfiguration } from "../../cfg"
import { getDate } from "../../components/Date" import { getDate } from "../../components/Date"
import { escapeHTML } from "../../util/escape" import { escapeHTML } from "../../util/escape"
import { FilePath, FullSlug, SimpleSlug, joinSegments, simplifySlug } from "../../util/path" import { FilePath, FullSlug, SimpleSlug, simplifySlug } from "../../util/path"
import { QuartzEmitterPlugin } from "../types" import { QuartzEmitterPlugin } from "../types"
import { toHtml } from "hast-util-to-html" import { toHtml } from "hast-util-to-html"
import path from "path" import path from "path"
@@ -37,7 +37,7 @@ const defaultOptions: Options = {
function generateSiteMap(cfg: GlobalConfiguration, idx: ContentIndex): string { function generateSiteMap(cfg: GlobalConfiguration, idx: ContentIndex): string {
const base = cfg.baseUrl ?? "" const base = cfg.baseUrl ?? ""
const createURLEntry = (slug: SimpleSlug, content: ContentDetails): string => `<url> const createURLEntry = (slug: SimpleSlug, content: ContentDetails): string => `<url>
<loc>https://${joinSegments(base, encodeURI(slug))}</loc> <loc>https://${base}/${encodeURI(slug)}</loc>
<lastmod>${content.date?.toISOString()}</lastmod> <lastmod>${content.date?.toISOString()}</lastmod>
</url>` </url>`
const urls = Array.from(idx) const urls = Array.from(idx)
@@ -52,8 +52,8 @@ function generateRSSFeed(cfg: GlobalConfiguration, idx: ContentIndex, limit?: nu
const createURLEntry = (slug: SimpleSlug, content: ContentDetails): string => `<item> const createURLEntry = (slug: SimpleSlug, content: ContentDetails): string => `<item>
<title>${escapeHTML(content.title)}</title> <title>${escapeHTML(content.title)}</title>
<link>${joinSegments(root, encodeURI(slug))}</link> <link>${root}/${encodeURI(slug)}</link>
<guid>${joinSegments(root, encodeURI(slug))}</guid> <guid>${root}/${encodeURI(slug)}</guid>
<description>${content.richContent ?? content.description}</description> <description>${content.richContent ?? content.description}</description>
<pubDate>${content.date?.toUTCString()}</pubDate> <pubDate>${content.date?.toUTCString()}</pubDate>
</item>` </item>`

View File

@@ -30,6 +30,5 @@ declare module "vfile" {
interface DataMap { interface DataMap {
slug: FullSlug slug: FullSlug
filePath: FilePath filePath: FilePath
relativePath: FilePath
} }
} }

View File

@@ -49,21 +49,13 @@ export const FrontMatter: QuartzTransformerPlugin<Partial<Options> | undefined>
data.title = file.stem ?? "Untitled" data.title = file.stem ?? "Untitled"
} }
if (data.tags) { if (data.tags && !Array.isArray(data.tags)) {
// coerce to array
if (!Array.isArray(data.tags)) {
data.tags = data.tags data.tags = data.tags
.toString() .toString()
.split(oneLineTagDelim) .split(oneLineTagDelim)
.map((tag: string) => tag.trim()) .map((tag: string) => tag.trim())
} }
// remove all non-string tags
data.tags = data.tags
.filter((tag: unknown) => typeof tag === "string" || typeof tag === "number")
.map((tag: string | number) => tag.toString())
}
// slug them all!! // slug them all!!
data.tags = [...new Set(data.tags?.map((tag: string) => slugTag(tag)))] data.tags = [...new Set(data.tags?.map((tag: string) => slugTag(tag)))]

View File

@@ -12,7 +12,6 @@ import {
import path from "path" import path from "path"
import { visit } from "unist-util-visit" import { visit } from "unist-util-visit"
import isAbsoluteUrl from "is-absolute-url" import isAbsoluteUrl from "is-absolute-url"
import { Root } from "hast"
interface Options { interface Options {
/** How to resolve Markdown paths */ /** How to resolve Markdown paths */
@@ -20,14 +19,12 @@ interface Options {
/** Strips folders from a link so that it looks nice */ /** Strips folders from a link so that it looks nice */
prettyLinks: boolean prettyLinks: boolean
openLinksInNewTab: boolean openLinksInNewTab: boolean
lazyLoad: boolean
} }
const defaultOptions: Options = { const defaultOptions: Options = {
markdownLinkResolution: "absolute", markdownLinkResolution: "absolute",
prettyLinks: true, prettyLinks: true,
openLinksInNewTab: false, openLinksInNewTab: false,
lazyLoad: false,
} }
export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => { export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
@@ -37,7 +34,7 @@ export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> =
htmlPlugins(ctx) { htmlPlugins(ctx) {
return [ return [
() => { () => {
return (tree: Root, file) => { return (tree, file) => {
const curSlug = simplifySlug(file.data.slug!) const curSlug = simplifySlug(file.data.slug!)
const outgoing: Set<SimpleSlug> = new Set() const outgoing: Set<SimpleSlug> = new Set()
@@ -54,8 +51,8 @@ export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> =
typeof node.properties.href === "string" typeof node.properties.href === "string"
) { ) {
let dest = node.properties.href as RelativeURL let dest = node.properties.href as RelativeURL
const classes = (node.properties.className ?? []) as string[] node.properties.className ??= []
classes.push(isAbsoluteUrl(dest) ? "external" : "internal") node.properties.className.push(isAbsoluteUrl(dest) ? "external" : "internal")
// Check if the link has alias text // Check if the link has alias text
if ( if (
@@ -64,9 +61,8 @@ export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> =
node.children[0].value !== dest node.children[0].value !== dest
) { ) {
// Add the 'alias' class if the text content is not the same as the href // Add the 'alias' class if the text content is not the same as the href
classes.push("alias") node.properties.className.push("alias")
} }
node.properties.className = classes
if (opts.openLinksInNewTab) { if (opts.openLinksInNewTab) {
node.properties.target = "_blank" node.properties.target = "_blank"
@@ -115,10 +111,6 @@ export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> =
node.properties && node.properties &&
typeof node.properties.src === "string" typeof node.properties.src === "string"
) { ) {
if (opts.lazyLoad) {
node.properties.loading = "lazy"
}
if (!isAbsoluteUrl(node.properties.src)) { if (!isAbsoluteUrl(node.properties.src)) {
let dest = node.properties.src as RelativeURL let dest = node.properties.src as RelativeURL
dest = node.properties.src = transformLink( dest = node.properties.src = transformLink(

View File

@@ -1,5 +1,5 @@
import { QuartzTransformerPlugin } from "../types" import { QuartzTransformerPlugin } from "../types"
import { Root, Html, BlockContent, DefinitionContent, Paragraph, Code } from "mdast" import { Root, Html, BlockContent, DefinitionContent, Code, Paragraph } from "mdast"
import { Element, Literal, Root as HtmlRoot } from "hast" import { Element, Literal, Root as HtmlRoot } from "hast"
import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace"
import { slug as slugAnchor } from "github-slugger" import { slug as slugAnchor } from "github-slugger"
@@ -105,8 +105,6 @@ function canonicalizeCallout(calloutName: string): keyof typeof callouts {
return calloutMapping[callout] ?? "note" return calloutMapping[callout] ?? "note"
} }
export const externalLinkRegex = /^https?:\/\//i
// !? -> optional embedding // !? -> optional embedding
// \[\[ -> open brace // \[\[ -> open brace
// ([^\[\]\|\#]+) -> one or more non-special characters ([,],|, or #) (name) // ([^\[\]\|\#]+) -> one or more non-special characters ([,],|, or #) (name)
@@ -160,19 +158,13 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
} }
src = src.replaceAll(wikilinkRegex, (value, ...capture) => { src = src.replaceAll(wikilinkRegex, (value, ...capture) => {
const [rawFp, rawHeader, rawAlias]: (string | undefined)[] = capture const [rawFp, rawHeader, rawAlias] = capture
const fp = rawFp ?? "" const fp = rawFp ?? ""
const anchor = rawHeader?.trim().replace(/^#+/, "") const anchor = rawHeader?.trim().replace(/^#+/, "")
const blockRef = Boolean(anchor?.startsWith("^")) ? "^" : "" const blockRef = Boolean(anchor?.startsWith("^")) ? "^" : ""
const displayAnchor = anchor ? `#${blockRef}${slugAnchor(anchor)}` : "" const displayAnchor = anchor ? `#${blockRef}${slugAnchor(anchor)}` : ""
const displayAlias = rawAlias ?? rawHeader?.replace("#", "|") ?? "" const displayAlias = rawAlias ?? rawHeader?.replace("#", "|") ?? ""
const embedDisplay = value.startsWith("!") ? "!" : "" const embedDisplay = value.startsWith("!") ? "!" : ""
if (rawFp?.match(externalLinkRegex)) {
return `${embedDisplay}[${displayAlias.replace(/^\|/, "")}](${rawFp})`
}
return `${embedDisplay}[[${fp}${displayAnchor}${displayAlias}]]` return `${embedDisplay}[[${fp}${displayAnchor}${displayAlias}]]`
}) })
} }
@@ -451,12 +443,11 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
}, },
htmlPlugins() { htmlPlugins() {
const plugins: PluggableList = [rehypeRaw] const plugins: PluggableList = [rehypeRaw]
if (opts.parseBlockReferences) { if (opts.parseBlockReferences) {
plugins.push(() => { plugins.push(() => {
const inlineTagTypes = new Set(["p", "li"]) const inlineTagTypes = new Set(["p", "li"])
const blockTagTypes = new Set(["blockquote"]) const blockTagTypes = new Set(["blockquote"])
return (tree: HtmlRoot, file) => { return (tree, file) => {
file.data.blocks = {} file.data.blocks = {}
visit(tree, "element", (node, index, parent) => { visit(tree, "element", (node, index, parent) => {

View File

@@ -91,9 +91,8 @@ export function createFileParser(ctx: BuildCtx, fps: FilePath[]) {
} }
// base data properties that plugins may use // base data properties that plugins may use
file.data.filePath = file.path as FilePath file.data.slug = slugifyFilePath(path.posix.relative(argv.directory, file.path) as FilePath)
file.data.relativePath = path.posix.relative(argv.directory, file.path) as FilePath file.data.filePath = fp
file.data.slug = slugifyFilePath(file.data.relativePath)
const ast = processor.parse(file) const ast = processor.parse(file)
const newAst = await processor.run(ast, file) const newAst = await processor.run(ast, file)

View File

@@ -328,7 +328,7 @@ figure[data-rehype-pretty-code-figure] {
pre { pre {
font-family: var(--codeFont); font-family: var(--codeFont);
padding: 0 0.5rem; padding: 0.5rem;
border-radius: 5px; border-radius: 5px;
overflow-x: auto; overflow-x: auto;
border: 1px solid var(--lightgray); border: 1px solid var(--lightgray);

View File

@@ -1,9 +1,5 @@
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 rfdc from "rfdc"
export const clone = rfdc()
// 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"
@@ -125,8 +121,7 @@ const _rebaseHastElement = (
} }
} }
export function normalizeHastElement(rawEl: HastElement, curBase: FullSlug, newBase: FullSlug) { export function normalizeHastElement(el: HastElement, curBase: FullSlug, newBase: FullSlug) {
const el = clone(rawEl) // clone so we dont modify the original page
_rebaseHastElement(el, "src", curBase, newBase) _rebaseHastElement(el, "src", curBase, newBase)
_rebaseHastElement(el, "href", curBase, newBase) _rebaseHastElement(el, "href", curBase, newBase)
if (el.children) { if (el.children) {

View File

@@ -26,12 +26,9 @@ export function JSResourceToScriptElement(resource: JSResource, preserve?: boole
} else { } else {
const content = resource.script const content = resource.script
return ( return (
<script <script key={randomUUID()} type={scriptType} spa-preserve={spaPreserve}>
key={randomUUID()} {content}
type={scriptType} </script>
spa-preserve={spaPreserve}
dangerouslySetInnerHTML={{ __html: content }}
></script>
) )
} }
} }