From bc543f81d9ada5e61cb9690834a5f83c02997d63 Mon Sep 17 00:00:00 2001 From: Hrishikesh Barman Date: Sat, 26 Aug 2023 11:22:23 +0530 Subject: [PATCH] feat(plugins): add OxHugoFlavouredMarkdown (#419) * feat(plugins): add OxHugoFlavouredMarkdown ox-hugo is an org exporter backend that exports org files to hugo-compatible markdown in an opinionated way. This plugin adds some tweaks to the generated markdown to make it compatible with quartz but the list of changes applied it is not extensive. In the future however, we could leapfrog ox-hugo altogether and create a quartz site directly out of org-roam files. That way we won't have to do all the ritual dancing that this plugin has to perform. See https://github.com/k2052/org-to-markdown * fix: add toml to remarkFrontmatter configuration * docs: add docs for OxHugoFlavouredMarkdown * fixup! docs: add docs for OxHugoFlavouredMarkdown --- docs/features/OxHugo compatibility.md | 38 +++++++++++ quartz/plugins/transformers/frontmatter.ts | 2 +- quartz/plugins/transformers/index.ts | 1 + quartz/plugins/transformers/oxhugofm.ts | 73 ++++++++++++++++++++++ 4 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 docs/features/OxHugo compatibility.md create mode 100644 quartz/plugins/transformers/oxhugofm.ts diff --git a/docs/features/OxHugo compatibility.md b/docs/features/OxHugo compatibility.md new file mode 100644 index 00000000..12774e72 --- /dev/null +++ b/docs/features/OxHugo compatibility.md @@ -0,0 +1,38 @@ +--- +tags: + - plugin/transformer +--- + +Quartz is a static-site generator that transforms markdown content into web pages. [org-roam](https://www.orgroam.com/) is a plain-text(`org`) personal knowledge management system for [emacs](https://en.wikipedia.org/wiki/Emacs). [ox-hugo](https://github.com/kaushalmodi/ox-hugo) is org exporter backend that exports `org-mode` files to [Hugo](https://gohugo.io/) compatible markdown. + +Because the markdown generated by ox-hugo is not pure markdown but Hugo specific, we need to transform it to fit into Quartz. This is done by `Plugin.OxHugoFlavouredMarkdown`. Even though this [[making plugins|plugin]] was written with `ox-hugo` in mind, it should work for any Hugo specific markdown. + +```typescript title="quartz.config.ts" +plugins: { + transformers: [ + Plugin.FrontMatter({ delims: "+++", language: "toml" }), // if toml frontmatter + // ... + Plugin.OxHugoFlavouredMarkdown(), + Plugin.GitHubFlavoredMarkdown(), + // ... + ], +}, +``` + +## Usage + +Quartz by default doesn't understand `org-roam` files as they aren't Markdown. You're responsible for using an external tool like `ox-hugo` to export the `org-roam` files as Markdown content to Quartz and managing the static assets so that they're available in the final output. + +## Configuration + +- Link resolution + - `wikilinks`: Whether to replace `{{ relref }}` with Quartz [[wikilinks]] + - `removePredefinedAnchor`: Whether to remove [pre-defined anchor set by ox-hugo](https://ox-hugo.scripter.co/doc/anchors/). +- Image handling + - `replaceFigureWithMdImg`: Whether to replace `
` with `![]()` +- Formatting + - `removeHugoShortcode`: Whether to remove hugo shortcode syntax (`{{}}`) + +> [!warning] +> +> While you can use `Plugin.OxHugoFlavoredMarkdown` and `Plugin.ObsidianFlavoredMarkdown` together, it's not recommended because it might mutate the file in unexpected ways. Use with caution. diff --git a/quartz/plugins/transformers/frontmatter.ts b/quartz/plugins/transformers/frontmatter.ts index a7249c19..571aa04d 100644 --- a/quartz/plugins/transformers/frontmatter.ts +++ b/quartz/plugins/transformers/frontmatter.ts @@ -21,7 +21,7 @@ export const FrontMatter: QuartzTransformerPlugin | undefined> name: "FrontMatter", markdownPlugins() { return [ - remarkFrontmatter, + [remarkFrontmatter, ["yaml", "toml"]], () => { return (_, file) => { const { data } = matter(file.value, { diff --git a/quartz/plugins/transformers/index.ts b/quartz/plugins/transformers/index.ts index 8013ab7c..d9f2854c 100644 --- a/quartz/plugins/transformers/index.ts +++ b/quartz/plugins/transformers/index.ts @@ -5,5 +5,6 @@ export { Latex } from "./latex" export { Description } from "./description" export { CrawlLinks } from "./links" export { ObsidianFlavoredMarkdown } from "./ofm" +export { OxHugoFlavouredMarkdown } from "./oxhugofm" export { SyntaxHighlighting } from "./syntax" export { TableOfContents } from "./toc" diff --git a/quartz/plugins/transformers/oxhugofm.ts b/quartz/plugins/transformers/oxhugofm.ts new file mode 100644 index 00000000..0d7b9199 --- /dev/null +++ b/quartz/plugins/transformers/oxhugofm.ts @@ -0,0 +1,73 @@ +import { QuartzTransformerPlugin } from "../types" + +export interface Options { + /** 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 +} + +const defaultOptions: Options = { + wikilinks: true, + removePredefinedAnchor: true, + removeHugoShortcode: true, + replaceFigureWithMdImg: true, +} + +const relrefRegex = new RegExp(/\[([^\]]+)\]\(\{\{< relref "([^"]+)" >\}\}\)/, "g") +const predefinedHeadingIdRegex = new RegExp(/(.*) {#(?:.*)}/, "g") +const hugoShortcodeRegex = new RegExp(/{{(.*)}}/, "g") +const figureTagRegex = new RegExp(/< ?figure src="(.*)" ?>/, "g") + +/** + * ox-hugo is an org exporter backend that exports org files to hugo-compatible + * markdown in an opinionated way. This plugin adds some tweaks to the generated + * markdown to make it compatible with quartz but the list of changes applied it + * is not exhaustive. + * */ +export const OxHugoFlavouredMarkdown: QuartzTransformerPlugin | undefined> = ( + userOpts, +) => { + const opts = { ...defaultOptions, ...userOpts } + return { + name: "OxHugoFlavouredMarkdown", + textTransform(_ctx, src) { + if (opts.wikilinks) { + src = src.toString() + src = src.replaceAll(relrefRegex, (value, ...capture) => { + const [text, link] = capture + return `[${text}](${link})` + }) + } + + if (opts.removePredefinedAnchor) { + src = src.toString() + src = src.replaceAll(predefinedHeadingIdRegex, (value, ...capture) => { + const [headingText] = capture + return headingText + }) + } + + if (opts.removeHugoShortcode) { + src = src.toString() + src = src.replaceAll(hugoShortcodeRegex, (value, ...capture) => { + const [scContent] = capture + return scContent + }) + } + + if (opts.replaceFigureWithMdImg) { + src = src.toString() + src = src.replaceAll(figureTagRegex, (value, ...capture) => { + const [src] = capture + return `![](${src})` + }) + } + return src + }, + } +}