bump to v4
This commit is contained in:
		@@ -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,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user