| | |
| | | import { PluggableList } from "unified" |
| | | import { QuartzTransformerPlugin } from "../types" |
| | | import { Root, HTML, BlockContent, DefinitionContent, Code } from "mdast" |
| | | import { Root, HTML, BlockContent, DefinitionContent, Code, Paragraph } from "mdast" |
| | | import { Replace, 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" |
| | | import path from "path" |
| | | import { JSResource } from "../../resources" |
| | | import { JSResource } from "../../util/resources" |
| | | // @ts-ignore |
| | | import calloutScript from "../../components/scripts/callout.inline.ts" |
| | | import { FilePath, canonicalizeServer, pathToRoot, slugTag, slugifyFilePath } from "../../path" |
| | | import { FilePath, canonicalizeServer, 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" |
| | |
| | | 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(/(?:^| )#([\w-_\/]+)/, "g") |
| | |
| | | ) => { |
| | | const opts = { ...defaultOptions, ...userOpts } |
| | | |
| | | const mdastToHtml = (ast: PhrasingContent | Paragraph) => { |
| | | 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) { |
| | | 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) |
| | |
| | | return { |
| | | name: "ObsidianFlavoredMarkdown", |
| | | textTransform(_ctx, src) { |
| | | // pre-transform blockquotes |
| | | if (opts.callouts) { |
| | | src = src.toString() |
| | | src = src.replaceAll(calloutLineRegex, (value) => { |
| | | // force newline after title of callout |
| | | return value + "\n> " |
| | | }) |
| | | } |
| | | |
| | | // pre-transform wikilinks (fix anchors to things that may contain illegal syntax e.g. codeblocks, latex) |
| | | if (opts.wikilinks) { |
| | | src = src.toString() |
| | | return src.replaceAll(wikilinkRegex, (value, ...capture) => { |
| | | src = src.replaceAll(wikilinkRegex, (value, ...capture) => { |
| | | const [rawFp, rawHeader, rawAlias] = capture |
| | | const fp = rawFp ?? "" |
| | | const anchor = rawHeader?.trim().slice(1) |
| | | const displayAnchor = anchor ? `#${slugAnchor(anchor)}` : "" |
| | | const displayAlias = rawAlias ?? rawHeader?.replace("#", "|") ?? "" |
| | | const displayAlias = rawAlias ?? rawHeader?.replace("#", "|") ?? "" |
| | | const embedDisplay = value.startsWith("!") ? "!" : "" |
| | | return `${embedDisplay}[[${fp}${displayAnchor}${displayAlias}]]` |
| | | }) |
| | | } |
| | | |
| | | return src |
| | | }, |
| | | markdownPlugins() { |
| | |
| | | } |
| | | |
| | | const text = firstChild.children[0].value |
| | | const restChildren = firstChild.children.splice(1) |
| | | const restChildren = firstChild.children.slice(1) |
| | | const [firstLine, ...remainingLines] = text.split("\n") |
| | | const remainingText = remainingLines.join("\n") |
| | | |
| | |
| | | ) |
| | | const collapse = collapseChar === "+" || collapseChar === "-" |
| | | const defaultState = collapseChar === "-" ? "collapsed" : "expanded" |
| | | const title = |
| | | const titleContent = |
| | | match.input.slice(calloutDirective.length).trim() || capitalize(calloutType) |
| | | const titleNode: Paragraph = { |
| | | type: "paragraph", |
| | | children: [{ type: "text", value: titleContent + " " }, ...restChildren], |
| | | } |
| | | const title = mdastToHtml(titleNode) |
| | | |
| | | const toggleIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="fold"> |
| | | <polyline points="6 9 12 15 18 9"></polyline> |
| | | </svg>` |
| | | |
| | | const titleNode: HTML = { |
| | | const titleHtml: HTML = { |
| | | type: "html", |
| | | value: `<div |
| | | class="callout-title" |
| | |
| | | </div>`, |
| | | } |
| | | |
| | | const blockquoteContent: (BlockContent | DefinitionContent)[] = [titleNode] |
| | | const blockquoteContent: (BlockContent | DefinitionContent)[] = [titleHtml] |
| | | if (remainingText.length > 0) { |
| | | blockquoteContent.push({ |
| | | type: "paragraph", |
| | |
| | | type: "text", |
| | | value: remainingText, |
| | | }, |
| | | ...restChildren, |
| | | ], |
| | | }) |
| | | } |