| | |
| | | |
| | | function canonicalizeCallout(calloutName: string): keyof typeof callouts { |
| | | let callout = calloutName.toLowerCase() as keyof typeof calloutMapping |
| | | return calloutMapping[callout] |
| | | return calloutMapping[callout] ?? calloutName |
| | | } |
| | | |
| | | |
| | | const capitalize = (s: string): string => { |
| | | return s.substring(0, 1).toUpperCase() + s.substring(1) |
| | | } |
| | |
| | | const calloutRegex = new RegExp(/^\[\!(\w+)\]([+-]?)/) |
| | | // (?:^| ) -> 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(/(?:^| )#(\w+)/, "g") |
| | | const tagRegex = new RegExp(/(?:^| )#([\w-_\/]+)/, "g") |
| | | |
| | | export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options> | undefined> = ( |
| | | userOpts, |
| | |
| | | |
| | | const findAndReplace = opts.enableInHtmlEmbed |
| | | ? (tree: Root, regex: RegExp, replace?: Replace | null | undefined) => { |
| | | if (replace) { |
| | | const mdastToHtml = (ast: PhrasingContent) => { |
| | | const hast = toHast(ast, { allowDangerousHtml: true })! |
| | | return toHtml(hast, { allowDangerousHtml: true }) |
| | | if (replace) { |
| | | const mdastToHtml = (ast: PhrasingContent) => { |
| | | const hast = toHast(ast, { allowDangerousHtml: true })! |
| | | return toHtml(hast, { allowDangerousHtml: true }) |
| | | } |
| | | |
| | | 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 |
| | | } |
| | | }) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | 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(tree, regex, replace) |
| | | } |
| | | : mdastFindReplace |
| | | |
| | | return { |
| | |
| | | |
| | | // embed cases |
| | | if (value.startsWith("!")) { |
| | | const ext: string | undefined = path.extname(fp).toLowerCase() |
| | | const ext: string = path.extname(fp).toLowerCase() |
| | | const url = slugifyFilePath(fp as FilePath) + ext |
| | | if ([".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg"].includes(ext)) { |
| | | const dims = alias ?? "" |
| | |
| | | type: "html", |
| | | value: `<iframe src="${url}"></iframe>`, |
| | | } |
| | | } else { |
| | | // TODO: this is the node embed case |
| | | } else if (ext === "") { |
| | | // TODO: note embed |
| | | } |
| | | // otherwise, fall through to regular link |
| | | } |
| | | |
| | | // internal link |
| | | // const url = transformInternalLink(fp + anchor) |
| | | const url = fp + anchor |
| | | return { |
| | | type: "link", |
| | |
| | | const match = firstLine.match(calloutRegex) |
| | | if (match && match.input) { |
| | | const [calloutDirective, typeString, collapseChar] = match |
| | | const calloutType = canonicalizeCallout(typeString.toLowerCase() as keyof typeof calloutMapping) |
| | | const calloutType = canonicalizeCallout( |
| | | typeString.toLowerCase() as keyof typeof calloutMapping, |
| | | ) |
| | | const collapse = collapseChar === "+" || collapseChar === "-" |
| | | const defaultState = collapseChar === "-" ? "collapsed" : "expanded" |
| | | const title = |
| | |
| | | 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, |
| | | }, |
| | |
| | | js.push({ |
| | | script: ` |
| | | import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.mjs'; |
| | | mermaid.initialize({ startOnLoad: true }); |
| | | const darkMode = document.documentElement.getAttribute('saved-theme') === 'dark' |
| | | mermaid.initialize({ |
| | | startOnLoad: false, |
| | | securityLevel: 'loose', |
| | | theme: darkMode ? 'dark' : 'default' |
| | | }); |
| | | document.addEventListener('nav', async () => { |
| | | await mermaid.run({ |
| | | querySelector: '.mermaid' |
| | | }) |
| | | }); |
| | | `, |
| | | loadTime: "afterDOMReady", |
| | | moduleType: "module", |