mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-12-30 16:24:06 -06:00
Merge branch 'v4' of https://github.com/jackyzha0/quartz into markdown-parser-rework-rework
This commit is contained in:
commit
4216e243a2
979
package-lock.json
generated
979
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
18
package.json
18
package.json
@ -41,10 +41,10 @@
|
|||||||
"@tweenjs/tween.js": "^25.0.0",
|
"@tweenjs/tween.js": "^25.0.0",
|
||||||
"async-mutex": "^0.5.0",
|
"async-mutex": "^0.5.0",
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
"chokidar": "^3.6.0",
|
"chokidar": "^4.0.1",
|
||||||
"cli-spinner": "^0.2.10",
|
"cli-spinner": "^0.2.10",
|
||||||
"d3": "^7.9.0",
|
"d3": "^7.9.0",
|
||||||
"esbuild-sass-plugin": "^2.16.1",
|
"esbuild-sass-plugin": "^3.3.1",
|
||||||
"flexsearch": "0.7.43",
|
"flexsearch": "0.7.43",
|
||||||
"github-slugger": "^2.0.0",
|
"github-slugger": "^2.0.0",
|
||||||
"globby": "^14.0.2",
|
"globby": "^14.0.2",
|
||||||
@ -54,19 +54,19 @@
|
|||||||
"hast-util-to-string": "^3.0.1",
|
"hast-util-to-string": "^3.0.1",
|
||||||
"is-absolute-url": "^4.0.1",
|
"is-absolute-url": "^4.0.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"lightningcss": "^1.26.0",
|
"lightningcss": "^1.27.0",
|
||||||
"mdast-util-find-and-replace": "^3.0.1",
|
"mdast-util-find-and-replace": "^3.0.1",
|
||||||
"mdast-util-to-hast": "^13.2.0",
|
"mdast-util-to-hast": "^13.2.0",
|
||||||
"mdast-util-to-string": "^4.0.0",
|
"mdast-util-to-string": "^4.0.0",
|
||||||
"micromorph": "^0.4.5",
|
"micromorph": "^0.4.5",
|
||||||
"pixi.js": "^8.4.1",
|
"pixi.js": "^8.4.1",
|
||||||
"preact": "^10.24.1",
|
"preact": "^10.24.2",
|
||||||
"preact-render-to-string": "^6.5.11",
|
"preact-render-to-string": "^6.5.11",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
"pretty-time": "^1.1.0",
|
"pretty-time": "^1.1.0",
|
||||||
"reading-time": "^1.5.0",
|
"reading-time": "^1.5.0",
|
||||||
"rehype-autolink-headings": "^7.1.0",
|
"rehype-autolink-headings": "^7.1.0",
|
||||||
"rehype-citation": "^2.1.1",
|
"rehype-citation": "^2.1.2",
|
||||||
"rehype-katex": "^7.0.1",
|
"rehype-katex": "^7.0.1",
|
||||||
"rehype-mathjax": "^6.0.0",
|
"rehype-mathjax": "^6.0.0",
|
||||||
"rehype-pretty-code": "^0.14.0",
|
"rehype-pretty-code": "^0.14.0",
|
||||||
@ -83,7 +83,7 @@
|
|||||||
"rfdc": "^1.4.1",
|
"rfdc": "^1.4.1",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
"serve-handler": "^6.1.5",
|
"serve-handler": "^6.1.5",
|
||||||
"shiki": "^1.18.0",
|
"shiki": "^1.22.0",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"to-vfile": "^8.0.0",
|
"to-vfile": "^8.0.0",
|
||||||
"toml": "^3.0.0",
|
"toml": "^3.0.0",
|
||||||
@ -99,14 +99,14 @@
|
|||||||
"@types/d3": "^7.4.3",
|
"@types/d3": "^7.4.3",
|
||||||
"@types/hast": "^3.0.4",
|
"@types/hast": "^3.0.4",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/node": "^22.7.4",
|
"@types/node": "^22.7.5",
|
||||||
"@types/pretty-time": "^1.1.5",
|
"@types/pretty-time": "^1.1.5",
|
||||||
"@types/source-map-support": "^0.5.10",
|
"@types/source-map-support": "^0.5.10",
|
||||||
"@types/ws": "^8.5.12",
|
"@types/ws": "^8.5.12",
|
||||||
"@types/yargs": "^17.0.33",
|
"@types/yargs": "^17.0.33",
|
||||||
"esbuild": "^0.19.9",
|
"esbuild": "^0.24.0",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"tsx": "^4.19.1",
|
"tsx": "^4.19.1",
|
||||||
"typescript": "^5.6.2"
|
"typescript": "^5.6.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,7 +44,7 @@ function canonicalizeCallout(calloutName: string): keyof typeof calloutMapping {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// from https://github.com/escwxyz/remark-obsidian-callout/blob/main/src/index.ts
|
// from https://github.com/escwxyz/remark-obsidian-callout/blob/main/src/index.ts
|
||||||
const calloutRegex = new RegExp(/^\[\!(\w+)\|?(.+?)?\]([+-]?)/)
|
const calloutRegex = new RegExp(/^\[\!([\w-]+)\|?(.+?)?\]([+-]?)/)
|
||||||
|
|
||||||
export const ObsidianFlavoredMarkdownCallouts: MarkdownTransformerPlugin = () => {
|
export const ObsidianFlavoredMarkdownCallouts: MarkdownTransformerPlugin = () => {
|
||||||
const mdastToHtml = (ast: PhrasingContent | Paragraph) => {
|
const mdastToHtml = (ast: PhrasingContent | Paragraph) => {
|
||||||
|
|||||||
@ -1,104 +1,13 @@
|
|||||||
import { QuartzTransformerPlugin, TextTransformerPlugin } from "../../types"
|
import { TextTransformerPlugin } from "../../types"
|
||||||
import { Root, Html, BlockContent, DefinitionContent, Paragraph, Code } from "mdast"
|
import { Paragraph } from "mdast"
|
||||||
import { Element, Literal, Root as HtmlRoot } from "hast"
|
import { Element, 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 { splitAnchor } from "../../../util/path"
|
||||||
import { JSResource } from "../../../util/resources"
|
|
||||||
// @ts-ignore
|
|
||||||
import calloutScript from "../../../components/scripts/callout.inline"
|
|
||||||
// @ts-ignore
|
|
||||||
import checkboxScript from "../../../components/scripts/checkbox.inline"
|
|
||||||
import { FilePath, pathToRoot, slugTag, slugifyFilePath } from "../../../util/path"
|
|
||||||
import { toHast } from "mdast-util-to-hast"
|
import { toHast } from "mdast-util-to-hast"
|
||||||
import { toHtml } from "hast-util-to-html"
|
import { toHtml } from "hast-util-to-html"
|
||||||
import { PhrasingContent } from "mdast-util-find-and-replace/lib"
|
import { PhrasingContent } from "mdast-util-find-and-replace/lib"
|
||||||
import { capitalize } from "../../../util/lang"
|
|
||||||
import { PluggableList } from "unified"
|
|
||||||
|
|
||||||
export interface Options {
|
|
||||||
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 defaultOptions: Options = {
|
|
||||||
comments: true,
|
|
||||||
highlight: true,
|
|
||||||
wikilinks: true,
|
|
||||||
callouts: true,
|
|
||||||
mermaid: true,
|
|
||||||
parseTags: true,
|
|
||||||
parseArrows: true,
|
|
||||||
parseBlockReferences: true,
|
|
||||||
enableInHtmlEmbed: false,
|
|
||||||
enableYouTubeEmbed: true,
|
|
||||||
enableVideoEmbed: true,
|
|
||||||
enableCheckbox: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
const arrowMapping: Record<string, string> = {
|
|
||||||
"->": "→",
|
|
||||||
"-->": "⇒",
|
|
||||||
"=>": "⇒",
|
|
||||||
"==>": "⇒",
|
|
||||||
"<-": "←",
|
|
||||||
"<--": "⇐",
|
|
||||||
"<=": "⇐",
|
|
||||||
"<==": "⇐",
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
export const externalLinkRegex = /^https?:\/\//i
|
export const externalLinkRegex = /^https?:\/\//i
|
||||||
|
|
||||||
export const arrowRegex = new RegExp(/(-{1,2}>|={1,2}>|<-{1,2}|<={1,2})/g)
|
|
||||||
|
|
||||||
// !? -> optional embedding
|
// !? -> optional embedding
|
||||||
// \[\[ -> open brace
|
// \[\[ -> open brace
|
||||||
// ([^\[\]\|\#]+) -> one or more non-special characters ([,],|, or #) (name)
|
// ([^\[\]\|\#]+) -> one or more non-special characters ([,],|, or #) (name)
|
||||||
@ -116,31 +25,7 @@ export const tableRegex = new RegExp(/^\|([^\n])+\|\n(\|)( ?:?-{3,}:? ?\|)+\n(\|
|
|||||||
// matches any wikilink, only used for escaping wikilinks inside tables
|
// matches any wikilink, only used for escaping wikilinks inside tables
|
||||||
export const tableWikilinkRegex = new RegExp(/(!?\[\[[^\]]*?\]\])/g)
|
export const tableWikilinkRegex = new RegExp(/(!?\[\[[^\]]*?\]\])/g)
|
||||||
|
|
||||||
const highlightRegex = new RegExp(/==([^=]+)==/g)
|
export const ObsidianFlavoredMarkdownWikilinks: TextTransformerPlugin = () => {
|
||||||
const commentRegex = new RegExp(/%%[\s\S]*?%%/g)
|
|
||||||
// from https://github.com/escwxyz/remark-obsidian-callout/blob/main/src/index.ts
|
|
||||||
const calloutRegex = new RegExp(/^\[\!(\w+)\|?(.+?)?\]([+-]?)/)
|
|
||||||
const calloutLineRegex = new RegExp(/^> *\[\!\w+\|?.*?\][+-]?.*$/gm)
|
|
||||||
// (?:^| ) -> 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,
|
|
||||||
)
|
|
||||||
const blockReferenceRegex = new RegExp(/\^([-_A-Za-z0-9]+)$/g)
|
|
||||||
const ytLinkRegex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/
|
|
||||||
const ytPlaylistLinkRegex = /[?&]list=([^#?&]*)/
|
|
||||||
const videoExtensionRegex = new RegExp(/\.(mp4|webm|ogg|avi|mov|flv|wmv|mkv|mpg|mpeg|3gp|m4v)$/)
|
|
||||||
const wikilinkImageEmbedRegex = new RegExp(
|
|
||||||
/^(?<alt>(?!^\d*x?\d*$).*?)?(\|?\s*?(?<width>\d+)(x(?<height>\d+))?)?$/,
|
|
||||||
)
|
|
||||||
|
|
||||||
export const ObsidianFlavoredMarkdownWikilinks: TextTransformerPlugin<Partial<Options>> = (
|
|
||||||
userOpts,
|
|
||||||
) => {
|
|
||||||
const opts = { ...defaultOptions, ...userOpts }
|
|
||||||
|
|
||||||
const mdastToHtml = (ast: PhrasingContent | Paragraph) => {
|
const mdastToHtml = (ast: PhrasingContent | Paragraph) => {
|
||||||
const hast = toHast(ast, { allowDangerousHtml: true })!
|
const hast = toHast(ast, { allowDangerousHtml: true })!
|
||||||
return toHtml(hast, { allowDangerousHtml: true })
|
return toHtml(hast, { allowDangerousHtml: true })
|
||||||
@ -149,64 +34,42 @@ export const ObsidianFlavoredMarkdownWikilinks: TextTransformerPlugin<Partial<Op
|
|||||||
return {
|
return {
|
||||||
name: "ObsidianFlavoredMarkdownWikilinks",
|
name: "ObsidianFlavoredMarkdownWikilinks",
|
||||||
transformation(_ctx, src) {
|
transformation(_ctx, src) {
|
||||||
// do comments at text level
|
|
||||||
if (opts.comments) {
|
|
||||||
if (src instanceof Buffer) {
|
|
||||||
src = src.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
src = src.replace(commentRegex, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// pre-transform blockquotes
|
|
||||||
if (opts.callouts) {
|
|
||||||
if (src instanceof Buffer) {
|
|
||||||
src = src.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
src = src.replace(calloutLineRegex, (value) => {
|
|
||||||
// force newline after title of callout
|
|
||||||
return value + "\n> "
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// pre-transform wikilinks (fix anchors to things that may contain illegal syntax e.g. codeblocks, latex)
|
// pre-transform wikilinks (fix anchors to things that may contain illegal syntax e.g. codeblocks, latex)
|
||||||
if (opts.wikilinks) {
|
|
||||||
if (src instanceof Buffer) {
|
if (src instanceof Buffer) {
|
||||||
src = src.toString()
|
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})`
|
||||||
}
|
}
|
||||||
|
|
||||||
// replace all wikilinks inside a table first
|
return `${embedDisplay}[[${fp}${displayAnchor}${displayAlias}]]`
|
||||||
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
|
return src
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user