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

feat(WIP): markdown parser rework #1427

Closed
wants to merge 40 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
adfe69e
Initial setup for markdown parser rewrite
saberzero1 Sep 18, 2024
d74b7e2
CommonMark
saberzero1 Sep 18, 2024
68e3a59
Restore default options
saberzero1 Sep 19, 2024
15a74bf
Split up parsers
saberzero1 Sep 19, 2024
add7f3a
Parser files
saberzero1 Sep 19, 2024
23e71e9
Obsidian Parsers (Arrow)
saberzero1 Sep 19, 2024
03f4cb8
Parser index
saberzero1 Sep 19, 2024
e697696
Delegate options to parsers
saberzero1 Sep 19, 2024
d9e0e0b
Obsidian Parsers (Highlights)
saberzero1 Sep 19, 2024
169a570
Obsidian Parsers (Wikilinks)
saberzero1 Sep 19, 2024
c3f1695
Added QuartzParserPlugin typing
saberzero1 Sep 19, 2024
50bbc82
Updated parsers with new typing
saberzero1 Sep 19, 2024
6808d7c
Updated Parser calls
saberzero1 Sep 19, 2024
e8f648d
Added custom parser template
saberzero1 Sep 19, 2024
81d892f
Updated QuartzParser plugin typing
saberzero1 Sep 19, 2024
2818bb1
Obsidian Parser (Callouts)
saberzero1 Sep 19, 2024
e464dc9
Updated config
saberzero1 Sep 19, 2024
f83ca16
Fixed Obsidian Highlights parser
saberzero1 Sep 19, 2024
e5558c7
Obsidian Parser (Comments)
saberzero1 Sep 19, 2024
ff398d6
Restore text
saberzero1 Sep 19, 2024
f176486
Updated parser typing + Mermaid parser
saberzero1 Sep 19, 2024
dd06210
Obsidian Parser (Checkboxes)
saberzero1 Sep 19, 2024
b07aaea
Parser skeleton
saberzero1 Sep 19, 2024
3e237cd
Removed unused
saberzero1 Sep 19, 2024
4878c24
vfile definition
saberzero1 Sep 19, 2024
ce5813d
Fix inline import path
saberzero1 Sep 19, 2024
e7965b1
remove duplicate rehypeRaw
saberzero1 Sep 19, 2024
dfd6abe
Separated Parsers and Plugins
saberzero1 Sep 19, 2024
97613c2
Added GitHub parsing
saberzero1 Sep 19, 2024
318a13c
Removed old GitHub Parser
saberzero1 Sep 19, 2024
7790db4
Obsidian Parser (Tags)
saberzero1 Sep 19, 2024
4e830a5
Obsidian Parser (InHtmlEmbed)
saberzero1 Sep 19, 2024
7a50b5e
Obsidian Parser (EmbedVideo)
saberzero1 Sep 19, 2024
cd52775
Obsidian Parser (YouTube)
saberzero1 Sep 19, 2024
7ed79cd
Obsidian Parser (BlockReference)
saberzero1 Sep 19, 2024
c1c8e26
Parsers work, tagpage/folderpage broken
saberzero1 Sep 19, 2024
b9f1243
Merge branch 'jackyzha0:v4' into markdown-parser-rework
saberzero1 Sep 22, 2024
8f96809
Rewrite parser application
saberzero1 Sep 22, 2024
35069bb
Merge branch 'markdown-parser-rework' of https://github.com/saberzero…
saberzero1 Sep 22, 2024
e73f28a
Formatting
saberzero1 Sep 22, 2024
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
2 changes: 1 addition & 1 deletion quartz.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" }),
Expand Down
Empty file.
43 changes: 43 additions & 0 deletions quartz/plugins/parsers/custom/default.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { QuartzParser } 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"
import { visit } from "unist-util-visit"
import { Element, Literal, Root as HtmlRoot } from "hast"

interface Options {
enabled: Boolean
}

const defaultOptions: Options = {
enabled: true,
}

export const CustomDefault: QuartzParser<Partial<Options>> = (userOpts) => {
const opts: Options = { ...defaultOptions, ...userOpts }
return {
name: "CustomDefault",
textTransform(_ctx, src: string | Buffer) {
if (src instanceof Buffer) {
src = src.toString()
}
return src
},
markdownPlugins(tree, file) {
return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction]
},
htmlPlugins(tree, file) {
if (opts.enabled) {
return () => {
visit(tree!, "element", (node) => {})
}
}
return {} as Pluggable
},
externalResources() {
const js = {} as JSResource
return js
},
}
}
1 change: 1 addition & 0 deletions quartz/plugins/parsers/custom/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import { CustomDefault } from "./default"
2 changes: 2 additions & 0 deletions quartz/plugins/parsers/github/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { GitHubSmartypants } from "./smartypants"
export { GitHubLinkheadings } from "./linkheadings"
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
import remarkGfm from "remark-gfm"
import smartypants from "remark-smartypants"
import { QuartzTransformerPlugin } from "../types"
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"

