feat: header and full-page transcludes (closes #557)
This commit is contained in:
		
							parent
							
								
									8223465bda
								
							
						
					
					
						commit
						74777118a7
					
				
					 2 changed files with 75 additions and 14 deletions
				
			
		| 
						 | 
				
			
			@ -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",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<Partial<Options>
 | 
			
		|||
                    value: `<iframe src="${url}"></iframe>`,
 | 
			
		||||
                  }
 | 
			
		||||
                } else if (ext === "") {
 | 
			
		||||
                  const block = anchor.slice(1)
 | 
			
		||||
                  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 block ${block}</a></blockquote>`,
 | 
			
		||||
                    }" class="transclude-inner">Transclude of ${url}${block}</a></blockquote>`,
 | 
			
		||||
                  }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -436,6 +436,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
 | 
			
		|||
          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<Partial<Options>
 | 
			
		|||
declare module "vfile" {
 | 
			
		||||
  interface DataMap {
 | 
			
		||||
    blocks: Record<string, Element>
 | 
			
		||||
    htmlAst: HtmlRoot
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue