Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Markdown Parser Rework #1496

Open
wants to merge 14 commits into
base: v4
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 7 additions & 19 deletions quartz.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { QuartzConfig } from "./quartz/cfg"
import * as Plugin from "./quartz/plugins"
import * as Text from "./quartz/plugins/transformers/text"
import * as Markdown from "./quartz/plugins/transformers/markdown"
import * as Html from "./quartz/plugins/transformers/html"
import * as Resources from "./quartz/plugins/transformers/resources"
import * as Presets from "./quartz/plugins/transformers/presets"

/**
* Quartz 4.0 Configuration
Expand Down Expand Up @@ -54,25 +59,8 @@ const config: QuartzConfig = {
},
},
plugins: {
transformers: [
Plugin.FrontMatter(),
Plugin.CreatedModifiedDate({
priority: ["frontmatter", "filesystem"],
}),
Plugin.SyntaxHighlighting({
theme: {
light: "github-light",
dark: "github-dark",
},
keepBackground: false,
}),
Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }),
Plugin.GitHubFlavoredMarkdown(),
Plugin.TableOfContents(),
Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }),
Plugin.Description(),
Plugin.Latex({ renderEngine: "katex" }),
],
//transformers: Presets.DefaultPreset(),
transformers: Presets.ObsidianPreset(),
filters: [Plugin.RemoveDrafts()],
emitters: [
Plugin.AliasRedirects(),
Expand Down
16 changes: 13 additions & 3 deletions quartz/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,21 @@ async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) {
const output = argv.output

const pluginCount = Object.values(cfg.plugins).flat().length
const pluginNames = (key: "transformers" | "filters" | "emitters") =>
cfg.plugins[key].map((plugin) => plugin.name)
const pluginNames = (key: "filters" | "emitters") => cfg.plugins[key].map((plugin) => plugin.name)
if (argv.verbose) {
console.log(`Loaded ${pluginCount} plugins`)
console.log(` Transformers: ${pluginNames("transformers").join(", ")}`)
console.log(
` Text Transformers: ${cfg.plugins["transformers"].textTransformers.map((plugin) => plugin.name).join(", ")}`,
)
console.log(
` Markdown Transformers: ${cfg.plugins["transformers"].markdownTransformers.map((plugin) => plugin.name).join(", ")}`,
)
console.log(
` Html Transformers: ${cfg.plugins["transformers"].htmlTransformers.map((plugin) => plugin.name).join(", ")}`,
)
console.log(
` External Resources: ${cfg.plugins["transformers"].externalResources.map((plugin) => plugin.name).join(", ")}`,
)
console.log(` Filters: ${pluginNames("filters").join(", ")}`)
console.log(` Emitters: ${pluginNames("emitters").join(", ")}`)
}
Expand Down
9 changes: 6 additions & 3 deletions quartz/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ export function getStaticResourcesFromPlugins(ctx: BuildCtx) {
js: [],
}

for (const transformer of ctx.cfg.plugins.transformers) {
const res = transformer.externalResources ? transformer.externalResources(ctx) : {}
for (const transformer of ctx.cfg.plugins.transformers.externalResources) {
const res = transformer ? transformer.transformation(ctx) : {}
if (res?.js) {
staticResources.js.push(...res.js)
}
Expand Down Expand Up @@ -38,7 +38,10 @@ export function getStaticResourcesFromPlugins(ctx: BuildCtx) {
return staticResources
}

export * from "./transformers"
export * as Text from "./transformers/text"
export * as Markdown from "./transformers/markdown"
export * as Html from "./transformers/html"
export * as Resources from "./transformers/resources"
export * from "./filters"
export * from "./emitters"

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import rehypeCitation from "rehype-citation"
import { PluggableList } from "unified"
import { visit } from "unist-util-visit"
import { QuartzTransformerPlugin } from "../types"
import { HtmlTransformerPlugin } from "../../types"

export interface Options {
bibliographyFile: string
Expand All @@ -17,11 +17,11 @@ const defaultOptions: Options = {
csl: "apa",
}

export const Citations: QuartzTransformerPlugin<Partial<Options>> = (userOpts) => {
export const Citations: HtmlTransformerPlugin<Partial<Options>> = (userOpts) => {
const opts = { ...defaultOptions, ...userOpts }
return {
name: "Citations",
htmlPlugins(ctx) {
transformation(ctx) {
const plugins: PluggableList = []

// Add rehype-citation to the list of plugins
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Root as HTMLRoot } from "hast"
import { toString } from "hast-util-to-string"
import { QuartzTransformerPlugin } from "../types"
import { escapeHTML } from "../../util/escape"
import { HtmlTransformerPlugin } from "../../types"
import { escapeHTML } from "../../../util/escape"

export interface Options {
descriptionLength: number
Expand All @@ -18,11 +18,11 @@ const urlRegex = new RegExp(
"g",
)

export const Description: QuartzTransformerPlugin<Partial<Options>> = (userOpts) => {
export const Description: HtmlTransformerPlugin<Partial<Options>> = (userOpts) => {
const opts = { ...defaultOptions, ...userOpts }
return {
name: "Description",
htmlPlugins() {
transformation() {
return [
() => {
return async (tree: HTMLRoot, file) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
import remarkGfm from "remark-gfm"
import smartypants from "remark-smartypants"
import { QuartzTransformerPlugin } from "../types"
import { HtmlTransformerPlugin } 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: QuartzTransformerPlugin<Partial<Options>> = (userOpts) => {
export const GitHubFlavoredMarkdownLinkHeadings: HtmlTransformerPlugin<Partial<Options>> = (
userOpts,
) => {
const opts = { ...defaultOptions, ...userOpts }
return {
name: "GitHubFlavoredMarkdown",
markdownPlugins() {
return opts.enableSmartyPants ? [remarkGfm, smartypants] : [remarkGfm]
},
htmlPlugins() {
name: "GitHubFlavoredMarkdownLinkHeadings",
transformation() {
if (opts.linkHeadings) {
return [
rehypeSlug,
Expand Down
9 changes: 9 additions & 0 deletions quartz/plugins/transformers/html/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export { Citations } from "./citations"
export { CrawlLinks } from "./links"
export { Description } from "./description"
export { GitHubFlavoredMarkdownLinkHeadings } from "./gfmLinkHeadings"
export { Latex } from "./latex"
export { SyntaxHighlighting } from "./syntax"
export { ObsidianFlavoredMarkdownBlockReferences } from "./ofmBlockReferences"
export { ObsidianFlavoredMarkdownCheckbox } from "./ofmCheckbox"
export { ObsidianFlavoredMarkdownYouTubeEmbed } from "./ofmYoutubeEmbed"
27 changes: 27 additions & 0 deletions quartz/plugins/transformers/html/latex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import rehypeKatex from "rehype-katex"
import rehypeMathjax from "rehype-mathjax/svg"
import { HtmlTransformerPlugin } from "../../types"

interface Options {
renderEngine: "katex" | "mathjax"
customMacros: MacroType
}

interface MacroType {
[key: string]: string
}

export const Latex: HtmlTransformerPlugin<Partial<Options>> = (opts) => {
const engine = opts?.renderEngine ?? "katex"
const macros = opts?.customMacros ?? {}
return {
name: "Latex",
transformation() {
if (engine === "katex") {
return [[rehypeKatex, { output: "html", macros }]]
} else {
return [[rehypeMathjax, { macros }]]
}
},
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { QuartzTransformerPlugin } from "../types"
import { HtmlTransformerPlugin, QuartzTransformerPlugin } from "../../types"
import {
FullSlug,
RelativeURL,
Expand All @@ -8,7 +8,7 @@ import {
simplifySlug,
splitAnchor,
transformLink,
} from "../../util/path"
} from "../../../util/path"
import path from "path"
import { visit } from "unist-util-visit"
import isAbsoluteUrl from "is-absolute-url"
Expand All @@ -32,11 +32,11 @@ const defaultOptions: Options = {
externalLinkIcon: true,
}

export const CrawlLinks: QuartzTransformerPlugin<Partial<Options>> = (userOpts) => {
export const CrawlLinks: HtmlTransformerPlugin<Partial<Options>> = (userOpts) => {
const opts = { ...defaultOptions, ...userOpts }
return {
name: "LinkProcessing",
htmlPlugins(ctx) {
transformation(ctx) {
return [
() => {
return (tree: Root, file) => {
Expand Down
99 changes: 99 additions & 0 deletions quartz/plugins/transformers/html/ofmBlockReferences.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { HtmlTransformerPlugin } from "../../types"
import { Element, Literal, Root as HtmlRoot } from "hast"
import rehypeRaw from "rehype-raw"
import { visit } from "unist-util-visit"
import { PluggableList } from "unified"

const blockReferenceRegex = new RegExp(/\^([-_A-Za-z0-9]+)$/g)

export const ObsidianFlavoredMarkdownBlockReferences: HtmlTransformerPlugin = () => {
return {
name: "ObsidianFlavoredMarkdownBlockReferences",
transformation() {
const plugins: PluggableList = [rehypeRaw]

plugins.push(() => {
const inlineTagTypes = new Set(["p", "li"])
const blockTagTypes = new Set(["blockquote"])
return (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 plugins
},
}
}

declare module "vfile" {
interface DataMap {
blocks: Record<string, Element>
htmlAst: HtmlRoot
}
}
32 changes: 32 additions & 0 deletions quartz/plugins/transformers/html/ofmCheckbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { HtmlTransformerPlugin } from "../../types"
import { Element, Root as HtmlRoot } from "hast"
import rehypeRaw from "rehype-raw"
import { visit } from "unist-util-visit"
import { PluggableList } from "unified"

export const ObsidianFlavoredMarkdownCheckbox: HtmlTransformerPlugin = () => {
return {
name: "ObsidianFlavoredMarkdownCheckbox",
transformation() {
const plugins: PluggableList = [rehypeRaw]

plugins.push(() => {
return (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 plugins
},
}
}
Loading