mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-12-01 02:07:55 +01:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64c7851939 | ||
|
|
ea6208c1f0 | ||
|
|
78b33fc2fb | ||
|
|
d2be097b76 | ||
|
|
ad1f964a5f | ||
|
|
150050f379 | ||
|
|
d979331dc7 | ||
|
|
972cf0a887 | ||
|
|
14e6b13ff1 | ||
|
|
3c01b92cc4 | ||
|
|
ed9bd43d9f | ||
|
|
c35818c336 | ||
|
|
a464ae5029 | ||
|
|
66e297c0ea | ||
|
|
4442847b37 | ||
|
|
e6b5ca33c9 | ||
|
|
1b92440009 | ||
|
|
c6546903f2 | ||
|
|
2c69b0c97d | ||
|
|
a7e20804f5 | ||
|
|
5196f3b9db | ||
|
|
f0ec6c9b92 | ||
|
|
9c88d5967f |
@@ -34,6 +34,8 @@ Component.Graph({
|
||||
linkDistance: 30, // how long should the links be by default?
|
||||
fontSize: 0.6, // what size should the node labels be?
|
||||
opacityScale: 1, // how quickly do we fade out the labels when zooming out?
|
||||
removeTags: [], // what tags to remove from the graph
|
||||
showTags: true, // whether to show tags in the graph
|
||||
},
|
||||
globalGraph: {
|
||||
drag: true,
|
||||
@@ -45,6 +47,8 @@ Component.Graph({
|
||||
linkDistance: 30,
|
||||
fontSize: 0.6,
|
||||
opacityScale: 1,
|
||||
removeTags: [], // what tags to remove from the graph
|
||||
showTags: true, // whether to show tags in the graph
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
@@ -14,3 +14,11 @@ This is enabled as a part of [[Obsidian compatibility]] and can be configured an
|
||||
- `[[Path to file | Here's the title override]]`: produces a link to `Path to file.md` with the text `Here's the title override`
|
||||
- `[[Path to file#Anchor]]`: produces a link to the anchor `Anchor` in the file `Path to file.md`
|
||||
- `[[Path to file#^block-ref]]`: produces a link to the specific block `block-ref` in the file `Path to file.md`
|
||||
|
||||
### Embeds
|
||||
|
||||
- `![[Path to image]]`: embeds an image into the page
|
||||
- `![[Path to image|100x145]]`: embeds an image into the page with dimensions 100px by 145px
|
||||
- `![[Path to file]]`: transclude an entire page
|
||||
- `![[Path to file#Anchor]]`: transclude everything under the header `Anchor`
|
||||
- `![[Path to file#^b15695]]`: transclude block with ID `^b15695`
|
||||
|
||||
@@ -4,7 +4,10 @@ title: Hosting
|
||||
|
||||
Quartz effectively turns your Markdown files and other resources into a bundle of HTML, JS, and CSS files (a website!).
|
||||
|
||||
However, if you'd like to publish your site to the world, you need a way to host it online. This guide will detail how to deploy with either GitHub Pages or Cloudflare pages but any service that allows you to deploy static HTML should work as well (e.g. Netlify, Replit, etc.)
|
||||
However, if you'd like to publish your site to the world, you need a way to host it online. This guide will detail how to deploy with common hosting providers but any service that allows you to deploy static HTML should work as well.
|
||||
|
||||
> [!warning]
|
||||
> The rest of this guide assumes that you've already created your own GitHub repository for Quartz. If you haven't already, [[setting up your GitHub repository|make sure you do so]].
|
||||
|
||||
> [!hint]
|
||||
> Some Quartz features (like [[RSS Feed]] and sitemap generation) require `baseUrl` to be configured properly in your [[configuration]] to work properly. Make sure you set this before deploying!
|
||||
@@ -26,12 +29,10 @@ Press "Save and deploy" and Cloudflare should have a deployed version of your si
|
||||
|
||||
To add a custom domain, check out [Cloudflare's documentation](https://developers.cloudflare.com/pages/platform/custom-domains/).
|
||||
|
||||
## GitHub Pages
|
||||
|
||||
Like Quartz 3, you can deploy the site generated by Quartz 4 via GitHub Pages.
|
||||
|
||||
> [!warning]
|
||||
> Quartz generates files in the format of `file.html` instead of `file/index.html` which means the trailing slashes for _non-folder paths_ are dropped. As GitHub pages does not do this redirect, this may cause existing links to your site that use trailing slashes to break. If not breaking existing links is important to you, consider using [[#Cloudflare Pages]].
|
||||
> Cloudflare Pages only allows shallow `git` clones so if you rely on `git` for timestamps, it is recommended you either add dates to your frontmatter (see [[authoring content#Syntax]]) or use another hosting provider.
|
||||
|
||||
## GitHub Pages
|
||||
|
||||
In your local Quartz, create a new file `quartz/.github/workflows/deploy.yml`.
|
||||
|
||||
@@ -93,6 +94,9 @@ Then:
|
||||
>
|
||||
> You can do this by going to your Settings page on your GitHub fork and going to the Environments tab and pressing the trash icon. The GitHub action will recreate the environment for you correctly the next time you sync your Quartz.
|
||||
|
||||
> [!info]
|
||||
> Quartz generates files in the format of `file.html` instead of `file/index.html` which means the trailing slashes for _non-folder paths_ are dropped. As GitHub pages does not do this redirect, this may cause existing links to your site that use trailing slashes to break. If not breaking existing links is important to you (e.g. you are migrating from Quartz 3), consider using [[#Cloudflare Pages]].
|
||||
|
||||
### Custom Domain
|
||||
|
||||
Here's how to add a custom domain to your GitHub pages deployment.
|
||||
@@ -169,8 +173,6 @@ Using `docs.example.com` is an example of a subdomain. They're a simple way of c
|
||||
|
||||
## Netlify
|
||||
|
||||
Like Vercel, you can also deploy the site generated by Quartz 4 via Netlify.
|
||||
|
||||
1. Log in to the [Netlify dashboard](https://app.netlify.com/) and click "Add new site".
|
||||
2. Select your Git provider and repository containing your Quartz project.
|
||||
3. Under "Build command", enter `npx quartz build`.
|
||||
@@ -180,8 +182,6 @@ Like Vercel, you can also deploy the site generated by Quartz 4 via Netlify.
|
||||
|
||||
## GitLab Pages
|
||||
|
||||
You can configure GitLab CI to build and deploy a Quartz 4 project.
|
||||
|
||||
In your local Quartz, create a new file `.gitlab-ci.yaml`.
|
||||
|
||||
```yaml title=".gitlab-ci.yaml"
|
||||
@@ -203,8 +203,6 @@ build:
|
||||
- hash -r
|
||||
- npm ci
|
||||
script:
|
||||
- npx prettier --write .
|
||||
- npm run check
|
||||
- npx quartz build
|
||||
artifacts:
|
||||
paths:
|
||||
@@ -227,6 +225,6 @@ pages:
|
||||
- public
|
||||
```
|
||||
|
||||
When `.gitlab-ci.yaml` is commited, GitLab will build and deploy the website as a GitLab Page. You can find the url under `Deploy` -> `Pages` in the sidebar.
|
||||
When `.gitlab-ci.yaml` is commited, GitLab will build and deploy the website as a GitLab Page. You can find the url under `Deploy > Pages` in the sidebar.
|
||||
|
||||
By default, the page is private and only visible when logged in to a GitLab account with access to the repository but can be opened in the settings under `Deploy` -> `Pages`.
|
||||
|
||||
BIN
docs/images/github-init-repo-options.png
Normal file
BIN
docs/images/github-init-repo-options.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 90 KiB |
BIN
docs/images/github-quick-setup.png
Normal file
BIN
docs/images/github-quick-setup.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 150 KiB |
@@ -2,7 +2,7 @@
|
||||
title: Welcome to Quartz 4
|
||||
---
|
||||
|
||||
Quartz is a fast, batteries-included static-site generator that transforms Markdown content into fully functional websites. Thousands of students, developers, and teachers are [[showcase|already using Quartz]] to publish personal notes, wikis, and [digital gardens](https://jzhao.xyz/posts/networked-thought) to the web.
|
||||
Quartz is a fast, batteries-included static-site generator that transforms Markdown content into fully functional websites. Thousands of students, developers, and teachers are [[showcase|already using Quartz]] to publish personal notes, websites, and [digital gardens](https://jzhao.xyz/posts/networked-thought) to the web.
|
||||
|
||||
## 🪴 Get Started
|
||||
|
||||
@@ -19,7 +19,7 @@ npx quartz create
|
||||
|
||||
This will guide you through initializing your Quartz with content. Once you've done so, see how to:
|
||||
|
||||
1. [[authoring content|Author content]] in Quartz
|
||||
1. [[authoring content|Writing content]] in Quartz
|
||||
2. [[configuration|Configure]] Quartz's behaviour
|
||||
3. Change Quartz's [[layout]]
|
||||
4. [[build|Build and preview]] Quartz
|
||||
|
||||
@@ -8,7 +8,9 @@ title: Philosophy of Quartz
|
||||
>
|
||||
> _(The Garden and the Stream)_
|
||||
|
||||
The problem with the file cabinet is that it focuses on efficiency of access and interoperability rather than generativity and creativity. Thinking is not linear, nor is it hierarchical. In fact, not many things are linear or hierarchical at all. Then why is it that most tools and thinking strategies assume a nice chronological or hierarchical order for my thought processes? The ideal tool for thought for me would embrace the messiness of my mind, and organically help insights emerge from chaos instead of forcing an artificial order. A rhizomatic, not arboresecent, form of note taking.
|
||||
The problem with the file cabinet is that it focuses on efficiency of access and interoperability rather than generativity and creativity. Thinking is not linear, nor is it hierarchical. In fact, not many things are linear or hierarchical at all. Then why is it that most tools and thinking strategies assume a nice chronological or hierarchical order for my thought processes?
|
||||
|
||||
The ideal tool for thought for me would embrace the messiness of my mind, and organically help insights emerge from chaos instead of forcing an artificial order. A rhizomatic, not arboresecent, form of note taking.
|
||||
|
||||
My goal with a digital garden is not purely as an organizing system and information store (though it works nicely for that). I want my digital garden to be a playground for new ways ideas can connect together. As a result, existing formal organizing systems like Zettelkasten or the hierarchical folder structures of Notion don’t work well for me. There is way too much upfront friction that by the time I’ve thought about how to organize my thought into folders categories, I’ve lost it.
|
||||
|
||||
@@ -25,4 +27,21 @@ Quartz is designed first and foremost as a tool for publishing [digital gardens]
|
||||
> “[One] who works with the door open gets all kinds of interruptions, but [they] also occasionally gets clues as to what the world is and what might be important.”
|
||||
> — Richard Hamming
|
||||
|
||||
**The goal of Quartz is to make sharing your digital garden free and simple.** At its core, Quartz is designed to be easy to use enough for non-technical people to get going but also powerful enough that senior developers can tweak it to work how they'd like it to work.
|
||||
**The goal of Quartz is to make sharing your digital garden free and simple.**
|
||||
|
||||
---
|
||||
|
||||
## A garden should be your own
|
||||
|
||||
At its core, Quartz is designed to be easy to use enough for non-technical people to get going but also powerful enough that senior developers can tweak it to work how they'd like it to work.
|
||||
|
||||
1. If you like the default configuration of Quartz and just want to change the content, the only thing that you need to change is the contents of the `content` folder.
|
||||
2. If you'd like to make basic configuration tweaks but don't want to edit source code, one can tweak the plugins and components in `quartz.config.ts` and `quartz.layout.ts` in a guided manner to their liking.
|
||||
3. If you'd like to tweak the actual source code of the underlying plugins, components, or even build process, Quartz purposefully ships its full source code to the end user to allow customization at this level too.
|
||||
|
||||
Most software either confines you to either
|
||||
|
||||
1. Makes it easy to tweak content but not the presentation
|
||||
2. Gives you too many knobs to tune the presentation without good opinionated defaults
|
||||
|
||||
**Quartz should feel powerful but ultimately be an intuitive tool fully within your control.** It should be a piece of [agentic software](https://jzhao.xyz/posts/agentic-computing). Ultimately, it should have the right affordances to nudge users towards good defaults but never dictate what the 'correct' way of using it is.
|
||||
|
||||
39
docs/setting up your GitHub repository.md
Normal file
39
docs/setting up your GitHub repository.md
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
title: Setting up your GitHub repository
|
||||
---
|
||||
|
||||
First, make sure you have Quartz [[index#🪴 Get Started|cloned and setup locally]].
|
||||
|
||||
Then, create a new repository on GitHub.com. Do **not** initialize the new repository with `README`, license, or `gitignore` files.
|
||||
|
||||
![[github-init-repo-options.png]]
|
||||
|
||||
At the top of your repository on GitHub.com's Quick Setup page, click the clipboard to copy the remote repository URL.
|
||||
|
||||
![[github-quick-setup.png]]
|
||||
|
||||
In your terminal of choice, navigate to the root of your Quartz folder. Then, run the following commands, replacing `REMOTE-URL` with the URL you just copied from the previous step.
|
||||
|
||||
```bash
|
||||
# add your repository
|
||||
git remote add origin REMOTE-URL
|
||||
|
||||
# track the main quartz repository for updates
|
||||
git remote add upstream https://github.com/jackyzha0/quartz.git
|
||||
```
|
||||
|
||||
To verify that you set the remote URL correctly, run the following command.
|
||||
|
||||
```bash
|
||||
git remote -v
|
||||
```
|
||||
|
||||
Then, you can sync the content to upload it to your repository.
|
||||
|
||||
```bash
|
||||
npx quartz sync --no-pull
|
||||
```
|
||||
|
||||
> [!hint]
|
||||
> If `npx quartz sync` fails with `fatal: --[no-]autostash option is only valid with --rebase`, you
|
||||
> may have an outdated version of `git`. Updating `git` should fix this issue.
|
||||
@@ -6,9 +6,9 @@ Want to see what Quartz can do? Here are some cool community gardens:
|
||||
|
||||
- [Quartz Documentation (this site!)](https://quartz.jzhao.xyz/)
|
||||
- [Jacky Zhao's Garden](https://jzhao.xyz/)
|
||||
- [Socratica Toolbox](https://toolbox.socratica.info/)
|
||||
- [Brandon Boswell's Garden](https://brandonkboswell.com)
|
||||
- [Scaling Synthesis - A hypertext research notebook](https://scalingsynthesis.com/)
|
||||
- [AWAGMI Intern Notes](https://notes.awagmi.xyz/)
|
||||
- [Data Dictionary 🧠](https://glossary.airbyte.com/)
|
||||
- [sspaeti.com's Second Brain](https://brain.sspaeti.com/)
|
||||
- [oldwinter の数字花园](https://garden.oldwinter.top/)
|
||||
|
||||
3687
package-lock.json
generated
3687
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
99
package.json
99
package.json
@@ -2,7 +2,7 @@
|
||||
"name": "@jackyzha0/quartz",
|
||||
"description": "🌱 publish your digital garden and notes as a website",
|
||||
"private": true,
|
||||
"version": "4.1.2",
|
||||
"version": "4.1.3",
|
||||
"type": "module",
|
||||
"author": "jackyzha0 <j.zhao2k19@gmail.com>",
|
||||
"license": "MIT",
|
||||
@@ -34,76 +34,77 @@
|
||||
"quartz": "./quartz/bootstrap-cli.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^0.6.3",
|
||||
"@floating-ui/dom": "^1.4.0",
|
||||
"@clack/prompts": "^0.7.0",
|
||||
"@floating-ui/dom": "^1.5.3",
|
||||
"@napi-rs/simple-git": "0.1.9",
|
||||
"async-mutex": "^0.4.0",
|
||||
"chalk": "^4.1.2",
|
||||
"chalk": "^5.3.0",
|
||||
"chokidar": "^3.5.3",
|
||||
"cli-spinner": "^0.2.10",
|
||||
"d3": "^7.8.5",
|
||||
"esbuild-sass-plugin": "^2.12.0",
|
||||
"esbuild-sass-plugin": "^2.16.0",
|
||||
"flexsearch": "0.7.21",
|
||||
"github-slugger": "^2.0.0",
|
||||
"globby": "^13.1.4",
|
||||
"globby": "^14.0.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
"hast-util-to-html": "^8.0.4",
|
||||
"hast-util-to-jsx-runtime": "^1.2.0",
|
||||
"hast-util-to-string": "^2.0.0",
|
||||
"hast-util-to-html": "^9.0.0",
|
||||
"hast-util-to-jsx-runtime": "^2.3.0",
|
||||
"hast-util-to-string": "^3.0.0",
|
||||
"is-absolute-url": "^4.0.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lightningcss": "1.21.7",
|
||||
"mdast-util-find-and-replace": "^2.2.2",
|
||||
"mdast-util-to-hast": "^12.3.0",
|
||||
"mdast-util-to-string": "^3.2.0",
|
||||
"lightningcss": "^1.22.1",
|
||||
"mdast-util-find-and-replace": "^3.0.1",
|
||||
"mdast-util-to-hast": "^13.0.2",
|
||||
"mdast-util-to-string": "^4.0.0",
|
||||
"micromorph": "^0.4.5",
|
||||
"plausible-tracker": "^0.3.8",
|
||||
"preact": "^10.14.1",
|
||||
"preact-render-to-string": "^6.0.3",
|
||||
"pretty-bytes": "^6.1.0",
|
||||
"preact": "^10.19.3",
|
||||
"preact-render-to-string": "^6.3.1",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pretty-time": "^1.1.0",
|
||||
"reading-time": "^1.5.0",
|
||||
"rehype-autolink-headings": "^6.1.1",
|
||||
"rehype-katex": "^6.0.3",
|
||||
"rehype-mathjax": "^4.0.3",
|
||||
"rehype-pretty-code": "^0.10.0",
|
||||
"rehype-raw": "^6.1.1",
|
||||
"rehype-slug": "^5.1.0",
|
||||
"remark": "^14.0.2",
|
||||
"remark-breaks": "^3.0.3",
|
||||
"remark-frontmatter": "^4.0.1",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-math": "^5.1.1",
|
||||
"remark-parse": "^10.0.1",
|
||||
"remark-rehype": "^10.1.0",
|
||||
"rehype-autolink-headings": "^7.1.0",
|
||||
"rehype-katex": "^7.0.0",
|
||||
"rehype-mathjax": "^5.0.0",
|
||||
"rehype-pretty-code": "^0.12.1",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"remark": "^15.0.1",
|
||||
"remark-breaks": "^4.0.0",
|
||||
"remark-frontmatter": "^5.0.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"remark-math": "^6.0.0",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-rehype": "^11.0.0",
|
||||
"remark-smartypants": "^2.0.0",
|
||||
"rimraf": "^5.0.1",
|
||||
"rimraf": "^5.0.5",
|
||||
"serve-handler": "^6.1.5",
|
||||
"shikiji": "^0.8.7",
|
||||
"source-map-support": "^0.5.21",
|
||||
"to-vfile": "^7.2.4",
|
||||
"to-vfile": "^8.0.0",
|
||||
"toml": "^3.0.0",
|
||||
"unified": "^10.1.2",
|
||||
"unist-util-visit": "^4.1.2",
|
||||
"vfile": "^5.3.7",
|
||||
"workerpool": "^6.4.0",
|
||||
"ws": "^8.13.0",
|
||||
"unified": "^11.0.4",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"vfile": "^6.0.1",
|
||||
"workerpool": "^8.0.0",
|
||||
"ws": "^8.15.1",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cli-spinner": "^0.2.1",
|
||||
"@types/d3": "^7.4.0",
|
||||
"@types/cli-spinner": "^0.2.3",
|
||||
"@types/d3": "^7.4.3",
|
||||
"@types/flexsearch": "^0.7.3",
|
||||
"@types/hast": "^2.3.4",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/hast": "^3.0.3",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/node": "^20.1.2",
|
||||
"@types/pretty-time": "^1.1.2",
|
||||
"@types/source-map-support": "^0.5.6",
|
||||
"@types/workerpool": "^6.4.0",
|
||||
"@types/ws": "^8.5.5",
|
||||
"@types/yargs": "^17.0.24",
|
||||
"esbuild": "0.19.2",
|
||||
"prettier": "^3.0.0",
|
||||
"tsx": "^3.12.7",
|
||||
"typescript": "^5.0.4"
|
||||
"@types/pretty-time": "^1.1.5",
|
||||
"@types/source-map-support": "^0.5.10",
|
||||
"@types/workerpool": "^6.4.7",
|
||||
"@types/ws": "^8.5.10",
|
||||
"@types/yargs": "^17.0.32",
|
||||
"esbuild": "^0.19.9",
|
||||
"prettier": "^3.1.1",
|
||||
"tsx": "^4.6.2",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,10 +152,10 @@ async function startServing(
|
||||
console.log(chalk.yellow(`Rebuild failed. Waiting on a change to fix the error...`))
|
||||
}
|
||||
|
||||
release()
|
||||
clientRefresh()
|
||||
toRebuild.clear()
|
||||
toRemove.clear()
|
||||
release()
|
||||
}
|
||||
|
||||
const watcher = chokidar.watch(".", {
|
||||
|
||||
@@ -196,6 +196,11 @@ See the [documentation](https://quartz.jzhao.xyz) for how to get started.
|
||||
)
|
||||
await fs.promises.writeFile(configFilePath, configContent)
|
||||
|
||||
// setup remote
|
||||
execSync(
|
||||
`git remote show upstream || git remote add upstream https://github.com/jackyzha0/quartz.git`,
|
||||
)
|
||||
|
||||
outro(`You're all set! Not sure what to do next? Try:
|
||||
• Customizing Quartz a bit more by editing \`quartz.config.ts\`
|
||||
• Running \`npx quartz build --serve\` to preview your Quartz locally
|
||||
@@ -438,11 +443,23 @@ export async function handleUpdate(argv) {
|
||||
console.log(
|
||||
"Pulling updates... you may need to resolve some `git` conflicts if you've made changes to components or plugins.",
|
||||
)
|
||||
gitPull(UPSTREAM_NAME, QUARTZ_SOURCE_BRANCH)
|
||||
|
||||
try {
|
||||
gitPull(UPSTREAM_NAME, QUARTZ_SOURCE_BRANCH)
|
||||
} catch {
|
||||
console.log(chalk.red("An error occured above while pulling updates."))
|
||||
await popContentFolder(contentFolder)
|
||||
return
|
||||
}
|
||||
|
||||
await popContentFolder(contentFolder)
|
||||
console.log("Ensuring dependencies are up to date")
|
||||
spawnSync("npm", ["i"], { stdio: "inherit" })
|
||||
console.log(chalk.green("Done!"))
|
||||
const res = spawnSync("npm", ["i"], { stdio: "inherit" })
|
||||
if (res.status === 0) {
|
||||
console.log(chalk.green("Done!"))
|
||||
} else {
|
||||
console.log(chalk.red("An error occurred above while installing dependencies."))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -499,13 +516,25 @@ export async function handleSync(argv) {
|
||||
console.log(
|
||||
"Pulling updates from your repository. You may need to resolve some `git` conflicts if you've made changes to components or plugins.",
|
||||
)
|
||||
gitPull(ORIGIN_NAME, QUARTZ_SOURCE_BRANCH)
|
||||
try {
|
||||
gitPull(ORIGIN_NAME, QUARTZ_SOURCE_BRANCH)
|
||||
} catch {
|
||||
console.log(chalk.red("An error occured above while pulling updates."))
|
||||
await popContentFolder(contentFolder)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
await popContentFolder(contentFolder)
|
||||
if (argv.push) {
|
||||
console.log("Pushing your changes")
|
||||
spawnSync("git", ["push", "-f", ORIGIN_NAME, QUARTZ_SOURCE_BRANCH], { stdio: "inherit" })
|
||||
const res = spawnSync("git", ["push", "-uf", ORIGIN_NAME, QUARTZ_SOURCE_BRANCH], {
|
||||
stdio: "inherit",
|
||||
})
|
||||
if (res.status !== 0) {
|
||||
console.log(chalk.red(`An error occurred above while pushing to remote ${ORIGIN_NAME}.`))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.green("Done!"))
|
||||
|
||||
@@ -36,7 +36,9 @@ export function gitPull(origin, branch) {
|
||||
const flags = ["--no-rebase", "--autostash", "-s", "recursive", "-X", "ours", "--no-edit"]
|
||||
const out = spawnSync("git", ["pull", ...flags, origin, branch], { stdio: "inherit" })
|
||||
if (out.stderr) {
|
||||
throw new Error(`Error while pulling updates: ${out.stderr}`)
|
||||
throw new Error(chalk.red(`Error while pulling updates: ${out.stderr}`))
|
||||
} else if (out.status !== 0) {
|
||||
throw new Error(chalk.red("Error while pulling updates"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,12 @@ function TagContent(props: QuartzComponentProps) {
|
||||
? fileData.description
|
||||
: htmlToJsx(fileData.filePath!, tree)
|
||||
|
||||
if (tag === "") {
|
||||
const tags = [...new Set(allFiles.flatMap((data) => data.frontmatter?.tags ?? []))]
|
||||
if (tag === "/") {
|
||||
const tags = [
|
||||
...new Set(
|
||||
allFiles.flatMap((data) => data.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes),
|
||||
),
|
||||
].sort((a, b) => a.localeCompare(b))
|
||||
const tagItemMap: Map<string, QuartzPluginData[]> = new Map()
|
||||
for (const tag of tags) {
|
||||
tagItemMap.set(tag, allPagesWithTag(tag))
|
||||
|
||||
@@ -74,13 +74,13 @@ export function renderPage(
|
||||
const classNames = (node.properties?.className ?? []) as string[]
|
||||
if (classNames.includes("transclude")) {
|
||||
const inner = node.children[0] as Element
|
||||
const transcludeTarget = inner.properties?.["data-slug"] as FullSlug
|
||||
const transcludeTarget = inner.properties["data-slug"] as FullSlug
|
||||
const page = getOrComputeFileIndex(componentData.allFiles).get(transcludeTarget)
|
||||
if (!page) {
|
||||
return
|
||||
}
|
||||
|
||||
let blockRef = node.properties?.dataBlock as string | undefined
|
||||
let blockRef = node.properties.dataBlock as string | undefined
|
||||
if (blockRef?.startsWith("#^")) {
|
||||
// block transclude
|
||||
blockRef = blockRef.slice("#^".length)
|
||||
@@ -90,6 +90,7 @@ export function renderPage(
|
||||
blockNode = {
|
||||
type: "element",
|
||||
tagName: "ul",
|
||||
properties: {},
|
||||
children: [blockNode],
|
||||
}
|
||||
}
|
||||
@@ -144,6 +145,7 @@ export function renderPage(
|
||||
{
|
||||
type: "element",
|
||||
tagName: "h1",
|
||||
properties: {},
|
||||
children: [
|
||||
{ type: "text", value: page.frontmatter?.title ?? `Transclude of ${page.slug}` },
|
||||
],
|
||||
|
||||
@@ -7,6 +7,10 @@ async function mouseEnterHandler(
|
||||
{ clientX, clientY }: { clientX: number; clientY: number },
|
||||
) {
|
||||
const link = this
|
||||
if (link.dataset.noPopover === "true") {
|
||||
return
|
||||
}
|
||||
|
||||
async function setPosition(popoverElement: HTMLElement) {
|
||||
const { x, y } = await computePosition(link, popoverElement, {
|
||||
middleware: [inline({ x: clientX, y: clientY }), shift(), flip()],
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
float: right;
|
||||
right: 0;
|
||||
padding: 0.4rem;
|
||||
margin: -0.2rem 0.3rem;
|
||||
margin: 0.3rem;
|
||||
color: var(--gray);
|
||||
border-color: var(--dark);
|
||||
background-color: var(--light);
|
||||
|
||||
@@ -9,7 +9,7 @@ export type QuartzComponentProps = {
|
||||
fileData: QuartzPluginData
|
||||
cfg: GlobalConfiguration
|
||||
children: (QuartzComponent | JSX.Element)[]
|
||||
tree: Node<QuartzPluginData>
|
||||
tree: Node
|
||||
allFiles: QuartzPluginData[]
|
||||
displayClass?: "mobile-only" | "desktop-only"
|
||||
} & JSX.IntrinsicAttributes & {
|
||||
|
||||
@@ -40,12 +40,13 @@ export const TagPage: QuartzEmitterPlugin<FullPageLayout> = (userOpts) => {
|
||||
const tags: Set<string> = new Set(
|
||||
allFiles.flatMap((data) => data.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes),
|
||||
)
|
||||
|
||||
// add base tag
|
||||
tags.add("index")
|
||||
|
||||
const tagDescriptions: Record<string, ProcessedContent> = Object.fromEntries(
|
||||
[...tags].map((tag) => {
|
||||
const title = tag === "" ? "Tag Index" : `Tag: #${tag}`
|
||||
const title = tag === "index" ? "Tag Index" : `Tag: #${tag}`
|
||||
return [
|
||||
tag,
|
||||
defaultProcessedContent({
|
||||
|
||||
@@ -4,15 +4,18 @@ import { QuartzTransformerPlugin } from "../types"
|
||||
import yaml from "js-yaml"
|
||||
import toml from "toml"
|
||||
import { slugTag } from "../../util/path"
|
||||
import { QuartzPluginData } from "../vfile"
|
||||
|
||||
export interface Options {
|
||||
delims: string | string[]
|
||||
language: "yaml" | "toml"
|
||||
oneLineTagDelim: string
|
||||
}
|
||||
|
||||
const defaultOptions: Options = {
|
||||
delims: "---",
|
||||
language: "yaml",
|
||||
oneLineTagDelim: ",",
|
||||
}
|
||||
|
||||
export const FrontMatter: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
|
||||
@@ -20,11 +23,13 @@ export const FrontMatter: QuartzTransformerPlugin<Partial<Options> | undefined>
|
||||
return {
|
||||
name: "FrontMatter",
|
||||
markdownPlugins() {
|
||||
const { oneLineTagDelim } = opts
|
||||
|
||||
return [
|
||||
[remarkFrontmatter, ["yaml", "toml"]],
|
||||
() => {
|
||||
return (_, file) => {
|
||||
const { data } = matter(file.value, {
|
||||
const { data } = matter(Buffer.from(file.value), {
|
||||
...opts,
|
||||
engines: {
|
||||
yaml: (s) => yaml.load(s, { schema: yaml.JSON_SCHEMA }) as object,
|
||||
@@ -40,24 +45,22 @@ export const FrontMatter: QuartzTransformerPlugin<Partial<Options> | undefined>
|
||||
// coerce title to string
|
||||
if (data.title) {
|
||||
data.title = data.title.toString()
|
||||
} else if (data.title === null || data.title === undefined) {
|
||||
data.title = file.stem ?? "Untitled"
|
||||
}
|
||||
|
||||
if (data.tags && !Array.isArray(data.tags)) {
|
||||
data.tags = data.tags
|
||||
.toString()
|
||||
.split(",")
|
||||
.split(oneLineTagDelim)
|
||||
.map((tag: string) => tag.trim())
|
||||
}
|
||||
|
||||
// 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)))]
|
||||
|
||||
// fill in frontmatter
|
||||
file.data.frontmatter = {
|
||||
title: file.stem ?? "Untitled",
|
||||
tags: [],
|
||||
...data,
|
||||
}
|
||||
file.data.frontmatter = data as QuartzPluginData["frontmatter"]
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
@@ -31,6 +31,11 @@ export const GitHubFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options> |
|
||||
rehypeAutolinkHeadings,
|
||||
{
|
||||
behavior: "append",
|
||||
properties: {
|
||||
ariaHidden: true,
|
||||
tabIndex: -1,
|
||||
"data-no-popover": true,
|
||||
},
|
||||
content: {
|
||||
type: "text",
|
||||
value: " §",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import remarkMath from "remark-math"
|
||||
import rehypeKatex from "rehype-katex"
|
||||
import rehypeMathjax from "rehype-mathjax/svg.js"
|
||||
import rehypeMathjax from "rehype-mathjax/svg"
|
||||
import { QuartzTransformerPlugin } from "../types"
|
||||
|
||||
interface Options {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { PluggableList } from "unified"
|
||||
import { QuartzTransformerPlugin } from "../types"
|
||||
import { Root, HTML, BlockContent, DefinitionContent, Code, Paragraph } from "mdast"
|
||||
import { Root, Html, BlockContent, DefinitionContent, Code, Paragraph } from "mdast"
|
||||
import { Element, Literal, Root as HtmlRoot } from "hast"
|
||||
import { Replace, 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 rehypeRaw from "rehype-raw"
|
||||
import { visit } from "unist-util-visit"
|
||||
@@ -15,6 +14,7 @@ import { toHast } from "mdast-util-to-hast"
|
||||
import { toHtml } from "hast-util-to-html"
|
||||
import { PhrasingContent } from "mdast-util-find-and-replace/lib"
|
||||
import { capitalize } from "../../util/lang"
|
||||
import { PluggableList } from "unified"
|
||||
|
||||
export interface Options {
|
||||
comments: boolean
|
||||
@@ -121,8 +121,8 @@ const calloutRegex = new RegExp(/^\[\!(\w+)\]([+-]?)/)
|
||||
const calloutLineRegex = new RegExp(/^> *\[\!\w+\][+-]?.*$/, "gm")
|
||||
// (?:^| ) -> non-capturing group, tag should start be separated by a space or be the start of the line
|
||||
// #(...) -> capturing group, tag itself must start with #
|
||||
// (?:[-_\p{L}])+ -> non-capturing group, non-empty string of (Unicode-aware) alpha-numeric characters, hyphens and/or underscores
|
||||
// (?:\/[-_\p{L}]+)*) -> non-capturing group, matches an arbitrary number of tag strings separated by "/"
|
||||
// (?:[-_\p{L}\d\p{Z}])+ -> non-capturing group, non-empty string of (Unicode-aware) alpha-numeric characters and symbols, hyphens and/or underscores
|
||||
// (?:\/[-_\p{L}\d\p{Z}]+)*) -> non-capturing group, matches an arbitrary number of tag strings separated by "/"
|
||||
const tagRegex = new RegExp(/(?:^| )#((?:[-_\p{L}\d])+(?:\/[-_\p{L}\d]+)*)/, "gu")
|
||||
const blockReferenceRegex = new RegExp(/\^([A-Za-z0-9]+)$/, "g")
|
||||
|
||||
@@ -136,39 +136,15 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
|
||||
return toHtml(hast, { allowDangerousHtml: true })
|
||||
}
|
||||
|
||||
const findAndReplace = opts.enableInHtmlEmbed
|
||||
? (tree: Root, regex: RegExp, replace?: Replace | null | undefined) => {
|
||||
if (replace) {
|
||||
visit(tree, "html", (node: HTML) => {
|
||||
if (typeof replace === "string") {
|
||||
node.value = node.value.replace(regex, replace)
|
||||
} else {
|
||||
node.value = node.value.replaceAll(regex, (substring: string, ...args) => {
|
||||
const replaceValue = replace(substring, ...args)
|
||||
if (typeof replaceValue === "string") {
|
||||
return replaceValue
|
||||
} else if (Array.isArray(replaceValue)) {
|
||||
return replaceValue.map(mdastToHtml).join("")
|
||||
} else if (typeof replaceValue === "object" && replaceValue !== null) {
|
||||
return mdastToHtml(replaceValue)
|
||||
} else {
|
||||
return substring
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
mdastFindReplace(tree, regex, replace)
|
||||
}
|
||||
: mdastFindReplace
|
||||
|
||||
return {
|
||||
name: "ObsidianFlavoredMarkdown",
|
||||
textTransform(_ctx, src) {
|
||||
// pre-transform blockquotes
|
||||
if (opts.callouts) {
|
||||
src = src.toString()
|
||||
if (src instanceof Buffer) {
|
||||
src = src.toString()
|
||||
}
|
||||
|
||||
src = src.replaceAll(calloutLineRegex, (value) => {
|
||||
// force newline after title of callout
|
||||
return value + "\n> "
|
||||
@@ -177,7 +153,10 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
|
||||
|
||||
// pre-transform wikilinks (fix anchors to things that may contain illegal syntax e.g. codeblocks, latex)
|
||||
if (opts.wikilinks) {
|
||||
src = src.toString()
|
||||
if (src instanceof Buffer) {
|
||||
src = src.toString()
|
||||
}
|
||||
|
||||
src = src.replaceAll(wikilinkRegex, (value, ...capture) => {
|
||||
const [rawFp, rawHeader, rawAlias] = capture
|
||||
const fp = rawFp ?? ""
|
||||
@@ -194,108 +173,172 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
|
||||
},
|
||||
markdownPlugins() {
|
||||
const plugins: PluggableList = []
|
||||
if (opts.wikilinks) {
|
||||
plugins.push(() => {
|
||||
return (tree: Root, _file) => {
|
||||
findAndReplace(tree, wikilinkRegex, (value: string, ...capture: string[]) => {
|
||||
let [rawFp, rawHeader, rawAlias] = capture
|
||||
const fp = rawFp?.trim() ?? ""
|
||||
const anchor = rawHeader?.trim() ?? ""
|
||||
const alias = rawAlias?.slice(1).trim()
|
||||
|
||||
// embed cases
|
||||
if (value.startsWith("!")) {
|
||||
const ext: string = path.extname(fp).toLowerCase()
|
||||
const url = slugifyFilePath(fp as FilePath)
|
||||
if ([".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg"].includes(ext)) {
|
||||
const dims = alias ?? ""
|
||||
let [width, height] = dims.split("x", 2)
|
||||
width ||= "auto"
|
||||
height ||= "auto"
|
||||
return {
|
||||
type: "image",
|
||||
url,
|
||||
data: {
|
||||
hProperties: {
|
||||
width,
|
||||
height,
|
||||
// regex replacements
|
||||
plugins.push(() => {
|
||||
return (tree: Root, file) => {
|
||||
const replacements: [RegExp, string | ReplaceFunction][] = []
|
||||
const base = pathToRoot(file.data.slug!)
|
||||
|
||||
if (opts.wikilinks) {
|
||||
replacements.push([
|
||||
wikilinkRegex,
|
||||
(value: string, ...capture: string[]) => {
|
||||
let [rawFp, rawHeader, rawAlias] = capture
|
||||
const fp = rawFp?.trim() ?? ""
|
||||
const anchor = rawHeader?.trim() ?? ""
|
||||
const alias = rawAlias?.slice(1).trim()
|
||||
|
||||
// embed cases
|
||||
if (value.startsWith("!")) {
|
||||
const ext: string = path.extname(fp).toLowerCase()
|
||||
const url = slugifyFilePath(fp as FilePath)
|
||||
if ([".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg"].includes(ext)) {
|
||||
const dims = alias ?? ""
|
||||
let [width, height] = dims.split("x", 2)
|
||||
width ||= "auto"
|
||||
height ||= "auto"
|
||||
return {
|
||||
type: "image",
|
||||
url,
|
||||
data: {
|
||||
hProperties: {
|
||||
width,
|
||||
height,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
} else if ([".mp4", ".webm", ".ogv", ".mov", ".mkv"].includes(ext)) {
|
||||
return {
|
||||
type: "html",
|
||||
value: `<video src="${url}" controls></video>`,
|
||||
}
|
||||
} else if (
|
||||
[".mp3", ".webm", ".wav", ".m4a", ".ogg", ".3gp", ".flac"].includes(ext)
|
||||
) {
|
||||
return {
|
||||
type: "html",
|
||||
value: `<audio src="${url}" controls></audio>`,
|
||||
}
|
||||
} else if ([".pdf"].includes(ext)) {
|
||||
return {
|
||||
type: "html",
|
||||
value: `<iframe src="${url}"></iframe>`,
|
||||
}
|
||||
} else if (ext === "") {
|
||||
const block = anchor
|
||||
return {
|
||||
type: "html",
|
||||
data: { hProperties: { transclude: true } },
|
||||
value: `<blockquote class="transclude" data-url="${url}" data-block="${block}"><a href="${
|
||||
url + anchor
|
||||
}" class="transclude-inner">Transclude of ${url}${block}</a></blockquote>`,
|
||||
}
|
||||
} else if ([".mp4", ".webm", ".ogv", ".mov", ".mkv"].includes(ext)) {
|
||||
return {
|
||||
type: "html",
|
||||
value: `<video src="${url}" controls></video>`,
|
||||
}
|
||||
} else if (
|
||||
[".mp3", ".webm", ".wav", ".m4a", ".ogg", ".3gp", ".flac"].includes(ext)
|
||||
) {
|
||||
return {
|
||||
type: "html",
|
||||
value: `<audio src="${url}" controls></audio>`,
|
||||
}
|
||||
} else if ([".pdf"].includes(ext)) {
|
||||
return {
|
||||
type: "html",
|
||||
value: `<iframe src="${url}"></iframe>`,
|
||||
}
|
||||
} else if (ext === "") {
|
||||
const block = anchor
|
||||
return {
|
||||
type: "html",
|
||||
data: { hProperties: { transclude: true } },
|
||||
value: `<blockquote class="transclude" data-url="${url}" data-block="${block}"><a href="${
|
||||
url + anchor
|
||||
}" class="transclude-inner">Transclude of ${url}${block}</a></blockquote>`,
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise, fall through to regular link
|
||||
}
|
||||
|
||||
// otherwise, fall through to regular link
|
||||
}
|
||||
// internal link
|
||||
const url = fp + anchor
|
||||
return {
|
||||
type: "link",
|
||||
url,
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
value: alias ?? fp,
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
// internal link
|
||||
const url = fp + anchor
|
||||
return {
|
||||
type: "link",
|
||||
url,
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
value: alias ?? fp,
|
||||
if (opts.highlight) {
|
||||
replacements.push([
|
||||
highlightRegex,
|
||||
(_value: string, ...capture: string[]) => {
|
||||
const [inner] = capture
|
||||
return {
|
||||
type: "html",
|
||||
value: `<span class="text-highlight">${inner}</span>`,
|
||||
}
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
if (opts.comments) {
|
||||
replacements.push([
|
||||
commentRegex,
|
||||
(_value: string, ..._capture: string[]) => {
|
||||
return {
|
||||
type: "text",
|
||||
value: "",
|
||||
}
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
if (opts.parseTags) {
|
||||
replacements.push([
|
||||
tagRegex,
|
||||
(_value: string, tag: string) => {
|
||||
// Check if the tag only includes numbers
|
||||
if (/^\d+$/.test(tag)) {
|
||||
return false
|
||||
}
|
||||
|
||||
tag = slugTag(tag)
|
||||
if (file.data.frontmatter && !file.data.frontmatter.tags.includes(tag)) {
|
||||
file.data.frontmatter.tags.push(tag)
|
||||
}
|
||||
|
||||
return {
|
||||
type: "link",
|
||||
url: base + `/tags/${tag}`,
|
||||
data: {
|
||||
hProperties: {
|
||||
className: ["tag-link"],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
value: `#${tag}`,
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (opts.highlight) {
|
||||
plugins.push(() => {
|
||||
return (tree: Root, _file) => {
|
||||
findAndReplace(tree, highlightRegex, (_value: string, ...capture: string[]) => {
|
||||
const [inner] = capture
|
||||
return {
|
||||
type: "html",
|
||||
value: `<span class="text-highlight">${inner}</span>`,
|
||||
if (opts.enableInHtmlEmbed) {
|
||||
visit(tree, "html", (node: Html) => {
|
||||
for (const [regex, replace] of replacements) {
|
||||
if (typeof replace === "string") {
|
||||
node.value = node.value.replace(regex, replace)
|
||||
} else {
|
||||
node.value = node.value.replaceAll(regex, (substring: string, ...args) => {
|
||||
const replaceValue = replace(substring, ...args)
|
||||
if (typeof replaceValue === "string") {
|
||||
return replaceValue
|
||||
} else if (Array.isArray(replaceValue)) {
|
||||
return replaceValue.map(mdastToHtml).join("")
|
||||
} else if (typeof replaceValue === "object" && replaceValue !== null) {
|
||||
return mdastToHtml(replaceValue)
|
||||
} else {
|
||||
return substring
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (opts.comments) {
|
||||
plugins.push(() => {
|
||||
return (tree: Root, _file) => {
|
||||
findAndReplace(tree, commentRegex, (_value: string, ..._capture: string[]) => {
|
||||
return {
|
||||
type: "text",
|
||||
value: "",
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
mdastFindReplace(tree, replacements)
|
||||
}
|
||||
})
|
||||
|
||||
if (opts.callouts) {
|
||||
plugins.push(() => {
|
||||
@@ -336,7 +379,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>`
|
||||
|
||||
const titleHtml: HTML = {
|
||||
const titleHtml: Html = {
|
||||
type: "html",
|
||||
value: `<div
|
||||
class="callout-title"
|
||||
@@ -396,44 +439,10 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
|
||||
})
|
||||
}
|
||||
|
||||
if (opts.parseTags) {
|
||||
plugins.push(() => {
|
||||
return (tree: Root, file) => {
|
||||
const base = pathToRoot(file.data.slug!)
|
||||
findAndReplace(tree, tagRegex, (_value: string, tag: string) => {
|
||||
// Check if the tag only includes numbers
|
||||
if (/^\d+$/.test(tag)) {
|
||||
return false
|
||||
}
|
||||
tag = slugTag(tag)
|
||||
if (file.data.frontmatter && !file.data.frontmatter.tags.includes(tag)) {
|
||||
file.data.frontmatter.tags.push(tag)
|
||||
}
|
||||
|
||||
return {
|
||||
type: "link",
|
||||
url: base + `/tags/${tag}`,
|
||||
data: {
|
||||
hProperties: {
|
||||
className: ["tag-link"],
|
||||
},
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
value: `#${tag}`,
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
return plugins
|
||||
},
|
||||
htmlPlugins() {
|
||||
const plugins = [rehypeRaw]
|
||||
|
||||
const plugins: PluggableList = [rehypeRaw]
|
||||
if (opts.parseBlockReferences) {
|
||||
plugins.push(() => {
|
||||
const inlineTagTypes = new Set(["p", "li"])
|
||||
|
||||
@@ -8,7 +8,11 @@ export const SyntaxHighlighting: QuartzTransformerPlugin = () => ({
|
||||
[
|
||||
rehypePrettyCode,
|
||||
{
|
||||
theme: "css-variables",
|
||||
keepBackground: false,
|
||||
theme: {
|
||||
dark: "github-dark",
|
||||
light: "github-light",
|
||||
},
|
||||
} satisfies Partial<CodeOptions>,
|
||||
],
|
||||
]
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Node, Parent } from "hast"
|
||||
import { Data, VFile } from "vfile"
|
||||
|
||||
export type QuartzPluginData = Data
|
||||
export type ProcessedContent = [Node<QuartzPluginData>, VFile]
|
||||
export type ProcessedContent = [Node, VFile]
|
||||
|
||||
export function defaultProcessedContent(vfileData: Partial<QuartzPluginData>): ProcessedContent {
|
||||
const root: Parent = { type: "root", children: [] }
|
||||
|
||||
@@ -14,27 +14,25 @@ import { QuartzLogger } from "../util/log"
|
||||
import { trace } from "../util/trace"
|
||||
import { BuildCtx } from "../util/ctx"
|
||||
|
||||
export type QuartzProcessor = Processor<MDRoot, HTMLRoot, void>
|
||||
export type QuartzProcessor = Processor<MDRoot, MDRoot, HTMLRoot>
|
||||
export function createProcessor(ctx: BuildCtx): QuartzProcessor {
|
||||
const transformers = ctx.cfg.plugins.transformers
|
||||
|
||||
// base Markdown -> MD AST
|
||||
let processor = unified().use(remarkParse)
|
||||
|
||||
// MD AST -> MD AST transforms
|
||||
for (const plugin of transformers.filter((p) => p.markdownPlugins)) {
|
||||
processor = processor.use(plugin.markdownPlugins!(ctx))
|
||||
}
|
||||
|
||||
// MD AST -> HTML AST
|
||||
processor = processor.use(remarkRehype, { allowDangerousHtml: true })
|
||||
|
||||
// HTML AST -> HTML AST transforms
|
||||
for (const plugin of transformers.filter((p) => p.htmlPlugins)) {
|
||||
processor = processor.use(plugin.htmlPlugins!(ctx))
|
||||
}
|
||||
|
||||
return processor
|
||||
return (
|
||||
unified()
|
||||
// base Markdown -> MD AST
|
||||
.use(remarkParse)
|
||||
// MD AST -> MD AST transforms
|
||||
.use(
|
||||
transformers
|
||||
.filter((p) => p.markdownPlugins)
|
||||
.flatMap((plugin) => plugin.markdownPlugins!(ctx)),
|
||||
)
|
||||
// MD AST -> HTML AST
|
||||
.use(remarkRehype, { allowDangerousHtml: true })
|
||||
// HTML AST -> HTML AST transforms
|
||||
.use(transformers.filter((p) => p.htmlPlugins).flatMap((plugin) => plugin.htmlPlugins!(ctx)))
|
||||
)
|
||||
}
|
||||
|
||||
function* chunks<T>(arr: T[], n: number) {
|
||||
@@ -89,7 +87,7 @@ export function createFileParser(ctx: BuildCtx, fps: FilePath[]) {
|
||||
|
||||
// Text -> Text transforms
|
||||
for (const plugin of cfg.plugins.transformers.filter((p) => p.textTransform)) {
|
||||
file.value = plugin.textTransform!(ctx, file.value)
|
||||
file.value = plugin.textTransform!(ctx, file.value.toString())
|
||||
}
|
||||
|
||||
// base data properties that plugins may use
|
||||
|
||||
@@ -64,11 +64,17 @@ a {
|
||||
color: var(--tertiary) !important;
|
||||
}
|
||||
|
||||
&.internal:not(:has(> img)) {
|
||||
&.internal {
|
||||
text-decoration: none;
|
||||
background-color: var(--highlight);
|
||||
padding: 0 0.1rem;
|
||||
border-radius: 5px;
|
||||
|
||||
&:has(> img) {
|
||||
background-color: none;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,8 +100,6 @@ a {
|
||||
}
|
||||
|
||||
& article {
|
||||
position: relative;
|
||||
|
||||
& > h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
@@ -300,11 +304,13 @@ h6 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
div[data-rehype-pretty-code-fragment] {
|
||||
figure[data-rehype-pretty-code-figure] {
|
||||
margin: 0;
|
||||
position: relative;
|
||||
line-height: 1.6rem;
|
||||
position: relative;
|
||||
|
||||
& > div[data-rehype-pretty-code-title] {
|
||||
& > [data-rehype-pretty-code-title] {
|
||||
font-family: var(--codeFont);
|
||||
font-size: 0.9rem;
|
||||
padding: 0.1rem 0.5rem;
|
||||
@@ -316,7 +322,7 @@ div[data-rehype-pretty-code-fragment] {
|
||||
}
|
||||
|
||||
& > pre {
|
||||
padding: 0.5rem 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,6 +344,7 @@ pre {
|
||||
counter-reset: line;
|
||||
counter-increment: line 0;
|
||||
display: grid;
|
||||
padding: 0.5rem 0;
|
||||
|
||||
& [data-highlighted-chars] {
|
||||
background-color: var(--highlight);
|
||||
|
||||
@@ -1,29 +1,17 @@
|
||||
// npx convert-sh-theme https://raw.githubusercontent.com/shikijs/shiki/main/packages/shiki/themes/github-light.json
|
||||
:root {
|
||||
--shiki-color-text: #24292e;
|
||||
--shiki-color-background: #f8f8f8;
|
||||
--shiki-token-constant: #005cc5;
|
||||
--shiki-token-string: #032f62;
|
||||
--shiki-token-comment: #6a737d;
|
||||
--shiki-token-keyword: #d73a49;
|
||||
--shiki-token-parameter: #24292e;
|
||||
--shiki-token-function: #24292e;
|
||||
--shiki-token-string-expression: #22863a;
|
||||
--shiki-token-punctuation: #24292e;
|
||||
--shiki-token-link: #24292e;
|
||||
code[data-theme*=" "] {
|
||||
color: var(--shiki-light);
|
||||
background-color: var(--shiki-light-bg);
|
||||
}
|
||||
|
||||
// npx convert-sh-theme https://raw.githubusercontent.com/shikijs/shiki/main/packages/shiki/themes/github-dark.json
|
||||
[saved-theme="dark"] {
|
||||
--shiki-color-text: #e1e4e8 !important;
|
||||
--shiki-color-background: #24292e !important;
|
||||
--shiki-token-constant: #79b8ff !important;
|
||||
--shiki-token-string: #9ecbff !important;
|
||||
--shiki-token-comment: #6a737d !important;
|
||||
--shiki-token-keyword: #f97583 !important;
|
||||
--shiki-token-parameter: #e1e4e8 !important;
|
||||
--shiki-token-function: #e1e4e8 !important;
|
||||
--shiki-token-string-expression: #85e89d !important;
|
||||
--shiki-token-punctuation: #e1e4e8 !important;
|
||||
--shiki-token-link: #e1e4e8 !important;
|
||||
code[data-theme*=" "] span {
|
||||
color: var(--shiki-light);
|
||||
}
|
||||
|
||||
[saved-theme="dark"] code[data-theme*=" "] {
|
||||
color: var(--shiki-dark);
|
||||
background-color: var(--shiki-dark-bg);
|
||||
}
|
||||
|
||||
[saved-theme="dark"] code[data-theme*=" "] span {
|
||||
color: var(--shiki-dark);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Components, Jsx, toJsxRuntime } from "hast-util-to-jsx-runtime"
|
||||
import { QuartzPluginData } from "../plugins/vfile"
|
||||
import { Node, Root } from "hast"
|
||||
import { Fragment, jsx, jsxs } from "preact/jsx-runtime"
|
||||
import { trace } from "./trace"
|
||||
@@ -13,7 +12,7 @@ const customComponents: Components = {
|
||||
),
|
||||
}
|
||||
|
||||
export function htmlToJsx(fp: FilePath, tree: Node<QuartzPluginData>) {
|
||||
export function htmlToJsx(fp: FilePath, tree: Node) {
|
||||
try {
|
||||
return toJsxRuntime(tree as Root, {
|
||||
Fragment,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { slug } from "github-slugger"
|
||||
import { slug as slugAnchor } from "github-slugger"
|
||||
import type { Element as HastElement } from "hast"
|
||||
// this file must be isomorphic so it can't use node libs (e.g. path)
|
||||
|
||||
@@ -43,6 +43,14 @@ export function getFullSlug(window: Window): FullSlug {
|
||||
return res
|
||||
}
|
||||
|
||||
function sluggify(s: string): string {
|
||||
return s
|
||||
.split("/")
|
||||
.map((segment) => segment.replace(/\s/g, "-").replace(/%/g, "-percent").replace(/\?/g, "-q")) // slugify all segments
|
||||
.join("/") // always use / as sep
|
||||
.replace(/\/$/, "")
|
||||
}
|
||||
|
||||
export function slugifyFilePath(fp: FilePath, excludeExt?: boolean): FullSlug {
|
||||
fp = _stripSlashes(fp) as FilePath
|
||||
let ext = _getFileExtension(fp)
|
||||
@@ -51,11 +59,7 @@ export function slugifyFilePath(fp: FilePath, excludeExt?: boolean): FullSlug {
|
||||
ext = ""
|
||||
}
|
||||
|
||||
let slug = withoutFileExt
|
||||
.split("/")
|
||||
.map((segment) => segment.replace(/\s/g, "-").replace(/%/g, "-percent").replace(/\?/g, "-q")) // slugify all segments
|
||||
.join("/") // always use / as sep
|
||||
.replace(/\/$/, "") // remove trailing slash
|
||||
let slug = sluggify(withoutFileExt)
|
||||
|
||||
// treat _index as index
|
||||
if (_endsWith(slug, "_index")) {
|
||||
@@ -156,14 +160,10 @@ export function splitAnchor(link: string): [string, string] {
|
||||
return [fp, anchor]
|
||||
}
|
||||
|
||||
export function slugAnchor(anchor: string) {
|
||||
return slug(anchor)
|
||||
}
|
||||
|
||||
export function slugTag(tag: string) {
|
||||
return tag
|
||||
.split("/")
|
||||
.map((tagSegment) => slug(tagSegment))
|
||||
.map((tagSegment) => sluggify(tagSegment))
|
||||
.join("/")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user