bump to v4
This commit is contained in:
		
							
								
								
									
										29
									
								
								quartz/plugins/emitters/cname.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								quartz/plugins/emitters/cname.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
import { FilePath, joinSegments } from "../../util/path"
 | 
			
		||||
import { QuartzEmitterPlugin } from "../types"
 | 
			
		||||
import fs from "fs"
 | 
			
		||||
import chalk from "chalk"
 | 
			
		||||
 | 
			
		||||
export function extractDomainFromBaseUrl(baseUrl: string) {
 | 
			
		||||
  const url = new URL(`https://${baseUrl}`)
 | 
			
		||||
  return url.hostname
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const CNAME: QuartzEmitterPlugin = () => ({
 | 
			
		||||
  name: "CNAME",
 | 
			
		||||
  getQuartzComponents() {
 | 
			
		||||
    return []
 | 
			
		||||
  },
 | 
			
		||||
  async emit({ argv, cfg }, _content, _resources, _emit): Promise<FilePath[]> {
 | 
			
		||||
    if (!cfg.configuration.baseUrl) {
 | 
			
		||||
      console.warn(chalk.yellow("CNAME emitter requires `baseUrl` to be set in your configuration"))
 | 
			
		||||
      return []
 | 
			
		||||
    }
 | 
			
		||||
    const path = joinSegments(argv.output, "CNAME")
 | 
			
		||||
    const content = extractDomainFromBaseUrl(cfg.configuration.baseUrl)
 | 
			
		||||
    if (!content) {
 | 
			
		||||
      return []
 | 
			
		||||
    }
 | 
			
		||||
    fs.writeFileSync(path, content)
 | 
			
		||||
    return [path] as FilePath[]
 | 
			
		||||
  },
 | 
			
		||||
})
 | 
			
		||||
@@ -4,8 +4,6 @@ import { QuartzEmitterPlugin } from "../types"
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
import spaRouterScript from "../../components/scripts/spa.inline"
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
import plausibleScript from "../../components/scripts/plausible.inline"
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
import popoverScript from "../../components/scripts/popover.inline"
 | 
			
		||||
import styles from "../../styles/custom.scss"
 | 
			
		||||
import popoverStyle from "../../components/styles/popover.scss"
 | 
			
		||||
@@ -14,6 +12,7 @@ import { StaticResources } from "../../util/resources"
 | 
			
		||||
import { QuartzComponent } from "../../components/types"
 | 
			
		||||
import { googleFontHref, joinStyles } from "../../util/theme"
 | 
			
		||||
import { Features, transform } from "lightningcss"
 | 
			
		||||
import { transform as transpile } from "esbuild"
 | 
			
		||||
 | 
			
		||||
type ComponentResources = {
 | 
			
		||||
  css: string[]
 | 
			
		||||
@@ -56,9 +55,16 @@ function getComponentResources(ctx: BuildCtx): ComponentResources {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function joinScripts(scripts: string[]): string {
 | 
			
		||||
async function joinScripts(scripts: string[]): Promise<string> {
 | 
			
		||||
  // wrap with iife to prevent scope collision
 | 
			
		||||
  return scripts.map((script) => `(function () {${script}})();`).join("\n")
 | 
			
		||||
  const script = scripts.map((script) => `(function () {${script}})();`).join("\n")
 | 
			
		||||
 | 
			
		||||
  // minify with esbuild
 | 
			
		||||
  const res = await transpile(script, {
 | 
			
		||||
    minify: true,
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  return res.code
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function addGlobalPageResources(
 | 
			
		||||
@@ -85,17 +91,30 @@ function addGlobalPageResources(
 | 
			
		||||
    componentResources.afterDOMLoaded.push(`
 | 
			
		||||
      window.dataLayer = window.dataLayer || [];
 | 
			
		||||
      function gtag() { dataLayer.push(arguments); }
 | 
			
		||||
      gtag(\`js\`, new Date());
 | 
			
		||||
      gtag(\`config\`, \`${tagId}\`, { send_page_view: false });
 | 
			
		||||
      gtag("js", new Date());
 | 
			
		||||
      gtag("config", "${tagId}", { send_page_view: false });
 | 
			
		||||
  
 | 
			
		||||
      document.addEventListener(\`nav\`, () => {
 | 
			
		||||
        gtag(\`event\`, \`page_view\`, {
 | 
			
		||||
      document.addEventListener("nav", () => {
 | 
			
		||||
        gtag("event", "page_view", {
 | 
			
		||||
          page_title: document.title,
 | 
			
		||||
          page_location: location.href,
 | 
			
		||||
        });
 | 
			
		||||
      });`)
 | 
			
		||||
  } else if (cfg.analytics?.provider === "plausible") {
 | 
			
		||||
    componentResources.afterDOMLoaded.push(plausibleScript)
 | 
			
		||||
    const plausibleHost = cfg.analytics.host ?? "https://plausible.io"
 | 
			
		||||
    componentResources.afterDOMLoaded.push(`
 | 
			
		||||
      const plausibleScript = document.createElement("script")
 | 
			
		||||
      plausibleScript.src = "${plausibleHost}/js/script.manual.js"
 | 
			
		||||
      plausibleScript.setAttribute("data-domain", location.hostname)
 | 
			
		||||
      plausibleScript.defer = true
 | 
			
		||||
      document.head.appendChild(plausibleScript)
 | 
			
		||||
 | 
			
		||||
      window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }
 | 
			
		||||
 | 
			
		||||
      document.addEventListener("nav", () => {
 | 
			
		||||
        plausible("pageview")
 | 
			
		||||
      })
 | 
			
		||||
    `)
 | 
			
		||||
  } else if (cfg.analytics?.provider === "umami") {
 | 
			
		||||
    componentResources.afterDOMLoaded.push(`
 | 
			
		||||
      const umamiScript = document.createElement("script")
 | 
			
		||||
@@ -165,8 +184,11 @@ export const ComponentResources: QuartzEmitterPlugin<Options> = (opts?: Partial<
 | 
			
		||||
      addGlobalPageResources(ctx, resources, componentResources)
 | 
			
		||||
 | 
			
		||||
      const stylesheet = joinStyles(ctx.cfg.configuration.theme, ...componentResources.css, styles)
 | 
			
		||||
      const prescript = joinScripts(componentResources.beforeDOMLoaded)
 | 
			
		||||
      const postscript = joinScripts(componentResources.afterDOMLoaded)
 | 
			
		||||
      const [prescript, postscript] = await Promise.all([
 | 
			
		||||
        joinScripts(componentResources.beforeDOMLoaded),
 | 
			
		||||
        joinScripts(componentResources.afterDOMLoaded),
 | 
			
		||||
      ])
 | 
			
		||||
 | 
			
		||||
      const fps = await Promise.all([
 | 
			
		||||
        emit({
 | 
			
		||||
          slug: "index" as FullSlug,
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ import { Root } from "hast"
 | 
			
		||||
import { GlobalConfiguration } from "../../cfg"
 | 
			
		||||
import { getDate } from "../../components/Date"
 | 
			
		||||
import { escapeHTML } from "../../util/escape"
 | 
			
		||||
import { FilePath, FullSlug, SimpleSlug, simplifySlug } from "../../util/path"
 | 
			
		||||
import { FilePath, FullSlug, SimpleSlug, joinSegments, simplifySlug } from "../../util/path"
 | 
			
		||||
import { QuartzEmitterPlugin } from "../types"
 | 
			
		||||
import { toHtml } from "hast-util-to-html"
 | 
			
		||||
import path from "path"
 | 
			
		||||
@@ -37,7 +37,7 @@ const defaultOptions: Options = {
 | 
			
		||||
function generateSiteMap(cfg: GlobalConfiguration, idx: ContentIndex): string {
 | 
			
		||||
  const base = cfg.baseUrl ?? ""
 | 
			
		||||
  const createURLEntry = (slug: SimpleSlug, content: ContentDetails): string => `<url>
 | 
			
		||||
    <loc>https://${base}/${encodeURI(slug)}</loc>
 | 
			
		||||
    <loc>https://${joinSegments(base, encodeURI(slug))}</loc>
 | 
			
		||||
    <lastmod>${content.date?.toISOString()}</lastmod>
 | 
			
		||||
  </url>`
 | 
			
		||||
  const urls = Array.from(idx)
 | 
			
		||||
@@ -52,8 +52,8 @@ function generateRSSFeed(cfg: GlobalConfiguration, idx: ContentIndex, limit?: nu
 | 
			
		||||
 | 
			
		||||
  const createURLEntry = (slug: SimpleSlug, content: ContentDetails): string => `<item>
 | 
			
		||||
    <title>${escapeHTML(content.title)}</title>
 | 
			
		||||
    <link>${root}/${encodeURI(slug)}</link>
 | 
			
		||||
    <guid>${root}/${encodeURI(slug)}</guid>
 | 
			
		||||
    <link>${joinSegments(root, encodeURI(slug))}</link>
 | 
			
		||||
    <guid>${joinSegments(root, encodeURI(slug))}</guid>
 | 
			
		||||
    <description>${content.richContent ?? content.description}</description>
 | 
			
		||||
    <pubDate>${content.date?.toUTCString()}</pubDate>
 | 
			
		||||
  </item>`
 | 
			
		||||
 
 | 
			
		||||
@@ -7,3 +7,4 @@ export { Assets } from "./assets"
 | 
			
		||||
export { Static } from "./static"
 | 
			
		||||
export { ComponentResources } from "./componentResources"
 | 
			
		||||
export { NotFoundPage } from "./404"
 | 
			
		||||
export { CNAME } from "./cname"
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,10 @@ export const Static: QuartzEmitterPlugin = () => ({
 | 
			
		||||
  async emit({ argv, cfg }, _content, _resources, _emit): Promise<FilePath[]> {
 | 
			
		||||
    const staticPath = joinSegments(QUARTZ, "static")
 | 
			
		||||
    const fps = await glob("**", staticPath, cfg.configuration.ignorePatterns)
 | 
			
		||||
    await fs.promises.cp(staticPath, joinSegments(argv.output, "static"), { recursive: true })
 | 
			
		||||
    await fs.promises.cp(staticPath, joinSegments(argv.output, "static"), {
 | 
			
		||||
      recursive: true,
 | 
			
		||||
      dereference: true,
 | 
			
		||||
    })
 | 
			
		||||
    return fps.map((fp) => joinSegments(argv.output, "static", fp)) as FilePath[]
 | 
			
		||||
  },
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -40,12 +40,13 @@ export const TagPage: QuartzEmitterPlugin<FullPageLayout> = (userOpts) => {
 | 
			
		||||
      const tags: Set<string> = new Set(
 | 
			
		||||
        allFiles.flatMap((data) => data.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes),
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
      // add base tag
 | 
			
		||||
      tags.add("index")
 | 
			
		||||
 | 
			
		||||
      const tagDescriptions: Record<string, ProcessedContent> = Object.fromEntries(
 | 
			
		||||
        [...tags].map((tag) => {
 | 
			
		||||
          const title = tag === "" ? "Tag Index" : `Tag: #${tag}`
 | 
			
		||||
          const title = tag === "index" ? "Tag Index" : `Tag: #${tag}`
 | 
			
		||||
          return [
 | 
			
		||||
            tag,
 | 
			
		||||
            defaultProcessedContent({
 | 
			
		||||
 
 | 
			
		||||
@@ -30,5 +30,6 @@ declare module "vfile" {
 | 
			
		||||
  interface DataMap {
 | 
			
		||||
    slug: FullSlug
 | 
			
		||||
    filePath: FilePath
 | 
			
		||||
    relativePath: FilePath
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,15 +4,18 @@ import { QuartzTransformerPlugin } from "../types"
 | 
			
		||||
import yaml from "js-yaml"
 | 
			
		||||
import toml from "toml"
 | 
			
		||||
import { slugTag } from "../../util/path"
 | 
			
		||||
import { QuartzPluginData } from "../vfile"
 | 
			
		||||
 | 
			
		||||
export interface Options {
 | 
			
		||||
  delims: string | string[]
 | 
			
		||||
  language: "yaml" | "toml"
 | 
			
		||||
  oneLineTagDelim: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const defaultOptions: Options = {
 | 
			
		||||
  delims: "---",
 | 
			
		||||
  language: "yaml",
 | 
			
		||||
  oneLineTagDelim: ",",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const FrontMatter: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
 | 
			
		||||
@@ -20,11 +23,13 @@ export const FrontMatter: QuartzTransformerPlugin<Partial<Options> | undefined>
 | 
			
		||||
  return {
 | 
			
		||||
    name: "FrontMatter",
 | 
			
		||||
    markdownPlugins() {
 | 
			
		||||
      const { oneLineTagDelim } = opts
 | 
			
		||||
 | 
			
		||||
      return [
 | 
			
		||||
        [remarkFrontmatter, ["yaml", "toml"]],
 | 
			
		||||
        () => {
 | 
			
		||||
          return (_, file) => {
 | 
			
		||||
            const { data } = matter(file.value, {
 | 
			
		||||
            const { data } = matter(Buffer.from(file.value), {
 | 
			
		||||
              ...opts,
 | 
			
		||||
              engines: {
 | 
			
		||||
                yaml: (s) => yaml.load(s, { schema: yaml.JSON_SCHEMA }) as object,
 | 
			
		||||
@@ -40,24 +45,30 @@ export const FrontMatter: QuartzTransformerPlugin<Partial<Options> | undefined>
 | 
			
		||||
            // coerce title to string
 | 
			
		||||
            if (data.title) {
 | 
			
		||||
              data.title = data.title.toString()
 | 
			
		||||
            } else if (data.title === null || data.title === undefined) {
 | 
			
		||||
              data.title = file.stem ?? "Untitled"
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (data.tags && !Array.isArray(data.tags)) {
 | 
			
		||||
            if (data.tags) {
 | 
			
		||||
              // coerce to array
 | 
			
		||||
              if (!Array.isArray(data.tags)) {
 | 
			
		||||
                data.tags = data.tags
 | 
			
		||||
                  .toString()
 | 
			
		||||
                  .split(oneLineTagDelim)
 | 
			
		||||
                  .map((tag: string) => tag.trim())
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              // remove all non-string tags
 | 
			
		||||
              data.tags = data.tags
 | 
			
		||||
                .toString()
 | 
			
		||||
                .split(",")
 | 
			
		||||
                .map((tag: string) => tag.trim())
 | 
			
		||||
                .filter((tag: unknown) => typeof tag === "string" || typeof tag === "number")
 | 
			
		||||
                .map((tag: string | number) => tag.toString())
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // slug them all!!
 | 
			
		||||
            data.tags = [...new Set(data.tags?.map((tag: string) => slugTag(tag)))] ?? []
 | 
			
		||||
            data.tags = [...new Set(data.tags?.map((tag: string) => slugTag(tag)))]
 | 
			
		||||
 | 
			
		||||
            // fill in frontmatter
 | 
			
		||||
            file.data.frontmatter = {
 | 
			
		||||
              title: file.stem ?? "Untitled",
 | 
			
		||||
              tags: [],
 | 
			
		||||
              ...data,
 | 
			
		||||
            }
 | 
			
		||||
            file.data.frontmatter = data as QuartzPluginData["frontmatter"]
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
      ]
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,11 @@ export const GitHubFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options> |
 | 
			
		||||
            rehypeAutolinkHeadings,
 | 
			
		||||
            {
 | 
			
		||||
              behavior: "append",
 | 
			
		||||
              properties: {
 | 
			
		||||
                ariaHidden: true,
 | 
			
		||||
                tabIndex: -1,
 | 
			
		||||
                "data-no-popover": true,
 | 
			
		||||
              },
 | 
			
		||||
              content: {
 | 
			
		||||
                type: "text",
 | 
			
		||||
                value: " §",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import remarkMath from "remark-math"
 | 
			
		||||
import rehypeKatex from "rehype-katex"
 | 
			
		||||
import rehypeMathjax from "rehype-mathjax/svg.js"
 | 
			
		||||
import rehypeMathjax from "rehype-mathjax/svg"
 | 
			
		||||
import { QuartzTransformerPlugin } from "../types"
 | 
			
		||||
 | 
			
		||||
interface Options {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ import {
 | 
			
		||||
import path from "path"
 | 
			
		||||
import { visit } from "unist-util-visit"
 | 
			
		||||
import isAbsoluteUrl from "is-absolute-url"
 | 
			
		||||
import { Root } from "hast"
 | 
			
		||||
 | 
			
		||||
interface Options {
 | 
			
		||||
  /** How to resolve Markdown paths */
 | 
			
		||||
@@ -19,12 +20,14 @@ interface Options {
 | 
			
		||||
  /** Strips folders from a link so that it looks nice */
 | 
			
		||||
  prettyLinks: boolean
 | 
			
		||||
  openLinksInNewTab: boolean
 | 
			
		||||
  lazyLoad: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const defaultOptions: Options = {
 | 
			
		||||
  markdownLinkResolution: "absolute",
 | 
			
		||||
  prettyLinks: true,
 | 
			
		||||
  openLinksInNewTab: false,
 | 
			
		||||
  lazyLoad: false,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
 | 
			
		||||
@@ -34,7 +37,7 @@ export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> =
 | 
			
		||||
    htmlPlugins(ctx) {
 | 
			
		||||
      return [
 | 
			
		||||
        () => {
 | 
			
		||||
          return (tree, file) => {
 | 
			
		||||
          return (tree: Root, file) => {
 | 
			
		||||
            const curSlug = simplifySlug(file.data.slug!)
 | 
			
		||||
            const outgoing: Set<SimpleSlug> = new Set()
 | 
			
		||||
 | 
			
		||||
@@ -51,8 +54,19 @@ export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> =
 | 
			
		||||
                typeof node.properties.href === "string"
 | 
			
		||||
              ) {
 | 
			
		||||
                let dest = node.properties.href as RelativeURL
 | 
			
		||||
                node.properties.className ??= []
 | 
			
		||||
                node.properties.className.push(isAbsoluteUrl(dest) ? "external" : "internal")
 | 
			
		||||
                const classes = (node.properties.className ?? []) as string[]
 | 
			
		||||
                classes.push(isAbsoluteUrl(dest) ? "external" : "internal")
 | 
			
		||||
 | 
			
		||||
                // Check if the link has alias text
 | 
			
		||||
                if (
 | 
			
		||||
                  node.children.length === 1 &&
 | 
			
		||||
                  node.children[0].type === "text" &&
 | 
			
		||||
                  node.children[0].value !== dest
 | 
			
		||||
                ) {
 | 
			
		||||
                  // Add the 'alias' class if the text content is not the same as the href
 | 
			
		||||
                  classes.push("alias")
 | 
			
		||||
                }
 | 
			
		||||
                node.properties.className = classes
 | 
			
		||||
 | 
			
		||||
                if (opts.openLinksInNewTab) {
 | 
			
		||||
                  node.properties.target = "_blank"
 | 
			
		||||
@@ -71,14 +85,16 @@ export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> =
 | 
			
		||||
                  // WHATWG equivalent https://nodejs.dev/en/api/v18/url/#urlresolvefrom-to
 | 
			
		||||
                  const url = new URL(dest, `https://base.com/${curSlug}`)
 | 
			
		||||
                  const canonicalDest = url.pathname
 | 
			
		||||
                  const [destCanonical, _destAnchor] = splitAnchor(canonicalDest)
 | 
			
		||||
                  let [destCanonical, _destAnchor] = splitAnchor(canonicalDest)
 | 
			
		||||
                  if (destCanonical.endsWith("/")) {
 | 
			
		||||
                    destCanonical += "index"
 | 
			
		||||
                  }
 | 
			
		||||
 | 
			
		||||
                  // need to decodeURIComponent here as WHATWG URL percent-encodes everything
 | 
			
		||||
                  const simple = decodeURIComponent(
 | 
			
		||||
                    simplifySlug(destCanonical as FullSlug),
 | 
			
		||||
                  ) as SimpleSlug
 | 
			
		||||
                  const full = decodeURIComponent(_stripSlashes(destCanonical, true)) as FullSlug
 | 
			
		||||
                  const simple = simplifySlug(full)
 | 
			
		||||
                  outgoing.add(simple)
 | 
			
		||||
                  node.properties["data-slug"] = simple
 | 
			
		||||
                  node.properties["data-slug"] = full
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // rewrite link internals if prettylinks is on
 | 
			
		||||
@@ -99,6 +115,10 @@ export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> =
 | 
			
		||||
                node.properties &&
 | 
			
		||||
                typeof node.properties.src === "string"
 | 
			
		||||
              ) {
 | 
			
		||||
                if (opts.lazyLoad) {
 | 
			
		||||
                  node.properties.loading = "lazy"
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!isAbsoluteUrl(node.properties.src)) {
 | 
			
		||||
                  let dest = node.properties.src as RelativeURL
 | 
			
		||||
                  dest = node.properties.src = transformLink(
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,7 @@
 | 
			
		||||
import { PluggableList } from "unified"
 | 
			
		||||
import { QuartzTransformerPlugin } from "../types"
 | 
			
		||||
import { Root, HTML, BlockContent, DefinitionContent, Code, Paragraph } from "mdast"
 | 
			
		||||
import { Root, Html, BlockContent, DefinitionContent, Paragraph, Code } from "mdast"
 | 
			
		||||
import { Element, Literal, Root as HtmlRoot } from "hast"
 | 
			
		||||
import { Replace, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace"
 | 
			
		||||
import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace"
 | 
			
		||||
import { slug as slugAnchor } from "github-slugger"
 | 
			
		||||
import rehypeRaw from "rehype-raw"
 | 
			
		||||
import { visit } from "unist-util-visit"
 | 
			
		||||
@@ -15,6 +14,7 @@ import { toHast } from "mdast-util-to-hast"
 | 
			
		||||
import { toHtml } from "hast-util-to-html"
 | 
			
		||||
import { PhrasingContent } from "mdast-util-find-and-replace/lib"
 | 
			
		||||
import { capitalize } from "../../util/lang"
 | 
			
		||||
import { PluggableList } from "unified"
 | 
			
		||||
 | 
			
		||||
export interface Options {
 | 
			
		||||
  comments: boolean
 | 
			
		||||
@@ -105,12 +105,17 @@ function canonicalizeCallout(calloutName: string): keyof typeof callouts {
 | 
			
		||||
  return calloutMapping[callout] ?? "note"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const externalLinkRegex = /^https?:\/\//i
 | 
			
		||||
 | 
			
		||||
// !?               -> optional embedding
 | 
			
		||||
// \[\[             -> open brace
 | 
			
		||||
// ([^\[\]\|\#]+)   -> one or more non-special characters ([,],|, or #) (name)
 | 
			
		||||
// (#[^\[\]\|\#]+)? -> # then one or more non-special characters (heading link)
 | 
			
		||||
// (|[^\[\]\|\#]+)? -> | then one or more non-special characters (alias)
 | 
			
		||||
const wikilinkRegex = new RegExp(/!?\[\[([^\[\]\|\#]+)?(#[^\[\]\|\#]+)?(\|[^\[\]\|\#]+)?\]\]/, "g")
 | 
			
		||||
export const wikilinkRegex = new RegExp(
 | 
			
		||||
  /!?\[\[([^\[\]\|\#]+)?(#+[^\[\]\|\#]+)?(\|[^\[\]\|\#]+)?\]\]/,
 | 
			
		||||
  "g",
 | 
			
		||||
)
 | 
			
		||||
const highlightRegex = new RegExp(/==([^=]+)==/, "g")
 | 
			
		||||
const commentRegex = new RegExp(/%%(.+)%%/, "g")
 | 
			
		||||
// from https://github.com/escwxyz/remark-obsidian-callout/blob/main/src/index.ts
 | 
			
		||||
@@ -118,8 +123,8 @@ const calloutRegex = new RegExp(/^\[\!(\w+)\]([+-]?)/)
 | 
			
		||||
const calloutLineRegex = new RegExp(/^> *\[\!\w+\][+-]?.*$/, "gm")
 | 
			
		||||
// (?:^| )              -> non-capturing group, tag should start be separated by a space or be the start of the line
 | 
			
		||||
// #(...)               -> capturing group, tag itself must start with #
 | 
			
		||||
// (?:[-_\p{L}])+       -> non-capturing group, non-empty string of (Unicode-aware) alpha-numeric characters, hyphens and/or underscores
 | 
			
		||||
// (?:\/[-_\p{L}]+)*)   -> non-capturing group, matches an arbitrary number of tag strings separated by "/"
 | 
			
		||||
// (?:[-_\p{L}\d\p{Z}])+       -> non-capturing group, non-empty string of (Unicode-aware) alpha-numeric characters and symbols, hyphens and/or underscores
 | 
			
		||||
// (?:\/[-_\p{L}\d\p{Z}]+)*)   -> non-capturing group, matches an arbitrary number of tag strings separated by "/"
 | 
			
		||||
const tagRegex = new RegExp(/(?:^| )#((?:[-_\p{L}\d])+(?:\/[-_\p{L}\d]+)*)/, "gu")
 | 
			
		||||
const blockReferenceRegex = new RegExp(/\^([A-Za-z0-9]+)$/, "g")
 | 
			
		||||
 | 
			
		||||
@@ -132,39 +137,16 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
 | 
			
		||||
    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) {
 | 
			
		||||
          visit(tree, "html", (node: HTML) => {
 | 
			
		||||
            if (typeof replace === "string") {
 | 
			
		||||
              node.value = node.value.replace(regex, replace)
 | 
			
		||||
            } else {
 | 
			
		||||
              node.value = node.value.replaceAll(regex, (substring: string, ...args) => {
 | 
			
		||||
                const replaceValue = replace(substring, ...args)
 | 
			
		||||
                if (typeof replaceValue === "string") {
 | 
			
		||||
                  return replaceValue
 | 
			
		||||
                } else if (Array.isArray(replaceValue)) {
 | 
			
		||||
                  return replaceValue.map(mdastToHtml).join("")
 | 
			
		||||
                } else if (typeof replaceValue === "object" && replaceValue !== null) {
 | 
			
		||||
                  return mdastToHtml(replaceValue)
 | 
			
		||||
                } else {
 | 
			
		||||
                  return substring
 | 
			
		||||
                }
 | 
			
		||||
              })
 | 
			
		||||
            }
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        mdastFindReplace(tree, regex, replace)
 | 
			
		||||
      }
 | 
			
		||||
    : mdastFindReplace
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    name: "ObsidianFlavoredMarkdown",
 | 
			
		||||
    textTransform(_ctx, src) {
 | 
			
		||||
      // pre-transform blockquotes
 | 
			
		||||
      if (opts.callouts) {
 | 
			
		||||
        src = src.toString()
 | 
			
		||||
        if (src instanceof Buffer) {
 | 
			
		||||
          src = src.toString()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        src = src.replaceAll(calloutLineRegex, (value) => {
 | 
			
		||||
          // force newline after title of callout
 | 
			
		||||
          return value + "\n> "
 | 
			
		||||
@@ -173,14 +155,24 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
 | 
			
		||||
 | 
			
		||||
      // pre-transform wikilinks (fix anchors to things that may contain illegal syntax e.g. codeblocks, latex)
 | 
			
		||||
      if (opts.wikilinks) {
 | 
			
		||||
        src = src.toString()
 | 
			
		||||
        if (src instanceof Buffer) {
 | 
			
		||||
          src = src.toString()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        src = src.replaceAll(wikilinkRegex, (value, ...capture) => {
 | 
			
		||||
          const [rawFp, rawHeader, rawAlias] = capture
 | 
			
		||||
          const [rawFp, rawHeader, rawAlias]: (string | undefined)[] = capture
 | 
			
		||||
 | 
			
		||||
          const fp = rawFp ?? ""
 | 
			
		||||
          const anchor = rawHeader?.trim().slice(1)
 | 
			
		||||
          const displayAnchor = anchor ? `#${slugAnchor(anchor)}` : ""
 | 
			
		||||
          const anchor = rawHeader?.trim().replace(/^#+/, "")
 | 
			
		||||
          const blockRef = Boolean(anchor?.startsWith("^")) ? "^" : ""
 | 
			
		||||
          const displayAnchor = anchor ? `#${blockRef}${slugAnchor(anchor)}` : ""
 | 
			
		||||
          const displayAlias = rawAlias ?? rawHeader?.replace("#", "|") ?? ""
 | 
			
		||||
          const embedDisplay = value.startsWith("!") ? "!" : ""
 | 
			
		||||
 | 
			
		||||
          if (rawFp?.match(externalLinkRegex)) {
 | 
			
		||||
            return `${embedDisplay}[${displayAlias.replace(/^\|/, "")}](${rawFp})`
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          return `${embedDisplay}[[${fp}${displayAnchor}${displayAlias}]]`
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
@@ -189,108 +181,172 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
 | 
			
		||||
    },
 | 
			
		||||
    markdownPlugins() {
 | 
			
		||||
      const plugins: PluggableList = []
 | 
			
		||||
      if (opts.wikilinks) {
 | 
			
		||||
        plugins.push(() => {
 | 
			
		||||
          return (tree: Root, _file) => {
 | 
			
		||||
            findAndReplace(tree, wikilinkRegex, (value: string, ...capture: string[]) => {
 | 
			
		||||
              let [rawFp, rawHeader, rawAlias] = capture
 | 
			
		||||
              const fp = rawFp?.trim() ?? ""
 | 
			
		||||
              const anchor = rawHeader?.trim() ?? ""
 | 
			
		||||
              const alias = rawAlias?.slice(1).trim()
 | 
			
		||||
 | 
			
		||||
              // embed cases
 | 
			
		||||
              if (value.startsWith("!")) {
 | 
			
		||||
                const ext: string = path.extname(fp).toLowerCase()
 | 
			
		||||
                const url = slugifyFilePath(fp as FilePath)
 | 
			
		||||
                if ([".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg"].includes(ext)) {
 | 
			
		||||
                  const dims = alias ?? ""
 | 
			
		||||
                  let [width, height] = dims.split("x", 2)
 | 
			
		||||
                  width ||= "auto"
 | 
			
		||||
                  height ||= "auto"
 | 
			
		||||
                  return {
 | 
			
		||||
                    type: "image",
 | 
			
		||||
                    url,
 | 
			
		||||
                    data: {
 | 
			
		||||
                      hProperties: {
 | 
			
		||||
                        width,
 | 
			
		||||
                        height,
 | 
			
		||||
      // regex replacements
 | 
			
		||||
      plugins.push(() => {
 | 
			
		||||
        return (tree: Root, file) => {
 | 
			
		||||
          const replacements: [RegExp, string | ReplaceFunction][] = []
 | 
			
		||||
          const base = pathToRoot(file.data.slug!)
 | 
			
		||||
 | 
			
		||||
          if (opts.wikilinks) {
 | 
			
		||||
            replacements.push([
 | 
			
		||||
              wikilinkRegex,
 | 
			
		||||
              (value: string, ...capture: string[]) => {
 | 
			
		||||
                let [rawFp, rawHeader, rawAlias] = capture
 | 
			
		||||
                const fp = rawFp?.trim() ?? ""
 | 
			
		||||
                const anchor = rawHeader?.trim() ?? ""
 | 
			
		||||
                const alias = rawAlias?.slice(1).trim()
 | 
			
		||||
 | 
			
		||||
                // embed cases
 | 
			
		||||
                if (value.startsWith("!")) {
 | 
			
		||||
                  const ext: string = path.extname(fp).toLowerCase()
 | 
			
		||||
                  const url = slugifyFilePath(fp as FilePath)
 | 
			
		||||
                  if ([".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg"].includes(ext)) {
 | 
			
		||||
                    const dims = alias ?? ""
 | 
			
		||||
                    let [width, height] = dims.split("x", 2)
 | 
			
		||||
                    width ||= "auto"
 | 
			
		||||
                    height ||= "auto"
 | 
			
		||||
                    return {
 | 
			
		||||
                      type: "image",
 | 
			
		||||
                      url,
 | 
			
		||||
                      data: {
 | 
			
		||||
                        hProperties: {
 | 
			
		||||
                          width,
 | 
			
		||||
                          height,
 | 
			
		||||
                        },
 | 
			
		||||
                      },
 | 
			
		||||
                    },
 | 
			
		||||
                  }
 | 
			
		||||
                } else if ([".mp4", ".webm", ".ogv", ".mov", ".mkv"].includes(ext)) {
 | 
			
		||||
                  return {
 | 
			
		||||
                    type: "html",
 | 
			
		||||
                    value: `<video src="${url}" controls></video>`,
 | 
			
		||||
                  }
 | 
			
		||||
                } else if (
 | 
			
		||||
                  [".mp3", ".webm", ".wav", ".m4a", ".ogg", ".3gp", ".flac"].includes(ext)
 | 
			
		||||
                ) {
 | 
			
		||||
                  return {
 | 
			
		||||
                    type: "html",
 | 
			
		||||
                    value: `<audio src="${url}" controls></audio>`,
 | 
			
		||||
                  }
 | 
			
		||||
                } else if ([".pdf"].includes(ext)) {
 | 
			
		||||
                  return {
 | 
			
		||||
                    type: "html",
 | 
			
		||||
                    value: `<iframe src="${url}"></iframe>`,
 | 
			
		||||
                  }
 | 
			
		||||
                } else if (ext === "") {
 | 
			
		||||
                  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 ${url}${block}</a></blockquote>`,
 | 
			
		||||
                    }
 | 
			
		||||
                  } else if ([".mp4", ".webm", ".ogv", ".mov", ".mkv"].includes(ext)) {
 | 
			
		||||
                    return {
 | 
			
		||||
                      type: "html",
 | 
			
		||||
                      value: `<video src="${url}" controls></video>`,
 | 
			
		||||
                    }
 | 
			
		||||
                  } else if (
 | 
			
		||||
                    [".mp3", ".webm", ".wav", ".m4a", ".ogg", ".3gp", ".flac"].includes(ext)
 | 
			
		||||
                  ) {
 | 
			
		||||
                    return {
 | 
			
		||||
                      type: "html",
 | 
			
		||||
                      value: `<audio src="${url}" controls></audio>`,
 | 
			
		||||
                    }
 | 
			
		||||
                  } else if ([".pdf"].includes(ext)) {
 | 
			
		||||
                    return {
 | 
			
		||||
                      type: "html",
 | 
			
		||||
                      value: `<iframe src="${url}"></iframe>`,
 | 
			
		||||
                    }
 | 
			
		||||
                  } else if (ext === "") {
 | 
			
		||||
                    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 ${url}${block}</a></blockquote>`,
 | 
			
		||||
                    }
 | 
			
		||||
                  }
 | 
			
		||||
 | 
			
		||||
                  // otherwise, fall through to regular link
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // otherwise, fall through to regular link
 | 
			
		||||
              }
 | 
			
		||||
                // internal link
 | 
			
		||||
                const url = fp + anchor
 | 
			
		||||
                return {
 | 
			
		||||
                  type: "link",
 | 
			
		||||
                  url,
 | 
			
		||||
                  children: [
 | 
			
		||||
                    {
 | 
			
		||||
                      type: "text",
 | 
			
		||||
                      value: alias ?? fp,
 | 
			
		||||
                    },
 | 
			
		||||
                  ],
 | 
			
		||||
                }
 | 
			
		||||
              },
 | 
			
		||||
            ])
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
              // internal link
 | 
			
		||||
              const url = fp + anchor
 | 
			
		||||
              return {
 | 
			
		||||
                type: "link",
 | 
			
		||||
                url,
 | 
			
		||||
                children: [
 | 
			
		||||
                  {
 | 
			
		||||
                    type: "text",
 | 
			
		||||
                    value: alias ?? fp,
 | 
			
		||||
          if (opts.highlight) {
 | 
			
		||||
            replacements.push([
 | 
			
		||||
              highlightRegex,
 | 
			
		||||
              (_value: string, ...capture: string[]) => {
 | 
			
		||||
                const [inner] = capture
 | 
			
		||||
                return {
 | 
			
		||||
                  type: "html",
 | 
			
		||||
                  value: `<span class="text-highlight">${inner}</span>`,
 | 
			
		||||
                }
 | 
			
		||||
              },
 | 
			
		||||
            ])
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (opts.comments) {
 | 
			
		||||
            replacements.push([
 | 
			
		||||
              commentRegex,
 | 
			
		||||
              (_value: string, ..._capture: string[]) => {
 | 
			
		||||
                return {
 | 
			
		||||
                  type: "text",
 | 
			
		||||
                  value: "",
 | 
			
		||||
                }
 | 
			
		||||
              },
 | 
			
		||||
            ])
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (opts.parseTags) {
 | 
			
		||||
            replacements.push([
 | 
			
		||||
              tagRegex,
 | 
			
		||||
              (_value: string, tag: string) => {
 | 
			
		||||
                // Check if the tag only includes numbers
 | 
			
		||||
                if (/^\d+$/.test(tag)) {
 | 
			
		||||
                  return false
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                tag = slugTag(tag)
 | 
			
		||||
                if (file.data.frontmatter && !file.data.frontmatter.tags.includes(tag)) {
 | 
			
		||||
                  file.data.frontmatter.tags.push(tag)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return {
 | 
			
		||||
                  type: "link",
 | 
			
		||||
                  url: base + `/tags/${tag}`,
 | 
			
		||||
                  data: {
 | 
			
		||||
                    hProperties: {
 | 
			
		||||
                      className: ["tag-link"],
 | 
			
		||||
                    },
 | 
			
		||||
                  },
 | 
			
		||||
                ],
 | 
			
		||||
              }
 | 
			
		||||
            })
 | 
			
		||||
                  children: [
 | 
			
		||||
                    {
 | 
			
		||||
                      type: "text",
 | 
			
		||||
                      value: `#${tag}`,
 | 
			
		||||
                    },
 | 
			
		||||
                  ],
 | 
			
		||||
                }
 | 
			
		||||
              },
 | 
			
		||||
            ])
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (opts.highlight) {
 | 
			
		||||
        plugins.push(() => {
 | 
			
		||||
          return (tree: Root, _file) => {
 | 
			
		||||
            findAndReplace(tree, highlightRegex, (_value: string, ...capture: string[]) => {
 | 
			
		||||
              const [inner] = capture
 | 
			
		||||
              return {
 | 
			
		||||
                type: "html",
 | 
			
		||||
                value: `<span class="text-highlight">${inner}</span>`,
 | 
			
		||||
          if (opts.enableInHtmlEmbed) {
 | 
			
		||||
            visit(tree, "html", (node: Html) => {
 | 
			
		||||
              for (const [regex, replace] of replacements) {
 | 
			
		||||
                if (typeof replace === "string") {
 | 
			
		||||
                  node.value = node.value.replace(regex, replace)
 | 
			
		||||
                } else {
 | 
			
		||||
                  node.value = node.value.replaceAll(regex, (substring: string, ...args) => {
 | 
			
		||||
                    const replaceValue = replace(substring, ...args)
 | 
			
		||||
                    if (typeof replaceValue === "string") {
 | 
			
		||||
                      return replaceValue
 | 
			
		||||
                    } else if (Array.isArray(replaceValue)) {
 | 
			
		||||
                      return replaceValue.map(mdastToHtml).join("")
 | 
			
		||||
                    } else if (typeof replaceValue === "object" && replaceValue !== null) {
 | 
			
		||||
                      return mdastToHtml(replaceValue)
 | 
			
		||||
                    } else {
 | 
			
		||||
                      return substring
 | 
			
		||||
                    }
 | 
			
		||||
                  })
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            })
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (opts.comments) {
 | 
			
		||||
        plugins.push(() => {
 | 
			
		||||
          return (tree: Root, _file) => {
 | 
			
		||||
            findAndReplace(tree, commentRegex, (_value: string, ..._capture: string[]) => {
 | 
			
		||||
              return {
 | 
			
		||||
                type: "text",
 | 
			
		||||
                value: "",
 | 
			
		||||
              }
 | 
			
		||||
            })
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
          mdastFindReplace(tree, replacements)
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      if (opts.callouts) {
 | 
			
		||||
        plugins.push(() => {
 | 
			
		||||
@@ -331,7 +387,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
 | 
			
		||||
                  <polyline points="6 9 12 15 18 9"></polyline>
 | 
			
		||||
                </svg>`
 | 
			
		||||
 | 
			
		||||
                const titleHtml: HTML = {
 | 
			
		||||
                const titleHtml: Html = {
 | 
			
		||||
                  type: "html",
 | 
			
		||||
                  value: `<div
 | 
			
		||||
                  class="callout-title"
 | 
			
		||||
@@ -391,51 +447,17 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (opts.parseTags) {
 | 
			
		||||
        plugins.push(() => {
 | 
			
		||||
          return (tree: Root, file) => {
 | 
			
		||||
            const base = pathToRoot(file.data.slug!)
 | 
			
		||||
            findAndReplace(tree, tagRegex, (_value: string, tag: string) => {
 | 
			
		||||
              // Check if the tag only includes numbers
 | 
			
		||||
              if (/^\d+$/.test(tag)) {
 | 
			
		||||
                return false
 | 
			
		||||
              }
 | 
			
		||||
              tag = slugTag(tag)
 | 
			
		||||
              if (file.data.frontmatter && !file.data.frontmatter.tags.includes(tag)) {
 | 
			
		||||
                file.data.frontmatter.tags.push(tag)
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              return {
 | 
			
		||||
                type: "link",
 | 
			
		||||
                url: base + `/tags/${tag}`,
 | 
			
		||||
                data: {
 | 
			
		||||
                  hProperties: {
 | 
			
		||||
                    className: ["tag-link"],
 | 
			
		||||
                  },
 | 
			
		||||
                },
 | 
			
		||||
                children: [
 | 
			
		||||
                  {
 | 
			
		||||
                    type: "text",
 | 
			
		||||
                    value: `#${tag}`,
 | 
			
		||||
                  },
 | 
			
		||||
                ],
 | 
			
		||||
              }
 | 
			
		||||
            })
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
      return plugins
 | 
			
		||||
    },
 | 
			
		||||
    htmlPlugins() {
 | 
			
		||||
      const plugins = [rehypeRaw]
 | 
			
		||||
      const plugins: PluggableList = [rehypeRaw]
 | 
			
		||||
 | 
			
		||||
      if (opts.parseBlockReferences) {
 | 
			
		||||
        plugins.push(() => {
 | 
			
		||||
          const inlineTagTypes = new Set(["p", "li"])
 | 
			
		||||
          const blockTagTypes = new Set(["blockquote"])
 | 
			
		||||
          return (tree, file) => {
 | 
			
		||||
          return (tree: HtmlRoot, file) => {
 | 
			
		||||
            file.data.blocks = {}
 | 
			
		||||
            file.data.htmlAst = tree
 | 
			
		||||
 | 
			
		||||
            visit(tree, "element", (node, index, parent) => {
 | 
			
		||||
              if (blockTagTypes.has(node.tagName)) {
 | 
			
		||||
@@ -477,6 +499,8 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            file.data.htmlAst = tree
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,11 @@ export const SyntaxHighlighting: QuartzTransformerPlugin = () => ({
 | 
			
		||||
      [
 | 
			
		||||
        rehypePrettyCode,
 | 
			
		||||
        {
 | 
			
		||||
          theme: "css-variables",
 | 
			
		||||
          keepBackground: false,
 | 
			
		||||
          theme: {
 | 
			
		||||
            dark: "github-dark",
 | 
			
		||||
            light: "github-light",
 | 
			
		||||
          },
 | 
			
		||||
        } satisfies Partial<CodeOptions>,
 | 
			
		||||
      ],
 | 
			
		||||
    ]
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ import { Root } from "mdast"
 | 
			
		||||
import { visit } from "unist-util-visit"
 | 
			
		||||
import { toString } from "mdast-util-to-string"
 | 
			
		||||
import Slugger from "github-slugger"
 | 
			
		||||
import { wikilinkRegex } from "./ofm"
 | 
			
		||||
 | 
			
		||||
export interface Options {
 | 
			
		||||
  maxDepth: 1 | 2 | 3 | 4 | 5 | 6
 | 
			
		||||
@@ -24,6 +25,7 @@ interface TocEntry {
 | 
			
		||||
  slug: string // this is just the anchor (#some-slug), not the canonical slug
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const regexMdLinks = new RegExp(/\[([^\[]+)\](\(.*\))/, "g")
 | 
			
		||||
export const TableOfContents: QuartzTransformerPlugin<Partial<Options> | undefined> = (
 | 
			
		||||
  userOpts,
 | 
			
		||||
) => {
 | 
			
		||||
@@ -41,7 +43,16 @@ export const TableOfContents: QuartzTransformerPlugin<Partial<Options> | undefin
 | 
			
		||||
              let highestDepth: number = opts.maxDepth
 | 
			
		||||
              visit(tree, "heading", (node) => {
 | 
			
		||||
                if (node.depth <= opts.maxDepth) {
 | 
			
		||||
                  const text = toString(node)
 | 
			
		||||
                  let text = toString(node)
 | 
			
		||||
 | 
			
		||||
                  // strip link formatting from toc entries
 | 
			
		||||
                  text = text.replace(wikilinkRegex, (_, rawFp, __, rawAlias) => {
 | 
			
		||||
                    const fp = rawFp?.trim() ?? ""
 | 
			
		||||
                    const alias = rawAlias?.slice(1).trim()
 | 
			
		||||
                    return alias ?? fp
 | 
			
		||||
                  })
 | 
			
		||||
                  text = text.replace(regexMdLinks, "$1")
 | 
			
		||||
 | 
			
		||||
                  highestDepth = Math.min(highestDepth, node.depth)
 | 
			
		||||
                  toc.push({
 | 
			
		||||
                    depth: node.depth,
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ import { Node, Parent } from "hast"
 | 
			
		||||
import { Data, VFile } from "vfile"
 | 
			
		||||
 | 
			
		||||
export type QuartzPluginData = Data
 | 
			
		||||
export type ProcessedContent = [Node<QuartzPluginData>, VFile]
 | 
			
		||||
export type ProcessedContent = [Node, VFile]
 | 
			
		||||
 | 
			
		||||
export function defaultProcessedContent(vfileData: Partial<QuartzPluginData>): ProcessedContent {
 | 
			
		||||
  const root: Parent = { type: "root", children: [] }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user