Merge commit '76f2664277e07a7d1b011fac236840c6e8e69fdd' into v4
This commit is contained in:
		@@ -1,6 +1,7 @@
 | 
			
		||||
import { Root as HTMLRoot } from "hast"
 | 
			
		||||
import { toString } from "hast-util-to-string"
 | 
			
		||||
import { QuartzTransformerPlugin } from "../types"
 | 
			
		||||
import { escapeHTML } from "../../util/escape"
 | 
			
		||||
 | 
			
		||||
export interface Options {
 | 
			
		||||
  descriptionLength: number
 | 
			
		||||
@@ -10,15 +11,6 @@ const defaultOptions: Options = {
 | 
			
		||||
  descriptionLength: 150,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const escapeHTML = (unsafe: string) => {
 | 
			
		||||
  return unsafe
 | 
			
		||||
    .replaceAll("&", "&")
 | 
			
		||||
    .replaceAll("<", "<")
 | 
			
		||||
    .replaceAll(">", ">")
 | 
			
		||||
    .replaceAll('"', """)
 | 
			
		||||
    .replaceAll("'", "'")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const Description: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
 | 
			
		||||
  const opts = { ...defaultOptions, ...userOpts }
 | 
			
		||||
  return {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,14 +2,17 @@ import matter from "gray-matter"
 | 
			
		||||
import remarkFrontmatter from "remark-frontmatter"
 | 
			
		||||
import { QuartzTransformerPlugin } from "../types"
 | 
			
		||||
import yaml from "js-yaml"
 | 
			
		||||
import toml from "toml"
 | 
			
		||||
import { slugTag } from "../../util/path"
 | 
			
		||||
 | 
			
		||||
export interface Options {
 | 
			
		||||
  delims: string | string[]
 | 
			
		||||
  language: "yaml" | "toml"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const defaultOptions: Options = {
 | 
			
		||||
  delims: "---",
 | 
			
		||||
  language: "yaml",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const FrontMatter: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
 | 
			
		||||
@@ -18,13 +21,14 @@ export const FrontMatter: QuartzTransformerPlugin<Partial<Options> | undefined>
 | 
			
		||||
    name: "FrontMatter",
 | 
			
		||||
    markdownPlugins() {
 | 
			
		||||
      return [
 | 
			
		||||
        remarkFrontmatter,
 | 
			
		||||
        [remarkFrontmatter, ["yaml", "toml"]],
 | 
			
		||||
        () => {
 | 
			
		||||
          return (_, file) => {
 | 
			
		||||
            const { data } = matter(file.value, {
 | 
			
		||||
              ...opts,
 | 
			
		||||
              engines: {
 | 
			
		||||
                yaml: (s) => yaml.load(s, { schema: yaml.JSON_SCHEMA }) as object,
 | 
			
		||||
                toml: (s) => toml.parse(s) as object,
 | 
			
		||||
              },
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
@@ -33,6 +37,11 @@ export const FrontMatter: QuartzTransformerPlugin<Partial<Options> | undefined>
 | 
			
		||||
              data.tags = data.tag
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // coerce title to string
 | 
			
		||||
            if (data.title) {
 | 
			
		||||
              data.title = data.title.toString()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (data.tags && !Array.isArray(data.tags)) {
 | 
			
		||||
              data.tags = data.tags
 | 
			
		||||
                .toString()
 | 
			
		||||
 
 | 
			
		||||
@@ -5,5 +5,7 @@ export { Latex } from "./latex"
 | 
			
		||||
export { Description } from "./description"
 | 
			
		||||
export { CrawlLinks } from "./links"
 | 
			
		||||
export { ObsidianFlavoredMarkdown } from "./ofm"
 | 
			
		||||
export { OxHugoFlavouredMarkdown } from "./oxhugofm"
 | 
			
		||||
export { SyntaxHighlighting } from "./syntax"
 | 
			
		||||
export { TableOfContents } from "./toc"
 | 
			
		||||
export { HardLineBreaks } from "./linebreaks"
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ import fs from "fs"
 | 
			
		||||
import path from "path"
 | 
			
		||||
import { Repository } from "@napi-rs/simple-git"
 | 
			
		||||
import { QuartzTransformerPlugin } from "../types"
 | 
			
		||||
import chalk from "chalk"
 | 
			
		||||
 | 
			
		||||
export interface Options {
 | 
			
		||||
  priority: ("frontmatter" | "git" | "filesystem")[]
 | 
			
		||||
@@ -11,6 +12,20 @@ const defaultOptions: Options = {
 | 
			
		||||
  priority: ["frontmatter", "git", "filesystem"],
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function coerceDate(fp: string, d: any): Date {
 | 
			
		||||
  const dt = new Date(d)
 | 
			
		||||
  const invalidDate = isNaN(dt.getTime()) || dt.getTime() === 0
 | 
			
		||||
  if (invalidDate && d !== undefined) {
 | 
			
		||||
    console.log(
 | 
			
		||||
      chalk.yellow(
 | 
			
		||||
        `\nWarning: found invalid date "${d}" in \`${fp}\`. Supported formats: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format`,
 | 
			
		||||
      ),
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return invalidDate ? new Date() : dt
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MaybeDate = undefined | string | number
 | 
			
		||||
export const CreatedModifiedDate: QuartzTransformerPlugin<Partial<Options> | undefined> = (
 | 
			
		||||
  userOpts,
 | 
			
		||||
@@ -27,10 +42,11 @@ export const CreatedModifiedDate: QuartzTransformerPlugin<Partial<Options> | und
 | 
			
		||||
            let modified: MaybeDate = undefined
 | 
			
		||||
            let published: MaybeDate = undefined
 | 
			
		||||
 | 
			
		||||
            const fp = path.posix.join(file.cwd, file.data.filePath as string)
 | 
			
		||||
            const fp = file.data.filePath!
 | 
			
		||||
            const fullFp = path.posix.join(file.cwd, fp)
 | 
			
		||||
            for (const source of opts.priority) {
 | 
			
		||||
              if (source === "filesystem") {
 | 
			
		||||
                const st = await fs.promises.stat(fp)
 | 
			
		||||
                const st = await fs.promises.stat(fullFp)
 | 
			
		||||
                created ||= st.birthtimeMs
 | 
			
		||||
                modified ||= st.mtimeMs
 | 
			
		||||
              } else if (source === "frontmatter" && file.data.frontmatter) {
 | 
			
		||||
@@ -49,9 +65,9 @@ export const CreatedModifiedDate: QuartzTransformerPlugin<Partial<Options> | und
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            file.data.dates = {
 | 
			
		||||
              created: created ? new Date(created) : new Date(),
 | 
			
		||||
              modified: modified ? new Date(modified) : new Date(),
 | 
			
		||||
              published: published ? new Date(published) : new Date(),
 | 
			
		||||
              created: coerceDate(fp, created),
 | 
			
		||||
              modified: coerceDate(fp, modified),
 | 
			
		||||
              published: coerceDate(fp, published),
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								quartz/plugins/transformers/linebreaks.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								quartz/plugins/transformers/linebreaks.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
import { QuartzTransformerPlugin } from "../types"
 | 
			
		||||
import remarkBreaks from "remark-breaks"
 | 
			
		||||
 | 
			
		||||
export const HardLineBreaks: QuartzTransformerPlugin = () => {
 | 
			
		||||
  return {
 | 
			
		||||
    name: "HardLineBreaks",
 | 
			
		||||
    markdownPlugins() {
 | 
			
		||||
      return [remarkBreaks]
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -5,7 +5,6 @@ import {
 | 
			
		||||
  SimpleSlug,
 | 
			
		||||
  TransformOptions,
 | 
			
		||||
  _stripSlashes,
 | 
			
		||||
  joinSegments,
 | 
			
		||||
  simplifySlug,
 | 
			
		||||
  splitAnchor,
 | 
			
		||||
  transformLink,
 | 
			
		||||
@@ -19,11 +18,13 @@ interface Options {
 | 
			
		||||
  markdownLinkResolution: TransformOptions["strategy"]
 | 
			
		||||
  /** Strips folders from a link so that it looks nice */
 | 
			
		||||
  prettyLinks: boolean
 | 
			
		||||
  openLinksInNewTab: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const defaultOptions: Options = {
 | 
			
		||||
  markdownLinkResolution: "absolute",
 | 
			
		||||
  prettyLinks: true,
 | 
			
		||||
  openLinksInNewTab: false,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
 | 
			
		||||
@@ -53,8 +54,13 @@ export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> =
 | 
			
		||||
                node.properties.className ??= []
 | 
			
		||||
                node.properties.className.push(isAbsoluteUrl(dest) ? "external" : "internal")
 | 
			
		||||
 | 
			
		||||
                if (opts.openLinksInNewTab) {
 | 
			
		||||
                  node.properties.target = "_blank"
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // don't process external links or intra-document anchors
 | 
			
		||||
                if (!(isAbsoluteUrl(dest) || dest.startsWith("#"))) {
 | 
			
		||||
                const isInternal = !(isAbsoluteUrl(dest) || dest.startsWith("#"))
 | 
			
		||||
                if (isInternal) {
 | 
			
		||||
                  dest = node.properties.href = transformLink(
 | 
			
		||||
                    file.data.slug!,
 | 
			
		||||
                    dest,
 | 
			
		||||
@@ -72,11 +78,13 @@ export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> =
 | 
			
		||||
                    simplifySlug(destCanonical as FullSlug),
 | 
			
		||||
                  ) as SimpleSlug
 | 
			
		||||
                  outgoing.add(simple)
 | 
			
		||||
                  node.properties["data-slug"] = simple
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // rewrite link internals if prettylinks is on
 | 
			
		||||
                if (
 | 
			
		||||
                  opts.prettyLinks &&
 | 
			
		||||
                  isInternal &&
 | 
			
		||||
                  node.children.length === 1 &&
 | 
			
		||||
                  node.children[0].type === "text" &&
 | 
			
		||||
                  !node.children[0].value.startsWith("#")
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import { PluggableList } from "unified"
 | 
			
		||||
import { QuartzTransformerPlugin } from "../types"
 | 
			
		||||
import { Root, HTML, BlockContent, DefinitionContent, Code, Paragraph } from "mdast"
 | 
			
		||||
import { Element, Literal, Root as HtmlRoot } from "hast"
 | 
			
		||||
import { Replace, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace"
 | 
			
		||||
import { slug as slugAnchor } from "github-slugger"
 | 
			
		||||
import rehypeRaw from "rehype-raw"
 | 
			
		||||
@@ -13,6 +14,7 @@ import { FilePath, pathToRoot, slugTag, slugifyFilePath } from "../../util/path"
 | 
			
		||||
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"
 | 
			
		||||
 | 
			
		||||
export interface Options {
 | 
			
		||||
  comments: boolean
 | 
			
		||||
@@ -21,6 +23,7 @@ export interface Options {
 | 
			
		||||
  callouts: boolean
 | 
			
		||||
  mermaid: boolean
 | 
			
		||||
  parseTags: boolean
 | 
			
		||||
  parseBlockReferences: boolean
 | 
			
		||||
  enableInHtmlEmbed: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -31,6 +34,7 @@ const defaultOptions: Options = {
 | 
			
		||||
  callouts: true,
 | 
			
		||||
  mermaid: true,
 | 
			
		||||
  parseTags: true,
 | 
			
		||||
  parseBlockReferences: true,
 | 
			
		||||
  enableInHtmlEmbed: false,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -69,6 +73,8 @@ const callouts = {
 | 
			
		||||
const calloutMapping: Record<string, keyof typeof callouts> = {
 | 
			
		||||
  note: "note",
 | 
			
		||||
  abstract: "abstract",
 | 
			
		||||
  summary: "abstract",
 | 
			
		||||
  tldr: "abstract",
 | 
			
		||||
  info: "info",
 | 
			
		||||
  todo: "todo",
 | 
			
		||||
  tip: "tip",
 | 
			
		||||
@@ -96,11 +102,7 @@ const calloutMapping: Record<string, keyof typeof callouts> = {
 | 
			
		||||
 | 
			
		||||
function canonicalizeCallout(calloutName: string): keyof typeof callouts {
 | 
			
		||||
  let callout = calloutName.toLowerCase() as keyof typeof calloutMapping
 | 
			
		||||
  return calloutMapping[callout] ?? calloutName
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const capitalize = (s: string): string => {
 | 
			
		||||
  return s.substring(0, 1).toUpperCase() + s.substring(1)
 | 
			
		||||
  return calloutMapping[callout] ?? "note"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// !?               -> optional embedding
 | 
			
		||||
@@ -109,14 +111,17 @@ const capitalize = (s: string): string => {
 | 
			
		||||
// (#[^\[\]\|\#]+)? -> # then one or more non-special characters (heading link)
 | 
			
		||||
// (|[^\[\]\|\#]+)? -> | then one or more non-special characters (alias)
 | 
			
		||||
const wikilinkRegex = new RegExp(/!?\[\[([^\[\]\|\#]+)?(#[^\[\]\|\#]+)?(\|[^\[\]\|\#]+)?\]\]/, "g")
 | 
			
		||||
const highlightRegex = 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
 | 
			
		||||
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
 | 
			
		||||
// #(\w+)    -> tag itself is # followed by a string of alpha-numeric characters
 | 
			
		||||
const tagRegex = new RegExp(/(?:^| )#(\p{L}+)/, "gu")
 | 
			
		||||
// (?:^| )              -> 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 "/"
 | 
			
		||||
const tagRegex = new RegExp(/(?:^| )#((?:[-_\p{L}\d])+(?:\/[-_\p{L}\d]+)*)/, "gu")
 | 
			
		||||
const blockReferenceRegex = new RegExp(/\^([A-Za-z0-9]+)$/, "g")
 | 
			
		||||
 | 
			
		||||
export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options> | undefined> = (
 | 
			
		||||
  userOpts,
 | 
			
		||||
@@ -230,8 +235,16 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
 | 
			
		||||
                    value: `<iframe src="${url}"></iframe>`,
 | 
			
		||||
                  }
 | 
			
		||||
                } else if (ext === "") {
 | 
			
		||||
                  // TODO: note embed
 | 
			
		||||
                  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
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
@@ -320,7 +333,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
 | 
			
		||||
 | 
			
		||||
                const titleHtml: HTML = {
 | 
			
		||||
                  type: "html",
 | 
			
		||||
                  value: `<div 
 | 
			
		||||
                  value: `<div
 | 
			
		||||
                  class="callout-title"
 | 
			
		||||
                >
 | 
			
		||||
                  <div class="callout-icon">${callouts[calloutType]}</div>
 | 
			
		||||
@@ -383,13 +396,18 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
 | 
			
		||||
          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/${slugTag(tag)}`,
 | 
			
		||||
                url: base + `/tags/${tag}`,
 | 
			
		||||
                data: {
 | 
			
		||||
                  hProperties: {
 | 
			
		||||
                    className: ["tag-link"],
 | 
			
		||||
@@ -406,11 +424,64 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return plugins
 | 
			
		||||
    },
 | 
			
		||||
    htmlPlugins() {
 | 
			
		||||
      return [rehypeRaw]
 | 
			
		||||
      const plugins = [rehypeRaw]
 | 
			
		||||
 | 
			
		||||
      if (opts.parseBlockReferences) {
 | 
			
		||||
        plugins.push(() => {
 | 
			
		||||
          const inlineTagTypes = new Set(["p", "li"])
 | 
			
		||||
          const blockTagTypes = new Set(["blockquote"])
 | 
			
		||||
          return (tree, file) => {
 | 
			
		||||
            file.data.blocks = {}
 | 
			
		||||
            file.data.htmlAst = tree
 | 
			
		||||
 | 
			
		||||
            visit(tree, "element", (node, index, parent) => {
 | 
			
		||||
              if (blockTagTypes.has(node.tagName)) {
 | 
			
		||||
                const nextChild = parent?.children.at(index! + 2) as Element
 | 
			
		||||
                if (nextChild && nextChild.tagName === "p") {
 | 
			
		||||
                  const text = nextChild.children.at(0) as Literal
 | 
			
		||||
                  if (text && text.value && text.type === "text") {
 | 
			
		||||
                    const matches = text.value.match(blockReferenceRegex)
 | 
			
		||||
                    if (matches && matches.length >= 1) {
 | 
			
		||||
                      parent!.children.splice(index! + 2, 1)
 | 
			
		||||
                      const block = matches[0].slice(1)
 | 
			
		||||
 | 
			
		||||
                      if (!Object.keys(file.data.blocks!).includes(block)) {
 | 
			
		||||
                        node.properties = {
 | 
			
		||||
                          ...node.properties,
 | 
			
		||||
                          id: block,
 | 
			
		||||
                        }
 | 
			
		||||
                        file.data.blocks![block] = node
 | 
			
		||||
                      }
 | 
			
		||||
                    }
 | 
			
		||||
                  }
 | 
			
		||||
                }
 | 
			
		||||
              } else if (inlineTagTypes.has(node.tagName)) {
 | 
			
		||||
                const last = node.children.at(-1) as Literal
 | 
			
		||||
                if (last && last.value && typeof last.value === "string") {
 | 
			
		||||
                  const matches = last.value.match(blockReferenceRegex)
 | 
			
		||||
                  if (matches && matches.length >= 1) {
 | 
			
		||||
                    last.value = last.value.slice(0, -matches[0].length)
 | 
			
		||||
                    const block = matches[0].slice(1)
 | 
			
		||||
 | 
			
		||||
                    if (!Object.keys(file.data.blocks!).includes(block)) {
 | 
			
		||||
                      node.properties = {
 | 
			
		||||
                        ...node.properties,
 | 
			
		||||
                        id: block,
 | 
			
		||||
                      }
 | 
			
		||||
                      file.data.blocks![block] = node
 | 
			
		||||
                    }
 | 
			
		||||
                  }
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            })
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return plugins
 | 
			
		||||
    },
 | 
			
		||||
    externalResources() {
 | 
			
		||||
      const js: JSResource[] = []
 | 
			
		||||
@@ -428,7 +499,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
 | 
			
		||||
          script: `
 | 
			
		||||
          import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.mjs';
 | 
			
		||||
          const darkMode = document.documentElement.getAttribute('saved-theme') === 'dark'
 | 
			
		||||
          mermaid.initialize({ 
 | 
			
		||||
          mermaid.initialize({
 | 
			
		||||
            startOnLoad: false,
 | 
			
		||||
            securityLevel: 'loose',
 | 
			
		||||
            theme: darkMode ? 'dark' : 'default'
 | 
			
		||||
@@ -449,3 +520,10 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
declare module "vfile" {
 | 
			
		||||
  interface DataMap {
 | 
			
		||||
    blocks: Record<string, Element>
 | 
			
		||||
    htmlAst: HtmlRoot
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										108
									
								
								quartz/plugins/transformers/oxhugofm.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								quartz/plugins/transformers/oxhugofm.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,108 @@
 | 
			
		||||
import { QuartzTransformerPlugin } from "../types"
 | 
			
		||||
 | 
			
		||||
export interface Options {
 | 
			
		||||
  /** Replace {{ relref }} with quartz wikilinks []() */
 | 
			
		||||
  wikilinks: boolean
 | 
			
		||||
  /** Remove pre-defined anchor (see https://ox-hugo.scripter.co/doc/anchors/) */
 | 
			
		||||
  removePredefinedAnchor: boolean
 | 
			
		||||
  /** Remove hugo shortcode syntax */
 | 
			
		||||
  removeHugoShortcode: boolean
 | 
			
		||||
  /** Replace <figure/> with ![]() */
 | 
			
		||||
  replaceFigureWithMdImg: boolean
 | 
			
		||||
 | 
			
		||||
  /** Replace org latex fragments with $ and $$ */
 | 
			
		||||
  replaceOrgLatex: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const defaultOptions: Options = {
 | 
			
		||||
  wikilinks: true,
 | 
			
		||||
  removePredefinedAnchor: true,
 | 
			
		||||
  removeHugoShortcode: true,
 | 
			
		||||
  replaceFigureWithMdImg: true,
 | 
			
		||||
  replaceOrgLatex: true,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const relrefRegex = new RegExp(/\[([^\]]+)\]\(\{\{< relref "([^"]+)" >\}\}\)/, "g")
 | 
			
		||||
const predefinedHeadingIdRegex = new RegExp(/(.*) {#(?:.*)}/, "g")
 | 
			
		||||
const hugoShortcodeRegex = new RegExp(/{{(.*)}}/, "g")
 | 
			
		||||
const figureTagRegex = new RegExp(/< ?figure src="(.*)" ?>/, "g")
 | 
			
		||||
// \\\\\( -> matches \\(
 | 
			
		||||
// (.+?) -> Lazy match for capturing the equation
 | 
			
		||||
// \\\\\) -> matches \\)
 | 
			
		||||
const inlineLatexRegex = new RegExp(/\\\\\((.+?)\\\\\)/, "g")
 | 
			
		||||
// (?:\\begin{equation}|\\\\\(|\\\\\[) -> start of equation
 | 
			
		||||
// ([\s\S]*?) -> Matches the block equation
 | 
			
		||||
// (?:\\\\\]|\\\\\)|\\end{equation}) -> end of equation
 | 
			
		||||
const blockLatexRegex = new RegExp(
 | 
			
		||||
  /(?:\\begin{equation}|\\\\\(|\\\\\[)([\s\S]*?)(?:\\\\\]|\\\\\)|\\end{equation})/,
 | 
			
		||||
  "g",
 | 
			
		||||
)
 | 
			
		||||
// \$\$[\s\S]*?\$\$ -> Matches block equations
 | 
			
		||||
// \$.*?\$ -> Matches inline equations
 | 
			
		||||
const quartzLatexRegex = new RegExp(/\$\$[\s\S]*?\$\$|\$.*?\$/, "g")
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ox-hugo is an org exporter backend that exports org files to hugo-compatible
 | 
			
		||||
 * markdown in an opinionated way. This plugin adds some tweaks to the generated
 | 
			
		||||
 * markdown to make it compatible with quartz but the list of changes applied it
 | 
			
		||||
 * is not exhaustive.
 | 
			
		||||
 * */
 | 
			
		||||
export const OxHugoFlavouredMarkdown: QuartzTransformerPlugin<Partial<Options> | undefined> = (
 | 
			
		||||
  userOpts,
 | 
			
		||||
) => {
 | 
			
		||||
  const opts = { ...defaultOptions, ...userOpts }
 | 
			
		||||
  return {
 | 
			
		||||
    name: "OxHugoFlavouredMarkdown",
 | 
			
		||||
    textTransform(_ctx, src) {
 | 
			
		||||
      if (opts.wikilinks) {
 | 
			
		||||
        src = src.toString()
 | 
			
		||||
        src = src.replaceAll(relrefRegex, (value, ...capture) => {
 | 
			
		||||
          const [text, link] = capture
 | 
			
		||||
          return `[${text}](${link})`
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (opts.removePredefinedAnchor) {
 | 
			
		||||
        src = src.toString()
 | 
			
		||||
        src = src.replaceAll(predefinedHeadingIdRegex, (value, ...capture) => {
 | 
			
		||||
          const [headingText] = capture
 | 
			
		||||
          return headingText
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (opts.removeHugoShortcode) {
 | 
			
		||||
        src = src.toString()
 | 
			
		||||
        src = src.replaceAll(hugoShortcodeRegex, (value, ...capture) => {
 | 
			
		||||
          const [scContent] = capture
 | 
			
		||||
          return scContent
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (opts.replaceFigureWithMdImg) {
 | 
			
		||||
        src = src.toString()
 | 
			
		||||
        src = src.replaceAll(figureTagRegex, (value, ...capture) => {
 | 
			
		||||
          const [src] = capture
 | 
			
		||||
          return ``
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (opts.replaceOrgLatex) {
 | 
			
		||||
        src = src.toString()
 | 
			
		||||
        src = src.replaceAll(inlineLatexRegex, (value, ...capture) => {
 | 
			
		||||
          const [eqn] = capture
 | 
			
		||||
          return `$${eqn}$`
 | 
			
		||||
        })
 | 
			
		||||
        src = src.replaceAll(blockLatexRegex, (value, ...capture) => {
 | 
			
		||||
          const [eqn] = capture
 | 
			
		||||
          return `$$${eqn}$$`
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        // ox-hugo escapes _ as \_
 | 
			
		||||
        src = src.replaceAll(quartzLatexRegex, (value) => {
 | 
			
		||||
          return value.replaceAll("\\_", "_")
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
      return src
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -8,12 +8,14 @@ export interface Options {
 | 
			
		||||
  maxDepth: 1 | 2 | 3 | 4 | 5 | 6
 | 
			
		||||
  minEntries: 1
 | 
			
		||||
  showByDefault: boolean
 | 
			
		||||
  collapseByDefault: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const defaultOptions: Options = {
 | 
			
		||||
  maxDepth: 3,
 | 
			
		||||
  minEntries: 1,
 | 
			
		||||
  showByDefault: true,
 | 
			
		||||
  collapseByDefault: false,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface TocEntry {
 | 
			
		||||
@@ -54,6 +56,7 @@ export const TableOfContents: QuartzTransformerPlugin<Partial<Options> | undefin
 | 
			
		||||
                  ...entry,
 | 
			
		||||
                  depth: entry.depth - highestDepth,
 | 
			
		||||
                }))
 | 
			
		||||
                file.data.collapseToc = opts.collapseByDefault
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
@@ -66,5 +69,6 @@ export const TableOfContents: QuartzTransformerPlugin<Partial<Options> | undefin
 | 
			
		||||
declare module "vfile" {
 | 
			
		||||
  interface DataMap {
 | 
			
		||||
    toc: TocEntry[]
 | 
			
		||||
    collapseToc: boolean
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user