Rewrite parser application

This commit is contained in:
Emile Bangma 2024-09-22 13:31:31 +00:00
parent c1c8e26722
commit 8f96809bf3
19 changed files with 429 additions and 397 deletions

View File

@ -65,7 +65,7 @@ const config: QuartzConfig = {
}, },
keepBackground: false, keepBackground: false,
}), }),
Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }), //Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }),
Plugin.GitHubFlavoredMarkdown(), Plugin.GitHubFlavoredMarkdown(),
Plugin.TableOfContents(), Plugin.TableOfContents(),
Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }), Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }),
@ -77,8 +77,8 @@ const config: QuartzConfig = {
Plugin.AliasRedirects(), Plugin.AliasRedirects(),
Plugin.ComponentResources(), Plugin.ComponentResources(),
Plugin.ContentPage(), Plugin.ContentPage(),
//Plugin.FolderPage(), Plugin.FolderPage(),
//Plugin.TagPage(), Plugin.TagPage(),
Plugin.ContentIndex({ Plugin.ContentIndex({
enableSiteMap: true, enableSiteMap: true,
enableRSS: true, enableRSS: true,

View File

@ -3,6 +3,8 @@ import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-
import { JSResource } from "../../../util/resources" import { JSResource } from "../../../util/resources"
import { Root } from "mdast" import { Root } from "mdast"
import { Pluggable } from "unified" import { Pluggable } from "unified"
import { visit } from "unist-util-visit"
import { Element, Literal, Root as HtmlRoot } from "hast"
interface Options { interface Options {
enabled: Boolean enabled: Boolean
@ -22,16 +24,16 @@ export const CustomDefault: QuartzParser<Partial<Options>> = (userOpts) => {
} }
return src return src
}, },
markdownPlugins(_ctx) { markdownPlugins(tree, file) {
const replacements: [RegExp, string | ReplaceFunction][] = [] return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction]
const plug: Pluggable = (tree: Root, _file) => {
mdastFindReplace(tree, replacements)
}
return replacements
}, },
htmlPlugins() { htmlPlugins(tree, file) {
const plug: Pluggable = () => {} if (opts.enabled) {
return plug return () => {
visit(tree!, "element", (node) => {})
}
}
return {} as Pluggable
}, },
externalResources() { externalResources() {
const js = {} as JSResource const js = {} as JSResource

View File

@ -1,5 +1,6 @@
import { QuartzParser } from "../../types" import { QuartzParser } from "../../types"
import { JSResource } from "../../../util/resources" import { JSResource } from "../../../util/resources"
import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace"
import { Pluggable } from "unified" import { Pluggable } from "unified"
import rehypeSlug from "rehype-slug" import rehypeSlug from "rehype-slug"
import rehypeAutolinkHeadings from "rehype-autolink-headings" import rehypeAutolinkHeadings from "rehype-autolink-headings"
@ -22,9 +23,8 @@ export const GitHubLinkheadings: QuartzParser<Partial<Options>> = (userOpts) =>
} }
return src return src
}, },
markdownPlugins(_ctx) { markdownPlugins(tree, file) {
const plug: Pluggable = () => {} return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction]
return plug
}, },
htmlPlugins() { htmlPlugins() {
if (opts.enabled) { if (opts.enabled) {
@ -74,10 +74,9 @@ export const GitHubLinkheadings: QuartzParser<Partial<Options>> = (userOpts) =>
}, },
}, },
], ],
] as Pluggable[] ] as Pluggable
} else {
return [] as Pluggable[]
} }
return [] as Pluggable
}, },
externalResources() { externalResources() {
const js = {} as JSResource const js = {} as JSResource

View File

@ -22,11 +22,11 @@ export const GitHubSmartypants: QuartzParser<Partial<Options>> = (userOpts) => {
} }
return src return src
}, },
markdownPlugins(_ctx) { markdownPlugins() {
if (opts.enabled) { if (opts.enabled) {
return [remarkGfm, smartypants] return smartypants as Pluggable
} }
return [remarkGfm] return {} as Pluggable
}, },
htmlPlugins() { htmlPlugins() {
const plug: Pluggable = () => {} const plug: Pluggable = () => {}

View File

@ -8,12 +8,10 @@ import { mdastFindReplaceInHtml } from "../../transformers/markdown"
interface Options { interface Options {
enabled: Boolean enabled: Boolean
inHtml: Boolean
} }
const defaultOptions: Options = { const defaultOptions: Options = {
enabled: true, enabled: true,
inHtml: false,
} }
const arrowMapping: Record<string, string> = { const arrowMapping: Record<string, string> = {
@ -41,24 +39,21 @@ export const ObsidianArrow: QuartzParser<Partial<Options>> = (userOpts) => {
} }
return src return src
}, },
markdownPlugins(_ctx) { markdownPlugins() {
const replacements: [RegExp, string | ReplaceFunction][] = [] if (opts.enabled) {
const plug: Pluggable = (tree: Root, _path) => { return [
if (opts.enabled) { arrowRegex,
replacements.push([ (value: string, ..._capture: string[]) => {
arrowRegex, const maybeArrow = arrowMapping[value]
(value: string, ..._capture: string[]) => { if (maybeArrow === undefined) return SKIP
const maybeArrow = arrowMapping[value] return {
if (maybeArrow === undefined) return SKIP type: "html",
return { value: `<span>${maybeArrow}</span>`,
type: "html", }
value: `<span>${maybeArrow}</span>`, },
} ] as [RegExp, string | ReplaceFunction]
},
])
}
} }
return replacements return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction]
}, },
htmlPlugins() { htmlPlugins() {
const plug: Pluggable = () => {} const plug: Pluggable = () => {}

View File

@ -26,18 +26,14 @@ export const ObsidianBlockReference: QuartzParser<Partial<Options>> = (userOpts)
} }
return src return src
}, },
markdownPlugins(_ctx) { markdownPlugins(_tree, _file) {
const replacements: [RegExp, string | ReplaceFunction][] = [] return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction]
const plug: Pluggable = (tree: Root, _file) => {
mdastFindReplace(tree, replacements)
}
return replacements
}, },
htmlPlugins() { htmlPlugins(tree, file) {
const inlineTagTypes = new Set(["p", "li"]) const inlineTagTypes = new Set(["p", "li"])
const blockTagTypes = new Set(["blockquote"]) const blockTagTypes = new Set(["blockquote"])
if (opts.enabled) { if (opts.enabled) {
const plug: Pluggable = (tree: HtmlRoot, file) => { return () => {
file.data.blocks = {} file.data.blocks = {}
visit(tree, "element", (node, index, parent) => { visit(tree, "element", (node, index, parent) => {
@ -106,7 +102,6 @@ export const ObsidianBlockReference: QuartzParser<Partial<Options>> = (userOpts)
file.data.htmlAst = tree file.data.htmlAst = tree
} }
return plug
} }
return {} as Pluggable return {} as Pluggable
}, },

View File

@ -77,113 +77,110 @@ export const ObsidianCallouts: QuartzParser<Partial<Options>> = (userOpts) => {
return src return src
}, },
markdownPlugins(_ctx) { markdownPlugins(tree, _file) {
const replacements: [RegExp, string | ReplaceFunction][] = [] if (opts.enabled && tree !== undefined) {
const plug: Pluggable = (tree: Root, _path) => { visit(tree, "blockquote", (node) => {
if (opts.enabled) { if (node.children.length === 0) {
visit(tree, "blockquote", (node) => { return
if (node.children.length === 0) { }
return
// find first line and callout content
const [firstChild, ...calloutContent] = node.children
if (firstChild.type !== "paragraph" || firstChild.children[0]?.type !== "text") {
return
}
const text = firstChild.children[0].value
const restOfTitle = firstChild.children.slice(1)
const [firstLine, ...remainingLines] = text.split("\n")
const remainingText = remainingLines.join("\n")
const match = firstLine.match(calloutRegex)
if (match && match.input) {
const [calloutDirective, typeString, calloutMetaData, collapseChar] = match
const calloutType = canonicalizeCallout(typeString.toLowerCase())
const collapse = collapseChar === "+" || collapseChar === "-"
const defaultState = collapseChar === "-" ? "collapsed" : "expanded"
const titleContent = match.input.slice(calloutDirective.length).trim()
const useDefaultTitle = titleContent === "" && restOfTitle.length === 0
const titleNode: Paragraph = {
type: "paragraph",
children: [
{
type: "text",
value: useDefaultTitle ? capitalize(typeString) : titleContent + " ",
},
...restOfTitle,
],
} }
const title = mdastToHtml(titleNode)
// find first line and callout content const toggleIcon = `<div class="fold-callout-icon"></div>`
const [firstChild, ...calloutContent] = node.children
if (firstChild.type !== "paragraph" || firstChild.children[0]?.type !== "text") {
return
}
const text = firstChild.children[0].value const titleHtml: Html = {
const restOfTitle = firstChild.children.slice(1) type: "html",
const [firstLine, ...remainingLines] = text.split("\n") value: `<div
const remainingText = remainingLines.join("\n")
const match = firstLine.match(calloutRegex)
if (match && match.input) {
const [calloutDirective, typeString, calloutMetaData, collapseChar] = match
const calloutType = canonicalizeCallout(typeString.toLowerCase())
const collapse = collapseChar === "+" || collapseChar === "-"
const defaultState = collapseChar === "-" ? "collapsed" : "expanded"
const titleContent = match.input.slice(calloutDirective.length).trim()
const useDefaultTitle = titleContent === "" && restOfTitle.length === 0
const titleNode: Paragraph = {
type: "paragraph",
children: [
{
type: "text",
value: useDefaultTitle ? capitalize(typeString) : titleContent + " ",
},
...restOfTitle,
],
}
const title = mdastToHtml(titleNode)
const toggleIcon = `<div class="fold-callout-icon"></div>`
const titleHtml: Html = {
type: "html",
value: `<div
class="callout-title" class="callout-title"
> >
<div class="callout-icon"></div> <div class="callout-icon"></div>
<div class="callout-title-inner">${title}</div> <div class="callout-title-inner">${title}</div>
${collapse ? toggleIcon : ""} ${collapse ? toggleIcon : ""}
</div>`, </div>`,
}
const blockquoteContent: (BlockContent | DefinitionContent)[] = [titleHtml]
if (remainingText.length > 0) {
blockquoteContent.push({
type: "paragraph",
children: [
{
type: "text",
value: remainingText,
},
],
})
}
// replace first line of blockquote with title and rest of the paragraph text
node.children.splice(0, 1, ...blockquoteContent)
const classNames = ["callout", calloutType]
if (collapse) {
classNames.push("is-collapsible")
}
if (defaultState === "collapsed") {
classNames.push("is-collapsed")
}
// add properties to base blockquote
node.data = {
hProperties: {
...(node.data?.hProperties ?? {}),
className: classNames.join(" "),
"data-callout": calloutType,
"data-callout-fold": collapse,
"data-callout-metadata": calloutMetaData,
},
}
// Add callout-content class to callout body if it has one.
if (calloutContent.length > 0) {
const contentData: BlockContent | DefinitionContent = {
data: {
hProperties: {
className: "callout-content",
},
hName: "div",
},
type: "blockquote",
children: [...calloutContent],
}
node.children = [node.children[0], contentData]
}
} }
})
} const blockquoteContent: (BlockContent | DefinitionContent)[] = [titleHtml]
if (remainingText.length > 0) {
blockquoteContent.push({
type: "paragraph",
children: [
{
type: "text",
value: remainingText,
},
],
})
}
// replace first line of blockquote with title and rest of the paragraph text
node.children.splice(0, 1, ...blockquoteContent)
const classNames = ["callout", calloutType]
if (collapse) {
classNames.push("is-collapsible")
}
if (defaultState === "collapsed") {
classNames.push("is-collapsed")
}
// add properties to base blockquote
node.data = {
hProperties: {
...(node.data?.hProperties ?? {}),
className: classNames.join(" "),
"data-callout": calloutType,
"data-callout-fold": collapse,
"data-callout-metadata": calloutMetaData,
},
}
// Add callout-content class to callout body if it has one.
if (calloutContent.length > 0) {
const contentData: BlockContent | DefinitionContent = {
data: {
hProperties: {
className: "callout-content",
},
hName: "div",
},
type: "blockquote",
children: [...calloutContent],
}
node.children = [node.children[0], contentData]
}
}
})
} }
return replacements return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction]
}, },
htmlPlugins() { htmlPlugins() {
const plug: Pluggable = () => {} const plug: Pluggable = () => {}

View File

@ -29,12 +29,8 @@ export const ObsidianCheckboxes: QuartzParser<Partial<Options>> = (userOpts) =>
} }
return src return src
}, },
markdownPlugins(_ctx) { markdownPlugins(_tree, _file) {
const replacements: [RegExp, string | ReplaceFunction][] = [] return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction]
const plug: Pluggable = (tree: Root, _file) => {
mdastFindReplaceInHtml(tree, replacements, opts.inHtml)
}
return replacements
}, },
htmlPlugins() { htmlPlugins() {
if (opts.enabled) { if (opts.enabled) {

View File

@ -3,6 +3,7 @@ import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-
import { JSResource } from "../../../util/resources" import { JSResource } from "../../../util/resources"
import { Root } from "mdast" import { Root } from "mdast"
import { Pluggable } from "unified" import { Pluggable } from "unified"
import { visit } from "unist-util-visit"
import { mdastFindReplaceInHtml } from "../../transformers/markdown" import { mdastFindReplaceInHtml } from "../../transformers/markdown"
interface Options { interface Options {
@ -32,14 +33,16 @@ export const ObsidianComments: QuartzParser<Partial<Options>> = (userOpts) => {
return src return src
}, },
markdownPlugins(_ctx) { markdownPlugins(tree, file) {
const replacements: [RegExp, string | ReplaceFunction][] = [] return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction]
const plug: Pluggable = (tree: Root, _file) => {}
return replacements
}, },
htmlPlugins() { htmlPlugins(tree, file) {
const plug: Pluggable = () => {} if (opts.enabled) {
return plug return () => {
visit(tree, "element", (node) => {})
}
}
return {} as Pluggable
}, },
externalResources() { externalResources() {
const js = {} as JSResource const js = {} as JSResource

View File

@ -7,12 +7,10 @@ import { mdastFindReplaceInHtml } from "../../transformers/markdown"
interface Options { interface Options {
enabled: Boolean enabled: Boolean
inHtml: Boolean
} }
const defaultOptions: Options = { const defaultOptions: Options = {
enabled: true, enabled: true,
inHtml: false,
} }
const highlightRegex = new RegExp(/==([^=]+)==/g) const highlightRegex = new RegExp(/==([^=]+)==/g)
@ -29,23 +27,20 @@ export const ObsidianHighlights: QuartzParser<Partial<Options>> = (userOpts) =>
} }
return src return src
}, },
markdownPlugins(_ctx) { markdownPlugins() {
const replacements: [RegExp, string | ReplaceFunction][] = [] if (opts.enabled) {
const plug: Pluggable = (tree: Root, _path) => { return [
if (opts.enabled) { highlightRegex,
replacements.push([ (_value: string, ...capture: string[]) => {
highlightRegex, const [inner] = capture
(_value: string, ...capture: string[]) => { return {
const [inner] = capture type: "html",
return { value: `<span class="text-highlight">${inner}</span>`,
type: "html", }
value: `<span class="text-highlight">${inner}</span>`, },
} ] as [RegExp, string | ReplaceFunction]
},
])
}
} }
return replacements return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction]
}, },
htmlPlugins() { htmlPlugins() {
const plug: Pluggable = () => {} const plug: Pluggable = () => {}

View File

@ -0,0 +1,71 @@
import { QuartzParser } from "../../types"
import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace"
import { JSResource } from "../../../util/resources"
import { Root, Html, Paragraph } from "mdast"
import { Pluggable } from "unified"
import { mdastFindReplaceInHtml } from "../../transformers/markdown"
import { SKIP, visit } from "unist-util-visit"
import { toHast } from "mdast-util-to-hast"
import { toHtml } from "hast-util-to-html"
import { PhrasingContent } from "mdast-util-find-and-replace/lib"
interface Options {
enabled: Boolean
}
const defaultOptions: Options = {
enabled: true,
}
const mdastToHtml = (ast: PhrasingContent | Paragraph) => {
const hast = toHast(ast, { allowDangerousHtml: true })!
return toHtml(hast, { allowDangerousHtml: true })
}
export const ObsidianHtml: QuartzParser<Partial<Options>> = (userOpts) => {
const opts: Options = { ...defaultOptions, ...userOpts }
return {
name: "ObsidianHtml",
textTransform(_ctx, src: string | Buffer) {
if (opts.enabled) {
if (src instanceof Buffer) {
src = src.toString()
}
}
return src
},
markdownPlugins(tree, _file, replacements) {
if (opts.enabled && tree !== undefined && replacements !== undefined) {
return 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.replace(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
}
})
}
}
})
}
return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction]
},
htmlPlugins() {
const plug: Pluggable = () => {}
return plug
},
externalResources() {
const js = {} as JSResource
return js
},
}
}

View File

@ -4,6 +4,7 @@ export { ObsidianCallouts } from "./callouts"
export { ObsidianCheckboxes } from "./checkboxes" export { ObsidianCheckboxes } from "./checkboxes"
export { ObsidianComments } from "./comments" export { ObsidianComments } from "./comments"
export { ObsidianHighlights } from "./highlights" export { ObsidianHighlights } from "./highlights"
export { ObsidianHtml } from "./html"
export { ObsidianMermaid } from "./mermaid" export { ObsidianMermaid } from "./mermaid"
export { ObsidianTags } from "./tags" export { ObsidianTags } from "./tags"
export { ObsidianWikilinks } from "./wikilinks" export { ObsidianWikilinks } from "./wikilinks"

View File

@ -25,22 +25,18 @@ export const ObsidianMermaid: QuartzParser<Partial<Options>> = (userOpts) => {
} }
return src return src
}, },
markdownPlugins() { markdownPlugins(tree, _file) {
const replacements: [RegExp, string | ReplaceFunction][] = [] if (opts.enabled && tree !== undefined) {
const plug: Pluggable = (tree: Root, _file) => { visit(tree, "code", (node: Code) => {
if (opts.enabled) { if (node.lang === "mermaid") {
visit(tree, "code", (node: Code) => { node.data = {
if (node.lang === "mermaid") { hProperties: {
node.data = { className: ["mermaid"],
hProperties: { },
className: ["mermaid"],
},
}
} }
}) }
} })
} }
return replacements
}, },
htmlPlugins() { htmlPlugins() {
const plug: Pluggable = () => {} const plug: Pluggable = () => {}

View File

@ -8,13 +8,11 @@ import { Pluggable } from "unified"
import { mdastFindReplaceInHtml } from "../../transformers/markdown" import { mdastFindReplaceInHtml } from "../../transformers/markdown"
interface Options { interface Options {
enabled: Boolean enabled: ConstrainBoolean
inHtml: Boolean
} }
const defaultOptions: Options = { const defaultOptions: Options = {
enabled: true, enabled: true,
inHtml: false,
} }
// (?:^| ) -> non-capturing group, tag should start be separated by a space or be the start of the line // (?:^| ) -> non-capturing group, tag should start be separated by a space or be the start of the line
@ -35,45 +33,43 @@ export const ObsidianTags: QuartzParser<Partial<Options>> = (userOpts) => {
} }
return src return src
}, },
markdownPlugins(_ctx) { markdownPlugins(_tree, file) {
const replacements: [RegExp, string | ReplaceFunction][] = [] console.log(file)
const plug: Pluggable = (tree: Root, file) => { if (opts.enabled && file !== undefined) {
if (opts.enabled) { const base = pathToRoot(file.data.slug!)
const base = pathToRoot(file.data.slug!) return [
replacements.push([ tagRegex,
tagRegex, (_value: string, tag: string) => {
(_value: string, tag: string) => { // Check if the tag only includes numbers and slashes
// Check if the tag only includes numbers and slashes if (/^[\/\d]+$/.test(tag)) {
if (/^[\/\d]+$/.test(tag)) { return false
return false }
}
tag = slugTag(tag) tag = slugTag(tag)
if (file.data.frontmatter) { if (file.data.frontmatter) {
const noteTags = file.data.frontmatter.tags ?? [] const noteTags = file.data.frontmatter.tags ?? []
file.data.frontmatter.tags = [...new Set([...noteTags, tag])] file.data.frontmatter.tags = [...new Set([...noteTags, tag])]
} }
return { return {
type: "link", type: "link",
url: base + `/tags/${tag}`, url: base + `/tags/${tag}`,
data: { data: {
hProperties: { hProperties: {
className: ["tag-link"], className: ["tag-link"],
},
}, },
children: [ },
{ children: [
type: "text", {
value: tag, type: "text",
}, value: tag,
], },
} ],
}, }
]) },
} ] as [RegExp, string | ReplaceFunction]
} }
return replacements return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction]
}, },
htmlPlugins() { htmlPlugins() {
const plug: Pluggable = () => {} const plug: Pluggable = () => {}

View File

@ -24,23 +24,20 @@ export const ObsidianVideo: QuartzParser<Partial<Options>> = (userOpts) => {
} }
return src return src
}, },
markdownPlugins(_ctx) { markdownPlugins(tree, _file) {
const plug: Pluggable = (tree: Root, _file) => { if (opts.enabled && tree !== undefined) {
if (opts.enabled) { visit(tree, "image", (node, index, parent) => {
visit(tree, "image", (node, index, parent) => { if (parent && index != undefined && videoExtensionRegex.test(node.url)) {
if (parent && index != undefined && videoExtensionRegex.test(node.url)) { const newNode: Html = {
const newNode: Html = { type: "html",
type: "html", value: `<video controls src="${node.url}"></video>`,
value: `<video controls src="${node.url}"></video>`,
}
parent.children.splice(index, 1, newNode)
return SKIP
} }
})
} parent.children.splice(index, 1, newNode)
return SKIP
}
})
} }
return plug
}, },
htmlPlugins() { htmlPlugins() {
const plug: Pluggable = () => {} const plug: Pluggable = () => {}

View File

@ -9,12 +9,10 @@ import path from "path"
interface Options { interface Options {
enabled: Boolean enabled: Boolean
inHtml: Boolean
} }
const defaultOptions: Options = { const defaultOptions: Options = {
enabled: true, enabled: true,
inHtml: false,
} }
const externalLinkRegex = /^https?:\/\//i const externalLinkRegex = /^https?:\/\//i
@ -82,86 +80,81 @@ export const ObsidianWikilinks: QuartzParser<Partial<Options>> = (userOpts) => {
return src return src
}, },
markdownPlugins(_ctx) { markdownPlugins() {
const replacements: [RegExp, string | ReplaceFunction][] = [] if (opts.enabled) {
const plug: Pluggable = (tree: Root, file) => { return [
if (opts.enabled) { wikilinkRegex,
replacements.push([ (value: string, ...capture: string[]) => {
wikilinkRegex, let [rawFp, rawHeader, rawAlias] = capture
(value: string, ...capture: string[]) => { const fp = rawFp?.trim() ?? ""
let [rawFp, rawHeader, rawAlias] = capture const anchor = rawHeader?.trim() ?? ""
const fp = rawFp?.trim() ?? "" const alias = rawAlias?.slice(1).trim()
const anchor = rawHeader?.trim() ?? ""
const alias = rawAlias?.slice(1).trim()
// embed cases // embed cases
if (value.startsWith("!")) { if (value.startsWith("!")) {
const ext: string = path.extname(fp).toLowerCase() const ext: string = path.extname(fp).toLowerCase()
const url = slugifyFilePath(fp as FilePath) const url = slugifyFilePath(fp as FilePath)
if ([".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg", ".webp"].includes(ext)) { if ([".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg", ".webp"].includes(ext)) {
const match = wikilinkImageEmbedRegex.exec(alias ?? "") const match = wikilinkImageEmbedRegex.exec(alias ?? "")
const alt = match?.groups?.alt ?? "" const alt = match?.groups?.alt ?? ""
const width = match?.groups?.width ?? "auto" const width = match?.groups?.width ?? "auto"
const height = match?.groups?.height ?? "auto" const height = match?.groups?.height ?? "auto"
return { return {
type: "image", type: "image",
url, url,
data: { data: {
hProperties: { hProperties: {
width, width,
height, height,
alt, alt,
},
}, },
}
} 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}" class="pdf"></iframe>`,
}
} else {
const block = anchor
return {
type: "html",
data: { hProperties: { transclude: true } },
value: `<blockquote class="transclude" data-url="${url}" data-block="${block}" data-embed-alias="${alias}"><a href="${
url + anchor
}" class="transclude-inner">Transclude of ${url}${block}</a></blockquote>`,
}
}
// otherwise, fall through to regular link
}
// internal link
const url = fp + anchor
return {
type: "link",
url,
children: [
{
type: "text",
value: alias ?? fp,
}, },
], }
} 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}" class="pdf"></iframe>`,
}
} else {
const block = anchor
return {
type: "html",
data: { hProperties: { transclude: true } },
value: `<blockquote class="transclude" data-url="${url}" data-block="${block}" data-embed-alias="${alias}"><a href="${
url + anchor
}" class="transclude-inner">Transclude of ${url}${block}</a></blockquote>`,
}
} }
},
]) // otherwise, fall through to regular link
} }
// internal link
const url = fp + anchor
return {
type: "link",
url,
children: [
{
type: "text",
value: alias ?? fp,
},
],
}
},
] as [RegExp, string | ReplaceFunction]
} }
return replacements return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction]
}, },
htmlPlugins() { htmlPlugins() {
const plug: Pluggable = () => {} const plug: Pluggable = () => {}

View File

@ -27,16 +27,12 @@ export const ObsidianYouTube: QuartzParser<Partial<Options>> = (userOpts) => {
} }
return src return src
}, },
markdownPlugins(_ctx) { markdownPlugins(_file, _tree) {
const replacements: [RegExp, string | ReplaceFunction][] = [] return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction]
const plug: Pluggable = (tree: Root, _file) => {
mdastFindReplace(tree, replacements)
}
return replacements
}, },
htmlPlugins() { htmlPlugins(tree, _file) {
if (opts.enabled) { if (opts.enabled) {
const plug: Pluggable = (tree: HtmlRoot, _file) => { return () => {
visit(tree, "element", (node) => { visit(tree, "element", (node) => {
if (node.tagName === "img" && typeof node.properties.src === "string") { if (node.tagName === "img" && typeof node.properties.src === "string") {
const match = node.properties.src.match(ytLinkRegex) const match = node.properties.src.match(ytLinkRegex)
@ -68,7 +64,6 @@ export const ObsidianYouTube: QuartzParser<Partial<Options>> = (userOpts) => {
} }
}) })
} }
return plug
} }
return {} as Pluggable return {} as Pluggable
}, },

View File

@ -44,6 +44,7 @@ import {
ObsidianCheckboxes, ObsidianCheckboxes,
ObsidianComments, ObsidianComments,
ObsidianHighlights, ObsidianHighlights,
ObsidianHtml,
ObsidianMermaid, ObsidianMermaid,
ObsidianTags, ObsidianTags,
ObsidianVideo, ObsidianVideo,
@ -249,14 +250,14 @@ export const GitHubFlavoredMarkdown: QuartzTransformerPlugin<Partial<GitHubOptio
const opts = { ...defaultGitHubOptions, ...userOpts } const opts = { ...defaultGitHubOptions, ...userOpts }
return { return {
name: "GitHubFlavoredMarkdown", name: "GitHubFlavoredMarkdown",
textTransform(ctx, src) { /*textTransform(ctx, src) {
return src return src
}, },*/
markdownPlugins(ctx) { markdownPlugins() {
const plugins: PluggableList = [] const plugins: PluggableList = [remarkGfm]
plugins.push( plugins.push(
GitHubSmartypants({ enabled: opts.enableSmartyPants }).markdownPlugins(ctx) as Pluggable, GitHubSmartypants({ enabled: opts.enableSmartyPants }).markdownPlugins() as Pluggable,
) )
return plugins return plugins
@ -264,15 +265,15 @@ export const GitHubFlavoredMarkdown: QuartzTransformerPlugin<Partial<GitHubOptio
htmlPlugins() { htmlPlugins() {
const plugins: PluggableList = [] const plugins: PluggableList = []
plugins.push.apply(GitHubLinkheadings({ enabled: opts.linkHeadings }).htmlPlugins()) plugins.push(GitHubLinkheadings({ enabled: opts.linkHeadings }).htmlPlugins())
return plugins return plugins
}, },
externalResources() { /*externalResources() {
const js: JSResource[] = [] const js: JSResource[] = []
return { js } return { js }
}, },*/
} }
} }
@ -289,67 +290,61 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<ObsidianO
return src return src
}, },
markdownPlugins(ctx) { markdownPlugins(_ctx) {
const plugins: PluggableList = [] const plugins: PluggableList = []
const inHtml = opts.enableInHtmlEmbed plugins.push((tree: Root, file) => {
const replacements: [RegExp, string | ReplaceFunction][] = []
const replacements: [RegExp, string | ReplaceFunction][] = [] if (file !== undefined && file.data !== undefined) {
const base = pathToRoot(file.data.slug!)
replacements.push.apply(
ObsidianWikilinks({ enabled: opts.wikilinks, inHtml: inHtml }).markdownPlugins(ctx),
)
replacements.push.apply(
ObsidianHighlights({ enabled: opts.highlight, inHtml: inHtml }).markdownPlugins(ctx),
)
replacements.push.apply(
ObsidianArrow({ enabled: opts.parseArrows, inHtml: inHtml }).markdownPlugins(ctx),
)
replacements.push.apply(
ObsidianTags({ enabled: opts.parseTags, inHtml: inHtml }).markdownPlugins(ctx),
)
plugins.push(() => {
return (tree: Root, file) => {
mdastFindReplaceInHtml(tree, replacements, inHtml)
} }
replacements.push(
ObsidianWikilinks({ enabled: opts.wikilinks }).markdownPlugins() as [
RegExp,
string | ReplaceFunction,
],
)
replacements.push(
ObsidianHighlights({ enabled: opts.highlight }).markdownPlugins() as [
RegExp,
string | ReplaceFunction,
],
)
replacements.push(
ObsidianArrow({ enabled: opts.parseArrows }).markdownPlugins() as [
RegExp,
string | ReplaceFunction,
],
)
replacements.push(
ObsidianTags({ enabled: opts.parseTags }).markdownPlugins() as [
RegExp,
string | ReplaceFunction,
],
)
/*ObsidianHtml({ enabled: opts.enableInHtmlEmbed }).markdownPlugins(
tree,
undefined,
replacements,
)*/
mdastFindReplace(tree, replacements)
}) })
/*plugins.push(() => { /*plugins.push(() => {
return (tree: Root, file) => { return (tree: Root, file) =>
//const replacements: [RegExp, string | ReplaceFunction][] = [] ObsidianVideo({ enabled: opts.enableVideoEmbed }).markdownPlugins(tree)
//const base = pathToRoot(file.data.slug!) })
plugins.push(() => {
ObsidianWikilinks({ enabled: opts.wikilinks }).markdownPlugins(ctx) return (tree: Root, file) =>
ObsidianCallouts({ enabled: opts.callouts }).markdownPlugins(tree)
ObsidianHighlights({ enabled: opts.highlight }).markdownPlugins(ctx) })
plugins.push(() => {
ObsidianArrow({ enabled: opts.parseArrows }).markdownPlugins(ctx) return (tree: Root, file) =>
ObsidianMermaid({ enabled: opts.mermaid }).markdownPlugins(tree)
//mdastFindReplace(tree, replacements)
}
})*/ })*/
/*plugins.push(
ObsidianWikilinks({ enabled: opts.wikilinks, inHtml: inHtml }).markdownPlugins(ctx),
)
plugins.push(
ObsidianHighlights({ enabled: opts.highlight, inHtml: inHtml }).markdownPlugins(ctx),
)
plugins.push(
ObsidianArrow({ enabled: opts.parseArrows, inHtml: inHtml }).markdownPlugins(ctx),
)
plugins.push(ObsidianTags({ enabled: opts.parseTags, inHtml: inHtml }).markdownPlugins(ctx))*/
plugins.push(() => {
return (tree: Root, file) =>
ObsidianVideo({ enabled: opts.enableVideoEmbed }).markdownPlugins(ctx)
})
plugins.push(() => {
return (tree: Root, file) =>
ObsidianCallouts({ enabled: opts.callouts }).markdownPlugins(ctx)
})
plugins.push(() => {
return (tree: Root, file) => ObsidianMermaid({ enabled: opts.mermaid }).markdownPlugins(ctx)
})
return plugins return plugins
}, },
@ -357,12 +352,12 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<ObsidianO
const plugins: PluggableList = [rehypeRaw] const plugins: PluggableList = [rehypeRaw]
plugins.push(() => { plugins.push(() => {
return (tree: Root, file) => return (tree: HtmlRoot, file) =>
ObsidianBlockReference({ enabled: opts.parseBlockReferences }).htmlPlugins() as Pluggable ObsidianBlockReference({ enabled: opts.parseBlockReferences }).htmlPlugins(tree, file)
}) })
plugins.push(() => { plugins.push(() => {
return (tree: Root, file) => return (tree: HtmlRoot, file) =>
ObsidianYouTube({ enabled: opts.enableYouTubeEmbed }).htmlPlugins() as Pluggable ObsidianYouTube({ enabled: opts.enableYouTubeEmbed }).htmlPlugins(tree, file)
}) })
/*plugins.push(() => { /*plugins.push(() => {
return (tree: HtmlRoot, file) => ObsidianCheckboxes({ enabled: opts.enableCheckbox }).htmlPlugins() as Pluggable} return (tree: HtmlRoot, file) => ObsidianCheckboxes({ enabled: opts.enableCheckbox }).htmlPlugins() as Pluggable}

View File

@ -6,6 +6,8 @@ import { QuartzComponent } from "../components/types"
import { FilePath } from "../util/path" import { FilePath } from "../util/path"
import { BuildCtx } from "../util/ctx" import { BuildCtx } from "../util/ctx"
import DepGraph from "../depgraph" import DepGraph from "../depgraph"
import { Root } from "mdast-util-find-and-replace/lib"
import { Element, Literal, Root as HtmlRoot } from "hast"
export interface PluginTypes { export interface PluginTypes {
transformers: QuartzTransformerPluginInstance[] transformers: QuartzTransformerPluginInstance[]
@ -57,7 +59,11 @@ export type QuartzParser<Options extends OptionType = undefined> = (
export type QuartzParserInstance = { export type QuartzParserInstance = {
name: string name: string
textTransform: (ctx: BuildCtx, src: string | Buffer) => string | Buffer textTransform: (ctx: BuildCtx, src: string | Buffer) => string | Buffer
markdownPlugins: (ctx: BuildCtx) => [RegExp, string | ReplaceFunction][] markdownPlugins: (
htmlPlugins: () => Pluggable | Pluggable[] tree?: Root,
file?: any,
replacements?: [RegExp, string | ReplaceFunction][],
) => [RegExp, string | ReplaceFunction] | Pluggable | void
htmlPlugins: (tree?: HtmlRoot, file?: any) => Pluggable
externalResources: () => JSResource | string externalResources: () => JSResource | string
} }