From 74777118a7fd19e4a296706c2a4b5fdca546c4fa Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Mon, 13 Nov 2023 22:51:40 -0800 Subject: [PATCH] feat: header and full-page transcludes (closes #557) --- quartz/components/renderPage.tsx | 81 ++++++++++++++++++++++++++---- quartz/plugins/transformers/ofm.ts | 8 +-- 2 files changed, 75 insertions(+), 14 deletions(-) diff --git a/quartz/components/renderPage.tsx b/quartz/components/renderPage.tsx index 451813b5e..36b06464f 100644 --- a/quartz/components/renderPage.tsx +++ b/quartz/components/renderPage.tsx @@ -5,7 +5,7 @@ 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" +import { Root, Element, ElementContent } from "hast" interface RenderComponents { head: QuartzComponent @@ -61,22 +61,81 @@ export function renderPage( 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 + const transcludeTarget = inner.properties?.["data-slug"] as FullSlug // 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], + const page = componentData.allFiles.find((f) => f.slug === transcludeTarget) + if (!page) { + return + } + + let blockRef = node.properties?.dataBlock as string | undefined + if (blockRef?.startsWith("^")) { + // block transclude + blockRef = blockRef.slice(1) + let blockNode = page.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` }], + }, + ] + } + } else if (blockRef?.startsWith("#") && page.htmlAst) { + // header transclude + blockRef = blockRef.slice(1) + let startIdx = undefined + let endIdx = undefined + for (const [i, el] of page.htmlAst.children.entries()) { + if (el.type === "element" && el.tagName.match(/h[1-6]/)) { + if (endIdx) { + break + } + + if (startIdx) { + endIdx = i + } else if (el.properties?.id === blockRef) { + startIdx = i + } } } + if (!startIdx) { + return + } + node.children = [ - blockNode, + ...(page.htmlAst.children.slice(startIdx, endIdx) as ElementContent[]), + { + type: "element", + tagName: "a", + properties: { href: inner.properties?.href, class: ["internal"] }, + children: [{ type: "text", value: `Link to original` }], + }, + ] + } else if (page.htmlAst) { + // page transclude + node.children = [ + { + type: "element", + tagName: "h1", + children: [ + { type: "text", value: page.frontmatter?.title ?? `Transclude of ${page.slug}` }, + ], + }, + ...(page.htmlAst.children as ElementContent[]), { type: "element", tagName: "a", diff --git a/quartz/plugins/transformers/ofm.ts b/quartz/plugins/transformers/ofm.ts index 226e9394e..50c4d5c64 100644 --- a/quartz/plugins/transformers/ofm.ts +++ b/quartz/plugins/transformers/ofm.ts @@ -1,7 +1,7 @@ import { PluggableList } from "unified" import { QuartzTransformerPlugin } from "../types" import { Root, HTML, BlockContent, DefinitionContent, Code, Paragraph } from "mdast" -import { Element, Literal } from "hast" +import { Element, Literal, Root as HtmlRoot } from "hast" import { Replace, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" import { slug as slugAnchor } from "github-slugger" import rehypeRaw from "rehype-raw" @@ -236,13 +236,13 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin value: ``, } } else if (ext === "") { - const block = anchor.slice(1) + const block = anchor return { type: "html", data: { hProperties: { transclude: true } }, value: `
Transclude of block ${block}
`, + }" class="transclude-inner">Transclude of ${url}${block}`, } } @@ -436,6 +436,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin const blockTagTypes = new Set(["blockquote"]) return (tree, file) => { file.data.blocks = {} + file.data.htmlAst = tree visit(tree, "element", (node, index, parent) => { if (blockTagTypes.has(node.tagName)) { @@ -524,5 +525,6 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin declare module "vfile" { interface DataMap { blocks: Record + htmlAst: HtmlRoot } }