From adfe69e13711e3c3ad37cd66b287f67c864f6f3c Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Wed, 18 Sep 2024 18:16:59 +0000 Subject: [PATCH 01/38] Initial setup for markdown parser rewrite --- quartz/plugins/parsers/custom/index.ts | 0 quartz/plugins/parsers/github/index.ts | 0 quartz/plugins/parsers/obsidian/index.ts | 0 quartz/plugins/parsers/oxhugo/index.ts | 0 quartz/plugins/parsers/roam/index.ts | 0 .../transformers/{gfm.ts => gfm.ts.old} | 2 +- quartz/plugins/transformers/index.ts | 9 ++-- quartz/plugins/transformers/markdown.ts | 46 +++++++++++++++++++ .../transformers/{ofm.ts => ofm.ts.old} | 2 +- .../{oxhugofm.ts => oxhugofm.ts.old} | 2 +- .../transformers/{roam.ts => roam.ts.old} | 2 +- 11 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 quartz/plugins/parsers/custom/index.ts create mode 100644 quartz/plugins/parsers/github/index.ts create mode 100644 quartz/plugins/parsers/obsidian/index.ts create mode 100644 quartz/plugins/parsers/oxhugo/index.ts create mode 100644 quartz/plugins/parsers/roam/index.ts rename quartz/plugins/transformers/{gfm.ts => gfm.ts.old} (95%) create mode 100644 quartz/plugins/transformers/markdown.ts rename quartz/plugins/transformers/{ofm.ts => ofm.ts.old} (99%) rename quartz/plugins/transformers/{oxhugofm.ts => oxhugofm.ts.old} (97%) rename quartz/plugins/transformers/{roam.ts => roam.ts.old} (98%) diff --git a/quartz/plugins/parsers/custom/index.ts b/quartz/plugins/parsers/custom/index.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/github/index.ts b/quartz/plugins/parsers/github/index.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/obsidian/index.ts b/quartz/plugins/parsers/obsidian/index.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/oxhugo/index.ts b/quartz/plugins/parsers/oxhugo/index.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/roam/index.ts b/quartz/plugins/parsers/roam/index.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/transformers/gfm.ts b/quartz/plugins/transformers/gfm.ts.old similarity index 95% rename from quartz/plugins/transformers/gfm.ts rename to quartz/plugins/transformers/gfm.ts.old index eec26f7b9b636..7353fcaeab02f 100644 --- a/quartz/plugins/transformers/gfm.ts +++ b/quartz/plugins/transformers/gfm.ts.old @@ -14,7 +14,7 @@ const defaultOptions: Options = { linkHeadings: true, } -export const GitHubFlavoredMarkdown: QuartzTransformerPlugin> = (userOpts) => { +export const GitHubFlavoredMarkdown_OLD: QuartzTransformerPlugin> = (userOpts) => { const opts = { ...defaultOptions, ...userOpts } return { name: "GitHubFlavoredMarkdown", diff --git a/quartz/plugins/transformers/index.ts b/quartz/plugins/transformers/index.ts index 8e2cd844fec21..9ac225c974ece 100644 --- a/quartz/plugins/transformers/index.ts +++ b/quartz/plugins/transformers/index.ts @@ -1,13 +1,14 @@ export { FrontMatter } from "./frontmatter" -export { GitHubFlavoredMarkdown } from "./gfm" +export { GitHubFlavoredMarkdown } from "./markdown" export { Citations } from "./citations" export { CreatedModifiedDate } from "./lastmod" export { Latex } from "./latex" export { Description } from "./description" export { CrawlLinks } from "./links" -export { ObsidianFlavoredMarkdown } from "./ofm" -export { OxHugoFlavouredMarkdown } from "./oxhugofm" +export { ObsidianFlavoredMarkdown } from "./markdown" +export { OxHugoFlavoredMarkdown } from "./markdown" export { SyntaxHighlighting } from "./syntax" export { TableOfContents } from "./toc" export { HardLineBreaks } from "./linebreaks" -export { RoamFlavoredMarkdown } from "./roam" +export { RoamFlavoredMarkdown } from "./markdown" +export { CustomFlavoredMarkdown } from "./markdown" diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts new file mode 100644 index 0000000000000..6969197bb394e --- /dev/null +++ b/quartz/plugins/transformers/markdown.ts @@ -0,0 +1,46 @@ +import { QuartzTransformerPlugin } from "../types" + +export interface Options { + option1: Boolean +} + +const defaultOptions: Options = { + option1: true, +} + +export const GitHubFlavoredMarkdown: QuartzTransformerPlugin> = (userOpts) => { + const opts = { ...defaultOptions, ...userOpts } + return { + name: "GitHubFlavoredMarkdown", + } +} + +export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin> = (userOpts) => { + const opts = { ...defaultOptions, ...userOpts } + return { + name: "ObsidianFlavoredMarkdown", + } +} + +export const RoamFlavoredMarkdown: QuartzTransformerPlugin | undefined> = ( + userOpts, +) => { + const opts = { ...defaultOptions, ...userOpts } + return { + name: "RoamFlavoredMarkdown", + } +} + +export const OxHugoFlavoredMarkdown: QuartzTransformerPlugin> = (userOpts) => { + const opts = { ...defaultOptions, ...userOpts } + return { + name: "OxHugoFlavoredMarkdown", + } +} + +export const CustomFlavoredMarkdown: QuartzTransformerPlugin> = (userOpts) => { + const opts = { ...defaultOptions, ...userOpts } + return { + name: "CustomFlavoredMarkdown", + } +} diff --git a/quartz/plugins/transformers/ofm.ts b/quartz/plugins/transformers/ofm.ts.old similarity index 99% rename from quartz/plugins/transformers/ofm.ts rename to quartz/plugins/transformers/ofm.ts.old index dd743b6d00309..64ca2a3ed0869 100644 --- a/quartz/plugins/transformers/ofm.ts +++ b/quartz/plugins/transformers/ofm.ts.old @@ -136,7 +136,7 @@ const wikilinkImageEmbedRegex = new RegExp( /^(?(?!^\d*x?\d*$).*?)?(\|?\s*?(?\d+)(x(?\d+))?)?$/, ) -export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin> = (userOpts) => { +export const ObsidianFlavoredMarkdown_OLD: QuartzTransformerPlugin> = (userOpts) => { const opts = { ...defaultOptions, ...userOpts } const mdastToHtml = (ast: PhrasingContent | Paragraph) => { diff --git a/quartz/plugins/transformers/oxhugofm.ts b/quartz/plugins/transformers/oxhugofm.ts.old similarity index 97% rename from quartz/plugins/transformers/oxhugofm.ts rename to quartz/plugins/transformers/oxhugofm.ts.old index cdbffcffd1d3e..f0c467cf10493 100644 --- a/quartz/plugins/transformers/oxhugofm.ts +++ b/quartz/plugins/transformers/oxhugofm.ts.old @@ -47,7 +47,7 @@ const quartzLatexRegex = new RegExp(/\$\$[\s\S]*?\$\$|\$.*?\$/, "g") * markdown to make it compatible with quartz but the list of changes applied it * is not exhaustive. * */ -export const OxHugoFlavouredMarkdown: QuartzTransformerPlugin> = (userOpts) => { +export const OxHugoFlavouredMarkdown_OLD: QuartzTransformerPlugin> = (userOpts) => { const opts = { ...defaultOptions, ...userOpts } return { name: "OxHugoFlavouredMarkdown", diff --git a/quartz/plugins/transformers/roam.ts b/quartz/plugins/transformers/roam.ts.old similarity index 98% rename from quartz/plugins/transformers/roam.ts rename to quartz/plugins/transformers/roam.ts.old index b3be8f54284b2..c03da4d4c5107 100644 --- a/quartz/plugins/transformers/roam.ts +++ b/quartz/plugins/transformers/roam.ts.old @@ -124,7 +124,7 @@ function transformSpecialEmbed(node: Paragraph, opts: Options): Html | null { } } -export const RoamFlavoredMarkdown: QuartzTransformerPlugin | undefined> = ( +export const RoamFlavoredMarkdown_OLD: QuartzTransformerPlugin | undefined> = ( userOpts, ) => { const opts = { ...defaultOptions, ...userOpts } From d74b7e274ca7eb1d70a4b236c52ae9273f4bb543 Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Wed, 18 Sep 2024 18:25:55 +0000 Subject: [PATCH 02/38] CommonMark --- quartz/plugins/parsers/commonmark/index.ts | 0 quartz/plugins/transformers/index.ts | 1 + quartz/plugins/transformers/markdown.ts | 7 +++++++ 3 files changed, 8 insertions(+) create mode 100644 quartz/plugins/parsers/commonmark/index.ts diff --git a/quartz/plugins/parsers/commonmark/index.ts b/quartz/plugins/parsers/commonmark/index.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/transformers/index.ts b/quartz/plugins/transformers/index.ts index 9ac225c974ece..12e54f37533e4 100644 --- a/quartz/plugins/transformers/index.ts +++ b/quartz/plugins/transformers/index.ts @@ -11,4 +11,5 @@ export { SyntaxHighlighting } from "./syntax" export { TableOfContents } from "./toc" export { HardLineBreaks } from "./linebreaks" export { RoamFlavoredMarkdown } from "./markdown" +export { CommonMarkFlavoredMarkdown } from "./markdown" export { CustomFlavoredMarkdown } from "./markdown" diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index 6969197bb394e..5d15d7d35dfa2 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -38,6 +38,13 @@ export const OxHugoFlavoredMarkdown: QuartzTransformerPlugin> = } } +export const CommonMarkFlavoredMarkdown: QuartzTransformerPlugin> = (userOpts) => { + const opts = { ...defaultOptions, ...userOpts } + return { + name: "CommonMarkFlavoredMarkdown", + } +} + export const CustomFlavoredMarkdown: QuartzTransformerPlugin> = (userOpts) => { const opts = { ...defaultOptions, ...userOpts } return { From 68e3a5984fea09b3b162cda8a76996747f556115 Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 08:29:38 +0000 Subject: [PATCH 03/38] Restore default options --- quartz/plugins/transformers/markdown.ts | 144 ++++++++++++++++++++---- 1 file changed, 124 insertions(+), 20 deletions(-) diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index 5d15d7d35dfa2..be7a0b8ff5bb9 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -1,53 +1,157 @@ import { QuartzTransformerPlugin } from "../types" -export interface Options { +export interface CommonMarkOptions { option1: Boolean } -const defaultOptions: Options = { +const defaultCommonMarkOptions: CommonMarkOptions = { option1: true, } -export const GitHubFlavoredMarkdown: QuartzTransformerPlugin> = (userOpts) => { - const opts = { ...defaultOptions, ...userOpts } +export interface CustomOptions { + option1: Boolean +} + +const defaultCustomOptions: CustomOptions = { + option1: true, +} + +export interface GitHubOptions { + enableSmartyPants: boolean + linkHeadings: boolean +} + +const defaultGitHubOptions: GitHubOptions = { + enableSmartyPants: true, + linkHeadings: true, +} + +export interface ObsidianOptions { + comments: boolean + highlight: boolean + wikilinks: boolean + callouts: boolean + mermaid: boolean + parseTags: boolean + parseArrows: boolean + parseBlockReferences: boolean + enableInHtmlEmbed: boolean + enableYouTubeEmbed: boolean + enableVideoEmbed: boolean + enableCheckbox: boolean +} + +const defaultObsidianOptions: ObsidianOptions = { + comments: true, + highlight: true, + wikilinks: true, + callouts: true, + mermaid: true, + parseTags: true, + parseArrows: true, + parseBlockReferences: true, + enableInHtmlEmbed: false, + enableYouTubeEmbed: true, + enableVideoEmbed: true, + enableCheckbox: false, +} + +export interface OxHugoOptions { + /** 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
with ![]() */ + replaceFigureWithMdImg: boolean + + /** Replace org latex fragments with $ and $$ */ + replaceOrgLatex: boolean +} + +const defaultOxHugoOptions: OxHugoOptions = { + wikilinks: true, + removePredefinedAnchor: true, + removeHugoShortcode: true, + replaceFigureWithMdImg: true, + replaceOrgLatex: true, +} + +export interface RoamOptions { + orComponent: boolean + TODOComponent: boolean + DONEComponent: boolean + videoComponent: boolean + audioComponent: boolean + pdfComponent: boolean + blockquoteComponent: boolean + tableComponent: boolean + attributeComponent: boolean +} + +const defaultRoamOptions: RoamOptions = { + orComponent: true, + TODOComponent: true, + DONEComponent: true, + videoComponent: true, + audioComponent: true, + pdfComponent: true, + blockquoteComponent: true, + tableComponent: true, + attributeComponent: true, +} + +export const CommonMarkFlavoredMarkdown: QuartzTransformerPlugin> = ( + userOpts, +) => { + const opts = { ...defaultCommonMarkOptions, ...userOpts } return { - name: "GitHubFlavoredMarkdown", + name: "CommonMarkFlavoredMarkdown", } } -export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin> = (userOpts) => { - const opts = { ...defaultOptions, ...userOpts } +export const CustomFlavoredMarkdown: QuartzTransformerPlugin> = ( + userOpts, +) => { + const opts = { ...defaultCustomOptions, ...userOpts } return { - name: "ObsidianFlavoredMarkdown", + name: "CustomFlavoredMarkdown", } } -export const RoamFlavoredMarkdown: QuartzTransformerPlugin | undefined> = ( +export const GitHubFlavoredMarkdown: QuartzTransformerPlugin> = ( userOpts, ) => { - const opts = { ...defaultOptions, ...userOpts } + const opts = { ...defaultGitHubOptions, ...userOpts } return { - name: "RoamFlavoredMarkdown", + name: "GitHubFlavoredMarkdown", } } -export const OxHugoFlavoredMarkdown: QuartzTransformerPlugin> = (userOpts) => { - const opts = { ...defaultOptions, ...userOpts } +export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin> = ( + userOpts, +) => { + const opts = { ...defaultObsidianOptions, ...userOpts } return { - name: "OxHugoFlavoredMarkdown", + name: "ObsidianFlavoredMarkdown", } } -export const CommonMarkFlavoredMarkdown: QuartzTransformerPlugin> = (userOpts) => { - const opts = { ...defaultOptions, ...userOpts } +export const OxHugoFlavoredMarkdown: QuartzTransformerPlugin> = ( + userOpts, +) => { + const opts = { ...defaultOxHugoOptions, ...userOpts } return { - name: "CommonMarkFlavoredMarkdown", + name: "OxHugoFlavoredMarkdown", } } -export const CustomFlavoredMarkdown: QuartzTransformerPlugin> = (userOpts) => { - const opts = { ...defaultOptions, ...userOpts } +export const RoamFlavoredMarkdown: QuartzTransformerPlugin | undefined> = ( + userOpts, +) => { + const opts = { ...defaultRoamOptions, ...userOpts } return { - name: "CustomFlavoredMarkdown", + name: "RoamFlavoredMarkdown", } } From 15a74bfb393b4afea018f85795fdc3fb2e53fac1 Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 08:41:58 +0000 Subject: [PATCH 04/38] Split up parsers --- quartz/plugins/transformers/markdown.ts | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index be7a0b8ff5bb9..18c5444f871bf 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -1,4 +1,39 @@ import { QuartzTransformerPlugin } from "../types" +import { + Root, + Html, + BlockContent, + DefinitionContent, + Paragraph, + Code, + Text, + Link, + Parent, +} from "mdast" +import { Element, Literal, Root as HtmlRoot } from "hast" +import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" +import rehypeRaw from "rehype-raw" +import { SKIP, visit } from "unist-util-visit" +import path from "path" +import { splitAnchor } from "../../util/path" +import { JSResource } from "../../util/resources" +// @ts-ignore +import calloutScript from "../../components/scripts/callout.inline.ts" +// @ts-ignore +import checkboxScript from "../../components/scripts/checkbox.inline.ts" +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" +import { PluggableList } from "unified" +import { Node } from "unist" +import { VFile } from "vfile" +import { BuildVisitor } from "unist-util-visit" +import remarkGfm from "remark-gfm" +import smartypants from "remark-smartypants" +import rehypeSlug from "rehype-slug" +import rehypeAutolinkHeadings from "rehype-autolink-headings" export interface CommonMarkOptions { option1: Boolean From add7f3ab2e62e8720162bc470513b4c58a2b1bee Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 08:48:55 +0000 Subject: [PATCH 05/38] Parser files --- quartz/plugins/parsers/github/linkheadings.ts | 0 quartz/plugins/parsers/obsidian/arrows.ts | 0 quartz/plugins/parsers/obsidian/blockreference.ts | 0 quartz/plugins/parsers/obsidian/callouts.ts | 0 quartz/plugins/parsers/obsidian/checkboxes.ts | 0 quartz/plugins/parsers/obsidian/external.ts | 0 quartz/plugins/parsers/obsidian/headings.ts | 0 quartz/plugins/parsers/obsidian/highlights.ts | 0 quartz/plugins/parsers/obsidian/html.ts | 0 quartz/plugins/parsers/obsidian/internal.ts | 0 quartz/plugins/parsers/obsidian/mermaid.ts | 0 quartz/plugins/parsers/obsidian/tables.ts | 0 quartz/plugins/parsers/obsidian/tags.ts | 0 quartz/plugins/parsers/obsidian/text.ts | 0 quartz/plugins/parsers/obsidian/wikilinks.ts | 0 quartz/plugins/parsers/oxhugo/anchors.ts | 0 quartz/plugins/parsers/oxhugo/figure.ts | 0 quartz/plugins/parsers/oxhugo/latex.ts | 0 quartz/plugins/parsers/oxhugo/shortcodes.ts | 0 quartz/plugins/parsers/oxhugo/wikilinks.ts | 0 quartz/plugins/parsers/roam/attributes.ts | 0 quartz/plugins/parsers/roam/blockquote.ts | 0 quartz/plugins/parsers/roam/done.ts | 0 quartz/plugins/parsers/roam/embeds.ts | 0 quartz/plugins/parsers/roam/tables.ts | 0 quartz/plugins/parsers/roam/text.ts | 0 quartz/plugins/parsers/roam/todo.ts | 0 27 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 quartz/plugins/parsers/github/linkheadings.ts create mode 100644 quartz/plugins/parsers/obsidian/arrows.ts create mode 100644 quartz/plugins/parsers/obsidian/blockreference.ts create mode 100644 quartz/plugins/parsers/obsidian/callouts.ts create mode 100644 quartz/plugins/parsers/obsidian/checkboxes.ts create mode 100644 quartz/plugins/parsers/obsidian/external.ts create mode 100644 quartz/plugins/parsers/obsidian/headings.ts create mode 100644 quartz/plugins/parsers/obsidian/highlights.ts create mode 100644 quartz/plugins/parsers/obsidian/html.ts create mode 100644 quartz/plugins/parsers/obsidian/internal.ts create mode 100644 quartz/plugins/parsers/obsidian/mermaid.ts create mode 100644 quartz/plugins/parsers/obsidian/tables.ts create mode 100644 quartz/plugins/parsers/obsidian/tags.ts create mode 100644 quartz/plugins/parsers/obsidian/text.ts create mode 100644 quartz/plugins/parsers/obsidian/wikilinks.ts create mode 100644 quartz/plugins/parsers/oxhugo/anchors.ts create mode 100644 quartz/plugins/parsers/oxhugo/figure.ts create mode 100644 quartz/plugins/parsers/oxhugo/latex.ts create mode 100644 quartz/plugins/parsers/oxhugo/shortcodes.ts create mode 100644 quartz/plugins/parsers/oxhugo/wikilinks.ts create mode 100644 quartz/plugins/parsers/roam/attributes.ts create mode 100644 quartz/plugins/parsers/roam/blockquote.ts create mode 100644 quartz/plugins/parsers/roam/done.ts create mode 100644 quartz/plugins/parsers/roam/embeds.ts create mode 100644 quartz/plugins/parsers/roam/tables.ts create mode 100644 quartz/plugins/parsers/roam/text.ts create mode 100644 quartz/plugins/parsers/roam/todo.ts diff --git a/quartz/plugins/parsers/github/linkheadings.ts b/quartz/plugins/parsers/github/linkheadings.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/obsidian/arrows.ts b/quartz/plugins/parsers/obsidian/arrows.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/obsidian/blockreference.ts b/quartz/plugins/parsers/obsidian/blockreference.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/obsidian/callouts.ts b/quartz/plugins/parsers/obsidian/callouts.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/obsidian/checkboxes.ts b/quartz/plugins/parsers/obsidian/checkboxes.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/obsidian/external.ts b/quartz/plugins/parsers/obsidian/external.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/obsidian/headings.ts b/quartz/plugins/parsers/obsidian/headings.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/obsidian/highlights.ts b/quartz/plugins/parsers/obsidian/highlights.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/obsidian/html.ts b/quartz/plugins/parsers/obsidian/html.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/obsidian/internal.ts b/quartz/plugins/parsers/obsidian/internal.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/obsidian/mermaid.ts b/quartz/plugins/parsers/obsidian/mermaid.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/obsidian/tables.ts b/quartz/plugins/parsers/obsidian/tables.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/obsidian/tags.ts b/quartz/plugins/parsers/obsidian/tags.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/obsidian/text.ts b/quartz/plugins/parsers/obsidian/text.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/obsidian/wikilinks.ts b/quartz/plugins/parsers/obsidian/wikilinks.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/oxhugo/anchors.ts b/quartz/plugins/parsers/oxhugo/anchors.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/oxhugo/figure.ts b/quartz/plugins/parsers/oxhugo/figure.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/oxhugo/latex.ts b/quartz/plugins/parsers/oxhugo/latex.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/oxhugo/shortcodes.ts b/quartz/plugins/parsers/oxhugo/shortcodes.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/oxhugo/wikilinks.ts b/quartz/plugins/parsers/oxhugo/wikilinks.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/roam/attributes.ts b/quartz/plugins/parsers/roam/attributes.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/roam/blockquote.ts b/quartz/plugins/parsers/roam/blockquote.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/roam/done.ts b/quartz/plugins/parsers/roam/done.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/roam/embeds.ts b/quartz/plugins/parsers/roam/embeds.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/roam/tables.ts b/quartz/plugins/parsers/roam/tables.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/roam/text.ts b/quartz/plugins/parsers/roam/text.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/quartz/plugins/parsers/roam/todo.ts b/quartz/plugins/parsers/roam/todo.ts new file mode 100644 index 0000000000000..e69de29bb2d1d From 23e71e95f487fc55e7470b4b7032ae22ccd54de4 Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 09:42:56 +0000 Subject: [PATCH 06/38] Obsidian Parsers (Arrow) --- quartz/plugins/parsers/obsidian/arrows.ts | 42 +++++++++++++++++++++++ quartz/plugins/parsers/obsidian/index.ts | 1 + quartz/plugins/transformers/markdown.ts | 35 +++++++++++++++++++ 3 files changed, 78 insertions(+) diff --git a/quartz/plugins/parsers/obsidian/arrows.ts b/quartz/plugins/parsers/obsidian/arrows.ts index e69de29bb2d1d..0916b9a592e0d 100644 --- a/quartz/plugins/parsers/obsidian/arrows.ts +++ b/quartz/plugins/parsers/obsidian/arrows.ts @@ -0,0 +1,42 @@ +import { QuartzTransformerPlugin } from "../../types" +import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" +import { SKIP } from "unist-util-visit" +import { Root } from "mdast" + +const arrowMapping: Record = { + "->": "→", + "-->": "⇒", + "=>": "⇒", + "==>": "⇒", + "<-": "←", + "<--": "⇐", + "<=": "⇐", + "<==": "⇐", +} + +const arrowRegex = new RegExp(/(-{1,2}>|={1,2}>|<-{1,2}|<={1,2})/g) + +export const ObsidianMarkdownArrow: QuartzTransformerPlugin = () => { + return { + name: "ObsidianMarkdownArrow", + markdownPlugins() { + return [ + (tree: Root) => { + const replacements: [RegExp, string | ReplaceFunction][] = [] + replacements.push([ + arrowRegex, + (value: string, ..._capture: string[]) => { + const maybeArrow = arrowMapping[value] + if (maybeArrow === undefined) return SKIP + return { + type: "html", + value: `${maybeArrow}`, + } + }, + ]) + mdastFindReplace(tree, replacements) + }, + ] + }, + } +} diff --git a/quartz/plugins/parsers/obsidian/index.ts b/quartz/plugins/parsers/obsidian/index.ts index e69de29bb2d1d..100d6069b2371 100644 --- a/quartz/plugins/parsers/obsidian/index.ts +++ b/quartz/plugins/parsers/obsidian/index.ts @@ -0,0 +1 @@ +export { ObsidianMarkdownArrow } from "./arrows" diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index 18c5444f871bf..4d901f52bbb42 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -35,6 +35,8 @@ import smartypants from "remark-smartypants" import rehypeSlug from "rehype-slug" import rehypeAutolinkHeadings from "rehype-autolink-headings" +import { ObsidianMarkdownArrow } from "../parsers/obsidian" + export interface CommonMarkOptions { option1: Boolean } @@ -170,6 +172,39 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin { + return (tree: Root, file) => { + //const replacements: [RegExp, string | ReplaceFunction][] = [] + //const base = pathToRoot(file.data.slug!) + + if (opts.parseArrows) { + ObsidianMarkdownArrow() + } + + //mdastFindReplace(tree, replacements) + } + }) + + return plugins + }, + htmlPlugins() { + const plugins: PluggableList = [rehypeRaw] + + plugins.push(() => {}) + + return plugins + }, + externalResources() { + const js: JSResource[] = [] + + return { js } + }, } } From 03f4cb8b8ad95366316e99df97449b7f68312ffe Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 09:46:05 +0000 Subject: [PATCH 07/38] Parser index --- quartz/plugins/transformers/index.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/quartz/plugins/transformers/index.ts b/quartz/plugins/transformers/index.ts index 12e54f37533e4..92f78cdf5bf67 100644 --- a/quartz/plugins/transformers/index.ts +++ b/quartz/plugins/transformers/index.ts @@ -1,15 +1,17 @@ export { FrontMatter } from "./frontmatter" -export { GitHubFlavoredMarkdown } from "./markdown" +export { + CommonMarkFlavoredMarkdown, + CustomFlavoredMarkdown, + GitHubFlavoredMarkdown, + ObsidianFlavoredMarkdown, + OxHugoFlavoredMarkdown, + RoamFlavoredMarkdown, +} from "./markdown" export { Citations } from "./citations" export { CreatedModifiedDate } from "./lastmod" export { Latex } from "./latex" export { Description } from "./description" export { CrawlLinks } from "./links" -export { ObsidianFlavoredMarkdown } from "./markdown" -export { OxHugoFlavoredMarkdown } from "./markdown" export { SyntaxHighlighting } from "./syntax" export { TableOfContents } from "./toc" export { HardLineBreaks } from "./linebreaks" -export { RoamFlavoredMarkdown } from "./markdown" -export { CommonMarkFlavoredMarkdown } from "./markdown" -export { CustomFlavoredMarkdown } from "./markdown" From e697696a2080a18d2dc3c0368f1da49d1eea99fc Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 10:16:38 +0000 Subject: [PATCH 08/38] Delegate options to parsers --- quartz/plugins/parsers/obsidian/arrows.ts | 39 +++++++++++++++-------- quartz/plugins/transformers/markdown.ts | 4 +-- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/quartz/plugins/parsers/obsidian/arrows.ts b/quartz/plugins/parsers/obsidian/arrows.ts index 0916b9a592e0d..05ae8362fb87f 100644 --- a/quartz/plugins/parsers/obsidian/arrows.ts +++ b/quartz/plugins/parsers/obsidian/arrows.ts @@ -3,6 +3,14 @@ import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util- import { SKIP } from "unist-util-visit" import { Root } from "mdast" +interface Options { + enabled: Boolean +} + +const defaultOptions: Options = { + enabled: true, +} + const arrowMapping: Record = { "->": "→", "-->": "⇒", @@ -16,25 +24,28 @@ const arrowMapping: Record = { const arrowRegex = new RegExp(/(-{1,2}>|={1,2}>|<-{1,2}|<={1,2})/g) -export const ObsidianMarkdownArrow: QuartzTransformerPlugin = () => { +export const ObsidianMarkdownArrow: QuartzTransformerPlugin> = (userOpts) => { + const opts: Options = { ...defaultOptions, ...userOpts } return { name: "ObsidianMarkdownArrow", markdownPlugins() { return [ (tree: Root) => { - const replacements: [RegExp, string | ReplaceFunction][] = [] - replacements.push([ - arrowRegex, - (value: string, ..._capture: string[]) => { - const maybeArrow = arrowMapping[value] - if (maybeArrow === undefined) return SKIP - return { - type: "html", - value: `${maybeArrow}`, - } - }, - ]) - mdastFindReplace(tree, replacements) + if (opts.enabled) { + const replacements: [RegExp, string | ReplaceFunction][] = [] + replacements.push([ + arrowRegex, + (value: string, ..._capture: string[]) => { + const maybeArrow = arrowMapping[value] + if (maybeArrow === undefined) return SKIP + return { + type: "html", + value: `${maybeArrow}`, + } + }, + ]) + mdastFindReplace(tree, replacements) + } }, ] }, diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index 4d901f52bbb42..3d23f77c286c9 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -183,9 +183,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin Date: Thu, 19 Sep 2024 10:22:14 +0000 Subject: [PATCH 09/38] Obsidian Parsers (Highlights) --- quartz/plugins/parsers/obsidian/highlights.ts | 40 +++++++++++++++++++ quartz/plugins/parsers/obsidian/index.ts | 1 + quartz/plugins/transformers/markdown.ts | 4 +- 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/quartz/plugins/parsers/obsidian/highlights.ts b/quartz/plugins/parsers/obsidian/highlights.ts index e69de29bb2d1d..03d2b02f1e01c 100644 --- a/quartz/plugins/parsers/obsidian/highlights.ts +++ b/quartz/plugins/parsers/obsidian/highlights.ts @@ -0,0 +1,40 @@ +import { QuartzTransformerPlugin } from "../../types" +import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" +import { Root } from "mdast" + +interface Options { + enabled: Boolean +} + +const defaultOptions: Options = { + enabled: true, +} + +const highlightRegex = new RegExp(/==([^=]+)==/g) + +export const ObsidianMarkdownHighlights: QuartzTransformerPlugin> = (userOpts) => { + const opts: Options = { ...defaultOptions, ...userOpts } + return { + name: "ObsidianMarkdownHighlights", + markdownPlugins() { + return [ + (tree: Root) => { + if (opts.enabled) { + const replacements: [RegExp, string | ReplaceFunction][] = [] + replacements.push([ + highlightRegex, + (_value: string, ...capture: string[]) => { + const [inner] = capture + return { + type: "html", + value: `${inner}`, + } + }, + ]) + mdastFindReplace(tree, replacements) + } + }, + ] + }, + } +} diff --git a/quartz/plugins/parsers/obsidian/index.ts b/quartz/plugins/parsers/obsidian/index.ts index 100d6069b2371..638823766a3cd 100644 --- a/quartz/plugins/parsers/obsidian/index.ts +++ b/quartz/plugins/parsers/obsidian/index.ts @@ -1 +1,2 @@ export { ObsidianMarkdownArrow } from "./arrows" +export { ObsidianMarkdownHighlights } from "./highlights" diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index 3d23f77c286c9..dda2683148e1c 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -35,7 +35,7 @@ import smartypants from "remark-smartypants" import rehypeSlug from "rehype-slug" import rehypeAutolinkHeadings from "rehype-autolink-headings" -import { ObsidianMarkdownArrow } from "../parsers/obsidian" +import { ObsidianMarkdownArrow, ObsidianMarkdownHighlights } from "../parsers/obsidian" export interface CommonMarkOptions { option1: Boolean @@ -183,6 +183,8 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin Date: Thu, 19 Sep 2024 11:28:22 +0000 Subject: [PATCH 10/38] Obsidian Parsers (Wikilinks) --- quartz/plugins/parsers/obsidian/arrows.ts | 9 +- quartz/plugins/parsers/obsidian/highlights.ts | 9 +- quartz/plugins/parsers/obsidian/index.ts | 5 +- quartz/plugins/parsers/obsidian/wikilinks.ts | 164 ++++++++++++++++++ quartz/plugins/transformers/markdown.ts | 14 +- 5 files changed, 186 insertions(+), 15 deletions(-) diff --git a/quartz/plugins/parsers/obsidian/arrows.ts b/quartz/plugins/parsers/obsidian/arrows.ts index 05ae8362fb87f..575ed48f0214a 100644 --- a/quartz/plugins/parsers/obsidian/arrows.ts +++ b/quartz/plugins/parsers/obsidian/arrows.ts @@ -2,6 +2,7 @@ import { QuartzTransformerPlugin } from "../../types" import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" import { SKIP } from "unist-util-visit" import { Root } from "mdast" +import { PluggableList } from "unified" interface Options { enabled: Boolean @@ -24,11 +25,11 @@ const arrowMapping: Record = { const arrowRegex = new RegExp(/(-{1,2}>|={1,2}>|<-{1,2}|<={1,2})/g) -export const ObsidianMarkdownArrow: QuartzTransformerPlugin> = (userOpts) => { +export const ObsidianArrow: QuartzTransformerPlugin> = (userOpts) => { const opts: Options = { ...defaultOptions, ...userOpts } return { - name: "ObsidianMarkdownArrow", - markdownPlugins() { + name: "ObsidianArrow", + markdownPlugins(_ctx) { return [ (tree: Root) => { if (opts.enabled) { @@ -47,7 +48,7 @@ export const ObsidianMarkdownArrow: QuartzTransformerPlugin> = mdastFindReplace(tree, replacements) } }, - ] + ] as PluggableList }, } } diff --git a/quartz/plugins/parsers/obsidian/highlights.ts b/quartz/plugins/parsers/obsidian/highlights.ts index 03d2b02f1e01c..6e9e110588c68 100644 --- a/quartz/plugins/parsers/obsidian/highlights.ts +++ b/quartz/plugins/parsers/obsidian/highlights.ts @@ -1,6 +1,7 @@ import { QuartzTransformerPlugin } from "../../types" import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" import { Root } from "mdast" +import { PluggableList } from "unified" interface Options { enabled: Boolean @@ -12,11 +13,11 @@ const defaultOptions: Options = { const highlightRegex = new RegExp(/==([^=]+)==/g) -export const ObsidianMarkdownHighlights: QuartzTransformerPlugin> = (userOpts) => { +export const ObsidianHighlights: QuartzTransformerPlugin> = (userOpts) => { const opts: Options = { ...defaultOptions, ...userOpts } return { - name: "ObsidianMarkdownHighlights", - markdownPlugins() { + name: "ObsidianHighlights", + markdownPlugins(ctx) { return [ (tree: Root) => { if (opts.enabled) { @@ -34,7 +35,7 @@ export const ObsidianMarkdownHighlights: QuartzTransformerPlugin matches the header row +// ( ?:?-{3,}:? ?\|)+ -> matches the header row separator +// (\|([^\n])+\|\n)+ -> matches the body rows +const tableRegex = new RegExp(/^\|([^\n])+\|\n(\|)( ?:?-{3,}:? ?\|)+\n(\|([^\n])+\|\n?)+/gm) + +// matches any wikilink, only used for escaping wikilinks inside tables +const tableWikilinkRegex = new RegExp(/(!?\[\[[^\]]*?\]\])/g) + +// !? -> optional embedding +// \[\[ -> open brace +// ([^\[\]\|\#]+) -> one or more non-special characters ([,],|, or #) (name) +// (#[^\[\]\|\#]+)? -> # then one or more non-special characters (heading link) +// (\\?\|[^\[\]\#]+)? -> optional escape \ then | then one or more non-special characters (alias) +const wikilinkRegex = new RegExp( + /!?\[\[([^\[\]\|\#\\]+)?(#+[^\[\]\|\#\\]+)?(\\?\|[^\[\]\#]+)?\]\]/g, +) + +const wikilinkImageEmbedRegex = new RegExp( + /^(?(?!^\d*x?\d*$).*?)?(\|?\s*?(?\d+)(x(?\d+))?)?$/, +) + +export const ObsidianWikilinks: QuartzTransformerPlugin> = (userOpts) => { + const opts: Options = { ...defaultOptions, ...userOpts } + return { + name: "ObsidianWikilinks", + textTransform(_ctx, src: string | Buffer) { + if (src instanceof Buffer) { + src = src.toString() + } + + // replace all wikilinks inside a table first + src = src.replace(tableRegex, (value) => { + // escape all aliases and headers in wikilinks inside a table + return value.replace(tableWikilinkRegex, (_value, raw) => { + // const [raw]: (string | undefined)[] = capture + let escaped = raw ?? "" + escaped = escaped.replace("#", "\\#") + // escape pipe characters if they are not already escaped + escaped = escaped.replace(/((^|[^\\])(\\\\)*)\|/g, "$1\\|") + + return escaped + }) + }) + + // replace all other wikilinks + src = src.replace(wikilinkRegex, (value, ...capture) => { + const [rawFp, rawHeader, rawAlias]: (string | undefined)[] = capture + + const [fp, anchor] = splitAnchor(`${rawFp ?? ""}${rawHeader ?? ""}`) + const blockRef = Boolean(rawHeader?.match(/^#?\^/)) ? "^" : "" + const displayAnchor = anchor ? `#${blockRef}${anchor.trim().replace(/^#+/, "")}` : "" + const displayAlias = rawAlias ?? rawHeader?.replace("#", "|") ?? "" + const embedDisplay = value.startsWith("!") ? "!" : "" + + if (rawFp?.match(externalLinkRegex)) { + return `${embedDisplay}[${displayAlias.replace(/^\|/, "")}](${rawFp})` + } + + return `${embedDisplay}[[${fp}${displayAnchor}${displayAlias}]]` + }) + + return src + }, + markdownPlugins(_ctx) { + return [ + (tree: Root, path) => { + if (opts.enabled) { + const replacements: [RegExp, string | ReplaceFunction][] = [] + replacements.push([ + wikilinkRegex, + (value: string, ...capture: string[]) => { + let [rawFp, rawHeader, rawAlias] = capture + const fp = rawFp?.trim() ?? "" + const anchor = rawHeader?.trim() ?? "" + const alias = rawAlias?.slice(1).trim() + + // embed cases + if (value.startsWith("!")) { + const ext: string = path.extname(fp).toLowerCase() + const url = slugifyFilePath(fp as FilePath) + if ([".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg", ".webp"].includes(ext)) { + const match = wikilinkImageEmbedRegex.exec(alias ?? "") + const alt = match?.groups?.alt ?? "" + const width = match?.groups?.width ?? "auto" + const height = match?.groups?.height ?? "auto" + return { + type: "image", + url, + data: { + hProperties: { + width, + height, + alt, + }, + }, + } + } else if ([".mp4", ".webm", ".ogv", ".mov", ".mkv"].includes(ext)) { + return { + type: "html", + value: ``, + } + } else if ( + [".mp3", ".webm", ".wav", ".m4a", ".ogg", ".3gp", ".flac"].includes(ext) + ) { + return { + type: "html", + value: ``, + } + } else if ([".pdf"].includes(ext)) { + return { + type: "html", + value: ``, + } + } else { + const block = anchor + return { + type: "html", + data: { hProperties: { transclude: true } }, + value: `
Transclude of ${url}${block}
`, + } + } + + // otherwise, fall through to regular link + } + + // internal link + const url = fp + anchor + return { + type: "link", + url, + children: [ + { + type: "text", + value: alias ?? fp, + }, + ], + } + }, + ]) + mdastFindReplace(tree, replacements) + } + }, + ] as PluggableList + }, + } +} diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index dda2683148e1c..d733f8e336ba0 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -35,7 +35,7 @@ import smartypants from "remark-smartypants" import rehypeSlug from "rehype-slug" import rehypeAutolinkHeadings from "rehype-autolink-headings" -import { ObsidianMarkdownArrow, ObsidianMarkdownHighlights } from "../parsers/obsidian" +import { ObsidianArrow, ObsidianHighlights, ObsidianWikilinks } from "../parsers/obsidian" export interface CommonMarkOptions { option1: Boolean @@ -172,10 +172,12 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin { @@ -183,9 +185,11 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin Date: Thu, 19 Sep 2024 11:48:28 +0000 Subject: [PATCH 11/38] Added QuartzParserPlugin typing --- quartz/plugins/types.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/quartz/plugins/types.ts b/quartz/plugins/types.ts index a23f5d6f4630a..8b995baf39e2f 100644 --- a/quartz/plugins/types.ts +++ b/quartz/plugins/types.ts @@ -10,6 +10,7 @@ export interface PluginTypes { transformers: QuartzTransformerPluginInstance[] filters: QuartzFilterPluginInstance[] emitters: QuartzEmitterPluginInstance[] + parsers: QuartzParserPluginInstance[] } type OptionType = object | undefined @@ -45,3 +46,14 @@ export type QuartzEmitterPluginInstance = { resources: StaticResources, ): Promise> } + +export type QuartzParserPlugin = ( + opts?: Options, +) => QuartzParserPluginInstance +export type QuartzParserPluginInstance = { + name: string + textTransform: (ctx: BuildCtx, src: string | Buffer) => string | Buffer + markdownPlugins: (ctx: BuildCtx) => PluggableList + htmlPlugins: (ctx: BuildCtx) => PluggableList + externalResources: (ctx: BuildCtx) => Partial +} From 50bbc82e730a45989ab66876fdc23d863f822b33 Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 11:48:40 +0000 Subject: [PATCH 12/38] Updated parsers with new typing --- quartz/plugins/parsers/obsidian/arrows.ts | 18 +++++++++++++++-- quartz/plugins/parsers/obsidian/highlights.ts | 20 ++++++++++++++++--- quartz/plugins/parsers/obsidian/wikilinks.ts | 12 +++++++++-- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/quartz/plugins/parsers/obsidian/arrows.ts b/quartz/plugins/parsers/obsidian/arrows.ts index 575ed48f0214a..e61bfe677552c 100644 --- a/quartz/plugins/parsers/obsidian/arrows.ts +++ b/quartz/plugins/parsers/obsidian/arrows.ts @@ -1,5 +1,6 @@ -import { QuartzTransformerPlugin } from "../../types" +import { QuartzParserPlugin } from "../../types" import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" +import { JSResource } from "../../../util/resources" import { SKIP } from "unist-util-visit" import { Root } from "mdast" import { PluggableList } from "unified" @@ -25,10 +26,16 @@ const arrowMapping: Record = { const arrowRegex = new RegExp(/(-{1,2}>|={1,2}>|<-{1,2}|<={1,2})/g) -export const ObsidianArrow: QuartzTransformerPlugin> = (userOpts) => { +export const ObsidianArrow: QuartzParserPlugin> = (userOpts) => { const opts: Options = { ...defaultOptions, ...userOpts } return { name: "ObsidianArrow", + textTransform(_ctx, src: string | Buffer) { + if (src instanceof Buffer) { + src = src.toString() + } + return src + }, markdownPlugins(_ctx) { return [ (tree: Root) => { @@ -50,5 +57,12 @@ export const ObsidianArrow: QuartzTransformerPlugin> = (userOpt }, ] as PluggableList }, + htmlPlugins(_ctx) { + return [] as PluggableList + }, + externalResources(_ctx) { + const js = [] as JSResource[] + return { js } + }, } } diff --git a/quartz/plugins/parsers/obsidian/highlights.ts b/quartz/plugins/parsers/obsidian/highlights.ts index 6e9e110588c68..2e4b46812c37b 100644 --- a/quartz/plugins/parsers/obsidian/highlights.ts +++ b/quartz/plugins/parsers/obsidian/highlights.ts @@ -1,5 +1,6 @@ -import { QuartzTransformerPlugin } from "../../types" +import { QuartzParserPlugin } from "../../types" import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" +import { JSResource } from "../../../util/resources" import { Root } from "mdast" import { PluggableList } from "unified" @@ -13,11 +14,17 @@ const defaultOptions: Options = { const highlightRegex = new RegExp(/==([^=]+)==/g) -export const ObsidianHighlights: QuartzTransformerPlugin> = (userOpts) => { +export const ObsidianHighlights: QuartzParserPlugin> = (userOpts) => { const opts: Options = { ...defaultOptions, ...userOpts } return { name: "ObsidianHighlights", - markdownPlugins(ctx) { + textTransform(_ctx, src: string | Buffer) { + if (src instanceof Buffer) { + src = src.toString() + } + return src + }, + markdownPlugins(_ctx) { return [ (tree: Root) => { if (opts.enabled) { @@ -37,5 +44,12 @@ export const ObsidianHighlights: QuartzTransformerPlugin> = (us }, ] as PluggableList }, + htmlPlugins(_ctx) { + return [] as PluggableList + }, + externalResources(_ctx) { + const js = [] as JSResource[] + return { js } + }, } } diff --git a/quartz/plugins/parsers/obsidian/wikilinks.ts b/quartz/plugins/parsers/obsidian/wikilinks.ts index ad4da31be28b9..c44f34c338422 100644 --- a/quartz/plugins/parsers/obsidian/wikilinks.ts +++ b/quartz/plugins/parsers/obsidian/wikilinks.ts @@ -1,6 +1,7 @@ -import { QuartzTransformerPlugin } from "../../types" +import { QuartzParserPlugin } from "../../types" import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" import { FilePath, splitAnchor, slugifyFilePath } from "../../../util/path" +import { JSResource } from "../../../util/resources" import { Root } from "mdast" import { PluggableList } from "unified" @@ -35,7 +36,7 @@ const wikilinkImageEmbedRegex = new RegExp( /^(?(?!^\d*x?\d*$).*?)?(\|?\s*?(?\d+)(x(?\d+))?)?$/, ) -export const ObsidianWikilinks: QuartzTransformerPlugin> = (userOpts) => { +export const ObsidianWikilinks: QuartzParserPlugin> = (userOpts) => { const opts: Options = { ...defaultOptions, ...userOpts } return { name: "ObsidianWikilinks", @@ -160,5 +161,12 @@ export const ObsidianWikilinks: QuartzTransformerPlugin> = (use }, ] as PluggableList }, + htmlPlugins(_ctx) { + return [] as PluggableList + }, + externalResources(_ctx) { + const js = [] as JSResource[] + return { js } + }, } } From 6808d7c58e819e37a8688690080b7242127396dc Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 11:51:07 +0000 Subject: [PATCH 13/38] Updated Parser calls --- quartz/plugins/transformers/markdown.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index d733f8e336ba0..da0006303b11c 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -173,7 +173,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin Date: Thu, 19 Sep 2024 11:55:34 +0000 Subject: [PATCH 14/38] Added custom parser template --- quartz/plugins/parsers/custom/default.ts | 43 ++++++++++++++++++++++++ quartz/plugins/parsers/custom/index.ts | 1 + 2 files changed, 44 insertions(+) create mode 100644 quartz/plugins/parsers/custom/default.ts diff --git a/quartz/plugins/parsers/custom/default.ts b/quartz/plugins/parsers/custom/default.ts new file mode 100644 index 0000000000000..b7a0cfdb0b23b --- /dev/null +++ b/quartz/plugins/parsers/custom/default.ts @@ -0,0 +1,43 @@ +import { QuartzParserPlugin } from "../../types" +import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" +import { JSResource } from "../../../util/resources" +import { Root } from "mdast" +import { PluggableList } from "unified" + +interface Options { + enabled: Boolean +} + +const defaultOptions: Options = { + enabled: true, +} + +export const CustomDefault: QuartzParserPlugin> = (userOpts) => { + const opts: Options = { ...defaultOptions, ...userOpts } + return { + name: "CustomDefault", + textTransform(_ctx, src: string | Buffer) { + if (src instanceof Buffer) { + src = src.toString() + } + return src + }, + markdownPlugins(_ctx) { + return [ + (tree: Root) => { + if (opts.enabled) { + const replacements: [RegExp, string | ReplaceFunction][] = [] + mdastFindReplace(tree, replacements) + } + }, + ] as PluggableList + }, + htmlPlugins(_ctx) { + return [] as PluggableList + }, + externalResources(_ctx) { + const js = [] as JSResource[] + return { js } + }, + } +} diff --git a/quartz/plugins/parsers/custom/index.ts b/quartz/plugins/parsers/custom/index.ts index e69de29bb2d1d..2226696fd5ca1 100644 --- a/quartz/plugins/parsers/custom/index.ts +++ b/quartz/plugins/parsers/custom/index.ts @@ -0,0 +1 @@ +import { CustomDefault } from "./default" From 81d892ffcc4da6f47e2065dddc4d029fe1bbe36a Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 12:28:01 +0000 Subject: [PATCH 15/38] Updated QuartzParser plugin typing --- quartz/plugins/types.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/quartz/plugins/types.ts b/quartz/plugins/types.ts index 8b995baf39e2f..43d8550c681a4 100644 --- a/quartz/plugins/types.ts +++ b/quartz/plugins/types.ts @@ -1,4 +1,4 @@ -import { PluggableList } from "unified" +import { PluggableList, Pluggable } from "unified" import { StaticResources } from "../util/resources" import { ProcessedContent } from "./vfile" import { QuartzComponent } from "../components/types" @@ -53,7 +53,7 @@ export type QuartzParserPlugin = ( export type QuartzParserPluginInstance = { name: string textTransform: (ctx: BuildCtx, src: string | Buffer) => string | Buffer - markdownPlugins: (ctx: BuildCtx) => PluggableList - htmlPlugins: (ctx: BuildCtx) => PluggableList + markdownPlugins: (ctx: BuildCtx) => Pluggable + htmlPlugins: (ctx: BuildCtx) => Pluggable externalResources: (ctx: BuildCtx) => Partial } From 2818bb1c70e474aac17355d08a531369b973853b Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 12:28:32 +0000 Subject: [PATCH 16/38] Obsidian Parser (Callouts) --- quartz/plugins/parsers/custom/default.ts | 20 +- quartz/plugins/parsers/obsidian/arrows.ts | 42 ++-- quartz/plugins/parsers/obsidian/callouts.ts | 203 +++++++++++++++++++ quartz/plugins/parsers/obsidian/index.ts | 1 + quartz/plugins/parsers/obsidian/wikilinks.ts | 158 +++++++-------- quartz/plugins/transformers/markdown.ts | 18 +- 6 files changed, 328 insertions(+), 114 deletions(-) diff --git a/quartz/plugins/parsers/custom/default.ts b/quartz/plugins/parsers/custom/default.ts index b7a0cfdb0b23b..6a05b6bbae652 100644 --- a/quartz/plugins/parsers/custom/default.ts +++ b/quartz/plugins/parsers/custom/default.ts @@ -2,7 +2,7 @@ import { QuartzParserPlugin } from "../../types" import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" import { JSResource } from "../../../util/resources" import { Root } from "mdast" -import { PluggableList } from "unified" +import { Pluggable } from "unified" interface Options { enabled: Boolean @@ -23,17 +23,17 @@ export const CustomDefault: QuartzParserPlugin> = (userOpts) => return src }, markdownPlugins(_ctx) { - return [ - (tree: Root) => { - if (opts.enabled) { - const replacements: [RegExp, string | ReplaceFunction][] = [] - mdastFindReplace(tree, replacements) - } - }, - ] as PluggableList + const plug: Pluggable = (tree: Root, _file) => { + if (opts.enabled) { + const replacements: [RegExp, string | ReplaceFunction][] = [] + mdastFindReplace(tree, replacements) + } + } + return plug }, htmlPlugins(_ctx) { - return [] as PluggableList + const plug: Pluggable = () => {} + return plug }, externalResources(_ctx) { const js = [] as JSResource[] diff --git a/quartz/plugins/parsers/obsidian/arrows.ts b/quartz/plugins/parsers/obsidian/arrows.ts index e61bfe677552c..93cd4866878c6 100644 --- a/quartz/plugins/parsers/obsidian/arrows.ts +++ b/quartz/plugins/parsers/obsidian/arrows.ts @@ -3,7 +3,7 @@ import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util- import { JSResource } from "../../../util/resources" import { SKIP } from "unist-util-visit" import { Root } from "mdast" -import { PluggableList } from "unified" +import { Pluggable } from "unified" interface Options { enabled: Boolean @@ -37,28 +37,28 @@ export const ObsidianArrow: QuartzParserPlugin> = (userOpts) => return src }, markdownPlugins(_ctx) { - return [ - (tree: Root) => { - if (opts.enabled) { - const replacements: [RegExp, string | ReplaceFunction][] = [] - replacements.push([ - arrowRegex, - (value: string, ..._capture: string[]) => { - const maybeArrow = arrowMapping[value] - if (maybeArrow === undefined) return SKIP - return { - type: "html", - value: `${maybeArrow}`, - } - }, - ]) - mdastFindReplace(tree, replacements) - } - }, - ] as PluggableList + const plug: Pluggable = (tree: Root, _path) => { + if (opts.enabled) { + const replacements: [RegExp, string | ReplaceFunction][] = [] + replacements.push([ + arrowRegex, + (value: string, ..._capture: string[]) => { + const maybeArrow = arrowMapping[value] + if (maybeArrow === undefined) return SKIP + return { + type: "html", + value: `${maybeArrow}`, + } + }, + ]) + mdastFindReplace(tree, replacements) + } + } + return plug }, htmlPlugins(_ctx) { - return [] as PluggableList + const plug: Pluggable = () => {} + return plug }, externalResources(_ctx) { const js = [] as JSResource[] diff --git a/quartz/plugins/parsers/obsidian/callouts.ts b/quartz/plugins/parsers/obsidian/callouts.ts index e69de29bb2d1d..3b80ec729320a 100644 --- a/quartz/plugins/parsers/obsidian/callouts.ts +++ b/quartz/plugins/parsers/obsidian/callouts.ts @@ -0,0 +1,203 @@ +import { QuartzParserPlugin } from "../../types" +import { JSResource } from "../../../util/resources" +import { Root, BlockContent, DefinitionContent, Paragraph, Html } from "mdast" +import { visit } from "unist-util-visit" +import { Pluggable } from "unified" +// @ts-ignore +import calloutScript from "../../components/scripts/callout.inline.ts" +import { PhrasingContent } from "mdast-util-find-and-replace/lib" +import { capitalize } from "../../../util/lang" +import { toHast } from "mdast-util-to-hast" +import { toHtml } from "hast-util-to-html" + +interface Options { + enabled: Boolean +} + +const defaultOptions: Options = { + enabled: true, +} + +// from https://github.com/escwxyz/remark-obsidian-callout/blob/main/src/index.ts +const calloutRegex = new RegExp(/^\[\!(\w+)\|?(.+?)?\]([+-]?)/) +const calloutLineRegex = new RegExp(/^> *\[\!\w+\|?.*?\][+-]?.*$/gm) + +const calloutMapping = { + note: "note", + abstract: "abstract", + summary: "abstract", + tldr: "abstract", + info: "info", + todo: "todo", + tip: "tip", + hint: "tip", + important: "tip", + success: "success", + check: "success", + done: "success", + question: "question", + help: "question", + faq: "question", + warning: "warning", + attention: "warning", + caution: "warning", + failure: "failure", + missing: "failure", + fail: "failure", + danger: "danger", + error: "danger", + bug: "bug", + example: "example", + quote: "quote", + cite: "quote", +} as const + +function canonicalizeCallout(calloutName: string): keyof typeof calloutMapping { + const normalizedCallout = calloutName.toLowerCase() as keyof typeof calloutMapping + // if callout is not recognized, make it a custom one + return calloutMapping[normalizedCallout] ?? calloutName +} + +const mdastToHtml = (ast: PhrasingContent | Paragraph) => { + const hast = toHast(ast, { allowDangerousHtml: true })! + return toHtml(hast, { allowDangerousHtml: true }) +} + +export const ObsidianCallouts: QuartzParserPlugin> = (userOpts) => { + const opts: Options = { ...defaultOptions, ...userOpts } + return { + name: "ObsidianCallouts", + textTransform(_ctx, src: string | Buffer) { + if (src instanceof Buffer) { + src = src.toString() + } + + src = src.replace(calloutLineRegex, (value) => { + // force newline after title of callout + return value + "\n> " + }) + + return src + }, + markdownPlugins(_ctx) { + const plug: Pluggable = (tree: Root, _path) => { + if (opts.enabled) { + visit(tree, "blockquote", (node) => { + 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) + + const toggleIcon = `
` + + const titleHtml: Html = { + type: "html", + value: `
+
+
${title}
+ ${collapse ? toggleIcon : ""} +
`, + } + + 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 plug + }, + htmlPlugins(_ctx) { + const plug: Pluggable = () => {} + return plug + }, + externalResources(_ctx) { + const js = [] as JSResource[] + js.push({ + script: calloutScript, + loadTime: "afterDOMReady", + contentType: "inline", + }) + return { js } + }, + } +} diff --git a/quartz/plugins/parsers/obsidian/index.ts b/quartz/plugins/parsers/obsidian/index.ts index f8c383be1caa4..7df56c7e61739 100644 --- a/quartz/plugins/parsers/obsidian/index.ts +++ b/quartz/plugins/parsers/obsidian/index.ts @@ -1,3 +1,4 @@ export { ObsidianArrow } from "./arrows" +export { ObsidianCallouts } from "./callouts" export { ObsidianHighlights } from "./highlights" export { ObsidianWikilinks } from "./wikilinks" diff --git a/quartz/plugins/parsers/obsidian/wikilinks.ts b/quartz/plugins/parsers/obsidian/wikilinks.ts index c44f34c338422..f6b427ac16270 100644 --- a/quartz/plugins/parsers/obsidian/wikilinks.ts +++ b/quartz/plugins/parsers/obsidian/wikilinks.ts @@ -3,7 +3,7 @@ import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util- import { FilePath, splitAnchor, slugifyFilePath } from "../../../util/path" import { JSResource } from "../../../util/resources" import { Root } from "mdast" -import { PluggableList } from "unified" +import { Pluggable } from "unified" interface Options { enabled: Boolean @@ -79,90 +79,90 @@ export const ObsidianWikilinks: QuartzParserPlugin> = (userOpts return src }, markdownPlugins(_ctx) { - return [ - (tree: Root, path) => { - if (opts.enabled) { - const replacements: [RegExp, string | ReplaceFunction][] = [] - replacements.push([ - wikilinkRegex, - (value: string, ...capture: string[]) => { - let [rawFp, rawHeader, rawAlias] = capture - const fp = rawFp?.trim() ?? "" - const anchor = rawHeader?.trim() ?? "" - const alias = rawAlias?.slice(1).trim() - - // embed cases - if (value.startsWith("!")) { - const ext: string = path.extname(fp).toLowerCase() - const url = slugifyFilePath(fp as FilePath) - if ([".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg", ".webp"].includes(ext)) { - const match = wikilinkImageEmbedRegex.exec(alias ?? "") - const alt = match?.groups?.alt ?? "" - const width = match?.groups?.width ?? "auto" - const height = match?.groups?.height ?? "auto" - return { - type: "image", - url, - data: { - hProperties: { - width, - height, - alt, - }, + const plug: Pluggable = (tree: Root, path) => { + if (opts.enabled) { + const replacements: [RegExp, string | ReplaceFunction][] = [] + replacements.push([ + wikilinkRegex, + (value: string, ...capture: string[]) => { + let [rawFp, rawHeader, rawAlias] = capture + const fp = rawFp?.trim() ?? "" + const anchor = rawHeader?.trim() ?? "" + const alias = rawAlias?.slice(1).trim() + + // embed cases + if (value.startsWith("!")) { + const ext: string = path.extname(fp).toLowerCase() + const url = slugifyFilePath(fp as FilePath) + if ([".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg", ".webp"].includes(ext)) { + const match = wikilinkImageEmbedRegex.exec(alias ?? "") + const alt = match?.groups?.alt ?? "" + const width = match?.groups?.width ?? "auto" + const height = match?.groups?.height ?? "auto" + return { + type: "image", + url, + data: { + hProperties: { + width, + height, + alt, }, - } - } else if ([".mp4", ".webm", ".ogv", ".mov", ".mkv"].includes(ext)) { - return { - type: "html", - value: ``, - } - } else if ( - [".mp3", ".webm", ".wav", ".m4a", ".ogg", ".3gp", ".flac"].includes(ext) - ) { - return { - type: "html", - value: ``, - } - } else if ([".pdf"].includes(ext)) { - return { - type: "html", - value: ``, - } - } else { - const block = anchor - return { - type: "html", - data: { hProperties: { transclude: true } }, - value: `
Transclude of ${url}${block}
`, - } + }, + } + } else if ([".mp4", ".webm", ".ogv", ".mov", ".mkv"].includes(ext)) { + return { + type: "html", + value: ``, + } + } else if ( + [".mp3", ".webm", ".wav", ".m4a", ".ogg", ".3gp", ".flac"].includes(ext) + ) { + return { + type: "html", + value: ``, + } + } else if ([".pdf"].includes(ext)) { + return { + type: "html", + value: ``, + } + } else { + const block = anchor + return { + type: "html", + data: { hProperties: { transclude: true } }, + value: `
Transclude of ${url}${block}
`, } - - // otherwise, fall through to regular link } - // internal link - const url = fp + anchor - return { - type: "link", - url, - children: [ - { - type: "text", - value: alias ?? fp, - }, - ], - } - }, - ]) - mdastFindReplace(tree, replacements) - } - }, - ] as PluggableList + // otherwise, fall through to regular link + } + + // internal link + const url = fp + anchor + return { + type: "link", + url, + children: [ + { + type: "text", + value: alias ?? fp, + }, + ], + } + }, + ]) + mdastFindReplace(tree, replacements) + } + } + return plug }, htmlPlugins(_ctx) { - return [] as PluggableList + const plug: Pluggable = () => {} + return plug }, externalResources(_ctx) { const js = [] as JSResource[] diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index da0006303b11c..942580e0c96a7 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -35,7 +35,12 @@ import smartypants from "remark-smartypants" import rehypeSlug from "rehype-slug" import rehypeAutolinkHeadings from "rehype-autolink-headings" -import { ObsidianArrow, ObsidianHighlights, ObsidianWikilinks } from "../parsers/obsidian" +import { + ObsidianArrow, + ObsidianCallouts, + ObsidianHighlights, + ObsidianWikilinks, +} from "../parsers/obsidian" export interface CommonMarkOptions { option1: Boolean @@ -173,14 +178,15 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin { + /*plugins.push(() => { return (tree: Root, file) => { //const replacements: [RegExp, string | ReplaceFunction][] = [] //const base = pathToRoot(file.data.slug!) @@ -193,7 +199,11 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin Date: Thu, 19 Sep 2024 12:31:54 +0000 Subject: [PATCH 17/38] Updated config --- quartz.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/quartz.config.ts b/quartz.config.ts index b6abbb2d3cc29..0eeca63ab02dc 100644 --- a/quartz.config.ts +++ b/quartz.config.ts @@ -87,6 +87,7 @@ const config: QuartzConfig = { Plugin.Static(), Plugin.NotFoundPage(), ], + parsers: [], }, } From f83ca160b8bc789f4c5a2075a7760404e04d9344 Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 12:32:09 +0000 Subject: [PATCH 18/38] Fixed Obsidian Highlights parser --- quartz/plugins/parsers/obsidian/highlights.ts | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/quartz/plugins/parsers/obsidian/highlights.ts b/quartz/plugins/parsers/obsidian/highlights.ts index 2e4b46812c37b..774aab1a2f8ce 100644 --- a/quartz/plugins/parsers/obsidian/highlights.ts +++ b/quartz/plugins/parsers/obsidian/highlights.ts @@ -2,7 +2,7 @@ import { QuartzParserPlugin } from "../../types" import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" import { JSResource } from "../../../util/resources" import { Root } from "mdast" -import { PluggableList } from "unified" +import { Pluggable } from "unified" interface Options { enabled: Boolean @@ -25,27 +25,27 @@ export const ObsidianHighlights: QuartzParserPlugin> = (userOpt return src }, markdownPlugins(_ctx) { - return [ - (tree: Root) => { - if (opts.enabled) { - const replacements: [RegExp, string | ReplaceFunction][] = [] - replacements.push([ - highlightRegex, - (_value: string, ...capture: string[]) => { - const [inner] = capture - return { - type: "html", - value: `${inner}`, - } - }, - ]) - mdastFindReplace(tree, replacements) - } - }, - ] as PluggableList + const plug: Pluggable = (tree: Root, _path) => { + if (opts.enabled) { + const replacements: [RegExp, string | ReplaceFunction][] = [] + replacements.push([ + highlightRegex, + (_value: string, ...capture: string[]) => { + const [inner] = capture + return { + type: "html", + value: `${inner}`, + } + }, + ]) + mdastFindReplace(tree, replacements) + } + } + return plug }, htmlPlugins(_ctx) { - return [] as PluggableList + const plug: Pluggable = () => {} + return plug }, externalResources(_ctx) { const js = [] as JSResource[] From e5558c76215081713594783da4861b1c050be6b4 Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 12:57:23 +0000 Subject: [PATCH 19/38] Obsidian Parser (Comments) --- quartz/plugins/parsers/obsidian/comments.ts | 49 +++++++++++++++++++++ quartz/plugins/parsers/obsidian/index.ts | 1 + quartz/plugins/parsers/obsidian/text.ts | 0 quartz/plugins/transformers/markdown.ts | 2 + 4 files changed, 52 insertions(+) create mode 100644 quartz/plugins/parsers/obsidian/comments.ts delete mode 100644 quartz/plugins/parsers/obsidian/text.ts diff --git a/quartz/plugins/parsers/obsidian/comments.ts b/quartz/plugins/parsers/obsidian/comments.ts new file mode 100644 index 0000000000000..235c75baf5be4 --- /dev/null +++ b/quartz/plugins/parsers/obsidian/comments.ts @@ -0,0 +1,49 @@ +import { QuartzParserPlugin } from "../../types" +import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" +import { JSResource } from "../../../util/resources" +import { Root } from "mdast" +import { Pluggable } from "unified" + +interface Options { + enabled: Boolean +} + +const defaultOptions: Options = { + enabled: true, +} + +const commentRegex = new RegExp(/%%[\s\S]*?%%/g) + +export const ObsidianComments: QuartzParserPlugin> = (userOpts) => { + const opts: Options = { ...defaultOptions, ...userOpts } + return { + name: "ObsidianComments", + textTransform(_ctx, src: string | Buffer) { + // do comments at text level + if (src instanceof Buffer) { + src = src.toString() + } + + src = src.replace(commentRegex, "") + + return src + }, + markdownPlugins(_ctx) { + const plug: Pluggable = (tree: Root, _file) => { + if (opts.enabled) { + const replacements: [RegExp, string | ReplaceFunction][] = [] + mdastFindReplace(tree, replacements) + } + } + return plug + }, + htmlPlugins(_ctx) { + const plug: Pluggable = () => {} + return plug + }, + externalResources(_ctx) { + const js = [] as JSResource[] + return { js } + }, + } +} diff --git a/quartz/plugins/parsers/obsidian/index.ts b/quartz/plugins/parsers/obsidian/index.ts index 7df56c7e61739..ca865e2da32a4 100644 --- a/quartz/plugins/parsers/obsidian/index.ts +++ b/quartz/plugins/parsers/obsidian/index.ts @@ -1,4 +1,5 @@ export { ObsidianArrow } from "./arrows" export { ObsidianCallouts } from "./callouts" +export { ObsidianComments } from "./comments" export { ObsidianHighlights } from "./highlights" export { ObsidianWikilinks } from "./wikilinks" diff --git a/quartz/plugins/parsers/obsidian/text.ts b/quartz/plugins/parsers/obsidian/text.ts deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index 942580e0c96a7..fdfecfa3c74ea 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -38,6 +38,7 @@ import rehypeAutolinkHeadings from "rehype-autolink-headings" import { ObsidianArrow, ObsidianCallouts, + ObsidianComments, ObsidianHighlights, ObsidianWikilinks, } from "../parsers/obsidian" @@ -178,6 +179,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin Date: Thu, 19 Sep 2024 12:57:58 +0000 Subject: [PATCH 20/38] Restore text --- quartz/plugins/parsers/obsidian/text.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 quartz/plugins/parsers/obsidian/text.ts diff --git a/quartz/plugins/parsers/obsidian/text.ts b/quartz/plugins/parsers/obsidian/text.ts new file mode 100644 index 0000000000000..e69de29bb2d1d From f176486cf2e1e5adcadd74dd396984ad279fcf80 Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 13:28:37 +0000 Subject: [PATCH 21/38] Updated parser typing + Mermaid parser --- quartz/plugins/parsers/custom/default.ts | 12 +- quartz/plugins/parsers/obsidian/arrows.ts | 12 +- quartz/plugins/parsers/obsidian/callouts.ts | 33 ++-- quartz/plugins/parsers/obsidian/comments.ts | 17 +- quartz/plugins/parsers/obsidian/highlights.ts | 12 +- quartz/plugins/parsers/obsidian/index.ts | 1 + quartz/plugins/parsers/obsidian/mermaid.ts | 78 +++++++++ quartz/plugins/parsers/obsidian/wikilinks.ts | 148 +++++++++--------- quartz/plugins/transformers/markdown.ts | 5 + quartz/plugins/types.ts | 4 +- 10 files changed, 204 insertions(+), 118 deletions(-) diff --git a/quartz/plugins/parsers/custom/default.ts b/quartz/plugins/parsers/custom/default.ts index 6a05b6bbae652..00882bb412518 100644 --- a/quartz/plugins/parsers/custom/default.ts +++ b/quartz/plugins/parsers/custom/default.ts @@ -24,10 +24,8 @@ export const CustomDefault: QuartzParserPlugin> = (userOpts) => }, markdownPlugins(_ctx) { const plug: Pluggable = (tree: Root, _file) => { - if (opts.enabled) { - const replacements: [RegExp, string | ReplaceFunction][] = [] - mdastFindReplace(tree, replacements) - } + const replacements: [RegExp, string | ReplaceFunction][] = [] + mdastFindReplace(tree, replacements) } return plug }, @@ -35,9 +33,9 @@ export const CustomDefault: QuartzParserPlugin> = (userOpts) => const plug: Pluggable = () => {} return plug }, - externalResources(_ctx) { - const js = [] as JSResource[] - return { js } + externalResources() { + const js = {} as JSResource + return js }, } } diff --git a/quartz/plugins/parsers/obsidian/arrows.ts b/quartz/plugins/parsers/obsidian/arrows.ts index 93cd4866878c6..f5e42a33f3018 100644 --- a/quartz/plugins/parsers/obsidian/arrows.ts +++ b/quartz/plugins/parsers/obsidian/arrows.ts @@ -31,8 +31,10 @@ export const ObsidianArrow: QuartzParserPlugin> = (userOpts) => return { name: "ObsidianArrow", textTransform(_ctx, src: string | Buffer) { - if (src instanceof Buffer) { - src = src.toString() + if (opts.enabled) { + if (src instanceof Buffer) { + src = src.toString() + } } return src }, @@ -60,9 +62,9 @@ export const ObsidianArrow: QuartzParserPlugin> = (userOpts) => const plug: Pluggable = () => {} return plug }, - externalResources(_ctx) { - const js = [] as JSResource[] - return { js } + externalResources() { + const js = {} as JSResource + return js }, } } diff --git a/quartz/plugins/parsers/obsidian/callouts.ts b/quartz/plugins/parsers/obsidian/callouts.ts index 3b80ec729320a..3f80973f96999 100644 --- a/quartz/plugins/parsers/obsidian/callouts.ts +++ b/quartz/plugins/parsers/obsidian/callouts.ts @@ -68,15 +68,16 @@ export const ObsidianCallouts: QuartzParserPlugin> = (userOpts) return { name: "ObsidianCallouts", textTransform(_ctx, src: string | Buffer) { - if (src instanceof Buffer) { - src = src.toString() + if (opts.enabled) { + if (src instanceof Buffer) { + src = src.toString() + } + src = src.replace(calloutLineRegex, (value) => { + // force newline after title of callout + return value + "\n> " + }) } - src = src.replace(calloutLineRegex, (value) => { - // force newline after title of callout - return value + "\n> " - }) - return src }, markdownPlugins(_ctx) { @@ -190,14 +191,16 @@ export const ObsidianCallouts: QuartzParserPlugin> = (userOpts) const plug: Pluggable = () => {} return plug }, - externalResources(_ctx) { - const js = [] as JSResource[] - js.push({ - script: calloutScript, - loadTime: "afterDOMReady", - contentType: "inline", - }) - return { js } + externalResources() { + if (opts.enabled) { + const js: JSResource = { + script: calloutScript, + loadTime: "afterDOMReady", + contentType: "inline", + } + return js + } + return {} as JSResource }, } } diff --git a/quartz/plugins/parsers/obsidian/comments.ts b/quartz/plugins/parsers/obsidian/comments.ts index 235c75baf5be4..b432966d4634f 100644 --- a/quartz/plugins/parsers/obsidian/comments.ts +++ b/quartz/plugins/parsers/obsidian/comments.ts @@ -19,12 +19,13 @@ export const ObsidianComments: QuartzParserPlugin> = (userOpts) return { name: "ObsidianComments", textTransform(_ctx, src: string | Buffer) { - // do comments at text level - if (src instanceof Buffer) { - src = src.toString() - } + if (opts.enabled) { + if (src instanceof Buffer) { + src = src.toString() + } - src = src.replace(commentRegex, "") + src = src.replace(commentRegex, "") + } return src }, @@ -41,9 +42,9 @@ export const ObsidianComments: QuartzParserPlugin> = (userOpts) const plug: Pluggable = () => {} return plug }, - externalResources(_ctx) { - const js = [] as JSResource[] - return { js } + externalResources() { + const js = {} as JSResource + return js }, } } diff --git a/quartz/plugins/parsers/obsidian/highlights.ts b/quartz/plugins/parsers/obsidian/highlights.ts index 774aab1a2f8ce..3befeed7b2ad5 100644 --- a/quartz/plugins/parsers/obsidian/highlights.ts +++ b/quartz/plugins/parsers/obsidian/highlights.ts @@ -19,8 +19,10 @@ export const ObsidianHighlights: QuartzParserPlugin> = (userOpt return { name: "ObsidianHighlights", textTransform(_ctx, src: string | Buffer) { - if (src instanceof Buffer) { - src = src.toString() + if (opts.enabled) { + if (src instanceof Buffer) { + src = src.toString() + } } return src }, @@ -47,9 +49,9 @@ export const ObsidianHighlights: QuartzParserPlugin> = (userOpt const plug: Pluggable = () => {} return plug }, - externalResources(_ctx) { - const js = [] as JSResource[] - return { js } + externalResources() { + const js = {} as JSResource + return js }, } } diff --git a/quartz/plugins/parsers/obsidian/index.ts b/quartz/plugins/parsers/obsidian/index.ts index ca865e2da32a4..33378d42a9bf2 100644 --- a/quartz/plugins/parsers/obsidian/index.ts +++ b/quartz/plugins/parsers/obsidian/index.ts @@ -2,4 +2,5 @@ export { ObsidianArrow } from "./arrows" export { ObsidianCallouts } from "./callouts" export { ObsidianComments } from "./comments" export { ObsidianHighlights } from "./highlights" +export { ObsidianMermaid } from "./mermaid" export { ObsidianWikilinks } from "./wikilinks" diff --git a/quartz/plugins/parsers/obsidian/mermaid.ts b/quartz/plugins/parsers/obsidian/mermaid.ts index e69de29bb2d1d..e338386ac79b3 100644 --- a/quartz/plugins/parsers/obsidian/mermaid.ts +++ b/quartz/plugins/parsers/obsidian/mermaid.ts @@ -0,0 +1,78 @@ +import { QuartzParserPlugin } from "../../types" +import { JSResource } from "../../../util/resources" +import { visit } from "unist-util-visit" +import { Root, Code } from "mdast" +import { Pluggable } from "unified" + +interface Options { + enabled: Boolean +} + +const defaultOptions: Options = { + enabled: true, +} + +export const ObsidianMermaid: QuartzParserPlugin> = (userOpts) => { + const opts: Options = { ...defaultOptions, ...userOpts } + return { + name: "ObsidianMermaid", + textTransform(_ctx, src: string | Buffer) { + if (opts.enabled) { + if (src instanceof Buffer) { + src = src.toString() + } + } + return src + }, + markdownPlugins(_ctx) { + const plug: Pluggable = (tree: Root, _file) => { + if (opts.enabled) { + visit(tree, "code", (node: Code) => { + if (node.lang === "mermaid") { + node.data = { + hProperties: { + className: ["mermaid"], + }, + } + } + }) + } + } + return plug + }, + htmlPlugins(_ctx) { + const plug: Pluggable = () => {} + return plug + }, + externalResources() { + if (opts.enabled) { + const js: JSResource = { + script: ` + let mermaidImport = undefined + document.addEventListener('nav', async () => { + if (document.querySelector("code.mermaid")) { + mermaidImport ||= await import('https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.7.0/mermaid.esm.min.mjs') + const mermaid = mermaidImport.default + const darkMode = document.documentElement.getAttribute('saved-theme') === 'dark' + mermaid.initialize({ + startOnLoad: false, + securityLevel: 'loose', + theme: darkMode ? 'dark' : 'default' + }) + + await mermaid.run({ + querySelector: '.mermaid' + }) + } + }); + `, + loadTime: "afterDOMReady", + moduleType: "module", + contentType: "inline", + } + return js + } + return {} as JSResource + }, + } +} diff --git a/quartz/plugins/parsers/obsidian/wikilinks.ts b/quartz/plugins/parsers/obsidian/wikilinks.ts index f6b427ac16270..b46f716c0fb73 100644 --- a/quartz/plugins/parsers/obsidian/wikilinks.ts +++ b/quartz/plugins/parsers/obsidian/wikilinks.ts @@ -80,83 +80,79 @@ export const ObsidianWikilinks: QuartzParserPlugin> = (userOpts }, markdownPlugins(_ctx) { const plug: Pluggable = (tree: Root, path) => { - if (opts.enabled) { - const replacements: [RegExp, string | ReplaceFunction][] = [] - replacements.push([ - wikilinkRegex, - (value: string, ...capture: string[]) => { - let [rawFp, rawHeader, rawAlias] = capture - const fp = rawFp?.trim() ?? "" - const anchor = rawHeader?.trim() ?? "" - const alias = rawAlias?.slice(1).trim() - - // embed cases - if (value.startsWith("!")) { - const ext: string = path.extname(fp).toLowerCase() - const url = slugifyFilePath(fp as FilePath) - if ([".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg", ".webp"].includes(ext)) { - const match = wikilinkImageEmbedRegex.exec(alias ?? "") - const alt = match?.groups?.alt ?? "" - const width = match?.groups?.width ?? "auto" - const height = match?.groups?.height ?? "auto" - return { - type: "image", - url, - data: { - hProperties: { - width, - height, - alt, - }, + const replacements: [RegExp, string | ReplaceFunction][] = [] + replacements.push([ + wikilinkRegex, + (value: string, ...capture: string[]) => { + let [rawFp, rawHeader, rawAlias] = capture + const fp = rawFp?.trim() ?? "" + const anchor = rawHeader?.trim() ?? "" + const alias = rawAlias?.slice(1).trim() + + // embed cases + if (value.startsWith("!")) { + const ext: string = path.extname(fp).toLowerCase() + const url = slugifyFilePath(fp as FilePath) + if ([".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg", ".webp"].includes(ext)) { + const match = wikilinkImageEmbedRegex.exec(alias ?? "") + const alt = match?.groups?.alt ?? "" + const width = match?.groups?.width ?? "auto" + const height = match?.groups?.height ?? "auto" + return { + type: "image", + url, + data: { + hProperties: { + width, + height, + alt, }, - } - } else if ([".mp4", ".webm", ".ogv", ".mov", ".mkv"].includes(ext)) { - return { - type: "html", - value: ``, - } - } else if ( - [".mp3", ".webm", ".wav", ".m4a", ".ogg", ".3gp", ".flac"].includes(ext) - ) { - return { - type: "html", - value: ``, - } - } else if ([".pdf"].includes(ext)) { - return { - type: "html", - value: ``, - } - } else { - const block = anchor - return { - type: "html", - data: { hProperties: { transclude: true } }, - value: `
Transclude of ${url}${block}
`, - } + }, + } + } else if ([".mp4", ".webm", ".ogv", ".mov", ".mkv"].includes(ext)) { + return { + type: "html", + value: ``, + } + } else if ([".mp3", ".webm", ".wav", ".m4a", ".ogg", ".3gp", ".flac"].includes(ext)) { + return { + type: "html", + value: ``, + } + } else if ([".pdf"].includes(ext)) { + return { + type: "html", + value: ``, + } + } else { + const block = anchor + return { + type: "html", + data: { hProperties: { transclude: true } }, + value: `
Transclude of ${url}${block}
`, } - - // otherwise, fall through to regular link } - // internal link - const url = fp + anchor - return { - type: "link", - url, - children: [ - { - type: "text", - value: alias ?? fp, - }, - ], - } - }, - ]) - mdastFindReplace(tree, replacements) - } + // otherwise, fall through to regular link + } + + // internal link + const url = fp + anchor + return { + type: "link", + url, + children: [ + { + type: "text", + value: alias ?? fp, + }, + ], + } + }, + ]) + mdastFindReplace(tree, replacements) } return plug }, @@ -164,9 +160,9 @@ export const ObsidianWikilinks: QuartzParserPlugin> = (userOpts const plug: Pluggable = () => {} return plug }, - externalResources(_ctx) { - const js = [] as JSResource[] - return { js } + externalResources() { + const js = {} as JSResource + return js }, } } diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index fdfecfa3c74ea..88aba4b68c14a 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -40,6 +40,7 @@ import { ObsidianCallouts, ObsidianComments, ObsidianHighlights, + ObsidianMermaid, ObsidianWikilinks, } from "../parsers/obsidian" @@ -206,6 +207,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin string | Buffer markdownPlugins: (ctx: BuildCtx) => Pluggable htmlPlugins: (ctx: BuildCtx) => Pluggable - externalResources: (ctx: BuildCtx) => Partial + externalResources: () => JSResource | string } From dd062107c433c587f8ff2565d13e859cb4d3a737 Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 13:43:43 +0000 Subject: [PATCH 22/38] Obsidian Parser (Checkboxes) --- quartz/plugins/parsers/custom/default.ts | 2 +- quartz/plugins/parsers/obsidian/arrows.ts | 2 +- quartz/plugins/parsers/obsidian/callouts.ts | 2 +- quartz/plugins/parsers/obsidian/checkboxes.ts | 67 +++++++++++++++++++ quartz/plugins/parsers/obsidian/highlights.ts | 2 +- quartz/plugins/parsers/obsidian/index.ts | 1 + quartz/plugins/parsers/obsidian/mermaid.ts | 2 +- quartz/plugins/parsers/obsidian/wikilinks.ts | 2 +- quartz/plugins/transformers/markdown.ts | 6 +- quartz/plugins/types.ts | 2 +- 10 files changed, 80 insertions(+), 8 deletions(-) diff --git a/quartz/plugins/parsers/custom/default.ts b/quartz/plugins/parsers/custom/default.ts index 00882bb412518..ab35814802679 100644 --- a/quartz/plugins/parsers/custom/default.ts +++ b/quartz/plugins/parsers/custom/default.ts @@ -29,7 +29,7 @@ export const CustomDefault: QuartzParserPlugin> = (userOpts) => } return plug }, - htmlPlugins(_ctx) { + htmlPlugins() { const plug: Pluggable = () => {} return plug }, diff --git a/quartz/plugins/parsers/obsidian/arrows.ts b/quartz/plugins/parsers/obsidian/arrows.ts index f5e42a33f3018..c0687571f0fd6 100644 --- a/quartz/plugins/parsers/obsidian/arrows.ts +++ b/quartz/plugins/parsers/obsidian/arrows.ts @@ -58,7 +58,7 @@ export const ObsidianArrow: QuartzParserPlugin> = (userOpts) => } return plug }, - htmlPlugins(_ctx) { + htmlPlugins() { const plug: Pluggable = () => {} return plug }, diff --git a/quartz/plugins/parsers/obsidian/callouts.ts b/quartz/plugins/parsers/obsidian/callouts.ts index 3f80973f96999..f8dc7647dd960 100644 --- a/quartz/plugins/parsers/obsidian/callouts.ts +++ b/quartz/plugins/parsers/obsidian/callouts.ts @@ -187,7 +187,7 @@ export const ObsidianCallouts: QuartzParserPlugin> = (userOpts) } return plug }, - htmlPlugins(_ctx) { + htmlPlugins() { const plug: Pluggable = () => {} return plug }, diff --git a/quartz/plugins/parsers/obsidian/checkboxes.ts b/quartz/plugins/parsers/obsidian/checkboxes.ts index e69de29bb2d1d..2fdf07292a689 100644 --- a/quartz/plugins/parsers/obsidian/checkboxes.ts +++ b/quartz/plugins/parsers/obsidian/checkboxes.ts @@ -0,0 +1,67 @@ +import { QuartzParserPlugin } from "../../types" +import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" +// @ts-ignore +import checkboxScript from "../../components/scripts/checkbox.inline.ts" +import { visit } from "unist-util-visit" +import { JSResource } from "../../../util/resources" +import { Element, Literal, Root as HtmlRoot } from "hast" +import { Root } from "mdast" +import { Pluggable } from "unified" + +interface Options { + enabled: Boolean +} + +const defaultOptions: Options = { + enabled: true, +} + +export const ObsidianCheckboxes: QuartzParserPlugin> = (userOpts) => { + const opts: Options = { ...defaultOptions, ...userOpts } + return { + name: "ObsidianCheckboxes", + textTransform(_ctx, src: string | Buffer) { + if (src instanceof Buffer) { + src = src.toString() + } + return src + }, + markdownPlugins(_ctx) { + const plug: Pluggable = (tree: Root, _file) => { + const replacements: [RegExp, string | ReplaceFunction][] = [] + mdastFindReplace(tree, replacements) + } + return plug + }, + htmlPlugins() { + if (opts.enabled) { + const plug: Pluggable = (tree: HtmlRoot, _file) => { + visit(tree, "element", (node) => { + if (node.tagName === "input" && node.properties.type === "checkbox") { + const isChecked = node.properties?.checked ?? false + node.properties = { + type: "checkbox", + disabled: false, + checked: isChecked, + class: "checkbox-toggle", + } + } + }) + } + return plug + } + return {} as Pluggable + }, + externalResources() { + if (opts.enabled) { + const js: JSResource = { + script: checkboxScript, + loadTime: "afterDOMReady", + contentType: "inline", + } + return js + } + return {} as JSResource + }, + } +} diff --git a/quartz/plugins/parsers/obsidian/highlights.ts b/quartz/plugins/parsers/obsidian/highlights.ts index 3befeed7b2ad5..6a1fd8a7b13e8 100644 --- a/quartz/plugins/parsers/obsidian/highlights.ts +++ b/quartz/plugins/parsers/obsidian/highlights.ts @@ -45,7 +45,7 @@ export const ObsidianHighlights: QuartzParserPlugin> = (userOpt } return plug }, - htmlPlugins(_ctx) { + htmlPlugins() { const plug: Pluggable = () => {} return plug }, diff --git a/quartz/plugins/parsers/obsidian/index.ts b/quartz/plugins/parsers/obsidian/index.ts index 33378d42a9bf2..baf0fd6273be5 100644 --- a/quartz/plugins/parsers/obsidian/index.ts +++ b/quartz/plugins/parsers/obsidian/index.ts @@ -1,5 +1,6 @@ export { ObsidianArrow } from "./arrows" export { ObsidianCallouts } from "./callouts" +export { ObsidianCheckboxes } from "./checkboxes" export { ObsidianComments } from "./comments" export { ObsidianHighlights } from "./highlights" export { ObsidianMermaid } from "./mermaid" diff --git a/quartz/plugins/parsers/obsidian/mermaid.ts b/quartz/plugins/parsers/obsidian/mermaid.ts index e338386ac79b3..6eaffee94c97c 100644 --- a/quartz/plugins/parsers/obsidian/mermaid.ts +++ b/quartz/plugins/parsers/obsidian/mermaid.ts @@ -24,7 +24,7 @@ export const ObsidianMermaid: QuartzParserPlugin> = (userOpts) } return src }, - markdownPlugins(_ctx) { + markdownPlugins() { const plug: Pluggable = (tree: Root, _file) => { if (opts.enabled) { visit(tree, "code", (node: Code) => { diff --git a/quartz/plugins/parsers/obsidian/wikilinks.ts b/quartz/plugins/parsers/obsidian/wikilinks.ts index b46f716c0fb73..dd2ab7b48e92c 100644 --- a/quartz/plugins/parsers/obsidian/wikilinks.ts +++ b/quartz/plugins/parsers/obsidian/wikilinks.ts @@ -156,7 +156,7 @@ export const ObsidianWikilinks: QuartzParserPlugin> = (userOpts } return plug }, - htmlPlugins(_ctx) { + htmlPlugins() { const plug: Pluggable = () => {} return plug }, diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index 88aba4b68c14a..9ae24fa137d09 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -38,6 +38,7 @@ import rehypeAutolinkHeadings from "rehype-autolink-headings" import { ObsidianArrow, ObsidianCallouts, + ObsidianCheckboxes, ObsidianComments, ObsidianHighlights, ObsidianMermaid, @@ -214,13 +215,16 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin {}) + plugins.push(ObsidianCheckboxes({ enabled: opts.enableCheckbox }).htmlPlugins()) return plugins }, externalResources() { const js: JSResource[] = [] + js.push( + ObsidianCheckboxes({ enabled: opts.enableCheckbox }).externalResources() as JSResource, + ) js.push(ObsidianCallouts({ enabled: opts.callouts }).externalResources() as JSResource) js.push(ObsidianMermaid({ enabled: opts.mermaid }).externalResources() as JSResource) diff --git a/quartz/plugins/types.ts b/quartz/plugins/types.ts index f89bd1d966231..c3e56c5dd3a22 100644 --- a/quartz/plugins/types.ts +++ b/quartz/plugins/types.ts @@ -54,6 +54,6 @@ export type QuartzParserPluginInstance = { name: string textTransform: (ctx: BuildCtx, src: string | Buffer) => string | Buffer markdownPlugins: (ctx: BuildCtx) => Pluggable - htmlPlugins: (ctx: BuildCtx) => Pluggable + htmlPlugins: () => Pluggable externalResources: () => JSResource | string } From b07aaeab751264a3a569412b11148e834ec72e8a Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 13:45:48 +0000 Subject: [PATCH 23/38] Parser skeleton --- quartz/plugins/transformers/markdown.ts | 90 +++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index 9ae24fa137d09..2dbc68534466a 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -153,6 +153,24 @@ export const CommonMarkFlavoredMarkdown: QuartzTransformerPlugin const opts = { ...defaultRoamOptions, ...userOpts } return { name: "RoamFlavoredMarkdown", + textTransform(ctx, src) { + return src + }, + markdownPlugins(ctx) { + const plugins: PluggableList = [] + + return plugins + }, + htmlPlugins() { + const plugins: PluggableList = [rehypeRaw] + + return plugins + }, + externalResources() { + const js: JSResource[] = [] + + return { js } + }, } } From 3e237cded4b8e7d515824a486dd2750cd424a568 Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 13:48:30 +0000 Subject: [PATCH 24/38] Removed unused --- quartz/plugins/parsers/obsidian/comments.ts | 2 +- quartz/plugins/parsers/obsidian/mermaid.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/quartz/plugins/parsers/obsidian/comments.ts b/quartz/plugins/parsers/obsidian/comments.ts index b432966d4634f..902874b71b097 100644 --- a/quartz/plugins/parsers/obsidian/comments.ts +++ b/quartz/plugins/parsers/obsidian/comments.ts @@ -38,7 +38,7 @@ export const ObsidianComments: QuartzParserPlugin> = (userOpts) } return plug }, - htmlPlugins(_ctx) { + htmlPlugins() { const plug: Pluggable = () => {} return plug }, diff --git a/quartz/plugins/parsers/obsidian/mermaid.ts b/quartz/plugins/parsers/obsidian/mermaid.ts index 6eaffee94c97c..343c962c29f42 100644 --- a/quartz/plugins/parsers/obsidian/mermaid.ts +++ b/quartz/plugins/parsers/obsidian/mermaid.ts @@ -40,7 +40,7 @@ export const ObsidianMermaid: QuartzParserPlugin> = (userOpts) } return plug }, - htmlPlugins(_ctx) { + htmlPlugins() { const plug: Pluggable = () => {} return plug }, From 4878c2434e12e30c39726f32f640ecdbd8b91082 Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 13:51:07 +0000 Subject: [PATCH 25/38] vfile definition --- quartz/plugins/transformers/markdown.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index 2dbc68534466a..c07b15ba386c5 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -340,3 +340,10 @@ export const RoamFlavoredMarkdown: QuartzTransformerPlugin }, } } + +declare module "vfile" { + interface DataMap { + blocks: Record + htmlAst: HtmlRoot + } +} From ce5813d978761011067288add3a53149c6c14596 Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 13:53:35 +0000 Subject: [PATCH 26/38] Fix inline import path --- quartz/plugins/parsers/obsidian/callouts.ts | 2 +- quartz/plugins/parsers/obsidian/checkboxes.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/quartz/plugins/parsers/obsidian/callouts.ts b/quartz/plugins/parsers/obsidian/callouts.ts index f8dc7647dd960..e3898270bb499 100644 --- a/quartz/plugins/parsers/obsidian/callouts.ts +++ b/quartz/plugins/parsers/obsidian/callouts.ts @@ -4,7 +4,7 @@ import { Root, BlockContent, DefinitionContent, Paragraph, Html } from "mdast" import { visit } from "unist-util-visit" import { Pluggable } from "unified" // @ts-ignore -import calloutScript from "../../components/scripts/callout.inline.ts" +import calloutScript from "../../../components/scripts/callout.inline.ts" import { PhrasingContent } from "mdast-util-find-and-replace/lib" import { capitalize } from "../../../util/lang" import { toHast } from "mdast-util-to-hast" diff --git a/quartz/plugins/parsers/obsidian/checkboxes.ts b/quartz/plugins/parsers/obsidian/checkboxes.ts index 2fdf07292a689..e184f527a8bee 100644 --- a/quartz/plugins/parsers/obsidian/checkboxes.ts +++ b/quartz/plugins/parsers/obsidian/checkboxes.ts @@ -1,7 +1,7 @@ import { QuartzParserPlugin } from "../../types" import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" // @ts-ignore -import checkboxScript from "../../components/scripts/checkbox.inline.ts" +import checkboxScript from "../../../components/scripts/checkbox.inline.ts" import { visit } from "unist-util-visit" import { JSResource } from "../../../util/resources" import { Element, Literal, Root as HtmlRoot } from "hast" From e7965b1467e57e6da24467c5c1be890706d6b74b Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 14:04:36 +0000 Subject: [PATCH 27/38] remove duplicate rehypeRaw --- quartz/plugins/transformers/markdown.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index c07b15ba386c5..d350537359604 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -162,7 +162,7 @@ export const CommonMarkFlavoredMarkdown: QuartzTransformerPlugin return plugins }, htmlPlugins() { - const plugins: PluggableList = [rehypeRaw] + const plugins: PluggableList = [] return plugins }, From dfd6abe6a67acce040bd3c965685f92d13794b6f Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 14:14:56 +0000 Subject: [PATCH 28/38] Separated Parsers and Plugins --- quartz.config.ts | 1 - quartz/plugins/parsers/custom/default.ts | 4 ++-- quartz/plugins/parsers/obsidian/arrows.ts | 4 ++-- quartz/plugins/parsers/obsidian/callouts.ts | 4 ++-- quartz/plugins/parsers/obsidian/checkboxes.ts | 4 ++-- quartz/plugins/parsers/obsidian/comments.ts | 4 ++-- quartz/plugins/parsers/obsidian/highlights.ts | 4 ++-- quartz/plugins/parsers/obsidian/mermaid.ts | 4 ++-- quartz/plugins/parsers/obsidian/wikilinks.ts | 4 ++-- quartz/plugins/types.ts | 11 +++++++---- 10 files changed, 23 insertions(+), 21 deletions(-) diff --git a/quartz.config.ts b/quartz.config.ts index 0eeca63ab02dc..b6abbb2d3cc29 100644 --- a/quartz.config.ts +++ b/quartz.config.ts @@ -87,7 +87,6 @@ const config: QuartzConfig = { Plugin.Static(), Plugin.NotFoundPage(), ], - parsers: [], }, } diff --git a/quartz/plugins/parsers/custom/default.ts b/quartz/plugins/parsers/custom/default.ts index ab35814802679..c7e2adfb0beb2 100644 --- a/quartz/plugins/parsers/custom/default.ts +++ b/quartz/plugins/parsers/custom/default.ts @@ -1,4 +1,4 @@ -import { QuartzParserPlugin } from "../../types" +import { QuartzParser } from "../../types" import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" import { JSResource } from "../../../util/resources" import { Root } from "mdast" @@ -12,7 +12,7 @@ const defaultOptions: Options = { enabled: true, } -export const CustomDefault: QuartzParserPlugin> = (userOpts) => { +export const CustomDefault: QuartzParser> = (userOpts) => { const opts: Options = { ...defaultOptions, ...userOpts } return { name: "CustomDefault", diff --git a/quartz/plugins/parsers/obsidian/arrows.ts b/quartz/plugins/parsers/obsidian/arrows.ts index c0687571f0fd6..3bd12d74bac71 100644 --- a/quartz/plugins/parsers/obsidian/arrows.ts +++ b/quartz/plugins/parsers/obsidian/arrows.ts @@ -1,4 +1,4 @@ -import { QuartzParserPlugin } from "../../types" +import { QuartzParser } from "../../types" import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" import { JSResource } from "../../../util/resources" import { SKIP } from "unist-util-visit" @@ -26,7 +26,7 @@ const arrowMapping: Record = { const arrowRegex = new RegExp(/(-{1,2}>|={1,2}>|<-{1,2}|<={1,2})/g) -export const ObsidianArrow: QuartzParserPlugin> = (userOpts) => { +export const ObsidianArrow: QuartzParser> = (userOpts) => { const opts: Options = { ...defaultOptions, ...userOpts } return { name: "ObsidianArrow", diff --git a/quartz/plugins/parsers/obsidian/callouts.ts b/quartz/plugins/parsers/obsidian/callouts.ts index e3898270bb499..41a45ed999756 100644 --- a/quartz/plugins/parsers/obsidian/callouts.ts +++ b/quartz/plugins/parsers/obsidian/callouts.ts @@ -1,4 +1,4 @@ -import { QuartzParserPlugin } from "../../types" +import { QuartzParser } from "../../types" import { JSResource } from "../../../util/resources" import { Root, BlockContent, DefinitionContent, Paragraph, Html } from "mdast" import { visit } from "unist-util-visit" @@ -63,7 +63,7 @@ const mdastToHtml = (ast: PhrasingContent | Paragraph) => { return toHtml(hast, { allowDangerousHtml: true }) } -export const ObsidianCallouts: QuartzParserPlugin> = (userOpts) => { +export const ObsidianCallouts: QuartzParser> = (userOpts) => { const opts: Options = { ...defaultOptions, ...userOpts } return { name: "ObsidianCallouts", diff --git a/quartz/plugins/parsers/obsidian/checkboxes.ts b/quartz/plugins/parsers/obsidian/checkboxes.ts index e184f527a8bee..3dcb4d3fa0321 100644 --- a/quartz/plugins/parsers/obsidian/checkboxes.ts +++ b/quartz/plugins/parsers/obsidian/checkboxes.ts @@ -1,4 +1,4 @@ -import { QuartzParserPlugin } from "../../types" +import { QuartzParser } from "../../types" import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" // @ts-ignore import checkboxScript from "../../../components/scripts/checkbox.inline.ts" @@ -16,7 +16,7 @@ const defaultOptions: Options = { enabled: true, } -export const ObsidianCheckboxes: QuartzParserPlugin> = (userOpts) => { +export const ObsidianCheckboxes: QuartzParser> = (userOpts) => { const opts: Options = { ...defaultOptions, ...userOpts } return { name: "ObsidianCheckboxes", diff --git a/quartz/plugins/parsers/obsidian/comments.ts b/quartz/plugins/parsers/obsidian/comments.ts index 902874b71b097..580eeed9e9048 100644 --- a/quartz/plugins/parsers/obsidian/comments.ts +++ b/quartz/plugins/parsers/obsidian/comments.ts @@ -1,4 +1,4 @@ -import { QuartzParserPlugin } from "../../types" +import { QuartzParser } from "../../types" import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" import { JSResource } from "../../../util/resources" import { Root } from "mdast" @@ -14,7 +14,7 @@ const defaultOptions: Options = { const commentRegex = new RegExp(/%%[\s\S]*?%%/g) -export const ObsidianComments: QuartzParserPlugin> = (userOpts) => { +export const ObsidianComments: QuartzParser> = (userOpts) => { const opts: Options = { ...defaultOptions, ...userOpts } return { name: "ObsidianComments", diff --git a/quartz/plugins/parsers/obsidian/highlights.ts b/quartz/plugins/parsers/obsidian/highlights.ts index 6a1fd8a7b13e8..09abe4fec0ea5 100644 --- a/quartz/plugins/parsers/obsidian/highlights.ts +++ b/quartz/plugins/parsers/obsidian/highlights.ts @@ -1,4 +1,4 @@ -import { QuartzParserPlugin } from "../../types" +import { QuartzParser } from "../../types" import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" import { JSResource } from "../../../util/resources" import { Root } from "mdast" @@ -14,7 +14,7 @@ const defaultOptions: Options = { const highlightRegex = new RegExp(/==([^=]+)==/g) -export const ObsidianHighlights: QuartzParserPlugin> = (userOpts) => { +export const ObsidianHighlights: QuartzParser> = (userOpts) => { const opts: Options = { ...defaultOptions, ...userOpts } return { name: "ObsidianHighlights", diff --git a/quartz/plugins/parsers/obsidian/mermaid.ts b/quartz/plugins/parsers/obsidian/mermaid.ts index 343c962c29f42..c868b8b7b98bd 100644 --- a/quartz/plugins/parsers/obsidian/mermaid.ts +++ b/quartz/plugins/parsers/obsidian/mermaid.ts @@ -1,4 +1,4 @@ -import { QuartzParserPlugin } from "../../types" +import { QuartzParser } from "../../types" import { JSResource } from "../../../util/resources" import { visit } from "unist-util-visit" import { Root, Code } from "mdast" @@ -12,7 +12,7 @@ const defaultOptions: Options = { enabled: true, } -export const ObsidianMermaid: QuartzParserPlugin> = (userOpts) => { +export const ObsidianMermaid: QuartzParser> = (userOpts) => { const opts: Options = { ...defaultOptions, ...userOpts } return { name: "ObsidianMermaid", diff --git a/quartz/plugins/parsers/obsidian/wikilinks.ts b/quartz/plugins/parsers/obsidian/wikilinks.ts index dd2ab7b48e92c..51eb53574a2d3 100644 --- a/quartz/plugins/parsers/obsidian/wikilinks.ts +++ b/quartz/plugins/parsers/obsidian/wikilinks.ts @@ -1,4 +1,4 @@ -import { QuartzParserPlugin } from "../../types" +import { QuartzParser } from "../../types" import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" import { FilePath, splitAnchor, slugifyFilePath } from "../../../util/path" import { JSResource } from "../../../util/resources" @@ -36,7 +36,7 @@ const wikilinkImageEmbedRegex = new RegExp( /^(?(?!^\d*x?\d*$).*?)?(\|?\s*?(?\d+)(x(?\d+))?)?$/, ) -export const ObsidianWikilinks: QuartzParserPlugin> = (userOpts) => { +export const ObsidianWikilinks: QuartzParser> = (userOpts) => { const opts: Options = { ...defaultOptions, ...userOpts } return { name: "ObsidianWikilinks", diff --git a/quartz/plugins/types.ts b/quartz/plugins/types.ts index c3e56c5dd3a22..4eb268939b4e5 100644 --- a/quartz/plugins/types.ts +++ b/quartz/plugins/types.ts @@ -10,7 +10,10 @@ export interface PluginTypes { transformers: QuartzTransformerPluginInstance[] filters: QuartzFilterPluginInstance[] emitters: QuartzEmitterPluginInstance[] - parsers: QuartzParserPluginInstance[] +} + +export interface ParserTypes { + parsers: QuartzParserInstance[] } type OptionType = object | undefined @@ -47,10 +50,10 @@ export type QuartzEmitterPluginInstance = { ): Promise> } -export type QuartzParserPlugin = ( +export type QuartzParser = ( opts?: Options, -) => QuartzParserPluginInstance -export type QuartzParserPluginInstance = { +) => QuartzParserInstance +export type QuartzParserInstance = { name: string textTransform: (ctx: BuildCtx, src: string | Buffer) => string | Buffer markdownPlugins: (ctx: BuildCtx) => Pluggable From 97613c2c099d1d914783442d6199ee2ae3afa522 Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 14:45:18 +0000 Subject: [PATCH 29/38] Added GitHub parsing --- quartz/plugins/parsers/github/index.ts | 2 + quartz/plugins/parsers/github/linkheadings.ts | 87 +++++++++++++++++++ quartz/plugins/parsers/github/smartypants.ts | 40 +++++++++ quartz/plugins/transformers/markdown.ts | 10 ++- quartz/plugins/types.ts | 2 +- 5 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 quartz/plugins/parsers/github/smartypants.ts diff --git a/quartz/plugins/parsers/github/index.ts b/quartz/plugins/parsers/github/index.ts index e69de29bb2d1d..edf2c56fde437 100644 --- a/quartz/plugins/parsers/github/index.ts +++ b/quartz/plugins/parsers/github/index.ts @@ -0,0 +1,2 @@ +export { GitHubSmartypants } from "./smartypants" +export { GitHubLinkheadings } from "./linkheadings" diff --git a/quartz/plugins/parsers/github/linkheadings.ts b/quartz/plugins/parsers/github/linkheadings.ts index e69de29bb2d1d..a802e24e12b6e 100644 --- a/quartz/plugins/parsers/github/linkheadings.ts +++ b/quartz/plugins/parsers/github/linkheadings.ts @@ -0,0 +1,87 @@ +import { QuartzParser } from "../../types" +import { JSResource } from "../../../util/resources" +import { Pluggable } from "unified" +import rehypeSlug from "rehype-slug" +import rehypeAutolinkHeadings from "rehype-autolink-headings" + +interface Options { + enabled: Boolean +} + +const defaultOptions: Options = { + enabled: true, +} + +export const GitHubLinkheadings: QuartzParser> = (userOpts) => { + const opts: Options = { ...defaultOptions, ...userOpts } + return { + name: "GitHubLinkheadings", + textTransform(_ctx, src: string | Buffer) { + if (src instanceof Buffer) { + src = src.toString() + } + return src + }, + markdownPlugins(_ctx) { + const plug: Pluggable = () => {} + return plug + }, + htmlPlugins() { + if (opts.enabled) { + return [ + rehypeSlug, + [ + rehypeAutolinkHeadings, + { + behavior: "append", + properties: { + role: "anchor", + ariaHidden: true, + tabIndex: -1, + "data-no-popover": true, + }, + content: { + type: "element", + tagName: "svg", + properties: { + width: 18, + height: 18, + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round", + }, + children: [ + { + type: "element", + tagName: "path", + properties: { + d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71", + }, + children: [], + }, + { + type: "element", + tagName: "path", + properties: { + d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71", + }, + children: [], + }, + ], + }, + }, + ], + ] as Pluggable[] + } else { + return [] as Pluggable[] + } + }, + externalResources() { + const js = {} as JSResource + return js + }, + } +} diff --git a/quartz/plugins/parsers/github/smartypants.ts b/quartz/plugins/parsers/github/smartypants.ts new file mode 100644 index 0000000000000..a7c38508844f2 --- /dev/null +++ b/quartz/plugins/parsers/github/smartypants.ts @@ -0,0 +1,40 @@ +import { QuartzParser } from "../../types" +import { JSResource } from "../../../util/resources" +import { Pluggable } from "unified" +import remarkGfm from "remark-gfm" +import smartypants from "remark-smartypants" + +interface Options { + enabled: Boolean +} + +const defaultOptions: Options = { + enabled: true, +} + +export const GitHubSmartypants: QuartzParser> = (userOpts) => { + const opts: Options = { ...defaultOptions, ...userOpts } + return { + name: "GitHubSmartypants", + textTransform(_ctx, src: string | Buffer) { + if (src instanceof Buffer) { + src = src.toString() + } + return src + }, + markdownPlugins(_ctx) { + if (opts.enabled) { + return [remarkGfm, smartypants] + } + return [remarkGfm] + }, + htmlPlugins() { + const plug: Pluggable = () => {} + return plug + }, + externalResources() { + const js = {} as JSResource + return js + }, + } +} diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index d350537359604..c40e47336eb26 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -26,7 +26,7 @@ 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" -import { PluggableList } from "unified" +import { Pluggable, PluggableList } from "unified" import { Node } from "unist" import { VFile } from "vfile" import { BuildVisitor } from "unist-util-visit" @@ -35,6 +35,8 @@ import smartypants from "remark-smartypants" import rehypeSlug from "rehype-slug" import rehypeAutolinkHeadings from "rehype-autolink-headings" +import { GitHubLinkheadings, GitHubSmartypants } from "../parsers/github" + import { ObsidianArrow, ObsidianCallouts, @@ -213,11 +215,15 @@ export const GitHubFlavoredMarkdown: QuartzTransformerPlugin string | Buffer markdownPlugins: (ctx: BuildCtx) => Pluggable - htmlPlugins: () => Pluggable + htmlPlugins: () => Pluggable | Pluggable[] externalResources: () => JSResource | string } From 318a13c4a3dea5a70aa2c98586dca911d40d9d17 Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 14:57:19 +0000 Subject: [PATCH 30/38] Removed old GitHub Parser --- quartz/plugins/transformers/gfm.ts.old | 78 -------------------------- 1 file changed, 78 deletions(-) delete mode 100644 quartz/plugins/transformers/gfm.ts.old diff --git a/quartz/plugins/transformers/gfm.ts.old b/quartz/plugins/transformers/gfm.ts.old deleted file mode 100644 index 7353fcaeab02f..0000000000000 --- a/quartz/plugins/transformers/gfm.ts.old +++ /dev/null @@ -1,78 +0,0 @@ -import remarkGfm from "remark-gfm" -import smartypants from "remark-smartypants" -import { QuartzTransformerPlugin } from "../types" -import rehypeSlug from "rehype-slug" -import rehypeAutolinkHeadings from "rehype-autolink-headings" - -export interface Options { - enableSmartyPants: boolean - linkHeadings: boolean -} - -const defaultOptions: Options = { - enableSmartyPants: true, - linkHeadings: true, -} - -export const GitHubFlavoredMarkdown_OLD: QuartzTransformerPlugin> = (userOpts) => { - const opts = { ...defaultOptions, ...userOpts } - return { - name: "GitHubFlavoredMarkdown", - markdownPlugins() { - return opts.enableSmartyPants ? [remarkGfm, smartypants] : [remarkGfm] - }, - htmlPlugins() { - if (opts.linkHeadings) { - return [ - rehypeSlug, - [ - rehypeAutolinkHeadings, - { - behavior: "append", - properties: { - role: "anchor", - ariaHidden: true, - tabIndex: -1, - "data-no-popover": true, - }, - content: { - type: "element", - tagName: "svg", - properties: { - width: 18, - height: 18, - viewBox: "0 0 24 24", - fill: "none", - stroke: "currentColor", - "stroke-width": "2", - "stroke-linecap": "round", - "stroke-linejoin": "round", - }, - children: [ - { - type: "element", - tagName: "path", - properties: { - d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71", - }, - children: [], - }, - { - type: "element", - tagName: "path", - properties: { - d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71", - }, - children: [], - }, - ], - }, - }, - ], - ] - } else { - return [] - } - }, - } -} From 7790db45c4ff7cae2a63542fca304c45156022b2 Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Thu, 19 Sep 2024 15:09:56 +0000 Subject: [PATCH 31/38] Obsidian Parser (Tags) --- quartz/plugins/parsers/obsidian/index.ts | 1 + quartz/plugins/parsers/obsidian/tags.ts | 82 ++++++++++++++++++++++++ quartz/plugins/transformers/markdown.ts | 2 + 3 files changed, 85 insertions(+) diff --git a/quartz/plugins/parsers/obsidian/index.ts b/quartz/plugins/parsers/obsidian/index.ts index baf0fd6273be5..4f1555b84b861 100644 --- a/quartz/plugins/parsers/obsidian/index.ts +++ b/quartz/plugins/parsers/obsidian/index.ts @@ -4,4 +4,5 @@ export { ObsidianCheckboxes } from "./checkboxes" export { ObsidianComments } from "./comments" export { ObsidianHighlights } from "./highlights" export { ObsidianMermaid } from "./mermaid" +export { ObsidianTags } from "./tags" export { ObsidianWikilinks } from "./wikilinks" diff --git a/quartz/plugins/parsers/obsidian/tags.ts b/quartz/plugins/parsers/obsidian/tags.ts index e69de29bb2d1d..7f30d59110f45 100644 --- a/quartz/plugins/parsers/obsidian/tags.ts +++ b/quartz/plugins/parsers/obsidian/tags.ts @@ -0,0 +1,82 @@ +import { QuartzParser } from "../../types" +import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" +import { FilePath, pathToRoot, slugTag, slugifyFilePath } from "../../../util/path" +import { JSResource } from "../../../util/resources" +import { Root } from "mdast" +import { Pluggable } from "unified" + +interface Options { + enabled: Boolean +} + +const defaultOptions: Options = { + enabled: true, +} + +// (?:^| ) -> 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}\d\p{Z}])+ -> non-capturing group, non-empty string of (Unicode-aware) alpha-numeric characters and symbols, hyphens and/or underscores +// (?:\/[-_\p{L}\d\p{Z}]+)*) -> non-capturing group, matches an arbitrary number of tag strings separated by "/" +const tagRegex = new RegExp( + /(?:^| )#((?:[-_\p{L}\p{Emoji}\p{M}\d])+(?:\/[-_\p{L}\p{Emoji}\p{M}\d]+)*)/gu, +) + +export const ObsidianTags: QuartzParser> = (userOpts) => { + const opts: Options = { ...defaultOptions, ...userOpts } + return { + name: "ObsidianTags", + textTransform(_ctx, src: string | Buffer) { + if (src instanceof Buffer) { + src = src.toString() + } + return src + }, + markdownPlugins(_ctx) { + const plug: Pluggable = (tree: Root, file) => { + const base = pathToRoot(file.data.slug!) + const replacements: [RegExp, string | ReplaceFunction][] = [] + replacements.push([ + tagRegex, + (_value: string, tag: string) => { + // Check if the tag only includes numbers and slashes + if (/^[\/\d]+$/.test(tag)) { + return false + } + + tag = slugTag(tag) + if (file.data.frontmatter) { + const noteTags = file.data.frontmatter.tags ?? [] + file.data.frontmatter.tags = [...new Set([...noteTags, tag])] + } + + return { + type: "link", + url: base + `/tags/${tag}`, + data: { + hProperties: { + className: ["tag-link"], + }, + }, + children: [ + { + type: "text", + value: tag, + }, + ], + } + }, + ]) + mdastFindReplace(tree, replacements) + } + return plug + }, + htmlPlugins() { + const plug: Pluggable = () => {} + return plug + }, + externalResources() { + const js = {} as JSResource + return js + }, + } +} diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index c40e47336eb26..681169306c2f4 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -44,6 +44,7 @@ import { ObsidianComments, ObsidianHighlights, ObsidianMermaid, + ObsidianTags, ObsidianWikilinks, } from "../parsers/obsidian" @@ -267,6 +268,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin Date: Thu, 19 Sep 2024 15:54:16 +0000 Subject: [PATCH 32/38] Obsidian Parser (InHtmlEmbed) --- quartz/plugins/parsers/obsidian/arrows.ts | 5 +- quartz/plugins/parsers/obsidian/callouts.ts | 8 +-- quartz/plugins/parsers/obsidian/checkboxes.ts | 5 +- quartz/plugins/parsers/obsidian/comments.ts | 5 +- quartz/plugins/parsers/obsidian/highlights.ts | 5 +- quartz/plugins/parsers/obsidian/html.ts | 0 quartz/plugins/parsers/obsidian/mermaid.ts | 2 + quartz/plugins/parsers/obsidian/tags.ts | 5 +- quartz/plugins/parsers/obsidian/wikilinks.ts | 5 +- quartz/plugins/transformers/markdown.ts | 57 +++++++++++++++++-- 10 files changed, 80 insertions(+), 17 deletions(-) delete mode 100644 quartz/plugins/parsers/obsidian/html.ts diff --git a/quartz/plugins/parsers/obsidian/arrows.ts b/quartz/plugins/parsers/obsidian/arrows.ts index 3bd12d74bac71..db62a0c1bcf87 100644 --- a/quartz/plugins/parsers/obsidian/arrows.ts +++ b/quartz/plugins/parsers/obsidian/arrows.ts @@ -4,13 +4,16 @@ import { JSResource } from "../../../util/resources" import { SKIP } from "unist-util-visit" import { Root } from "mdast" import { Pluggable } from "unified" +import { mdastFindReplaceInHtml } from "../../transformers/markdown" interface Options { enabled: Boolean + inHtml: Boolean } const defaultOptions: Options = { enabled: true, + inHtml: false, } const arrowMapping: Record = { @@ -53,7 +56,7 @@ export const ObsidianArrow: QuartzParser> = (userOpts) => { } }, ]) - mdastFindReplace(tree, replacements) + mdastFindReplaceInHtml(tree, replacements, opts.inHtml) } } return plug diff --git a/quartz/plugins/parsers/obsidian/callouts.ts b/quartz/plugins/parsers/obsidian/callouts.ts index 41a45ed999756..d2eb513d3fe32 100644 --- a/quartz/plugins/parsers/obsidian/callouts.ts +++ b/quartz/plugins/parsers/obsidian/callouts.ts @@ -9,13 +9,16 @@ import { PhrasingContent } from "mdast-util-find-and-replace/lib" import { capitalize } from "../../../util/lang" import { toHast } from "mdast-util-to-hast" import { toHtml } from "hast-util-to-html" +import { mdastToHtml } from "../../transformers/markdown" interface Options { enabled: Boolean + inHtml: Boolean } const defaultOptions: Options = { enabled: true, + inHtml: false, } // from https://github.com/escwxyz/remark-obsidian-callout/blob/main/src/index.ts @@ -58,11 +61,6 @@ function canonicalizeCallout(calloutName: string): keyof typeof calloutMapping { return calloutMapping[normalizedCallout] ?? calloutName } -const mdastToHtml = (ast: PhrasingContent | Paragraph) => { - const hast = toHast(ast, { allowDangerousHtml: true })! - return toHtml(hast, { allowDangerousHtml: true }) -} - export const ObsidianCallouts: QuartzParser> = (userOpts) => { const opts: Options = { ...defaultOptions, ...userOpts } return { diff --git a/quartz/plugins/parsers/obsidian/checkboxes.ts b/quartz/plugins/parsers/obsidian/checkboxes.ts index 3dcb4d3fa0321..8236e3875e24f 100644 --- a/quartz/plugins/parsers/obsidian/checkboxes.ts +++ b/quartz/plugins/parsers/obsidian/checkboxes.ts @@ -7,13 +7,16 @@ import { JSResource } from "../../../util/resources" import { Element, Literal, Root as HtmlRoot } from "hast" import { Root } from "mdast" import { Pluggable } from "unified" +import { mdastFindReplaceInHtml } from "../../transformers/markdown" interface Options { enabled: Boolean + inHtml: Boolean } const defaultOptions: Options = { enabled: true, + inHtml: false, } export const ObsidianCheckboxes: QuartzParser> = (userOpts) => { @@ -29,7 +32,7 @@ export const ObsidianCheckboxes: QuartzParser> = (userOpts) => markdownPlugins(_ctx) { const plug: Pluggable = (tree: Root, _file) => { const replacements: [RegExp, string | ReplaceFunction][] = [] - mdastFindReplace(tree, replacements) + mdastFindReplaceInHtml(tree, replacements, opts.inHtml) } return plug }, diff --git a/quartz/plugins/parsers/obsidian/comments.ts b/quartz/plugins/parsers/obsidian/comments.ts index 580eeed9e9048..ed9325c30fd0f 100644 --- a/quartz/plugins/parsers/obsidian/comments.ts +++ b/quartz/plugins/parsers/obsidian/comments.ts @@ -3,13 +3,16 @@ import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util- import { JSResource } from "../../../util/resources" import { Root } from "mdast" import { Pluggable } from "unified" +import { mdastFindReplaceInHtml } from "../../transformers/markdown" interface Options { enabled: Boolean + inHtml: Boolean } const defaultOptions: Options = { enabled: true, + inHtml: false, } const commentRegex = new RegExp(/%%[\s\S]*?%%/g) @@ -33,7 +36,7 @@ export const ObsidianComments: QuartzParser> = (userOpts) => { const plug: Pluggable = (tree: Root, _file) => { if (opts.enabled) { const replacements: [RegExp, string | ReplaceFunction][] = [] - mdastFindReplace(tree, replacements) + mdastFindReplaceInHtml(tree, replacements, opts.inHtml) } } return plug diff --git a/quartz/plugins/parsers/obsidian/highlights.ts b/quartz/plugins/parsers/obsidian/highlights.ts index 09abe4fec0ea5..3000e9091c299 100644 --- a/quartz/plugins/parsers/obsidian/highlights.ts +++ b/quartz/plugins/parsers/obsidian/highlights.ts @@ -3,13 +3,16 @@ import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util- import { JSResource } from "../../../util/resources" import { Root } from "mdast" import { Pluggable } from "unified" +import { mdastFindReplaceInHtml } from "../../transformers/markdown" interface Options { enabled: Boolean + inHtml: Boolean } const defaultOptions: Options = { enabled: true, + inHtml: false, } const highlightRegex = new RegExp(/==([^=]+)==/g) @@ -40,7 +43,7 @@ export const ObsidianHighlights: QuartzParser> = (userOpts) => } }, ]) - mdastFindReplace(tree, replacements) + mdastFindReplaceInHtml(tree, replacements, opts.inHtml) } } return plug diff --git a/quartz/plugins/parsers/obsidian/html.ts b/quartz/plugins/parsers/obsidian/html.ts deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/quartz/plugins/parsers/obsidian/mermaid.ts b/quartz/plugins/parsers/obsidian/mermaid.ts index c868b8b7b98bd..8b64686bcc7f3 100644 --- a/quartz/plugins/parsers/obsidian/mermaid.ts +++ b/quartz/plugins/parsers/obsidian/mermaid.ts @@ -6,10 +6,12 @@ import { Pluggable } from "unified" interface Options { enabled: Boolean + inHtml: Boolean } const defaultOptions: Options = { enabled: true, + inHtml: false, } export const ObsidianMermaid: QuartzParser> = (userOpts) => { diff --git a/quartz/plugins/parsers/obsidian/tags.ts b/quartz/plugins/parsers/obsidian/tags.ts index 7f30d59110f45..d6257a8c7921c 100644 --- a/quartz/plugins/parsers/obsidian/tags.ts +++ b/quartz/plugins/parsers/obsidian/tags.ts @@ -4,13 +4,16 @@ import { FilePath, pathToRoot, slugTag, slugifyFilePath } from "../../../util/pa import { JSResource } from "../../../util/resources" import { Root } from "mdast" import { Pluggable } from "unified" +import { mdastFindReplaceInHtml } from "../../transformers/markdown" interface Options { enabled: Boolean + inHtml: Boolean } const defaultOptions: Options = { enabled: true, + inHtml: false, } // (?:^| ) -> non-capturing group, tag should start be separated by a space or be the start of the line @@ -66,7 +69,7 @@ export const ObsidianTags: QuartzParser> = (userOpts) => { } }, ]) - mdastFindReplace(tree, replacements) + mdastFindReplaceInHtml(tree, replacements, opts.inHtml) } return plug }, diff --git a/quartz/plugins/parsers/obsidian/wikilinks.ts b/quartz/plugins/parsers/obsidian/wikilinks.ts index 51eb53574a2d3..7024104432c97 100644 --- a/quartz/plugins/parsers/obsidian/wikilinks.ts +++ b/quartz/plugins/parsers/obsidian/wikilinks.ts @@ -4,13 +4,16 @@ import { FilePath, splitAnchor, slugifyFilePath } from "../../../util/path" import { JSResource } from "../../../util/resources" import { Root } from "mdast" import { Pluggable } from "unified" +import { mdastFindReplaceInHtml } from "../../transformers/markdown" interface Options { enabled: Boolean + inHtml: Boolean } const defaultOptions: Options = { enabled: true, + inHtml: false, } const externalLinkRegex = /^https?:\/\//i @@ -152,7 +155,7 @@ export const ObsidianWikilinks: QuartzParser> = (userOpts) => { } }, ]) - mdastFindReplace(tree, replacements) + mdastFindReplaceInHtml(tree, replacements, opts.inHtml) } return plug }, diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index 681169306c2f4..6ac0fab0c14f5 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -150,6 +150,41 @@ const defaultRoamOptions: RoamOptions = { attributeComponent: true, } +export const mdastToHtml = (ast: PhrasingContent | Paragraph) => { + const hast = toHast(ast, { allowDangerousHtml: true })! + return toHtml(hast, { allowDangerousHtml: true }) +} + +export const mdastFindReplaceInHtml = ( + tree: Root, + replacements: [RegExp, string | ReplaceFunction][], + inHtml: Boolean, +) => { + if (inHtml) { + 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 + } + }) + } + } + }) + } + mdastFindReplace(tree, replacements) +} + export const CommonMarkFlavoredMarkdown: QuartzTransformerPlugin> = ( userOpts, ) => { @@ -251,6 +286,8 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin { return (tree: Root, file) => { //const replacements: [RegExp, string | ReplaceFunction][] = [] @@ -265,12 +302,20 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin Date: Thu, 19 Sep 2024 16:07:49 +0000 Subject: [PATCH 33/38] Obsidian Parser (EmbedVideo) --- quartz/plugins/parsers/obsidian/callouts.ts | 2 - quartz/plugins/parsers/obsidian/index.ts | 1 + quartz/plugins/parsers/obsidian/mermaid.ts | 2 - quartz/plugins/parsers/obsidian/video.ts | 54 +++++++++++++++++++++ quartz/plugins/transformers/markdown.ts | 8 +-- 5 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 quartz/plugins/parsers/obsidian/video.ts diff --git a/quartz/plugins/parsers/obsidian/callouts.ts b/quartz/plugins/parsers/obsidian/callouts.ts index d2eb513d3fe32..6a9f337b5796a 100644 --- a/quartz/plugins/parsers/obsidian/callouts.ts +++ b/quartz/plugins/parsers/obsidian/callouts.ts @@ -13,12 +13,10 @@ import { mdastToHtml } from "../../transformers/markdown" interface Options { enabled: Boolean - inHtml: Boolean } const defaultOptions: Options = { enabled: true, - inHtml: false, } // from https://github.com/escwxyz/remark-obsidian-callout/blob/main/src/index.ts diff --git a/quartz/plugins/parsers/obsidian/index.ts b/quartz/plugins/parsers/obsidian/index.ts index 4f1555b84b861..b1115200f113c 100644 --- a/quartz/plugins/parsers/obsidian/index.ts +++ b/quartz/plugins/parsers/obsidian/index.ts @@ -6,3 +6,4 @@ export { ObsidianHighlights } from "./highlights" export { ObsidianMermaid } from "./mermaid" export { ObsidianTags } from "./tags" export { ObsidianWikilinks } from "./wikilinks" +export { ObsidianVideo } from "./video" diff --git a/quartz/plugins/parsers/obsidian/mermaid.ts b/quartz/plugins/parsers/obsidian/mermaid.ts index 8b64686bcc7f3..c868b8b7b98bd 100644 --- a/quartz/plugins/parsers/obsidian/mermaid.ts +++ b/quartz/plugins/parsers/obsidian/mermaid.ts @@ -6,12 +6,10 @@ import { Pluggable } from "unified" interface Options { enabled: Boolean - inHtml: Boolean } const defaultOptions: Options = { enabled: true, - inHtml: false, } export const ObsidianMermaid: QuartzParser> = (userOpts) => { diff --git a/quartz/plugins/parsers/obsidian/video.ts b/quartz/plugins/parsers/obsidian/video.ts new file mode 100644 index 0000000000000..bb806b1b54bd1 --- /dev/null +++ b/quartz/plugins/parsers/obsidian/video.ts @@ -0,0 +1,54 @@ +import { QuartzParser } from "../../types" +import { JSResource } from "../../../util/resources" +import { Root, Html } from "mdast" +import { Pluggable } from "unified" +import { SKIP, visit } from "unist-util-visit" + +interface Options { + enabled: Boolean +} + +const defaultOptions: Options = { + enabled: true, +} + +const videoExtensionRegex = new RegExp(/\.(mp4|webm|ogg|avi|mov|flv|wmv|mkv|mpg|mpeg|3gp|m4v)$/) + +export const ObsidianVideo: QuartzParser> = (userOpts) => { + const opts: Options = { ...defaultOptions, ...userOpts } + return { + name: "ObsidianVideo", + textTransform(_ctx, src: string | Buffer) { + if (src instanceof Buffer) { + src = src.toString() + } + return src + }, + markdownPlugins(_ctx) { + const plug: Pluggable = (tree: Root, _file) => { + if (opts.enabled) { + visit(tree, "image", (node, index, parent) => { + if (parent && index != undefined && videoExtensionRegex.test(node.url)) { + const newNode: Html = { + type: "html", + value: ``, + } + + parent.children.splice(index, 1, newNode) + return SKIP + } + }) + } + } + return plug + }, + htmlPlugins() { + const plug: Pluggable = () => {} + return plug + }, + externalResources() { + const js = {} as JSResource + return js + }, + } +} diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index 6ac0fab0c14f5..f1d6d758e01bc 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -45,6 +45,7 @@ import { ObsidianHighlights, ObsidianMermaid, ObsidianTags, + ObsidianVideo, ObsidianWikilinks, } from "../parsers/obsidian" @@ -312,10 +313,9 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin Date: Thu, 19 Sep 2024 16:32:41 +0000 Subject: [PATCH 34/38] Obsidian Parser (YouTube) --- quartz/plugins/parsers/obsidian/index.ts | 1 + quartz/plugins/parsers/obsidian/youtube.ts | 80 ++++++++++++++++++++++ quartz/plugins/transformers/markdown.ts | 6 +- 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 quartz/plugins/parsers/obsidian/youtube.ts diff --git a/quartz/plugins/parsers/obsidian/index.ts b/quartz/plugins/parsers/obsidian/index.ts index b1115200f113c..4c37fe3c0c9f1 100644 --- a/quartz/plugins/parsers/obsidian/index.ts +++ b/quartz/plugins/parsers/obsidian/index.ts @@ -7,3 +7,4 @@ export { ObsidianMermaid } from "./mermaid" export { ObsidianTags } from "./tags" export { ObsidianWikilinks } from "./wikilinks" export { ObsidianVideo } from "./video" +export { ObsidianYouTube } from "./youtube" diff --git a/quartz/plugins/parsers/obsidian/youtube.ts b/quartz/plugins/parsers/obsidian/youtube.ts new file mode 100644 index 0000000000000..3da312e04c219 --- /dev/null +++ b/quartz/plugins/parsers/obsidian/youtube.ts @@ -0,0 +1,80 @@ +import { QuartzParser } from "../../types" +import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" +import { JSResource } from "../../../util/resources" +import { visit } from "unist-util-visit" +import { Root } from "mdast" +import { Element, Literal, Root as HtmlRoot } from "hast" +import { Pluggable } from "unified" + +interface Options { + enabled: Boolean +} + +const defaultOptions: Options = { + enabled: true, +} + +const ytLinkRegex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/ +const ytPlaylistLinkRegex = /[?&]list=([^#?&]*)/ + +export const ObsidianYouTube: QuartzParser> = (userOpts) => { + const opts: Options = { ...defaultOptions, ...userOpts } + return { + name: "ObsidianYouTube", + textTransform(_ctx, src: string | Buffer) { + if (src instanceof Buffer) { + src = src.toString() + } + return src + }, + markdownPlugins(_ctx) { + const plug: Pluggable = (tree: Root, _file) => { + const replacements: [RegExp, string | ReplaceFunction][] = [] + mdastFindReplace(tree, replacements) + } + return plug + }, + htmlPlugins() { + if (opts.enabled) { + const plug: Pluggable = (tree: HtmlRoot, _file) => { + visit(tree, "element", (node) => { + if (node.tagName === "img" && typeof node.properties.src === "string") { + const match = node.properties.src.match(ytLinkRegex) + const videoId = match && match[2].length == 11 ? match[2] : null + const playlistId = node.properties.src.match(ytPlaylistLinkRegex)?.[1] + if (videoId) { + // YouTube video (with optional playlist) + node.tagName = "iframe" + node.properties = { + class: "external-embed youtube", + allow: "fullscreen", + frameborder: 0, + width: "600px", + src: playlistId + ? `https://www.youtube.com/embed/${videoId}?list=${playlistId}` + : `https://www.youtube.com/embed/${videoId}`, + } + } else if (playlistId) { + // YouTube playlist only. + node.tagName = "iframe" + node.properties = { + class: "external-embed youtube", + allow: "fullscreen", + frameborder: 0, + width: "600px", + src: `https://www.youtube.com/embed/videoseries?list=${playlistId}`, + } + } + } + }) + } + return plug + } + return {} as Pluggable + }, + externalResources() { + const js = {} as JSResource + return js + }, + } +} diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index f1d6d758e01bc..6c54df83c2bdf 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -47,6 +47,7 @@ import { ObsidianTags, ObsidianVideo, ObsidianWikilinks, + ObsidianYouTube, } from "../parsers/obsidian" export interface CommonMarkOptions { @@ -322,7 +323,10 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin Date: Thu, 19 Sep 2024 16:40:44 +0000 Subject: [PATCH 35/38] Obsidian Parser (BlockReference) --- .../parsers/obsidian/blockreference.ts | 118 ++++++++++++++++++ quartz/plugins/parsers/obsidian/index.ts | 1 + quartz/plugins/transformers/markdown.ts | 4 + 3 files changed, 123 insertions(+) diff --git a/quartz/plugins/parsers/obsidian/blockreference.ts b/quartz/plugins/parsers/obsidian/blockreference.ts index e69de29bb2d1d..64cf392ec9d08 100644 --- a/quartz/plugins/parsers/obsidian/blockreference.ts +++ b/quartz/plugins/parsers/obsidian/blockreference.ts @@ -0,0 +1,118 @@ +import { QuartzParser } from "../../types" +import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" +import { JSResource } from "../../../util/resources" +import { visit } from "unist-util-visit" +import { Root } from "mdast" +import { Pluggable } from "unified" +import { Element, Literal, Root as HtmlRoot } from "hast" + +interface Options { + enabled: Boolean +} + +const defaultOptions: Options = { + enabled: true, +} + +const blockReferenceRegex = new RegExp(/\^([-_A-Za-z0-9]+)$/g) + +export const ObsidianBlockReference: QuartzParser> = (userOpts) => { + const opts: Options = { ...defaultOptions, ...userOpts } + return { + name: "ObsidianBlockReference", + textTransform(_ctx, src: string | Buffer) { + if (src instanceof Buffer) { + src = src.toString() + } + return src + }, + markdownPlugins(_ctx) { + const plug: Pluggable = (tree: Root, _file) => { + const replacements: [RegExp, string | ReplaceFunction][] = [] + mdastFindReplace(tree, replacements) + } + return plug + }, + htmlPlugins() { + const inlineTagTypes = new Set(["p", "li"]) + const blockTagTypes = new Set(["blockquote"]) + if (opts.enabled) { + const plug: Pluggable = (tree: HtmlRoot, file) => { + file.data.blocks = {} + + 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 (last.value === "") { + // this is an inline block ref but the actual block + // is the previous element above it + let idx = (index ?? 1) - 1 + while (idx >= 0) { + const element = parent?.children.at(idx) + if (!element) break + if (element.type !== "element") { + idx -= 1 + } else { + if (!Object.keys(file.data.blocks!).includes(block)) { + element.properties = { + ...element.properties, + id: block, + } + file.data.blocks![block] = element + } + return + } + } + } else { + // normal paragraph transclude + if (!Object.keys(file.data.blocks!).includes(block)) { + node.properties = { + ...node.properties, + id: block, + } + file.data.blocks![block] = node + } + } + } + } + } + }) + + file.data.htmlAst = tree + } + return plug + } + return {} as Pluggable + }, + externalResources() { + const js = {} as JSResource + return js + }, + } +} diff --git a/quartz/plugins/parsers/obsidian/index.ts b/quartz/plugins/parsers/obsidian/index.ts index 4c37fe3c0c9f1..a54406941f579 100644 --- a/quartz/plugins/parsers/obsidian/index.ts +++ b/quartz/plugins/parsers/obsidian/index.ts @@ -1,4 +1,5 @@ export { ObsidianArrow } from "./arrows" +export { ObsidianBlockReference } from "./blockreference" export { ObsidianCallouts } from "./callouts" export { ObsidianCheckboxes } from "./checkboxes" export { ObsidianComments } from "./comments" diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index 6c54df83c2bdf..bb19d51ddd435 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -39,6 +39,7 @@ import { GitHubLinkheadings, GitHubSmartypants } from "../parsers/github" import { ObsidianArrow, + ObsidianBlockReference, ObsidianCallouts, ObsidianCheckboxes, ObsidianComments, @@ -323,6 +324,9 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin Date: Thu, 19 Sep 2024 18:17:33 +0000 Subject: [PATCH 36/38] Parsers work, tagpage/folderpage broken --- quartz.config.ts | 4 +- quartz/plugins/parsers/custom/default.ts | 4 +- quartz/plugins/parsers/obsidian/arrows.ts | 5 +- .../parsers/obsidian/blockreference.ts | 4 +- quartz/plugins/parsers/obsidian/callouts.ts | 4 +- quartz/plugins/parsers/obsidian/checkboxes.ts | 4 +- quartz/plugins/parsers/obsidian/comments.ts | 10 +- quartz/plugins/parsers/obsidian/highlights.ts | 5 +- quartz/plugins/parsers/obsidian/mermaid.ts | 4 +- quartz/plugins/parsers/obsidian/tags.ts | 66 ++++---- quartz/plugins/parsers/obsidian/wikilinks.ts | 146 +++++++++--------- quartz/plugins/parsers/obsidian/youtube.ts | 4 +- quartz/plugins/transformers/markdown.ts | 64 ++++++-- quartz/plugins/types.ts | 3 +- 14 files changed, 184 insertions(+), 143 deletions(-) diff --git a/quartz.config.ts b/quartz.config.ts index b6abbb2d3cc29..8ab0ac66200c1 100644 --- a/quartz.config.ts +++ b/quartz.config.ts @@ -77,8 +77,8 @@ const config: QuartzConfig = { Plugin.AliasRedirects(), Plugin.ComponentResources(), Plugin.ContentPage(), - Plugin.FolderPage(), - Plugin.TagPage(), + //Plugin.FolderPage(), + //Plugin.TagPage(), Plugin.ContentIndex({ enableSiteMap: true, enableRSS: true, diff --git a/quartz/plugins/parsers/custom/default.ts b/quartz/plugins/parsers/custom/default.ts index c7e2adfb0beb2..d01ed46a0ab6c 100644 --- a/quartz/plugins/parsers/custom/default.ts +++ b/quartz/plugins/parsers/custom/default.ts @@ -23,11 +23,11 @@ export const CustomDefault: QuartzParser> = (userOpts) => { return src }, markdownPlugins(_ctx) { + const replacements: [RegExp, string | ReplaceFunction][] = [] const plug: Pluggable = (tree: Root, _file) => { - const replacements: [RegExp, string | ReplaceFunction][] = [] mdastFindReplace(tree, replacements) } - return plug + return replacements }, htmlPlugins() { const plug: Pluggable = () => {} diff --git a/quartz/plugins/parsers/obsidian/arrows.ts b/quartz/plugins/parsers/obsidian/arrows.ts index db62a0c1bcf87..137ca8038e0c8 100644 --- a/quartz/plugins/parsers/obsidian/arrows.ts +++ b/quartz/plugins/parsers/obsidian/arrows.ts @@ -42,9 +42,9 @@ export const ObsidianArrow: QuartzParser> = (userOpts) => { return src }, markdownPlugins(_ctx) { + const replacements: [RegExp, string | ReplaceFunction][] = [] const plug: Pluggable = (tree: Root, _path) => { if (opts.enabled) { - const replacements: [RegExp, string | ReplaceFunction][] = [] replacements.push([ arrowRegex, (value: string, ..._capture: string[]) => { @@ -56,10 +56,9 @@ export const ObsidianArrow: QuartzParser> = (userOpts) => { } }, ]) - mdastFindReplaceInHtml(tree, replacements, opts.inHtml) } } - return plug + return replacements }, htmlPlugins() { const plug: Pluggable = () => {} diff --git a/quartz/plugins/parsers/obsidian/blockreference.ts b/quartz/plugins/parsers/obsidian/blockreference.ts index 64cf392ec9d08..8e9118d4ff86f 100644 --- a/quartz/plugins/parsers/obsidian/blockreference.ts +++ b/quartz/plugins/parsers/obsidian/blockreference.ts @@ -27,11 +27,11 @@ export const ObsidianBlockReference: QuartzParser> = (userOpts) return src }, markdownPlugins(_ctx) { + const replacements: [RegExp, string | ReplaceFunction][] = [] const plug: Pluggable = (tree: Root, _file) => { - const replacements: [RegExp, string | ReplaceFunction][] = [] mdastFindReplace(tree, replacements) } - return plug + return replacements }, htmlPlugins() { const inlineTagTypes = new Set(["p", "li"]) diff --git a/quartz/plugins/parsers/obsidian/callouts.ts b/quartz/plugins/parsers/obsidian/callouts.ts index 6a9f337b5796a..3c6dd4cc05a71 100644 --- a/quartz/plugins/parsers/obsidian/callouts.ts +++ b/quartz/plugins/parsers/obsidian/callouts.ts @@ -1,6 +1,7 @@ import { QuartzParser } from "../../types" import { JSResource } from "../../../util/resources" import { Root, BlockContent, DefinitionContent, Paragraph, Html } from "mdast" +import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" import { visit } from "unist-util-visit" import { Pluggable } from "unified" // @ts-ignore @@ -77,6 +78,7 @@ export const ObsidianCallouts: QuartzParser> = (userOpts) => { return src }, markdownPlugins(_ctx) { + const replacements: [RegExp, string | ReplaceFunction][] = [] const plug: Pluggable = (tree: Root, _path) => { if (opts.enabled) { visit(tree, "blockquote", (node) => { @@ -181,7 +183,7 @@ export const ObsidianCallouts: QuartzParser> = (userOpts) => { }) } } - return plug + return replacements }, htmlPlugins() { const plug: Pluggable = () => {} diff --git a/quartz/plugins/parsers/obsidian/checkboxes.ts b/quartz/plugins/parsers/obsidian/checkboxes.ts index 8236e3875e24f..e1d5672516d39 100644 --- a/quartz/plugins/parsers/obsidian/checkboxes.ts +++ b/quartz/plugins/parsers/obsidian/checkboxes.ts @@ -30,11 +30,11 @@ export const ObsidianCheckboxes: QuartzParser> = (userOpts) => return src }, markdownPlugins(_ctx) { + const replacements: [RegExp, string | ReplaceFunction][] = [] const plug: Pluggable = (tree: Root, _file) => { - const replacements: [RegExp, string | ReplaceFunction][] = [] mdastFindReplaceInHtml(tree, replacements, opts.inHtml) } - return plug + return replacements }, htmlPlugins() { if (opts.enabled) { diff --git a/quartz/plugins/parsers/obsidian/comments.ts b/quartz/plugins/parsers/obsidian/comments.ts index ed9325c30fd0f..8329af72199aa 100644 --- a/quartz/plugins/parsers/obsidian/comments.ts +++ b/quartz/plugins/parsers/obsidian/comments.ts @@ -33,13 +33,9 @@ export const ObsidianComments: QuartzParser> = (userOpts) => { return src }, markdownPlugins(_ctx) { - const plug: Pluggable = (tree: Root, _file) => { - if (opts.enabled) { - const replacements: [RegExp, string | ReplaceFunction][] = [] - mdastFindReplaceInHtml(tree, replacements, opts.inHtml) - } - } - return plug + const replacements: [RegExp, string | ReplaceFunction][] = [] + const plug: Pluggable = (tree: Root, _file) => {} + return replacements }, htmlPlugins() { const plug: Pluggable = () => {} diff --git a/quartz/plugins/parsers/obsidian/highlights.ts b/quartz/plugins/parsers/obsidian/highlights.ts index 3000e9091c299..82ed209a9fa42 100644 --- a/quartz/plugins/parsers/obsidian/highlights.ts +++ b/quartz/plugins/parsers/obsidian/highlights.ts @@ -30,9 +30,9 @@ export const ObsidianHighlights: QuartzParser> = (userOpts) => return src }, markdownPlugins(_ctx) { + const replacements: [RegExp, string | ReplaceFunction][] = [] const plug: Pluggable = (tree: Root, _path) => { if (opts.enabled) { - const replacements: [RegExp, string | ReplaceFunction][] = [] replacements.push([ highlightRegex, (_value: string, ...capture: string[]) => { @@ -43,10 +43,9 @@ export const ObsidianHighlights: QuartzParser> = (userOpts) => } }, ]) - mdastFindReplaceInHtml(tree, replacements, opts.inHtml) } } - return plug + return replacements }, htmlPlugins() { const plug: Pluggable = () => {} diff --git a/quartz/plugins/parsers/obsidian/mermaid.ts b/quartz/plugins/parsers/obsidian/mermaid.ts index c868b8b7b98bd..234500af684bb 100644 --- a/quartz/plugins/parsers/obsidian/mermaid.ts +++ b/quartz/plugins/parsers/obsidian/mermaid.ts @@ -1,5 +1,6 @@ import { QuartzParser } from "../../types" import { JSResource } from "../../../util/resources" +import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" import { visit } from "unist-util-visit" import { Root, Code } from "mdast" import { Pluggable } from "unified" @@ -25,6 +26,7 @@ export const ObsidianMermaid: QuartzParser> = (userOpts) => { return src }, markdownPlugins() { + const replacements: [RegExp, string | ReplaceFunction][] = [] const plug: Pluggable = (tree: Root, _file) => { if (opts.enabled) { visit(tree, "code", (node: Code) => { @@ -38,7 +40,7 @@ export const ObsidianMermaid: QuartzParser> = (userOpts) => { }) } } - return plug + return replacements }, htmlPlugins() { const plug: Pluggable = () => {} diff --git a/quartz/plugins/parsers/obsidian/tags.ts b/quartz/plugins/parsers/obsidian/tags.ts index d6257a8c7921c..13b59d86d012c 100644 --- a/quartz/plugins/parsers/obsidian/tags.ts +++ b/quartz/plugins/parsers/obsidian/tags.ts @@ -3,6 +3,7 @@ import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util- import { FilePath, pathToRoot, slugTag, slugifyFilePath } from "../../../util/path" import { JSResource } from "../../../util/resources" import { Root } from "mdast" +import path from "path" import { Pluggable } from "unified" import { mdastFindReplaceInHtml } from "../../transformers/markdown" @@ -35,43 +36,44 @@ export const ObsidianTags: QuartzParser> = (userOpts) => { return src }, markdownPlugins(_ctx) { + const replacements: [RegExp, string | ReplaceFunction][] = [] const plug: Pluggable = (tree: Root, file) => { - const base = pathToRoot(file.data.slug!) - const replacements: [RegExp, string | ReplaceFunction][] = [] - replacements.push([ - tagRegex, - (_value: string, tag: string) => { - // Check if the tag only includes numbers and slashes - if (/^[\/\d]+$/.test(tag)) { - return false - } + if (opts.enabled) { + const base = pathToRoot(file.data.slug!) + replacements.push([ + tagRegex, + (_value: string, tag: string) => { + // Check if the tag only includes numbers and slashes + if (/^[\/\d]+$/.test(tag)) { + return false + } - tag = slugTag(tag) - if (file.data.frontmatter) { - const noteTags = file.data.frontmatter.tags ?? [] - file.data.frontmatter.tags = [...new Set([...noteTags, tag])] - } + tag = slugTag(tag) + if (file.data.frontmatter) { + const noteTags = file.data.frontmatter.tags ?? [] + file.data.frontmatter.tags = [...new Set([...noteTags, tag])] + } - return { - type: "link", - url: base + `/tags/${tag}`, - data: { - hProperties: { - className: ["tag-link"], + return { + type: "link", + url: base + `/tags/${tag}`, + data: { + hProperties: { + className: ["tag-link"], + }, }, - }, - children: [ - { - type: "text", - value: tag, - }, - ], - } - }, - ]) - mdastFindReplaceInHtml(tree, replacements, opts.inHtml) + children: [ + { + type: "text", + value: tag, + }, + ], + } + }, + ]) + } } - return plug + return replacements }, htmlPlugins() { const plug: Pluggable = () => {} diff --git a/quartz/plugins/parsers/obsidian/wikilinks.ts b/quartz/plugins/parsers/obsidian/wikilinks.ts index 7024104432c97..7a88b8e11e845 100644 --- a/quartz/plugins/parsers/obsidian/wikilinks.ts +++ b/quartz/plugins/parsers/obsidian/wikilinks.ts @@ -5,6 +5,7 @@ import { JSResource } from "../../../util/resources" import { Root } from "mdast" import { Pluggable } from "unified" import { mdastFindReplaceInHtml } from "../../transformers/markdown" +import path from "path" interface Options { enabled: Boolean @@ -82,82 +83,85 @@ export const ObsidianWikilinks: QuartzParser> = (userOpts) => { return src }, markdownPlugins(_ctx) { - const plug: Pluggable = (tree: Root, path) => { - const replacements: [RegExp, string | ReplaceFunction][] = [] - replacements.push([ - wikilinkRegex, - (value: string, ...capture: string[]) => { - let [rawFp, rawHeader, rawAlias] = capture - const fp = rawFp?.trim() ?? "" - const anchor = rawHeader?.trim() ?? "" - const alias = rawAlias?.slice(1).trim() - - // embed cases - if (value.startsWith("!")) { - const ext: string = path.extname(fp).toLowerCase() - const url = slugifyFilePath(fp as FilePath) - if ([".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg", ".webp"].includes(ext)) { - const match = wikilinkImageEmbedRegex.exec(alias ?? "") - const alt = match?.groups?.alt ?? "" - const width = match?.groups?.width ?? "auto" - const height = match?.groups?.height ?? "auto" - return { - type: "image", - url, - data: { - hProperties: { - width, - height, - alt, + const replacements: [RegExp, string | ReplaceFunction][] = [] + const plug: Pluggable = (tree: Root, file) => { + if (opts.enabled) { + replacements.push([ + wikilinkRegex, + (value: string, ...capture: string[]) => { + let [rawFp, rawHeader, rawAlias] = capture + const fp = rawFp?.trim() ?? "" + const anchor = rawHeader?.trim() ?? "" + const alias = rawAlias?.slice(1).trim() + + // embed cases + if (value.startsWith("!")) { + const ext: string = path.extname(fp).toLowerCase() + const url = slugifyFilePath(fp as FilePath) + if ([".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg", ".webp"].includes(ext)) { + const match = wikilinkImageEmbedRegex.exec(alias ?? "") + const alt = match?.groups?.alt ?? "" + const width = match?.groups?.width ?? "auto" + const height = match?.groups?.height ?? "auto" + return { + type: "image", + url, + data: { + hProperties: { + width, + height, + alt, + }, }, - }, - } - } else if ([".mp4", ".webm", ".ogv", ".mov", ".mkv"].includes(ext)) { - return { - type: "html", - value: ``, - } - } else if ([".mp3", ".webm", ".wav", ".m4a", ".ogg", ".3gp", ".flac"].includes(ext)) { - return { - type: "html", - value: ``, - } - } else if ([".pdf"].includes(ext)) { - return { - type: "html", - value: ``, - } - } else { - const block = anchor - return { - type: "html", - data: { hProperties: { transclude: true } }, - value: `
Transclude of ${url}${block}
`, + } + } else if ([".mp4", ".webm", ".ogv", ".mov", ".mkv"].includes(ext)) { + return { + type: "html", + value: ``, + } + } else if ( + [".mp3", ".webm", ".wav", ".m4a", ".ogg", ".3gp", ".flac"].includes(ext) + ) { + return { + type: "html", + value: ``, + } + } else if ([".pdf"].includes(ext)) { + return { + type: "html", + value: ``, + } + } else { + const block = anchor + return { + type: "html", + data: { hProperties: { transclude: true } }, + value: `
Transclude of ${url}${block}
`, + } } + + // otherwise, fall through to regular link } - // otherwise, fall through to regular link - } - - // internal link - const url = fp + anchor - return { - type: "link", - url, - children: [ - { - type: "text", - value: alias ?? fp, - }, - ], - } - }, - ]) - mdastFindReplaceInHtml(tree, replacements, opts.inHtml) + // internal link + const url = fp + anchor + return { + type: "link", + url, + children: [ + { + type: "text", + value: alias ?? fp, + }, + ], + } + }, + ]) + } } - return plug + return replacements }, htmlPlugins() { const plug: Pluggable = () => {} diff --git a/quartz/plugins/parsers/obsidian/youtube.ts b/quartz/plugins/parsers/obsidian/youtube.ts index 3da312e04c219..7a240689ecc75 100644 --- a/quartz/plugins/parsers/obsidian/youtube.ts +++ b/quartz/plugins/parsers/obsidian/youtube.ts @@ -28,11 +28,11 @@ export const ObsidianYouTube: QuartzParser> = (userOpts) => { return src }, markdownPlugins(_ctx) { + const replacements: [RegExp, string | ReplaceFunction][] = [] const plug: Pluggable = (tree: Root, _file) => { - const replacements: [RegExp, string | ReplaceFunction][] = [] mdastFindReplace(tree, replacements) } - return plug + return replacements }, htmlPlugins() { if (opts.enabled) { diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index bb19d51ddd435..8e3e5714420ec 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -184,8 +184,9 @@ export const mdastFindReplaceInHtml = ( } } }) + } else { + mdastFindReplace(tree, replacements) } - mdastFindReplace(tree, replacements) } export const CommonMarkFlavoredMarkdown: QuartzTransformerPlugin> = ( @@ -254,7 +255,9 @@ export const GitHubFlavoredMarkdown: QuartzTransformerPlugin { + return (tree: Root, file) => { + mdastFindReplaceInHtml(tree, replacements, inHtml) + } + }) + /*plugins.push(() => { return (tree: Root, file) => { //const replacements: [RegExp, string | ReplaceFunction][] = [] @@ -305,7 +329,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin { + 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 }, htmlPlugins() { const plugins: PluggableList = [rehypeRaw] - plugins.push( - ObsidianBlockReference({ enabled: opts.parseBlockReferences }).htmlPlugins() as Pluggable, - ) - plugins.push(ObsidianYouTube({ enabled: opts.enableYouTubeEmbed }).htmlPlugins() as Pluggable) - plugins.push.apply( - ObsidianCheckboxes({ enabled: opts.enableCheckbox }).htmlPlugins() as Pluggable[], - ) + plugins.push(() => { + return (tree: Root, file) => + ObsidianBlockReference({ enabled: opts.parseBlockReferences }).htmlPlugins() as Pluggable + }) + plugins.push(() => { + return (tree: Root, file) => + ObsidianYouTube({ enabled: opts.enableYouTubeEmbed }).htmlPlugins() as Pluggable + }) + /*plugins.push(() => { + return (tree: HtmlRoot, file) => ObsidianCheckboxes({ enabled: opts.enableCheckbox }).htmlPlugins() as Pluggable} + )*/ return plugins }, diff --git a/quartz/plugins/types.ts b/quartz/plugins/types.ts index a621b84871c71..6017566abeab3 100644 --- a/quartz/plugins/types.ts +++ b/quartz/plugins/types.ts @@ -1,5 +1,6 @@ import { PluggableList, Pluggable } from "unified" import { JSResource, StaticResources } from "../util/resources" +import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" import { ProcessedContent } from "./vfile" import { QuartzComponent } from "../components/types" import { FilePath } from "../util/path" @@ -56,7 +57,7 @@ export type QuartzParser = ( export type QuartzParserInstance = { name: string textTransform: (ctx: BuildCtx, src: string | Buffer) => string | Buffer - markdownPlugins: (ctx: BuildCtx) => Pluggable + markdownPlugins: (ctx: BuildCtx) => [RegExp, string | ReplaceFunction][] htmlPlugins: () => Pluggable | Pluggable[] externalResources: () => JSResource | string } From 8f96809bf37c6df64cf65dca801e06f7607b52f0 Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Sun, 22 Sep 2024 13:31:31 +0000 Subject: [PATCH 37/38] Rewrite parser application --- quartz.config.ts | 6 +- quartz/plugins/parsers/custom/default.ts | 20 +- quartz/plugins/parsers/github/linkheadings.ts | 11 +- quartz/plugins/parsers/github/smartypants.ts | 6 +- quartz/plugins/parsers/obsidian/arrows.ts | 33 ++-- .../parsers/obsidian/blockreference.ts | 13 +- quartz/plugins/parsers/obsidian/callouts.ts | 175 +++++++++--------- quartz/plugins/parsers/obsidian/checkboxes.ts | 8 +- quartz/plugins/parsers/obsidian/comments.ts | 17 +- quartz/plugins/parsers/obsidian/highlights.ts | 31 ++-- quartz/plugins/parsers/obsidian/html.ts | 71 +++++++ quartz/plugins/parsers/obsidian/index.ts | 1 + quartz/plugins/parsers/obsidian/mermaid.ts | 24 +-- quartz/plugins/parsers/obsidian/tags.ts | 72 ++++--- quartz/plugins/parsers/obsidian/video.ts | 27 ++- quartz/plugins/parsers/obsidian/wikilinks.ts | 147 +++++++-------- quartz/plugins/parsers/obsidian/youtube.ts | 13 +- quartz/plugins/transformers/markdown.ts | 117 ++++++------ quartz/plugins/types.ts | 10 +- 19 files changed, 417 insertions(+), 385 deletions(-) create mode 100644 quartz/plugins/parsers/obsidian/html.ts diff --git a/quartz.config.ts b/quartz.config.ts index 8ab0ac66200c1..6f87edef30b64 100644 --- a/quartz.config.ts +++ b/quartz.config.ts @@ -65,7 +65,7 @@ const config: QuartzConfig = { }, keepBackground: false, }), - Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }), + //Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }), Plugin.GitHubFlavoredMarkdown(), Plugin.TableOfContents(), Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }), @@ -77,8 +77,8 @@ const config: QuartzConfig = { Plugin.AliasRedirects(), Plugin.ComponentResources(), Plugin.ContentPage(), - //Plugin.FolderPage(), - //Plugin.TagPage(), + Plugin.FolderPage(), + Plugin.TagPage(), Plugin.ContentIndex({ enableSiteMap: true, enableRSS: true, diff --git a/quartz/plugins/parsers/custom/default.ts b/quartz/plugins/parsers/custom/default.ts index d01ed46a0ab6c..b381920d3d054 100644 --- a/quartz/plugins/parsers/custom/default.ts +++ b/quartz/plugins/parsers/custom/default.ts @@ -3,6 +3,8 @@ import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util- import { JSResource } from "../../../util/resources" import { Root } from "mdast" import { Pluggable } from "unified" +import { visit } from "unist-util-visit" +import { Element, Literal, Root as HtmlRoot } from "hast" interface Options { enabled: Boolean @@ -22,16 +24,16 @@ export const CustomDefault: QuartzParser> = (userOpts) => { } return src }, - markdownPlugins(_ctx) { - const replacements: [RegExp, string | ReplaceFunction][] = [] - const plug: Pluggable = (tree: Root, _file) => { - mdastFindReplace(tree, replacements) - } - return replacements + markdownPlugins(tree, file) { + return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction] }, - htmlPlugins() { - const plug: Pluggable = () => {} - return plug + htmlPlugins(tree, file) { + if (opts.enabled) { + return () => { + visit(tree!, "element", (node) => {}) + } + } + return {} as Pluggable }, externalResources() { const js = {} as JSResource diff --git a/quartz/plugins/parsers/github/linkheadings.ts b/quartz/plugins/parsers/github/linkheadings.ts index a802e24e12b6e..fdb2ba6232816 100644 --- a/quartz/plugins/parsers/github/linkheadings.ts +++ b/quartz/plugins/parsers/github/linkheadings.ts @@ -1,5 +1,6 @@ import { QuartzParser } from "../../types" import { JSResource } from "../../../util/resources" +import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" import { Pluggable } from "unified" import rehypeSlug from "rehype-slug" import rehypeAutolinkHeadings from "rehype-autolink-headings" @@ -22,9 +23,8 @@ export const GitHubLinkheadings: QuartzParser> = (userOpts) => } return src }, - markdownPlugins(_ctx) { - const plug: Pluggable = () => {} - return plug + markdownPlugins(tree, file) { + return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction] }, htmlPlugins() { if (opts.enabled) { @@ -74,10 +74,9 @@ export const GitHubLinkheadings: QuartzParser> = (userOpts) => }, }, ], - ] as Pluggable[] - } else { - return [] as Pluggable[] + ] as Pluggable } + return [] as Pluggable }, externalResources() { const js = {} as JSResource diff --git a/quartz/plugins/parsers/github/smartypants.ts b/quartz/plugins/parsers/github/smartypants.ts index a7c38508844f2..91cec570330a7 100644 --- a/quartz/plugins/parsers/github/smartypants.ts +++ b/quartz/plugins/parsers/github/smartypants.ts @@ -22,11 +22,11 @@ export const GitHubSmartypants: QuartzParser> = (userOpts) => { } return src }, - markdownPlugins(_ctx) { + markdownPlugins() { if (opts.enabled) { - return [remarkGfm, smartypants] + return smartypants as Pluggable } - return [remarkGfm] + return {} as Pluggable }, htmlPlugins() { const plug: Pluggable = () => {} diff --git a/quartz/plugins/parsers/obsidian/arrows.ts b/quartz/plugins/parsers/obsidian/arrows.ts index 137ca8038e0c8..76a183a2af705 100644 --- a/quartz/plugins/parsers/obsidian/arrows.ts +++ b/quartz/plugins/parsers/obsidian/arrows.ts @@ -8,12 +8,10 @@ import { mdastFindReplaceInHtml } from "../../transformers/markdown" interface Options { enabled: Boolean - inHtml: Boolean } const defaultOptions: Options = { enabled: true, - inHtml: false, } const arrowMapping: Record = { @@ -41,24 +39,21 @@ export const ObsidianArrow: QuartzParser> = (userOpts) => { } return src }, - markdownPlugins(_ctx) { - const replacements: [RegExp, string | ReplaceFunction][] = [] - const plug: Pluggable = (tree: Root, _path) => { - if (opts.enabled) { - replacements.push([ - arrowRegex, - (value: string, ..._capture: string[]) => { - const maybeArrow = arrowMapping[value] - if (maybeArrow === undefined) return SKIP - return { - type: "html", - value: `${maybeArrow}`, - } - }, - ]) - } + markdownPlugins() { + if (opts.enabled) { + return [ + arrowRegex, + (value: string, ..._capture: string[]) => { + const maybeArrow = arrowMapping[value] + if (maybeArrow === undefined) return SKIP + return { + type: "html", + value: `${maybeArrow}`, + } + }, + ] as [RegExp, string | ReplaceFunction] } - return replacements + return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction] }, htmlPlugins() { const plug: Pluggable = () => {} diff --git a/quartz/plugins/parsers/obsidian/blockreference.ts b/quartz/plugins/parsers/obsidian/blockreference.ts index 8e9118d4ff86f..c81908f96aebe 100644 --- a/quartz/plugins/parsers/obsidian/blockreference.ts +++ b/quartz/plugins/parsers/obsidian/blockreference.ts @@ -26,18 +26,14 @@ export const ObsidianBlockReference: QuartzParser> = (userOpts) } return src }, - markdownPlugins(_ctx) { - const replacements: [RegExp, string | ReplaceFunction][] = [] - const plug: Pluggable = (tree: Root, _file) => { - mdastFindReplace(tree, replacements) - } - return replacements + markdownPlugins(_tree, _file) { + return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction] }, - htmlPlugins() { + htmlPlugins(tree, file) { const inlineTagTypes = new Set(["p", "li"]) const blockTagTypes = new Set(["blockquote"]) if (opts.enabled) { - const plug: Pluggable = (tree: HtmlRoot, file) => { + return () => { file.data.blocks = {} visit(tree, "element", (node, index, parent) => { @@ -106,7 +102,6 @@ export const ObsidianBlockReference: QuartzParser> = (userOpts) file.data.htmlAst = tree } - return plug } return {} as Pluggable }, diff --git a/quartz/plugins/parsers/obsidian/callouts.ts b/quartz/plugins/parsers/obsidian/callouts.ts index 3c6dd4cc05a71..62bc0defd17f7 100644 --- a/quartz/plugins/parsers/obsidian/callouts.ts +++ b/quartz/plugins/parsers/obsidian/callouts.ts @@ -77,113 +77,110 @@ export const ObsidianCallouts: QuartzParser> = (userOpts) => { return src }, - markdownPlugins(_ctx) { - const replacements: [RegExp, string | ReplaceFunction][] = [] - const plug: Pluggable = (tree: Root, _path) => { - if (opts.enabled) { - visit(tree, "blockquote", (node) => { - 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 + markdownPlugins(tree, _file) { + if (opts.enabled && tree !== undefined) { + visit(tree, "blockquote", (node) => { + 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) - 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) - - const toggleIcon = `
` + const toggleIcon = `
` - const titleHtml: Html = { - type: "html", - value: `
${title}
${collapse ? toggleIcon : ""}
`, - } + } - const blockquoteContent: (BlockContent | DefinitionContent)[] = [titleHtml] - if (remainingText.length > 0) { - blockquoteContent.push({ - type: "paragraph", - children: [ - { - type: "text", - value: remainingText, - }, - ], - }) - } + 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) + // 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") - } + 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 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", + // Add callout-content class to callout body if it has one. + if (calloutContent.length > 0) { + const contentData: BlockContent | DefinitionContent = { + data: { + hProperties: { + className: "callout-content", }, - type: "blockquote", - children: [...calloutContent], - } - node.children = [node.children[0], contentData] + hName: "div", + }, + type: "blockquote", + children: [...calloutContent], } + node.children = [node.children[0], contentData] } - }) - } + } + }) } - return replacements + return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction] }, htmlPlugins() { const plug: Pluggable = () => {} diff --git a/quartz/plugins/parsers/obsidian/checkboxes.ts b/quartz/plugins/parsers/obsidian/checkboxes.ts index e1d5672516d39..259996280dbf3 100644 --- a/quartz/plugins/parsers/obsidian/checkboxes.ts +++ b/quartz/plugins/parsers/obsidian/checkboxes.ts @@ -29,12 +29,8 @@ export const ObsidianCheckboxes: QuartzParser> = (userOpts) => } return src }, - markdownPlugins(_ctx) { - const replacements: [RegExp, string | ReplaceFunction][] = [] - const plug: Pluggable = (tree: Root, _file) => { - mdastFindReplaceInHtml(tree, replacements, opts.inHtml) - } - return replacements + markdownPlugins(_tree, _file) { + return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction] }, htmlPlugins() { if (opts.enabled) { diff --git a/quartz/plugins/parsers/obsidian/comments.ts b/quartz/plugins/parsers/obsidian/comments.ts index 8329af72199aa..bb574770459aa 100644 --- a/quartz/plugins/parsers/obsidian/comments.ts +++ b/quartz/plugins/parsers/obsidian/comments.ts @@ -3,6 +3,7 @@ import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util- import { JSResource } from "../../../util/resources" import { Root } from "mdast" import { Pluggable } from "unified" +import { visit } from "unist-util-visit" import { mdastFindReplaceInHtml } from "../../transformers/markdown" interface Options { @@ -32,14 +33,16 @@ export const ObsidianComments: QuartzParser> = (userOpts) => { return src }, - markdownPlugins(_ctx) { - const replacements: [RegExp, string | ReplaceFunction][] = [] - const plug: Pluggable = (tree: Root, _file) => {} - return replacements + markdownPlugins(tree, file) { + return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction] }, - htmlPlugins() { - const plug: Pluggable = () => {} - return plug + htmlPlugins(tree, file) { + if (opts.enabled) { + return () => { + visit(tree, "element", (node) => {}) + } + } + return {} as Pluggable }, externalResources() { const js = {} as JSResource diff --git a/quartz/plugins/parsers/obsidian/highlights.ts b/quartz/plugins/parsers/obsidian/highlights.ts index 82ed209a9fa42..560cbb36e77b7 100644 --- a/quartz/plugins/parsers/obsidian/highlights.ts +++ b/quartz/plugins/parsers/obsidian/highlights.ts @@ -7,12 +7,10 @@ import { mdastFindReplaceInHtml } from "../../transformers/markdown" interface Options { enabled: Boolean - inHtml: Boolean } const defaultOptions: Options = { enabled: true, - inHtml: false, } const highlightRegex = new RegExp(/==([^=]+)==/g) @@ -29,23 +27,20 @@ export const ObsidianHighlights: QuartzParser> = (userOpts) => } return src }, - markdownPlugins(_ctx) { - const replacements: [RegExp, string | ReplaceFunction][] = [] - const plug: Pluggable = (tree: Root, _path) => { - if (opts.enabled) { - replacements.push([ - highlightRegex, - (_value: string, ...capture: string[]) => { - const [inner] = capture - return { - type: "html", - value: `${inner}`, - } - }, - ]) - } + markdownPlugins() { + if (opts.enabled) { + return [ + highlightRegex, + (_value: string, ...capture: string[]) => { + const [inner] = capture + return { + type: "html", + value: `${inner}`, + } + }, + ] as [RegExp, string | ReplaceFunction] } - return replacements + return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction] }, htmlPlugins() { const plug: Pluggable = () => {} diff --git a/quartz/plugins/parsers/obsidian/html.ts b/quartz/plugins/parsers/obsidian/html.ts new file mode 100644 index 0000000000000..2500052d6ad2f --- /dev/null +++ b/quartz/plugins/parsers/obsidian/html.ts @@ -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> = (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 + }, + } +} diff --git a/quartz/plugins/parsers/obsidian/index.ts b/quartz/plugins/parsers/obsidian/index.ts index a54406941f579..aec2f0dbda971 100644 --- a/quartz/plugins/parsers/obsidian/index.ts +++ b/quartz/plugins/parsers/obsidian/index.ts @@ -4,6 +4,7 @@ export { ObsidianCallouts } from "./callouts" export { ObsidianCheckboxes } from "./checkboxes" export { ObsidianComments } from "./comments" export { ObsidianHighlights } from "./highlights" +export { ObsidianHtml } from "./html" export { ObsidianMermaid } from "./mermaid" export { ObsidianTags } from "./tags" export { ObsidianWikilinks } from "./wikilinks" diff --git a/quartz/plugins/parsers/obsidian/mermaid.ts b/quartz/plugins/parsers/obsidian/mermaid.ts index 234500af684bb..830933568aa86 100644 --- a/quartz/plugins/parsers/obsidian/mermaid.ts +++ b/quartz/plugins/parsers/obsidian/mermaid.ts @@ -25,22 +25,18 @@ export const ObsidianMermaid: QuartzParser> = (userOpts) => { } return src }, - markdownPlugins() { - const replacements: [RegExp, string | ReplaceFunction][] = [] - const plug: Pluggable = (tree: Root, _file) => { - if (opts.enabled) { - visit(tree, "code", (node: Code) => { - if (node.lang === "mermaid") { - node.data = { - hProperties: { - className: ["mermaid"], - }, - } + markdownPlugins(tree, _file) { + if (opts.enabled && tree !== undefined) { + visit(tree, "code", (node: Code) => { + if (node.lang === "mermaid") { + node.data = { + hProperties: { + className: ["mermaid"], + }, } - }) - } + } + }) } - return replacements }, htmlPlugins() { const plug: Pluggable = () => {} diff --git a/quartz/plugins/parsers/obsidian/tags.ts b/quartz/plugins/parsers/obsidian/tags.ts index 13b59d86d012c..a3cdb77fdc382 100644 --- a/quartz/plugins/parsers/obsidian/tags.ts +++ b/quartz/plugins/parsers/obsidian/tags.ts @@ -8,13 +8,11 @@ import { Pluggable } from "unified" import { mdastFindReplaceInHtml } from "../../transformers/markdown" interface Options { - enabled: Boolean - inHtml: Boolean + enabled: ConstrainBoolean } const defaultOptions: Options = { enabled: true, - inHtml: false, } // (?:^| ) -> 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> = (userOpts) => { } return src }, - markdownPlugins(_ctx) { - const replacements: [RegExp, string | ReplaceFunction][] = [] - const plug: Pluggable = (tree: Root, file) => { - if (opts.enabled) { - const base = pathToRoot(file.data.slug!) - replacements.push([ - tagRegex, - (_value: string, tag: string) => { - // Check if the tag only includes numbers and slashes - if (/^[\/\d]+$/.test(tag)) { - return false - } + markdownPlugins(_tree, file) { + console.log(file) + if (opts.enabled && file !== undefined) { + const base = pathToRoot(file.data.slug!) + return [ + tagRegex, + (_value: string, tag: string) => { + // Check if the tag only includes numbers and slashes + if (/^[\/\d]+$/.test(tag)) { + return false + } - tag = slugTag(tag) - if (file.data.frontmatter) { - const noteTags = file.data.frontmatter.tags ?? [] - file.data.frontmatter.tags = [...new Set([...noteTags, tag])] - } + tag = slugTag(tag) + if (file.data.frontmatter) { + const noteTags = file.data.frontmatter.tags ?? [] + file.data.frontmatter.tags = [...new Set([...noteTags, tag])] + } - return { - type: "link", - url: base + `/tags/${tag}`, - data: { - hProperties: { - className: ["tag-link"], - }, + return { + type: "link", + url: base + `/tags/${tag}`, + data: { + hProperties: { + className: ["tag-link"], }, - children: [ - { - type: "text", - value: tag, - }, - ], - } - }, - ]) - } + }, + children: [ + { + type: "text", + value: tag, + }, + ], + } + }, + ] as [RegExp, string | ReplaceFunction] } - return replacements + return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction] }, htmlPlugins() { const plug: Pluggable = () => {} diff --git a/quartz/plugins/parsers/obsidian/video.ts b/quartz/plugins/parsers/obsidian/video.ts index bb806b1b54bd1..816368053230f 100644 --- a/quartz/plugins/parsers/obsidian/video.ts +++ b/quartz/plugins/parsers/obsidian/video.ts @@ -24,23 +24,20 @@ export const ObsidianVideo: QuartzParser> = (userOpts) => { } return src }, - markdownPlugins(_ctx) { - const plug: Pluggable = (tree: Root, _file) => { - if (opts.enabled) { - visit(tree, "image", (node, index, parent) => { - if (parent && index != undefined && videoExtensionRegex.test(node.url)) { - const newNode: Html = { - type: "html", - value: ``, - } - - parent.children.splice(index, 1, newNode) - return SKIP + markdownPlugins(tree, _file) { + if (opts.enabled && tree !== undefined) { + visit(tree, "image", (node, index, parent) => { + if (parent && index != undefined && videoExtensionRegex.test(node.url)) { + const newNode: Html = { + type: "html", + value: ``, } - }) - } + + parent.children.splice(index, 1, newNode) + return SKIP + } + }) } - return plug }, htmlPlugins() { const plug: Pluggable = () => {} diff --git a/quartz/plugins/parsers/obsidian/wikilinks.ts b/quartz/plugins/parsers/obsidian/wikilinks.ts index 7a88b8e11e845..724c03f704cc8 100644 --- a/quartz/plugins/parsers/obsidian/wikilinks.ts +++ b/quartz/plugins/parsers/obsidian/wikilinks.ts @@ -9,12 +9,10 @@ import path from "path" interface Options { enabled: Boolean - inHtml: Boolean } const defaultOptions: Options = { enabled: true, - inHtml: false, } const externalLinkRegex = /^https?:\/\//i @@ -82,86 +80,81 @@ export const ObsidianWikilinks: QuartzParser> = (userOpts) => { return src }, - markdownPlugins(_ctx) { - const replacements: [RegExp, string | ReplaceFunction][] = [] - const plug: Pluggable = (tree: Root, file) => { - if (opts.enabled) { - replacements.push([ - wikilinkRegex, - (value: string, ...capture: string[]) => { - let [rawFp, rawHeader, rawAlias] = capture - const fp = rawFp?.trim() ?? "" - const anchor = rawHeader?.trim() ?? "" - const alias = rawAlias?.slice(1).trim() - - // embed cases - if (value.startsWith("!")) { - const ext: string = path.extname(fp).toLowerCase() - const url = slugifyFilePath(fp as FilePath) - if ([".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg", ".webp"].includes(ext)) { - const match = wikilinkImageEmbedRegex.exec(alias ?? "") - const alt = match?.groups?.alt ?? "" - const width = match?.groups?.width ?? "auto" - const height = match?.groups?.height ?? "auto" - return { - type: "image", - url, - data: { - hProperties: { - width, - height, - alt, - }, + markdownPlugins() { + if (opts.enabled) { + return [ + wikilinkRegex, + (value: string, ...capture: string[]) => { + let [rawFp, rawHeader, rawAlias] = capture + const fp = rawFp?.trim() ?? "" + const anchor = rawHeader?.trim() ?? "" + const alias = rawAlias?.slice(1).trim() + + // embed cases + if (value.startsWith("!")) { + const ext: string = path.extname(fp).toLowerCase() + const url = slugifyFilePath(fp as FilePath) + if ([".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg", ".webp"].includes(ext)) { + const match = wikilinkImageEmbedRegex.exec(alias ?? "") + const alt = match?.groups?.alt ?? "" + const width = match?.groups?.width ?? "auto" + const height = match?.groups?.height ?? "auto" + return { + type: "image", + url, + data: { + hProperties: { + width, + height, + alt, }, - } - } else if ([".mp4", ".webm", ".ogv", ".mov", ".mkv"].includes(ext)) { - return { - type: "html", - value: ``, - } - } else if ( - [".mp3", ".webm", ".wav", ".m4a", ".ogg", ".3gp", ".flac"].includes(ext) - ) { - return { - type: "html", - value: ``, - } - } else if ([".pdf"].includes(ext)) { - return { - type: "html", - value: ``, - } - } else { - const block = anchor - return { - type: "html", - data: { hProperties: { transclude: true } }, - value: `
Transclude of ${url}${block}
`, - } + }, + } + } else if ([".mp4", ".webm", ".ogv", ".mov", ".mkv"].includes(ext)) { + return { + type: "html", + value: ``, + } + } else if ([".mp3", ".webm", ".wav", ".m4a", ".ogg", ".3gp", ".flac"].includes(ext)) { + return { + type: "html", + value: ``, + } + } else if ([".pdf"].includes(ext)) { + return { + type: "html", + value: ``, + } + } else { + const block = anchor + return { + type: "html", + data: { hProperties: { transclude: true } }, + value: `
Transclude of ${url}${block}
`, } - - // otherwise, fall through to regular link } - // internal link - const url = fp + anchor - return { - type: "link", - url, - children: [ - { - type: "text", - value: alias ?? fp, - }, - ], - } - }, - ]) - } + // 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() { const plug: Pluggable = () => {} diff --git a/quartz/plugins/parsers/obsidian/youtube.ts b/quartz/plugins/parsers/obsidian/youtube.ts index 7a240689ecc75..b68627e155541 100644 --- a/quartz/plugins/parsers/obsidian/youtube.ts +++ b/quartz/plugins/parsers/obsidian/youtube.ts @@ -27,16 +27,12 @@ export const ObsidianYouTube: QuartzParser> = (userOpts) => { } return src }, - markdownPlugins(_ctx) { - const replacements: [RegExp, string | ReplaceFunction][] = [] - const plug: Pluggable = (tree: Root, _file) => { - mdastFindReplace(tree, replacements) - } - return replacements + markdownPlugins(_file, _tree) { + return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction] }, - htmlPlugins() { + htmlPlugins(tree, _file) { if (opts.enabled) { - const plug: Pluggable = (tree: HtmlRoot, _file) => { + return () => { visit(tree, "element", (node) => { if (node.tagName === "img" && typeof node.properties.src === "string") { const match = node.properties.src.match(ytLinkRegex) @@ -68,7 +64,6 @@ export const ObsidianYouTube: QuartzParser> = (userOpts) => { } }) } - return plug } return {} as Pluggable }, diff --git a/quartz/plugins/transformers/markdown.ts b/quartz/plugins/transformers/markdown.ts index 8e3e5714420ec..b58772144f072 100644 --- a/quartz/plugins/transformers/markdown.ts +++ b/quartz/plugins/transformers/markdown.ts @@ -44,6 +44,7 @@ import { ObsidianCheckboxes, ObsidianComments, ObsidianHighlights, + ObsidianHtml, ObsidianMermaid, ObsidianTags, ObsidianVideo, @@ -249,14 +250,14 @@ export const GitHubFlavoredMarkdown: QuartzTransformerPlugin { - return (tree: Root, file) => { - mdastFindReplaceInHtml(tree, replacements, inHtml) + plugins.push((tree: Root, file) => { + const replacements: [RegExp, string | ReplaceFunction][] = [] + if (file !== undefined && file.data !== undefined) { + const base = pathToRoot(file.data.slug!) } + + 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(() => { - return (tree: Root, file) => { - //const replacements: [RegExp, string | ReplaceFunction][] = [] - //const base = pathToRoot(file.data.slug!) - - ObsidianWikilinks({ enabled: opts.wikilinks }).markdownPlugins(ctx) - - ObsidianHighlights({ enabled: opts.highlight }).markdownPlugins(ctx) - - ObsidianArrow({ enabled: opts.parseArrows }).markdownPlugins(ctx) - - //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) + ObsidianVideo({ enabled: opts.enableVideoEmbed }).markdownPlugins(tree) }) plugins.push(() => { return (tree: Root, file) => - ObsidianCallouts({ enabled: opts.callouts }).markdownPlugins(ctx) + ObsidianCallouts({ enabled: opts.callouts }).markdownPlugins(tree) }) plugins.push(() => { - return (tree: Root, file) => ObsidianMermaid({ enabled: opts.mermaid }).markdownPlugins(ctx) - }) + return (tree: Root, file) => + ObsidianMermaid({ enabled: opts.mermaid }).markdownPlugins(tree) + })*/ return plugins }, @@ -357,12 +352,12 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin { - return (tree: Root, file) => - ObsidianBlockReference({ enabled: opts.parseBlockReferences }).htmlPlugins() as Pluggable + return (tree: HtmlRoot, file) => + ObsidianBlockReference({ enabled: opts.parseBlockReferences }).htmlPlugins(tree, file) }) plugins.push(() => { - return (tree: Root, file) => - ObsidianYouTube({ enabled: opts.enableYouTubeEmbed }).htmlPlugins() as Pluggable + return (tree: HtmlRoot, file) => + ObsidianYouTube({ enabled: opts.enableYouTubeEmbed }).htmlPlugins(tree, file) }) /*plugins.push(() => { return (tree: HtmlRoot, file) => ObsidianCheckboxes({ enabled: opts.enableCheckbox }).htmlPlugins() as Pluggable} diff --git a/quartz/plugins/types.ts b/quartz/plugins/types.ts index 6017566abeab3..b1ad01739c803 100644 --- a/quartz/plugins/types.ts +++ b/quartz/plugins/types.ts @@ -6,6 +6,8 @@ import { QuartzComponent } from "../components/types" import { FilePath } from "../util/path" import { BuildCtx } from "../util/ctx" import DepGraph from "../depgraph" +import { Root } from "mdast-util-find-and-replace/lib" +import { Element, Literal, Root as HtmlRoot } from "hast" export interface PluginTypes { transformers: QuartzTransformerPluginInstance[] @@ -57,7 +59,11 @@ export type QuartzParser = ( export type QuartzParserInstance = { name: string textTransform: (ctx: BuildCtx, src: string | Buffer) => string | Buffer - markdownPlugins: (ctx: BuildCtx) => [RegExp, string | ReplaceFunction][] - htmlPlugins: () => Pluggable | Pluggable[] + markdownPlugins: ( + tree?: Root, + file?: any, + replacements?: [RegExp, string | ReplaceFunction][], + ) => [RegExp, string | ReplaceFunction] | Pluggable | void + htmlPlugins: (tree?: HtmlRoot, file?: any) => Pluggable externalResources: () => JSResource | string } From e73f28afdb3382b045b687a70c32bc941eccda6f Mon Sep 17 00:00:00 2001 From: Emile Bangma Date: Sun, 22 Sep 2024 14:36:16 +0000 Subject: [PATCH 38/38] Formatting --- quartz/plugins/parsers/obsidian/blockreference.ts | 2 +- quartz/plugins/parsers/obsidian/comments.ts | 7 +------ quartz/plugins/parsers/obsidian/youtube.ts | 4 ++-- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/quartz/plugins/parsers/obsidian/blockreference.ts b/quartz/plugins/parsers/obsidian/blockreference.ts index c81908f96aebe..9185d845bc781 100644 --- a/quartz/plugins/parsers/obsidian/blockreference.ts +++ b/quartz/plugins/parsers/obsidian/blockreference.ts @@ -32,7 +32,7 @@ export const ObsidianBlockReference: QuartzParser> = (userOpts) htmlPlugins(tree, file) { const inlineTagTypes = new Set(["p", "li"]) const blockTagTypes = new Set(["blockquote"]) - if (opts.enabled) { + if (opts.enabled && tree !== undefined) { return () => { file.data.blocks = {} diff --git a/quartz/plugins/parsers/obsidian/comments.ts b/quartz/plugins/parsers/obsidian/comments.ts index bb574770459aa..d1ad42075b5ce 100644 --- a/quartz/plugins/parsers/obsidian/comments.ts +++ b/quartz/plugins/parsers/obsidian/comments.ts @@ -36,12 +36,7 @@ export const ObsidianComments: QuartzParser> = (userOpts) => { markdownPlugins(tree, file) { return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction] }, - htmlPlugins(tree, file) { - if (opts.enabled) { - return () => { - visit(tree, "element", (node) => {}) - } - } + htmlPlugins() { return {} as Pluggable }, externalResources() { diff --git a/quartz/plugins/parsers/obsidian/youtube.ts b/quartz/plugins/parsers/obsidian/youtube.ts index b68627e155541..1de6b7989062d 100644 --- a/quartz/plugins/parsers/obsidian/youtube.ts +++ b/quartz/plugins/parsers/obsidian/youtube.ts @@ -30,8 +30,8 @@ export const ObsidianYouTube: QuartzParser> = (userOpts) => { markdownPlugins(_file, _tree) { return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction] }, - htmlPlugins(tree, _file) { - if (opts.enabled) { + htmlPlugins(tree) { + if (opts.enabled && tree !== undefined) { return () => { visit(tree, "element", (node) => { if (node.tagName === "img" && typeof node.properties.src === "string") {