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 { JSResourceToScriptElement, StaticResources } from "../util/resources"
 | 
				
			||||||
import { FullSlug, RelativeURL, joinSegments } from "../util/path"
 | 
					import { FullSlug, RelativeURL, joinSegments } from "../util/path"
 | 
				
			||||||
import { visit } from "unist-util-visit"
 | 
					import { visit } from "unist-util-visit"
 | 
				
			||||||
import { Root, Element } from "hast"
 | 
					import { Root, Element, ElementContent } from "hast"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface RenderComponents {
 | 
					interface RenderComponents {
 | 
				
			||||||
  head: QuartzComponent
 | 
					  head: QuartzComponent
 | 
				
			||||||
| 
						 | 
					@ -61,11 +61,19 @@ export function renderPage(
 | 
				
			||||||
      const classNames = (node.properties?.className ?? []) as string[]
 | 
					      const classNames = (node.properties?.className ?? []) as string[]
 | 
				
			||||||
      if (classNames.includes("transclude")) {
 | 
					      if (classNames.includes("transclude")) {
 | 
				
			||||||
        const inner = node.children[0] as Element
 | 
					        const inner = node.children[0] as Element
 | 
				
			||||||
        const blockSlug = inner.properties?.["data-slug"] as FullSlug
 | 
					        const transcludeTarget = 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
 | 
					        // TODO: avoid this expensive find operation and construct an index ahead of time
 | 
				
			||||||
        let blockNode = componentData.allFiles.find((f) => f.slug === blockSlug)?.blocks?.[blockRef]
 | 
					        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) {
 | 
				
			||||||
            if (blockNode.tagName === "li") {
 | 
					            if (blockNode.tagName === "li") {
 | 
				
			||||||
              blockNode = {
 | 
					              blockNode = {
 | 
				
			||||||
| 
						 | 
					@ -85,6 +93,57 @@ export function renderPage(
 | 
				
			||||||
              },
 | 
					              },
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					        } 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 = [
 | 
				
			||||||
 | 
					            ...(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",
 | 
				
			||||||
 | 
					              properties: { href: inner.properties?.href, class: ["internal"] },
 | 
				
			||||||
 | 
					              children: [{ type: "text", value: `Link to original` }],
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
import { PluggableList } from "unified"
 | 
					import { PluggableList } from "unified"
 | 
				
			||||||
import { QuartzTransformerPlugin } from "../types"
 | 
					import { QuartzTransformerPlugin } from "../types"
 | 
				
			||||||
import { Root, HTML, BlockContent, DefinitionContent, Code, Paragraph } from "mdast"
 | 
					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 { Replace, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace"
 | 
				
			||||||
import { slug as slugAnchor } from "github-slugger"
 | 
					import { slug as slugAnchor } from "github-slugger"
 | 
				
			||||||
import rehypeRaw from "rehype-raw"
 | 
					import rehypeRaw from "rehype-raw"
 | 
				
			||||||
| 
						 | 
					@ -236,13 +236,13 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
 | 
				
			||||||
                    value: `<iframe src="${url}"></iframe>`,
 | 
					                    value: `<iframe src="${url}"></iframe>`,
 | 
				
			||||||
                  }
 | 
					                  }
 | 
				
			||||||
                } else if (ext === "") {
 | 
					                } else if (ext === "") {
 | 
				
			||||||
                  const block = anchor.slice(1)
 | 
					                  const block = anchor
 | 
				
			||||||
                  return {
 | 
					                  return {
 | 
				
			||||||
                    type: "html",
 | 
					                    type: "html",
 | 
				
			||||||
                    data: { hProperties: { transclude: true } },
 | 
					                    data: { hProperties: { transclude: true } },
 | 
				
			||||||
                    value: `<blockquote class="transclude" data-url="${url}" data-block="${block}"><a href="${
 | 
					                    value: `<blockquote class="transclude" data-url="${url}" data-block="${block}"><a href="${
 | 
				
			||||||
                      url + anchor
 | 
					                      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"])
 | 
					          const blockTagTypes = new Set(["blockquote"])
 | 
				
			||||||
          return (tree, file) => {
 | 
					          return (tree, file) => {
 | 
				
			||||||
            file.data.blocks = {}
 | 
					            file.data.blocks = {}
 | 
				
			||||||
 | 
					            file.data.htmlAst = tree
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            visit(tree, "element", (node, index, parent) => {
 | 
					            visit(tree, "element", (node, index, parent) => {
 | 
				
			||||||
              if (blockTagTypes.has(node.tagName)) {
 | 
					              if (blockTagTypes.has(node.tagName)) {
 | 
				
			||||||
| 
						 | 
					@ -524,5 +525,6 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
 | 
				
			||||||
declare module "vfile" {
 | 
					declare module "vfile" {
 | 
				
			||||||
  interface DataMap {
 | 
					  interface DataMap {
 | 
				
			||||||
    blocks: Record<string, Element>
 | 
					    blocks: Record<string, Element>
 | 
				
			||||||
 | 
					    htmlAst: HtmlRoot
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue