From cce389c81d262d1d2a2bd8140c879efd68e3c6dd Mon Sep 17 00:00:00 2001
From: Jacky Zhao <j.zhao2k19@gmail.com>
Date: Wed, 13 Sep 2023 18:28:53 +0000
Subject: [PATCH] feat: note transclusion (#475)
---
quartz/plugins/transformers/links.ts | 1
quartz/styles/base.scss | 6 +++
package-lock.json | 4 +-
quartz/plugins/transformers/ofm.ts | 52 +++++++++++++++++++++----
docs/features/wikilinks.md | 4 -
package.json | 2
quartz/components/renderPage.tsx | 36 ++++++++++++++++++
docs/index.md | 2
8 files changed, 91 insertions(+), 16 deletions(-)
diff --git a/docs/features/wikilinks.md b/docs/features/wikilinks.md
index 704a0d0..50bbb1b 100644
--- a/docs/features/wikilinks.md
+++ b/docs/features/wikilinks.md
@@ -13,6 +13,4 @@
- `[[Path to file]]`: produces a link to `Path to file.md` (or `Path-to-file.md`) with the text `Path to file`
- `[[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`
-
-> [!warning]
-> Currently, Quartz does not support block references or note embed syntax.
+- `[[Path to file#^block-ref]]`: produces a link to the specific block `block-ref` in the file `Path to file.md`
diff --git a/docs/index.md b/docs/index.md
index e5b9dfe..05de2ba 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -30,7 +30,7 @@
## 🔧 Features
-- [[Obsidian compatibility]], [[full-text search]], [[graph view]], [[wikilinks]], [[backlinks]], [[Latex]], [[syntax highlighting]], [[popover previews]], and [many more](./features) right out of the box
+- [[Obsidian compatibility]], [[full-text search]], [[graph view]], note transclusion, [[wikilinks]], [[backlinks]], [[Latex]], [[syntax highlighting]], [[popover previews]], and [many more](./features) right out of the box
- Hot-reload for both configuration and content
- Simple JSX layouts and [[creating components|page components]]
- [[SPA Routing|Ridiculously fast page loads]] and tiny bundle sizes
diff --git a/package-lock.json b/package-lock.json
index a19d81c..a879078 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@jackyzha0/quartz",
- "version": "4.0.10",
+ "version": "4.0.11",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@jackyzha0/quartz",
- "version": "4.0.10",
+ "version": "4.0.11",
"license": "MIT",
"dependencies": {
"@clack/prompts": "^0.6.3",
diff --git a/package.json b/package.json
index 95c57cd..0a2085c 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "@jackyzha0/quartz",
"description": "🌱 publish your digital garden and notes as a website",
"private": true,
- "version": "4.0.10",
+ "version": "4.0.11",
"type": "module",
"author": "jackyzha0 <j.zhao2k19@gmail.com>",
"license": "MIT",
diff --git a/quartz/components/renderPage.tsx b/quartz/components/renderPage.tsx
index 25297f2..451813b 100644
--- a/quartz/components/renderPage.tsx
+++ b/quartz/components/renderPage.tsx
@@ -4,6 +4,8 @@
import BodyConstructor from "./Body"
import { JSResourceToScriptElement, StaticResources } from "../util/resources"
import { FullSlug, RelativeURL, joinSegments } from "../util/path"
+import { visit } from "unist-util-visit"
+import { Root, Element } from "hast"
interface RenderComponents {
head: QuartzComponent
@@ -53,6 +55,40 @@
components: RenderComponents,
pageResources: StaticResources,
): string {
+ // process transcludes in componentData
+ visit(componentData.tree as Root, "element", (node, _index, _parent) => {
+ if (node.tagName === "blockquote") {
+ const classNames = (node.properties?.className ?? []) as string[]
+ if (classNames.includes("transclude")) {
+ const inner = node.children[0] as Element
+ const blockSlug = inner.properties?.["data-slug"] as FullSlug
+ const blockRef = node.properties!.dataBlock as string
+
+ // TODO: avoid this expensive find operation and construct an index ahead of time
+ let blockNode = componentData.allFiles.find((f) => f.slug === blockSlug)?.blocks?.[blockRef]
+ if (blockNode) {
+ if (blockNode.tagName === "li") {
+ blockNode = {
+ type: "element",
+ tagName: "ul",
+ children: [blockNode],
+ }
+ }
+
+ node.children = [
+ blockNode,
+ {
+ type: "element",
+ tagName: "a",
+ properties: { href: inner.properties?.href, class: ["internal"] },
+ children: [{ type: "text", value: `Link to original` }],
+ },
+ ]
+ }
+ }
+ }
+ })
+
const {
head: Head,
header,
diff --git a/quartz/plugins/transformers/links.ts b/quartz/plugins/transformers/links.ts
index 02ced15..e050e00 100644
--- a/quartz/plugins/transformers/links.ts
+++ b/quartz/plugins/transformers/links.ts
@@ -72,6 +72,7 @@
simplifySlug(destCanonical as FullSlug),
) as SimpleSlug
outgoing.add(simple)
+ node.properties["data-slug"] = simple
}
// rewrite link internals if prettylinks is on
diff --git a/quartz/plugins/transformers/ofm.ts b/quartz/plugins/transformers/ofm.ts
index 811d659..8306f40 100644
--- a/quartz/plugins/transformers/ofm.ts
+++ b/quartz/plugins/transformers/ofm.ts
@@ -135,6 +135,7 @@
const hast = toHast(ast, { allowDangerousHtml: true })!
return toHtml(hast, { allowDangerousHtml: true })
}
+
const findAndReplace = opts.enableInHtmlEmbed
? (tree: Root, regex: RegExp, replace?: Replace | null | undefined) => {
if (replace) {
@@ -238,8 +239,16 @@
value: `<iframe src="${url}"></iframe>`,
}
} else if (ext === "") {
- // TODO: note embed
+ const block = anchor.slice(1)
+ 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 block ${block}</a></blockquote>`,
+ }
}
+
// otherwise, fall through to regular link
}
@@ -422,22 +431,47 @@
if (opts.parseBlockReferences) {
plugins.push(() => {
+ const inlineTagTypes = new Set(["p", "li"])
+ const blockTagTypes = new Set(["blockquote"])
return (tree, file) => {
file.data.blocks = {}
- const validTagTypes = new Set(["blockquote", "p", "li"])
- visit(tree, "element", (node, _index, _parent) => {
- if (validTagTypes.has(node.tagName)) {
+
+ visit(tree, "element", (node, index, parent) => {
+ if (blockTagTypes.has(node.tagName)) {
+ const nextChild = parent?.children.at(index! + 2) as Element
+ if (nextChild && nextChild.tagName === "p") {
+ const text = nextChild.children.at(0) as Literal
+ if (text && text.value && text.type === "text") {
+ const matches = text.value.match(blockReferenceRegex)
+ if (matches && matches.length >= 1) {
+ parent!.children.splice(index! + 2, 1)
+ const block = matches[0].slice(1)
+
+ if (!Object.keys(file.data.blocks!).includes(block)) {
+ node.properties = {
+ ...node.properties,
+ id: block,
+ }
+ file.data.blocks![block] = node
+ }
+ }
+ }
+ }
+ } else if (inlineTagTypes.has(node.tagName)) {
const last = node.children.at(-1) as Literal
- if (last.value && typeof last.value === "string") {
+ if (last && last.value && typeof last.value === "string") {
const matches = last.value.match(blockReferenceRegex)
if (matches && matches.length >= 1) {
last.value = last.value.slice(0, -matches[0].length)
const block = matches[0].slice(1)
- node.properties = {
- ...node.properties,
- id: block,
+
+ if (!Object.keys(file.data.blocks!).includes(block)) {
+ node.properties = {
+ ...node.properties,
+ id: block,
+ }
+ file.data.blocks![block] = node
}
- file.data.blocks![block] = node
}
}
}
diff --git a/quartz/styles/base.scss b/quartz/styles/base.scss
index 34def87..92c0f84 100644
--- a/quartz/styles/base.scss
+++ b/quartz/styles/base.scss
@@ -470,3 +470,9 @@
background: linear-gradient(transparent 0px, var(--light));
}
}
+
+.transclude {
+ ul {
+ padding-left: 1rem;
+ }
+}
--
Gitblit v1.10.0