Compare commits
10 Commits
0d314db1f8
...
a7e20804f5
Author | SHA1 | Date | |
---|---|---|---|
|
a7e20804f5 | ||
|
5196f3b9db | ||
|
f0ec6c9b92 | ||
|
9c88d5967f | ||
|
0d8c025d6a | ||
|
54b4a5567c | ||
|
610b04406f | ||
|
82bd08d14a | ||
|
649090de1b | ||
|
b5fec6c87f |
@ -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!).
|
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]
|
> [!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!
|
> 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/).
|
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]
|
> [!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`.
|
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.
|
> 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
|
### Custom Domain
|
||||||
|
|
||||||
Here's how to add a custom domain to your GitHub pages deployment.
|
Here's how to add a custom domain to your GitHub pages deployment.
|
||||||
@ -167,9 +171,16 @@ Using `docs.example.com` is an example of a subdomain. They're a simple way of c
|
|||||||
4. Go to the Settings tab and then click Domains in the sidebar
|
4. Go to the Settings tab and then click Domains in the sidebar
|
||||||
5. Enter your subdomain into the field and press Add
|
5. Enter your subdomain into the field and press Add
|
||||||
|
|
||||||
## GitLab Pages
|
## Netlify
|
||||||
|
|
||||||
You can configure GitLab CI to build and deploy a Quartz 4 project.
|
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`.
|
||||||
|
4. Under "Publish directory", enter `public`.
|
||||||
|
5. Press Deploy. Once it's live, you'll have a `*.netlify.app` URL to view the page.
|
||||||
|
6. To add a custom domain, check "Domain management" in the left sidebar, just like with Vercel.
|
||||||
|
|
||||||
|
## GitLab Pages
|
||||||
|
|
||||||
In your local Quartz, create a new file `.gitlab-ci.yaml`.
|
In your local Quartz, create a new file `.gitlab-ci.yaml`.
|
||||||
|
|
||||||
@ -192,8 +203,6 @@ build:
|
|||||||
- hash -r
|
- hash -r
|
||||||
- npm ci
|
- npm ci
|
||||||
script:
|
script:
|
||||||
- npx prettier --write .
|
|
||||||
- npm run check
|
|
||||||
- npx quartz build
|
- npx quartz build
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
@ -216,6 +225,6 @@ pages:
|
|||||||
- public
|
- 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`.
|
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
|
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
|
## 🪴 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:
|
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
|
2. [[configuration|Configure]] Quartz's behaviour
|
||||||
3. Change Quartz's [[layout]]
|
3. Change Quartz's [[layout]]
|
||||||
4. [[build|Build and preview]] Quartz
|
4. [[build|Build and preview]] Quartz
|
||||||
|
31
docs/setting up your GitHub repository.md
Normal file
31
docs/setting up your GitHub repository.md
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
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 command, replacing `REMOTE-URL` with the URL you just copied from the previous step.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git remote add origin REMOTE-URL
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
7
package-lock.json
generated
7
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@jackyzha0/quartz",
|
"name": "@jackyzha0/quartz",
|
||||||
"version": "4.0.11",
|
"version": "4.1.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@jackyzha0/quartz",
|
"name": "@jackyzha0/quartz",
|
||||||
"version": "4.0.11",
|
"version": "4.1.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@clack/prompts": "^0.6.3",
|
"@clack/prompts": "^0.6.3",
|
||||||
@ -85,7 +85,8 @@
|
|||||||
"typescript": "^5.0.4"
|
"typescript": "^5.0.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.14"
|
"node": ">=18.14",
|
||||||
|
"npm": ">=9.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@clack/core": {
|
"node_modules/@clack/core": {
|
||||||
|
@ -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.1",
|
"version": "4.1.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"author": "jackyzha0 <j.zhao2k19@gmail.com>",
|
"author": "jackyzha0 <j.zhao2k19@gmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -196,6 +196,11 @@ See the [documentation](https://quartz.jzhao.xyz) for how to get started.
|
|||||||
)
|
)
|
||||||
await fs.promises.writeFile(configFilePath, configContent)
|
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:
|
outro(`You're all set! Not sure what to do next? Try:
|
||||||
• Customizing Quartz a bit more by editing \`quartz.config.ts\`
|
• Customizing Quartz a bit more by editing \`quartz.config.ts\`
|
||||||
• Running \`npx quartz build --serve\` to preview your Quartz locally
|
• Running \`npx quartz build --serve\` to preview your Quartz locally
|
||||||
|
@ -27,7 +27,7 @@ function TagContent(props: QuartzComponentProps) {
|
|||||||
? fileData.description
|
? fileData.description
|
||||||
: htmlToJsx(fileData.filePath!, tree)
|
: htmlToJsx(fileData.filePath!, tree)
|
||||||
|
|
||||||
if (tag === "") {
|
if (tag === "/") {
|
||||||
const tags = [...new Set(allFiles.flatMap((data) => data.frontmatter?.tags ?? []))]
|
const tags = [...new Set(allFiles.flatMap((data) => data.frontmatter?.tags ?? []))]
|
||||||
const tagItemMap: Map<string, QuartzPluginData[]> = new Map()
|
const tagItemMap: Map<string, QuartzPluginData[]> = new Map()
|
||||||
for (const tag of tags) {
|
for (const tag of tags) {
|
||||||
|
@ -3,9 +3,10 @@ import { QuartzComponent, QuartzComponentProps } from "./types"
|
|||||||
import HeaderConstructor from "./Header"
|
import HeaderConstructor from "./Header"
|
||||||
import BodyConstructor from "./Body"
|
import BodyConstructor from "./Body"
|
||||||
import { JSResourceToScriptElement, StaticResources } from "../util/resources"
|
import { JSResourceToScriptElement, StaticResources } from "../util/resources"
|
||||||
import { FullSlug, RelativeURL, joinSegments } from "../util/path"
|
import { FullSlug, RelativeURL, joinSegments, normalizeHastElement } from "../util/path"
|
||||||
import { visit } from "unist-util-visit"
|
import { visit } from "unist-util-visit"
|
||||||
import { Root, Element, ElementContent } from "hast"
|
import { Root, Element, ElementContent } from "hast"
|
||||||
|
import { QuartzPluginData } from "../plugins/vfile"
|
||||||
|
|
||||||
interface RenderComponents {
|
interface RenderComponents {
|
||||||
head: QuartzComponent
|
head: QuartzComponent
|
||||||
@ -49,6 +50,18 @@ export function pageResources(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let pageIndex: Map<FullSlug, QuartzPluginData> | undefined = undefined
|
||||||
|
function getOrComputeFileIndex(allFiles: QuartzPluginData[]): Map<FullSlug, QuartzPluginData> {
|
||||||
|
if (!pageIndex) {
|
||||||
|
pageIndex = new Map()
|
||||||
|
for (const file of allFiles) {
|
||||||
|
pageIndex.set(file.slug!, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pageIndex
|
||||||
|
}
|
||||||
|
|
||||||
export function renderPage(
|
export function renderPage(
|
||||||
slug: FullSlug,
|
slug: FullSlug,
|
||||||
componentData: QuartzComponentProps,
|
componentData: QuartzComponentProps,
|
||||||
@ -62,17 +75,15 @@ export function renderPage(
|
|||||||
if (classNames.includes("transclude")) {
|
if (classNames.includes("transclude")) {
|
||||||
const inner = node.children[0] as Element
|
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)
|
||||||
// TODO: avoid this expensive find operation and construct an index ahead of time
|
|
||||||
const page = componentData.allFiles.find((f) => f.slug === transcludeTarget)
|
|
||||||
if (!page) {
|
if (!page) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let blockRef = node.properties?.dataBlock as string | undefined
|
let blockRef = node.properties?.dataBlock as string | undefined
|
||||||
if (blockRef?.startsWith("^")) {
|
if (blockRef?.startsWith("#^")) {
|
||||||
// block transclude
|
// block transclude
|
||||||
blockRef = blockRef.slice(1)
|
blockRef = blockRef.slice("#^".length)
|
||||||
let blockNode = page.blocks?.[blockRef]
|
let blockNode = page.blocks?.[blockRef]
|
||||||
if (blockNode) {
|
if (blockNode) {
|
||||||
if (blockNode.tagName === "li") {
|
if (blockNode.tagName === "li") {
|
||||||
@ -84,7 +95,7 @@ export function renderPage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
node.children = [
|
node.children = [
|
||||||
blockNode,
|
normalizeHastElement(blockNode, slug, transcludeTarget),
|
||||||
{
|
{
|
||||||
type: "element",
|
type: "element",
|
||||||
tagName: "a",
|
tagName: "a",
|
||||||
@ -117,7 +128,9 @@ export function renderPage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
node.children = [
|
node.children = [
|
||||||
...(page.htmlAst.children.slice(startIdx, endIdx) as ElementContent[]),
|
...(page.htmlAst.children.slice(startIdx, endIdx) as ElementContent[]).map((child) =>
|
||||||
|
normalizeHastElement(child as Element, slug, transcludeTarget),
|
||||||
|
),
|
||||||
{
|
{
|
||||||
type: "element",
|
type: "element",
|
||||||
tagName: "a",
|
tagName: "a",
|
||||||
@ -135,7 +148,9 @@ export function renderPage(
|
|||||||
{ type: "text", value: page.frontmatter?.title ?? `Transclude of ${page.slug}` },
|
{ type: "text", value: page.frontmatter?.title ?? `Transclude of ${page.slug}` },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
...(page.htmlAst.children as ElementContent[]),
|
...(page.htmlAst.children as ElementContent[]).map((child) =>
|
||||||
|
normalizeHastElement(child as Element, slug, transcludeTarget),
|
||||||
|
),
|
||||||
{
|
{
|
||||||
type: "element",
|
type: "element",
|
||||||
tagName: "a",
|
tagName: "a",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { ContentDetails } from "../../plugins/emitters/contentIndex"
|
import type { ContentDetails, ContentIndex } from "../../plugins/emitters/contentIndex"
|
||||||
import * as d3 from "d3"
|
import * as d3 from "d3"
|
||||||
import { registerEscapeHandler, removeAllChildren } from "./util"
|
import { registerEscapeHandler, removeAllChildren } from "./util"
|
||||||
import { FullSlug, SimpleSlug, getFullSlug, resolveRelative, simplifySlug } from "../../util/path"
|
import { FullSlug, SimpleSlug, getFullSlug, resolveRelative, simplifySlug } from "../../util/path"
|
||||||
@ -46,20 +46,22 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
|
|||||||
showTags,
|
showTags,
|
||||||
} = JSON.parse(graph.dataset["cfg"]!)
|
} = JSON.parse(graph.dataset["cfg"]!)
|
||||||
|
|
||||||
const data = await fetchData
|
const data: Map<SimpleSlug, ContentDetails> = new Map(
|
||||||
|
Object.entries<ContentDetails>(await fetchData).map(([k, v]) => [
|
||||||
|
simplifySlug(k as FullSlug),
|
||||||
|
v,
|
||||||
|
]),
|
||||||
|
)
|
||||||
const links: LinkData[] = []
|
const links: LinkData[] = []
|
||||||
const tags: SimpleSlug[] = []
|
const tags: SimpleSlug[] = []
|
||||||
|
|
||||||
const validLinks = new Set(Object.keys(data).map((slug) => simplifySlug(slug as FullSlug)))
|
const validLinks = new Set(data.keys())
|
||||||
|
for (const [source, details] of data.entries()) {
|
||||||
for (const [src, details] of Object.entries<ContentDetails>(data)) {
|
|
||||||
const source = simplifySlug(src as FullSlug)
|
|
||||||
const outgoing = details.links ?? []
|
const outgoing = details.links ?? []
|
||||||
|
|
||||||
for (const dest of outgoing) {
|
for (const dest of outgoing) {
|
||||||
if (validLinks.has(dest)) {
|
if (validLinks.has(dest)) {
|
||||||
links.push({ source, target: dest })
|
links.push({ source: source, target: dest })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +73,7 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
|
|||||||
tags.push(...localTags.filter((tag) => !tags.includes(tag)))
|
tags.push(...localTags.filter((tag) => !tags.includes(tag)))
|
||||||
|
|
||||||
for (const tag of localTags) {
|
for (const tag of localTags) {
|
||||||
links.push({ source, target: tag })
|
links.push({ source: source, target: tag })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -93,17 +95,17 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Object.keys(data).forEach((id) => neighbourhood.add(simplifySlug(id as FullSlug)))
|
validLinks.forEach((id) => neighbourhood.add(id))
|
||||||
if (showTags) tags.forEach((tag) => neighbourhood.add(tag))
|
if (showTags) tags.forEach((tag) => neighbourhood.add(tag))
|
||||||
}
|
}
|
||||||
|
|
||||||
const graphData: { nodes: NodeData[]; links: LinkData[] } = {
|
const graphData: { nodes: NodeData[]; links: LinkData[] } = {
|
||||||
nodes: [...neighbourhood].map((url) => {
|
nodes: [...neighbourhood].map((url) => {
|
||||||
const text = url.startsWith("tags/") ? "#" + url.substring(5) : data[url]?.title ?? url
|
const text = url.startsWith("tags/") ? "#" + url.substring(5) : data.get(url)?.title ?? url
|
||||||
return {
|
return {
|
||||||
id: url,
|
id: url,
|
||||||
text: text,
|
text: text,
|
||||||
tags: data[url]?.tags ?? [],
|
tags: data.get(url)?.tags ?? [],
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
links: links.filter((l) => neighbourhood.has(l.source) && neighbourhood.has(l.target)),
|
links: links.filter((l) => neighbourhood.has(l.source) && neighbourhood.has(l.target)),
|
||||||
@ -200,7 +202,7 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
|
|||||||
window.spaNavigate(new URL(targ, window.location.toString()))
|
window.spaNavigate(new URL(targ, window.location.toString()))
|
||||||
})
|
})
|
||||||
.on("mouseover", function (_, d) {
|
.on("mouseover", function (_, d) {
|
||||||
const neighbours: SimpleSlug[] = data[fullSlug].links ?? []
|
const neighbours: SimpleSlug[] = data.get(slug)?.links ?? []
|
||||||
const neighbourNodes = d3
|
const neighbourNodes = d3
|
||||||
.selectAll<HTMLElement, NodeData>(".node")
|
.selectAll<HTMLElement, NodeData>(".node")
|
||||||
.filter((d) => neighbours.includes(d.id))
|
.filter((d) => neighbours.includes(d.id))
|
||||||
|
@ -7,6 +7,10 @@ async function mouseEnterHandler(
|
|||||||
{ clientX, clientY }: { clientX: number; clientY: number },
|
{ clientX, clientY }: { clientX: number; clientY: number },
|
||||||
) {
|
) {
|
||||||
const link = this
|
const link = this
|
||||||
|
if (link.dataset.noPopover === "true") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
async function setPosition(popoverElement: HTMLElement) {
|
async function setPosition(popoverElement: HTMLElement) {
|
||||||
const { x, y } = await computePosition(link, popoverElement, {
|
const { x, y } = await computePosition(link, popoverElement, {
|
||||||
middleware: [inline({ x: clientX, y: clientY }), shift(), flip()],
|
middleware: [inline({ x: clientX, y: clientY }), shift(), flip()],
|
||||||
@ -32,8 +36,6 @@ async function mouseEnterHandler(
|
|||||||
const hash = targetUrl.hash
|
const hash = targetUrl.hash
|
||||||
targetUrl.hash = ""
|
targetUrl.hash = ""
|
||||||
targetUrl.search = ""
|
targetUrl.search = ""
|
||||||
// prevent hover of the same page
|
|
||||||
if (thisUrl.toString() === targetUrl.toString()) return
|
|
||||||
|
|
||||||
const contents = await fetch(`${targetUrl}`)
|
const contents = await fetch(`${targetUrl}`)
|
||||||
.then((res) => res.text())
|
.then((res) => res.text())
|
||||||
|
@ -45,7 +45,7 @@ export const TagPage: QuartzEmitterPlugin<FullPageLayout> = (userOpts) => {
|
|||||||
|
|
||||||
const tagDescriptions: Record<string, ProcessedContent> = Object.fromEntries(
|
const tagDescriptions: Record<string, ProcessedContent> = Object.fromEntries(
|
||||||
[...tags].map((tag) => {
|
[...tags].map((tag) => {
|
||||||
const title = tag === "" ? "Tag Index" : `Tag: #${tag}`
|
const title = tag === "index" ? "Tag Index" : `Tag: #${tag}`
|
||||||
return [
|
return [
|
||||||
tag,
|
tag,
|
||||||
defaultProcessedContent({
|
defaultProcessedContent({
|
||||||
|
@ -8,11 +8,13 @@ import { slugTag } from "../../util/path"
|
|||||||
export interface Options {
|
export interface Options {
|
||||||
delims: string | string[]
|
delims: string | string[]
|
||||||
language: "yaml" | "toml"
|
language: "yaml" | "toml"
|
||||||
|
oneLineTagDelim: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultOptions: Options = {
|
const defaultOptions: Options = {
|
||||||
delims: "---",
|
delims: "---",
|
||||||
language: "yaml",
|
language: "yaml",
|
||||||
|
oneLineTagDelim: ",",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FrontMatter: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
|
export const FrontMatter: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
|
||||||
@ -20,6 +22,8 @@ export const FrontMatter: QuartzTransformerPlugin<Partial<Options> | undefined>
|
|||||||
return {
|
return {
|
||||||
name: "FrontMatter",
|
name: "FrontMatter",
|
||||||
markdownPlugins() {
|
markdownPlugins() {
|
||||||
|
const { oneLineTagDelim } = opts
|
||||||
|
|
||||||
return [
|
return [
|
||||||
[remarkFrontmatter, ["yaml", "toml"]],
|
[remarkFrontmatter, ["yaml", "toml"]],
|
||||||
() => {
|
() => {
|
||||||
@ -45,7 +49,7 @@ export const FrontMatter: QuartzTransformerPlugin<Partial<Options> | undefined>
|
|||||||
if (data.tags && !Array.isArray(data.tags)) {
|
if (data.tags && !Array.isArray(data.tags)) {
|
||||||
data.tags = data.tags
|
data.tags = data.tags
|
||||||
.toString()
|
.toString()
|
||||||
.split(",")
|
.split(oneLineTagDelim)
|
||||||
.map((tag: string) => tag.trim())
|
.map((tag: string) => tag.trim())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +31,11 @@ export const GitHubFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options> |
|
|||||||
rehypeAutolinkHeadings,
|
rehypeAutolinkHeadings,
|
||||||
{
|
{
|
||||||
behavior: "append",
|
behavior: "append",
|
||||||
|
properties: {
|
||||||
|
ariaHidden: true,
|
||||||
|
tabIndex: -1,
|
||||||
|
"data-no-popover": true,
|
||||||
|
},
|
||||||
content: {
|
content: {
|
||||||
type: "text",
|
type: "text",
|
||||||
value: " §",
|
value: " §",
|
||||||
|
@ -81,14 +81,16 @@ export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> =
|
|||||||
// WHATWG equivalent https://nodejs.dev/en/api/v18/url/#urlresolvefrom-to
|
// WHATWG equivalent https://nodejs.dev/en/api/v18/url/#urlresolvefrom-to
|
||||||
const url = new URL(dest, `https://base.com/${curSlug}`)
|
const url = new URL(dest, `https://base.com/${curSlug}`)
|
||||||
const canonicalDest = url.pathname
|
const canonicalDest = url.pathname
|
||||||
const [destCanonical, _destAnchor] = splitAnchor(canonicalDest)
|
let [destCanonical, _destAnchor] = splitAnchor(canonicalDest)
|
||||||
|
if (destCanonical.endsWith("/")) {
|
||||||
|
destCanonical += "index"
|
||||||
|
}
|
||||||
|
|
||||||
// need to decodeURIComponent here as WHATWG URL percent-encodes everything
|
// need to decodeURIComponent here as WHATWG URL percent-encodes everything
|
||||||
const simple = decodeURIComponent(
|
const full = decodeURIComponent(_stripSlashes(destCanonical, true)) as FullSlug
|
||||||
simplifySlug(destCanonical as FullSlug),
|
const simple = simplifySlug(full)
|
||||||
) as SimpleSlug
|
|
||||||
outgoing.add(simple)
|
outgoing.add(simple)
|
||||||
node.properties["data-slug"] = simple
|
node.properties["data-slug"] = full
|
||||||
}
|
}
|
||||||
|
|
||||||
// rewrite link internals if prettylinks is on
|
// rewrite link internals if prettylinks is on
|
||||||
|
@ -182,7 +182,8 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
|
|||||||
const [rawFp, rawHeader, rawAlias] = capture
|
const [rawFp, rawHeader, rawAlias] = capture
|
||||||
const fp = rawFp ?? ""
|
const fp = rawFp ?? ""
|
||||||
const anchor = rawHeader?.trim().replace(/^#+/, "")
|
const anchor = rawHeader?.trim().replace(/^#+/, "")
|
||||||
const displayAnchor = anchor ? `#${slugAnchor(anchor)}` : ""
|
const blockRef = Boolean(anchor?.startsWith("^")) ? "^" : ""
|
||||||
|
const displayAnchor = anchor ? `#${blockRef}${slugAnchor(anchor)}` : ""
|
||||||
const displayAlias = rawAlias ?? rawHeader?.replace("#", "|") ?? ""
|
const displayAlias = rawAlias ?? rawHeader?.replace("#", "|") ?? ""
|
||||||
const embedDisplay = value.startsWith("!") ? "!" : ""
|
const embedDisplay = value.startsWith("!") ? "!" : ""
|
||||||
return `${embedDisplay}[[${fp}${displayAnchor}${displayAlias}]]`
|
return `${embedDisplay}[[${fp}${displayAnchor}${displayAlias}]]`
|
||||||
|
@ -83,7 +83,7 @@ describe("transforms", () => {
|
|||||||
test("simplifySlug", () => {
|
test("simplifySlug", () => {
|
||||||
asserts(
|
asserts(
|
||||||
[
|
[
|
||||||
["index", ""],
|
["index", "/"],
|
||||||
["abc", "abc"],
|
["abc", "abc"],
|
||||||
["abc/index", "abc/"],
|
["abc/index", "abc/"],
|
||||||
["abc/def", "abc/def"],
|
["abc/def", "abc/def"],
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { slug } from "github-slugger"
|
import { slug } 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)
|
// this file must be isomorphic so it can't use node libs (e.g. path)
|
||||||
|
|
||||||
export const QUARTZ = "quartz"
|
export const QUARTZ = "quartz"
|
||||||
@ -24,7 +25,7 @@ export function isFullSlug(s: string): s is FullSlug {
|
|||||||
/** Shouldn't be a relative path and shouldn't have `/index` as an ending or a file extension. It _can_ however have a trailing slash to indicate a folder path. */
|
/** Shouldn't be a relative path and shouldn't have `/index` as an ending or a file extension. It _can_ however have a trailing slash to indicate a folder path. */
|
||||||
export type SimpleSlug = SlugLike<"simple">
|
export type SimpleSlug = SlugLike<"simple">
|
||||||
export function isSimpleSlug(s: string): s is SimpleSlug {
|
export function isSimpleSlug(s: string): s is SimpleSlug {
|
||||||
const validStart = !(s.startsWith(".") || s.startsWith("/"))
|
const validStart = !(s.startsWith(".") || (s.length > 1 && s.startsWith("/")))
|
||||||
const validEnding = !(s.endsWith("/index") || s === "index")
|
const validEnding = !(s.endsWith("/index") || s === "index")
|
||||||
return validStart && !_containsForbiddenCharacters(s) && validEnding && !_hasFileExtension(s)
|
return validStart && !_containsForbiddenCharacters(s) && validEnding && !_hasFileExtension(s)
|
||||||
}
|
}
|
||||||
@ -65,7 +66,8 @@ export function slugifyFilePath(fp: FilePath, excludeExt?: boolean): FullSlug {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function simplifySlug(fp: FullSlug): SimpleSlug {
|
export function simplifySlug(fp: FullSlug): SimpleSlug {
|
||||||
return _stripSlashes(_trimSuffix(fp, "index"), true) as SimpleSlug
|
const res = _stripSlashes(_trimSuffix(fp, "index"), true)
|
||||||
|
return (res.length === 0 ? "/" : res) as SimpleSlug
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transformInternalLink(link: string): RelativeURL {
|
export function transformInternalLink(link: string): RelativeURL {
|
||||||
@ -86,20 +88,47 @@ export function transformInternalLink(link: string): RelativeURL {
|
|||||||
|
|
||||||
// from micromorph/src/utils.ts
|
// from micromorph/src/utils.ts
|
||||||
// https://github.com/natemoo-re/micromorph/blob/main/src/utils.ts#L5
|
// https://github.com/natemoo-re/micromorph/blob/main/src/utils.ts#L5
|
||||||
|
const _rebaseHtmlElement = (el: Element, attr: string, newBase: string | URL) => {
|
||||||
|
const rebased = new URL(el.getAttribute(attr)!, newBase)
|
||||||
|
el.setAttribute(attr, rebased.pathname + rebased.hash)
|
||||||
|
}
|
||||||
export function normalizeRelativeURLs(el: Element | Document, destination: string | URL) {
|
export function normalizeRelativeURLs(el: Element | Document, destination: string | URL) {
|
||||||
const rebase = (el: Element, attr: string, newBase: string | URL) => {
|
|
||||||
const rebased = new URL(el.getAttribute(attr)!, newBase)
|
|
||||||
el.setAttribute(attr, rebased.pathname + rebased.hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
el.querySelectorAll('[href^="./"], [href^="../"]').forEach((item) =>
|
el.querySelectorAll('[href^="./"], [href^="../"]').forEach((item) =>
|
||||||
rebase(item, "href", destination),
|
_rebaseHtmlElement(item, "href", destination),
|
||||||
)
|
)
|
||||||
el.querySelectorAll('[src^="./"], [src^="../"]').forEach((item) =>
|
el.querySelectorAll('[src^="./"], [src^="../"]').forEach((item) =>
|
||||||
rebase(item, "src", destination),
|
_rebaseHtmlElement(item, "src", destination),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _rebaseHastElement = (
|
||||||
|
el: HastElement,
|
||||||
|
attr: string,
|
||||||
|
curBase: FullSlug,
|
||||||
|
newBase: FullSlug,
|
||||||
|
) => {
|
||||||
|
if (el.properties?.[attr]) {
|
||||||
|
if (!isRelativeURL(String(el.properties[attr]))) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const rel = joinSegments(resolveRelative(curBase, newBase), "..", el.properties[attr] as string)
|
||||||
|
el.properties[attr] = rel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeHastElement(el: HastElement, curBase: FullSlug, newBase: FullSlug) {
|
||||||
|
_rebaseHastElement(el, "src", curBase, newBase)
|
||||||
|
_rebaseHastElement(el, "href", curBase, newBase)
|
||||||
|
if (el.children) {
|
||||||
|
el.children = el.children.map((child) =>
|
||||||
|
normalizeHastElement(child as HastElement, curBase, newBase),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return el
|
||||||
|
}
|
||||||
|
|
||||||
// resolve /a/b/c to ../..
|
// resolve /a/b/c to ../..
|
||||||
export function pathToRoot(slug: FullSlug): RelativeURL {
|
export function pathToRoot(slug: FullSlug): RelativeURL {
|
||||||
let rootPath = slug
|
let rootPath = slug
|
||||||
|
Loading…
Reference in New Issue
Block a user