export interface Options {
enableSmartyPants: boolean
linkHeadings: boolean
interface Options {
enabled: Boolean
}

const defaultOptions: Options = {
enableSmartyPants: true,
linkHeadings: true,
enabled: true,
}

export const GitHubFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>> = (userOpts) => {
const opts = { ...defaultOptions, ...userOpts }
export const GitHubLinkheadings: QuartzParser<Partial<Options>> = (userOpts) => {
const opts: Options = { ...defaultOptions, ...userOpts }
return {
name: "GitHubFlavoredMarkdown",
markdownPlugins() {
return opts.enableSmartyPants ? [remarkGfm, smartypants] : [remarkGfm]
name: "GitHubLinkheadings",
textTransform(_ctx, src: string | Buffer) {
if (src instanceof Buffer) {
src = src.toString()
}
return src
},
markdownPlugins(tree, file) {
return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction]
},
htmlPlugins() {
if (opts.linkHeadings) {
if (opts.enabled) {
return [
rehypeSlug,
[
Expand Down Expand Up @@ -69,10 +74,13 @@ export const GitHubFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>> =
},
},
],
]
} else {
return []
] as Pluggable
}
return [] as Pluggable
},
externalResources() {
const js = {} as JSResource
return js
},
}
}
40 changes: 40 additions & 0 deletions quartz/plugins/parsers/github/smartypants.ts
Original file line number Diff line number Diff line change
@@ -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<Partial<Options>> = (userOpts) => {
const opts: Options = { ...defaultOptions, ...userOpts }
return {
name: "GitHubSmartypants",
textTransform(_ctx, src: string | Buffer) {
if (src instanceof Buffer) {
src = src.toString()
}
return src
},
markdownPlugins() {
if (opts.enabled) {
return smartypants as Pluggable
}
return {} as Pluggable
},
htmlPlugins() {
const plug: Pluggable = () => {}
return plug
},
externalResources() {
const js = {} as JSResource
return js
},
}
}
67 changes: 67 additions & 0 deletions quartz/plugins/parsers/obsidian/arrows.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
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"
import { Root } from "mdast"
import { Pluggable } from "unified"
import { mdastFindReplaceInHtml } from "../../transformers/markdown"

interface Options {
enabled: Boolean
}

const defaultOptions: Options = {
enabled: true,
}

const arrowMapping: Record<string, string> = {
"->": "&rarr;",
"-->": "&rArr;",
"=>": "&rArr;",
"==>": "&rArr;",
"<-": "&larr;",
"<--": "&lArr;",
"<=": "&lArr;",
"<==": "&lArr;",
}

const arrowRegex = new RegExp(/(-{1,2}>|={1,2}>|<-{1,2}|<={1,2})/g)

export const ObsidianArrow: QuartzParser<Partial<Options>> = (userOpts) => {
const opts: Options = { ...defaultOptions, ...userOpts }
return {
name: "ObsidianArrow",
textTransform(_ctx, src: string | Buffer) {
if (opts.enabled) {
if (src instanceof Buffer) {
src = src.toString()
}
}
return src
},
markdownPlugins() {
if (opts.enabled) {
return [
arrowRegex,
(value: string, ..._capture: string[]) => {
const maybeArrow = arrowMapping[value]
if (maybeArrow === undefined) return SKIP
return {
type: "html",
value: `<span>${maybeArrow}</span>`,
}
},
] as [RegExp, string | ReplaceFunction]
}
return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction]
},
htmlPlugins() {
const plug: Pluggable = () => {}
return plug
},
externalResources() {
const js = {} as JSResource
return js
},
}
}
113 changes: 113 additions & 0 deletions quartz/plugins/parsers/obsidian/blockreference.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
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<Partial<Options>> = (userOpts) => {
const opts: Options = { ...defaultOptions, ...userOpts }
return {
name: "ObsidianBlockReference",
textTransform(_ctx, src: string | Buffer) {
if (src instanceof Buffer) {
src = src.toString()
}
return src
},
markdownPlugins(_tree, _file) {
return [new RegExp(""), ""] as [RegExp, string | ReplaceFunction]
},
htmlPlugins(tree, file) {
const inlineTagTypes = new Set(["p", "li"])
const blockTagTypes = new Set(["blockquote"])
if (opts.enabled && tree !== undefined) {
return () => {
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 {} as Pluggable
},
externalResources() {
const js = {} as JSResource
return js
},
}
}
Loading