| | |
| | | import { PluggableList } from "unified" |
| | | import { QuartzTransformerPlugin } from "../types" |
| | | import { Root, HTML, BlockContent, DefinitionContent, Code, Paragraph } from "mdast" |
| | | import { Element, Literal } 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" |
| | |
| | | callouts: boolean |
| | | mermaid: boolean |
| | | parseTags: boolean |
| | | parseBlockReferences: boolean |
| | | enableInHtmlEmbed: boolean |
| | | } |
| | | |
| | |
| | | callouts: true, |
| | | mermaid: true, |
| | | parseTags: true, |
| | | parseBlockReferences: true, |
| | | enableInHtmlEmbed: false, |
| | | } |
| | | |
| | |
| | | // (?:[-_\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, |
| | |
| | | } |
| | | 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) |
| | | 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 { |
| | |
| | | node.data = { |
| | | hProperties: { |
| | | ...(node.data?.hProperties ?? {}), |
| | | className: `callout ${collapse ? "is-collapsible" : ""} ${ |
| | | defaultState === "collapsed" ? "is-collapsed" : "" |
| | | }`, |
| | | className: `callout ${collapse ? "is-collapsible" : ""} ${defaultState === "collapsed" ? "is-collapsed" : "" |
| | | }`, |
| | | "data-callout": calloutType, |
| | | "data-callout-fold": collapse, |
| | | }, |
| | |
| | | } |
| | | }) |
| | | } |
| | | |
| | | return plugins |
| | | }, |
| | | htmlPlugins() { |
| | | return [rehypeRaw] |
| | | const plugins = [rehypeRaw] |
| | | |
| | | if (opts.parseBlockReferences) { |
| | | plugins.push(() => { |
| | | return (tree, file) => { |
| | | file.data.blocks = {} |
| | | const validTagTypes = new Set(["blockquote", "p", "li"]) |
| | | visit(tree, "element", (node, _index, _parent) => { |
| | | if (validTagTypes.has(node.tagName)) { |
| | | const last = node.children.at(-1) as Literal |
| | | if (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) |
| | | node.properties = { |
| | | ...node.properties, |
| | | id: block |
| | | } |
| | | file.data.blocks![block] = node |
| | | } |
| | | } |
| | | } |
| | | }) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | return plugins |
| | | }, |
| | | externalResources() { |
| | | const js: JSResource[] = [] |
| | |
| | | }, |
| | | } |
| | | } |
| | | |
| | | declare module "vfile" { |
| | | interface DataMap { |
| | | blocks: Record<string, Element> |
| | | } |
| | | } |
| | | |