Skip to content

Commit

Permalink
Merge branch 'v4' of https://github.com/jackyzha0/quartz into v4
Browse files Browse the repository at this point in the history
  • Loading branch information
zenodotus280 committed Jan 13, 2025
2 parents 5c3af6b + 09f8670 commit d039e36
Show file tree
Hide file tree
Showing 31 changed files with 707 additions and 982 deletions.
2 changes: 1 addition & 1 deletion docs/advanced/making plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ export const ContentPage: QuartzEmitterPlugin = () => {
const allFiles = content.map((c) => c[1].data)
for (const [tree, file] of content) {
const slug = canonicalizeServer(file.data.slug!)
const externalResources = pageResources(slug, resources)
const externalResources = pageResources(slug, file.data, resources)
const componentData: QuartzComponentProps = {
fileData: file.data,
externalResources,
Expand Down
1 change: 1 addition & 0 deletions docs/features/backlinks.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ A backlink for a note is a link from another note to that note. Links in the bac
## Customization

- Removing backlinks: delete all usages of `Component.Backlinks()` from `quartz.layout.ts`.
- Hide when empty: hide `Backlinks` if given page doesn't contain any backlinks (default to `true`). To disable this, use `Component.Backlinks({ hideWhenEmpty: false })`.
- Component: `quartz/components/Backlinks.tsx`
- Style: `quartz/components/styles/backlinks.scss`
- Script: `quartz/components/scripts/search.inline.ts`
2 changes: 1 addition & 1 deletion docs/plugins/ObsidianFlavoredMarkdown.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ This plugin accepts the following configuration options:

- Category: Transformer
- Function name: `Plugin.ObsidianFlavoredMarkdown()`.
- Source: [`quartz/plugins/transformers/toc.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/toc.ts).
- Source: [`quartz/plugins/transformers/ofm.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/ofm.ts)
1,204 changes: 361 additions & 843 deletions package-lock.json

Large diffs are not rendered by default.

19 changes: 9 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,29 +37,28 @@
"dependencies": {
"@clack/prompts": "^0.8.2",
"@floating-ui/dom": "^1.6.12",
"@myriaddreamin/rehype-typst": "^0.5.0-rc7",
"@myriaddreamin/rehype-typst": "^0.5.4",
"@napi-rs/simple-git": "0.1.19",
"@tweenjs/tween.js": "^25.0.0",
"async-mutex": "^0.5.0",
"chalk": "^5.3.0",
"chokidar": "^4.0.1",
"chalk": "^5.4.1",
"chokidar": "^4.0.3",
"cli-spinner": "^0.2.10",
"d3": "^7.9.0",
"esbuild-sass-plugin": "^3.3.1",
"flexsearch": "0.7.43",
"github-slugger": "^2.0.0",
"globby": "^14.0.2",
"gray-matter": "^4.0.3",
"hast-util-to-html": "^9.0.3",
"hast-util-to-html": "^9.0.4",
"hast-util-to-jsx-runtime": "^2.3.2",
"hast-util-to-string": "^3.0.1",
"is-absolute-url": "^4.0.1",
"js-yaml": "^4.1.0",
"lightningcss": "^1.28.1",
"mdast-util-find-and-replace": "^3.0.1",
"lightningcss": "^1.28.2",
"mdast-util-find-and-replace": "^3.0.2",
"mdast-util-to-hast": "^13.2.0",
"mdast-util-to-string": "^4.0.0",
"mermaid": "^11.4.0",
"micromorph": "^0.4.5",
"pixi.js": "^8.5.2",
"preact": "^10.25.0",
Expand Down Expand Up @@ -87,7 +86,7 @@
"satori": "^0.12.0",
"serve-handler": "^6.1.6",
"sharp": "^0.33.5",
"shiki": "^1.23.1",
"shiki": "^1.26.1",
"source-map-support": "^0.5.21",
"to-vfile": "^8.0.0",
"toml": "^3.0.0",
Expand All @@ -108,8 +107,8 @@
"@types/source-map-support": "^0.5.10",
"@types/ws": "^8.5.13",
"@types/yargs": "^17.0.33",
"esbuild": "^0.24.0",
"prettier": "^3.3.3",
"esbuild": "^0.24.2",
"prettier": "^3.4.2",
"tsx": "^4.19.2",
"typescript": "^5.7.2"
}
Expand Down
5 changes: 3 additions & 2 deletions quartz/bootstrap-worker.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env node
import workerpool from "workerpool"
const cacheFile = "./.quartz-cache/transpiled-worker.mjs"
const { parseFiles } = await import(cacheFile)
const { parseMarkdown, processHtml } = await import(cacheFile)
workerpool.worker({
parseFiles,
parseMarkdown,
processHtml,
})
6 changes: 3 additions & 3 deletions quartz/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,9 @@ async function startServing(

const buildFromEntry = argv.fastRebuild ? partialRebuildFromEntrypoint : rebuildFromEntrypoint
watcher
.on("add", (fp) => buildFromEntry(fp, "add", clientRefresh, buildData))
.on("change", (fp) => buildFromEntry(fp, "change", clientRefresh, buildData))
.on("unlink", (fp) => buildFromEntry(fp, "delete", clientRefresh, buildData))
.on("add", (fp) => buildFromEntry(fp as string, "add", clientRefresh, buildData))
.on("change", (fp) => buildFromEntry(fp as string, "change", clientRefresh, buildData))
.on("unlink", (fp) => buildFromEntry(fp as string, "delete", clientRefresh, buildData))

return async () => {
await watcher.close()
Expand Down
72 changes: 44 additions & 28 deletions quartz/components/Backlinks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,49 @@ import { resolveRelative, simplifySlug } from "../util/path"
import { i18n } from "../i18n"
import { classNames } from "../util/lang"

const Backlinks: QuartzComponent = ({
fileData,
allFiles,
displayClass,
cfg,
}: QuartzComponentProps) => {
const slug = simplifySlug(fileData.slug!)
const backlinkFiles = allFiles.filter((file) => file.links?.includes(slug))
return (
<div class={classNames(displayClass, "backlinks")}>
<h3>{i18n(cfg.locale).components.backlinks.title}</h3>
<ul class="overflow">
{backlinkFiles.length > 0 ? (
backlinkFiles.map((f) => (
<li>
<a href={resolveRelative(fileData.slug!, f.slug!)} class="internal">
{f.frontmatter?.title}
</a>
</li>
))
) : (
<li>{i18n(cfg.locale).components.backlinks.noBacklinksFound}</li>
)}
</ul>
</div>
)
interface BacklinksOptions {
hideWhenEmpty: boolean
}

Backlinks.css = style
export default (() => Backlinks) satisfies QuartzComponentConstructor
const defaultOptions: BacklinksOptions = {
hideWhenEmpty: true,
}

export default ((opts?: Partial<BacklinksOptions>) => {
const options: BacklinksOptions = { ...defaultOptions, ...opts }

const Backlinks: QuartzComponent = ({
fileData,
allFiles,
displayClass,
cfg,
}: QuartzComponentProps) => {
const slug = simplifySlug(fileData.slug!)
const backlinkFiles = allFiles.filter((file) => file.links?.includes(slug))
if (options.hideWhenEmpty && backlinkFiles.length == 0) {
return null
}
return (
<div class={classNames(displayClass, "backlinks")}>
<h3>{i18n(cfg.locale).components.backlinks.title}</h3>
<ul class="overflow">
{backlinkFiles.length > 0 ? (
backlinkFiles.map((f) => (
<li>
<a href={resolveRelative(fileData.slug!, f.slug!)} class="internal">
{f.frontmatter?.title}
</a>
</li>
))
) : (
<li>{i18n(cfg.locale).components.backlinks.noBacklinksFound}</li>
)}
</ul>
</div>
)
}

Backlinks.css = style

return Backlinks
}) satisfies QuartzComponentConstructor
1 change: 1 addition & 0 deletions quartz/components/Head.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ export default (() => {
<link rel="stylesheet" href={googleFontHref(cfg.theme)} />
</>
)}
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossOrigin={"anonymous"} />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{/* OG/Twitter meta tags */}
<meta name="og:site_name" content={cfg.pageTitle}></meta>
Expand Down
6 changes: 3 additions & 3 deletions quartz/components/pages/FolderContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export default ((opts?: Partial<FolderContentOptions>) => {
})

const cssClasses: string[] = fileData.frontmatter?.cssclasses ?? []
const classes = ["popover-hint", ...cssClasses].join(" ")
const classes = cssClasses.join(" ")
const listProps = {
...props,
sort: options.sort,
Expand All @@ -84,8 +84,8 @@ export default ((opts?: Partial<FolderContentOptions>) => {
: htmlToJsx(fileData.filePath!, tree)

return (
<div class={classes}>
<article>{content}</article>
<div class="popover-hint">
<article class={classes}>{content}</article>
<div class="page-listing">
{options.showFolderCount && (
<p>
Expand Down
12 changes: 6 additions & 6 deletions quartz/components/pages/TagContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default ((opts?: Partial<TagContentOptions>) => {
? fileData.description
: htmlToJsx(fileData.filePath!, tree)
const cssClasses: string[] = fileData.frontmatter?.cssclasses ?? []
const classes = ["popover-hint", ...cssClasses].join(" ")
const classes = cssClasses.join(" ")
if (tag === "/") {
const tags = [
...new Set(
Expand All @@ -50,8 +50,8 @@ export default ((opts?: Partial<TagContentOptions>) => {
tagItemMap.set(tag, allPagesWithTag(tag))
}
return (
<div class={classes}>
<article>
<div class="popover-hint">
<article class={classes}>
<p>{content}</p>
</article>
<p>{i18n(cfg.locale).pages.tagContent.totalTags({ count: tags.length })}</p>
Expand Down Expand Up @@ -93,7 +93,7 @@ export default ((opts?: Partial<TagContentOptions>) => {
</>
)}
</p>
<PageList limit={options.numPages} {...listProps} sort={opts?.sort} />
<PageList limit={options.numPages} {...listProps} sort={options?.sort} />
</div>
</div>
)
Expand All @@ -110,11 +110,11 @@ export default ((opts?: Partial<TagContentOptions>) => {

return (
<div class={classes}>
<article>{content}</article>
<article class="popover-hint">{content}</article>
<div class="page-listing">
<p>{i18n(cfg.locale).pages.tagContent.itemsUnderTag({ count: pages.length })}</p>
<div>
<PageList {...listProps} />
<PageList {...listProps} sort={options?.sort} />
</div>
</div>
</div>
Expand Down
33 changes: 26 additions & 7 deletions quartz/components/renderPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import { visit } from "unist-util-visit"
import { Root, Element, ElementContent } from "hast"
import { GlobalConfiguration } from "../cfg"
import { i18n } from "../i18n"
// @ts-ignore
import mermaidScript from "./scripts/mermaid.inline"
import mermaidStyle from "./styles/mermaid.inline.scss"
import { QuartzPluginData } from "../plugins/vfile"

interface RenderComponents {
head: QuartzComponent
Expand All @@ -23,12 +27,13 @@ interface RenderComponents {
const headerRegex = new RegExp(/h[1-6]/)
export function pageResources(
baseDir: FullSlug | RelativeURL,
fileData: QuartzPluginData,
staticResources: StaticResources,
): StaticResources {
const contentIndexPath = joinSegments(baseDir, "static/contentIndex.json")
const contentIndexScript = `const fetchData = fetch("${contentIndexPath}").then(data => data.json())`

return {
const resources: StaticResources = {
css: [
{
content: joinSegments(baseDir, "index.css"),
Expand All @@ -48,14 +53,28 @@ export function pageResources(
script: contentIndexScript,
},
...staticResources.js,
{
src: joinSegments(baseDir, "postscript.js"),
loadTime: "afterDOMReady",
moduleType: "module",
contentType: "external",
},
],
}

if (fileData.hasMermaidDiagram) {
resources.js.push({
script: mermaidScript,
loadTime: "afterDOMReady",
moduleType: "module",
contentType: "inline",
})
resources.css.push({ content: mermaidStyle, inline: true })
}

// NOTE: we have to put this last to make sure spa.inline.ts is the last item.
resources.js.push({
src: joinSegments(baseDir, "postscript.js"),
loadTime: "afterDOMReady",
moduleType: "module",
contentType: "external",
})

return resources
}

export function renderPage(
Expand Down
8 changes: 7 additions & 1 deletion quartz/components/scripts/mermaid.inline.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { removeAllChildren } from "./util"
import mermaid from "mermaid"

interface Position {
x: number
Expand Down Expand Up @@ -144,6 +143,7 @@ const cssVars = [
"--codeFont",
] as const

let mermaidImport = undefined
document.addEventListener("nav", async () => {
const center = document.querySelector(".center") as HTMLElement
const nodes = center.querySelectorAll("code.mermaid") as NodeListOf<HTMLElement>
Expand All @@ -157,6 +157,12 @@ document.addEventListener("nav", async () => {
{} as Record<(typeof cssVars)[number], string>,
)

mermaidImport ||= await import(
//@ts-ignore
"https://cdnjs.cloudflare.com/ajax/libs/mermaid/11.4.0/mermaid.esm.min.mjs"
)
const mermaid = mermaidImport.default

const darkMode = document.documentElement.getAttribute("saved-theme") === "dark"
mermaid.initialize({
startOnLoad: false,
Expand Down
3 changes: 2 additions & 1 deletion quartz/components/scripts/popover.inline.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { computePosition, flip, inline, shift } from "@floating-ui/dom"
import { normalizeRelativeURLs } from "../../util/path"
import { fetchCanonical } from "./util"

const p = new DOMParser()
async function mouseEnterHandler(
Expand Down Expand Up @@ -37,7 +38,7 @@ async function mouseEnterHandler(
targetUrl.hash = ""
targetUrl.search = ""

const response = await fetch(`${targetUrl}`).catch((err) => {
const response = await fetchCanonical(targetUrl).catch((err) => {
console.error(err)
})

Expand Down
18 changes: 17 additions & 1 deletion quartz/components/scripts/spa.inline.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import micromorph from "micromorph"
import { FullSlug, RelativeURL, getFullSlug, normalizeRelativeURLs } from "../../util/path"
import { fetchCanonical } from "./util"

// adapted from `micromorph`
// https://github.com/natemoo-re/micromorph
Expand Down Expand Up @@ -42,10 +43,24 @@ function notifyNav(url: FullSlug) {
const cleanupFns: Set<(...args: any[]) => void> = new Set()
window.addCleanup = (fn) => cleanupFns.add(fn)

function startLoading() {
const loadingBar = document.createElement("div")
loadingBar.className = "navigation-progress"
loadingBar.style.width = "0"
if (!document.body.contains(loadingBar)) {
document.body.appendChild(loadingBar)
}

setTimeout(() => {
loadingBar.style.width = "80%"
}, 100)
}

let p: DOMParser
async function navigate(url: URL, isBack: boolean = false) {
startLoading()
p = p || new DOMParser()
const contents = await fetch(`${url}`)
const contents = await fetchCanonical(url)
.then((res) => {
const contentType = res.headers.get("content-type")
if (contentType?.startsWith("text/html")) {
Expand Down Expand Up @@ -104,6 +119,7 @@ async function navigate(url: URL, isBack: boolean = false) {
if (!isBack) {
history.pushState({}, "", url)
}

notifyNav(getFullSlug(window))
delete announcer.dataset.persist
}
Expand Down
Loading

0 comments on commit d039e36

Please sign in to comment.