mirror of
https://github.com/alrayyes/wiki.git
synced 2025-05-03 23:32:23 +00:00
refactor plugins to be functions instead of classes
This commit is contained in:
parent
b8c011410d
commit
352075ae81
20 changed files with 464 additions and 507 deletions
quartz/plugins/transformers
|
@ -1,4 +1,3 @@
|
|||
import { PluggableList } from "unified"
|
||||
import { Root as HTMLRoot } from 'hast'
|
||||
import { toString } from "hast-util-to-string"
|
||||
import { QuartzTransformerPlugin } from "../types"
|
||||
|
@ -11,41 +10,36 @@ const defaultOptions: Options = {
|
|||
descriptionLength: 150
|
||||
}
|
||||
|
||||
export class Description extends QuartzTransformerPlugin {
|
||||
name = "Description"
|
||||
opts: Options
|
||||
export const Description: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
|
||||
const opts = { ...defaultOptions, ...userOpts }
|
||||
return {
|
||||
name: "Description",
|
||||
markdownPlugins() {
|
||||
return []
|
||||
},
|
||||
htmlPlugins() {
|
||||
return [
|
||||
() => {
|
||||
return async (tree: HTMLRoot, file) => {
|
||||
const frontMatterDescription = file.data.frontmatter?.description
|
||||
const text = toString(tree)
|
||||
|
||||
constructor(opts?: Partial<Options>) {
|
||||
super()
|
||||
this.opts = { ...defaultOptions, ...opts }
|
||||
}
|
||||
const desc = frontMatterDescription ?? text
|
||||
const sentences = desc.replace(/\s+/g, ' ').split('.')
|
||||
let finalDesc = ""
|
||||
let sentenceIdx = 0
|
||||
const len = opts.descriptionLength
|
||||
while (finalDesc.length < len) {
|
||||
finalDesc += sentences[sentenceIdx] + '.'
|
||||
sentenceIdx++
|
||||
}
|
||||
|
||||
markdownPlugins(): PluggableList {
|
||||
return []
|
||||
}
|
||||
|
||||
htmlPlugins(): PluggableList {
|
||||
return [
|
||||
() => {
|
||||
return async (tree: HTMLRoot, file) => {
|
||||
const frontMatterDescription = file.data.frontmatter?.description
|
||||
const text = toString(tree)
|
||||
|
||||
const desc = frontMatterDescription ?? text
|
||||
const sentences = desc.replace(/\s+/g, ' ').split('.')
|
||||
let finalDesc = ""
|
||||
let sentenceIdx = 0
|
||||
const len = this.opts.descriptionLength
|
||||
while (finalDesc.length < len) {
|
||||
finalDesc += sentences[sentenceIdx] + '.'
|
||||
sentenceIdx++
|
||||
file.data.description = finalDesc
|
||||
file.data.text = text
|
||||
}
|
||||
|
||||
file.data.description = finalDesc
|
||||
file.data.text = text
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { PluggableList } from "unified"
|
||||
import matter from "gray-matter"
|
||||
import remarkFrontmatter from 'remark-frontmatter'
|
||||
import { QuartzTransformerPlugin } from "../types"
|
||||
|
@ -13,35 +12,30 @@ const defaultOptions: Options = {
|
|||
delims: '---'
|
||||
}
|
||||
|
||||
export class FrontMatter extends QuartzTransformerPlugin {
|
||||
name = "FrontMatter"
|
||||
opts: Options
|
||||
export const FrontMatter: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
|
||||
const opts = { ...defaultOptions, ...userOpts }
|
||||
return {
|
||||
name: "FrontMatter",
|
||||
markdownPlugins() {
|
||||
return [
|
||||
remarkFrontmatter,
|
||||
() => {
|
||||
return (_, file) => {
|
||||
const { data } = matter(file.value, opts)
|
||||
|
||||
constructor(opts?: Partial<Options>) {
|
||||
super()
|
||||
this.opts = { ...defaultOptions, ...opts }
|
||||
}
|
||||
|
||||
markdownPlugins(): PluggableList {
|
||||
return [
|
||||
remarkFrontmatter,
|
||||
() => {
|
||||
return (_, file) => {
|
||||
const { data } = matter(file.value, this.opts)
|
||||
|
||||
// fill in frontmatter
|
||||
file.data.frontmatter = {
|
||||
title: file.stem ?? "Untitled",
|
||||
tags: [],
|
||||
...data
|
||||
// fill in frontmatter
|
||||
file.data.frontmatter = {
|
||||
title: file.stem ?? "Untitled",
|
||||
tags: [],
|
||||
...data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
htmlPlugins(): PluggableList {
|
||||
return []
|
||||
]
|
||||
},
|
||||
htmlPlugins() {
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,27 +15,24 @@ const defaultOptions: Options = {
|
|||
linkHeadings: true
|
||||
}
|
||||
|
||||
export class GitHubFlavoredMarkdown extends QuartzTransformerPlugin {
|
||||
name = "GitHubFlavoredMarkdown"
|
||||
opts: Options
|
||||
|
||||
constructor(opts?: Partial<Options>) {
|
||||
super()
|
||||
this.opts = { ...defaultOptions, ...opts }
|
||||
}
|
||||
|
||||
markdownPlugins(): PluggableList {
|
||||
return this.opts.enableSmartyPants ? [remarkGfm] : [remarkGfm, smartypants]
|
||||
}
|
||||
|
||||
htmlPlugins(): PluggableList {
|
||||
return this.opts.linkHeadings
|
||||
? [rehypeSlug, [rehypeAutolinkHeadings, {
|
||||
behavior: 'append', content: {
|
||||
type: 'text',
|
||||
value: ' §'
|
||||
}
|
||||
}]]
|
||||
: []
|
||||
export const GitHubFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
|
||||
const opts = { ...defaultOptions, ...userOpts }
|
||||
return {
|
||||
name: "GitHubFlavoredMarkdown",
|
||||
markdownPlugins() {
|
||||
return opts.enableSmartyPants ? [remarkGfm] : [remarkGfm, smartypants]
|
||||
},
|
||||
htmlPlugins() {
|
||||
if (opts.linkHeadings) {
|
||||
return [rehypeSlug, [rehypeAutolinkHeadings, {
|
||||
behavior: 'append', content: {
|
||||
type: 'text',
|
||||
value: ' §'
|
||||
}
|
||||
}]]
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { PluggableList } from "unified"
|
||||
import fs from "fs"
|
||||
import path from 'path'
|
||||
import { Repository } from "@napi-rs/simple-git"
|
||||
|
@ -12,59 +11,51 @@ const defaultOptions: Options = {
|
|||
priority: ['frontmatter', 'git', 'filesystem']
|
||||
}
|
||||
|
||||
export class CreatedModifiedDate extends QuartzTransformerPlugin {
|
||||
name = "CreatedModifiedDate"
|
||||
opts: Options
|
||||
export const CreatedModifiedDate: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
|
||||
const opts = { ...defaultOptions, ...userOpts }
|
||||
return {
|
||||
name: "CreatedModifiedDate",
|
||||
markdownPlugins() {
|
||||
return [
|
||||
() => {
|
||||
let repo: Repository | undefined = undefined
|
||||
return async (_tree, file) => {
|
||||
let created: undefined | Date = undefined
|
||||
let modified: undefined | Date = undefined
|
||||
let published: undefined | Date = undefined
|
||||
|
||||
constructor(opts?: Partial<Options>) {
|
||||
super()
|
||||
this.opts = {
|
||||
...defaultOptions,
|
||||
...opts,
|
||||
}
|
||||
}
|
||||
const fp = path.join(file.cwd, file.data.filePath as string)
|
||||
for (const source of opts.priority) {
|
||||
if (source === "filesystem") {
|
||||
const st = await fs.promises.stat(fp)
|
||||
created ||= new Date(st.birthtimeMs)
|
||||
modified ||= new Date(st.mtimeMs)
|
||||
} else if (source === "frontmatter" && file.data.frontmatter) {
|
||||
created ||= file.data.frontmatter.date
|
||||
modified ||= file.data.frontmatter.lastmod
|
||||
modified ||= file.data.frontmatter["last-modified"]
|
||||
published ||= file.data.frontmatter.publishDate
|
||||
} else if (source === "git") {
|
||||
if (!repo) {
|
||||
repo = new Repository(file.cwd)
|
||||
}
|
||||
|
||||
markdownPlugins(): PluggableList {
|
||||
return [
|
||||
() => {
|
||||
let repo: Repository | undefined = undefined
|
||||
return async (_tree, file) => {
|
||||
let created: undefined | Date = undefined
|
||||
let modified: undefined | Date = undefined
|
||||
let published: undefined | Date = undefined
|
||||
|
||||
const fp = path.join(file.cwd, file.data.filePath as string)
|
||||
for (const source of this.opts.priority) {
|
||||
if (source === "filesystem") {
|
||||
const st = await fs.promises.stat(fp)
|
||||
created ||= new Date(st.birthtimeMs)
|
||||
modified ||= new Date(st.mtimeMs)
|
||||
} else if (source === "frontmatter" && file.data.frontmatter) {
|
||||
created ||= file.data.frontmatter.date
|
||||
modified ||= file.data.frontmatter.lastmod
|
||||
modified ||= file.data.frontmatter["last-modified"]
|
||||
published ||= file.data.frontmatter.publishDate
|
||||
} else if (source === "git") {
|
||||
if (!repo) {
|
||||
repo = new Repository(file.cwd)
|
||||
modified ||= new Date(await repo.getFileLatestModifiedDateAsync(file.data.filePath!))
|
||||
}
|
||||
}
|
||||
|
||||
modified ||= new Date(await repo.getFileLatestModifiedDateAsync(file.data.filePath!))
|
||||
file.data.dates = {
|
||||
created: created ?? new Date(),
|
||||
modified: modified ?? new Date(),
|
||||
published: published ?? new Date()
|
||||
}
|
||||
}
|
||||
|
||||
file.data.dates = {
|
||||
created: created ?? new Date(),
|
||||
modified: modified ?? new Date(),
|
||||
published: published ?? new Date()
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
htmlPlugins(): PluggableList {
|
||||
return []
|
||||
]
|
||||
},
|
||||
htmlPlugins() {
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,24 +1,20 @@
|
|||
import { PluggableList } from "unified"
|
||||
import remarkMath from "remark-math"
|
||||
import rehypeKatex from 'rehype-katex'
|
||||
import { StaticResources } from "../../resources"
|
||||
import { QuartzTransformerPlugin } from "../types"
|
||||
|
||||
export class Katex extends QuartzTransformerPlugin {
|
||||
name = "Katex"
|
||||
markdownPlugins(): PluggableList {
|
||||
export const Katex: QuartzTransformerPlugin = () => ({
|
||||
name: "Katex",
|
||||
markdownPlugins() {
|
||||
return [remarkMath]
|
||||
}
|
||||
|
||||
htmlPlugins(): PluggableList {
|
||||
},
|
||||
htmlPlugins() {
|
||||
return [
|
||||
[rehypeKatex, {
|
||||
output: 'html',
|
||||
}]
|
||||
]
|
||||
}
|
||||
|
||||
externalResources: Partial<StaticResources> = {
|
||||
},
|
||||
externalResources: {
|
||||
css: [
|
||||
// base css
|
||||
"https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css",
|
||||
|
@ -31,4 +27,4 @@ export class Katex extends QuartzTransformerPlugin {
|
|||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { PluggableList } from "unified"
|
||||
import { QuartzTransformerPlugin } from "../types"
|
||||
import { relative, relativeToRoot, slugify } from "../../path"
|
||||
import path from "path"
|
||||
|
@ -17,65 +16,60 @@ const defaultOptions: Options = {
|
|||
prettyLinks: true
|
||||
}
|
||||
|
||||
export class ResolveLinks extends QuartzTransformerPlugin {
|
||||
name = "LinkProcessing"
|
||||
opts: Options
|
||||
|
||||
constructor(opts?: Partial<Options>) {
|
||||
super()
|
||||
this.opts = { ...defaultOptions, ...opts }
|
||||
}
|
||||
|
||||
markdownPlugins(): PluggableList {
|
||||
return []
|
||||
}
|
||||
|
||||
htmlPlugins(): PluggableList {
|
||||
return [() => {
|
||||
return (tree, file) => {
|
||||
const curSlug = file.data.slug!
|
||||
const transformLink = (target: string) => {
|
||||
const targetSlug = slugify(decodeURI(target).trim())
|
||||
if (this.opts.markdownLinkResolution === 'relative' && !path.isAbsolute(targetSlug)) {
|
||||
return './' + relative(curSlug, targetSlug)
|
||||
} else {
|
||||
return './' + relativeToRoot(curSlug, targetSlug)
|
||||
export const ResolveLinks: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
|
||||
const opts = { ...defaultOptions, ...userOpts }
|
||||
return {
|
||||
name: "LinkProcessing",
|
||||
markdownPlugins() {
|
||||
return []
|
||||
},
|
||||
htmlPlugins() {
|
||||
return [() => {
|
||||
return (tree, file) => {
|
||||
const curSlug = file.data.slug!
|
||||
const transformLink = (target: string) => {
|
||||
const targetSlug = slugify(decodeURI(target).trim())
|
||||
if (opts.markdownLinkResolution === 'relative' && !path.isAbsolute(targetSlug)) {
|
||||
return './' + relative(curSlug, targetSlug)
|
||||
} else {
|
||||
return './' + relativeToRoot(curSlug, targetSlug)
|
||||
}
|
||||
}
|
||||
|
||||
visit(tree, 'element', (node, _index, _parent) => {
|
||||
// rewrite all links
|
||||
if (
|
||||
node.tagName === 'a' &&
|
||||
node.properties &&
|
||||
typeof node.properties.href === 'string'
|
||||
) {
|
||||
node.properties.className = isAbsoluteUrl(node.properties.href) ? "external" : "internal"
|
||||
|
||||
// don't process external links or intra-document anchors
|
||||
if (!(isAbsoluteUrl(node.properties.href) || node.properties.href.startsWith("#"))) {
|
||||
node.properties.href = transformLink(node.properties.href)
|
||||
}
|
||||
|
||||
// rewrite link internals if prettylinks is on
|
||||
if (opts.prettyLinks && node.children.length === 1 && node.children[0].type === 'text') {
|
||||
node.children[0].value = path.basename(node.children[0].value)
|
||||
}
|
||||
}
|
||||
|
||||
// transform all images
|
||||
if (
|
||||
node.tagName === 'img' &&
|
||||
node.properties &&
|
||||
typeof node.properties.src === 'string'
|
||||
) {
|
||||
if (!isAbsoluteUrl(node.properties.src)) {
|
||||
const ext = path.extname(node.properties.src)
|
||||
node.properties.src = transformLink(path.join("assets", node.properties.src)) + ext
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
visit(tree, 'element', (node, _index, _parent) => {
|
||||
// rewrite all links
|
||||
if (
|
||||
node.tagName === 'a' &&
|
||||
node.properties &&
|
||||
typeof node.properties.href === 'string'
|
||||
) {
|
||||
node.properties.className = isAbsoluteUrl(node.properties.href) ? "external" : "internal"
|
||||
|
||||
// don't process external links or intra-document anchors
|
||||
if (!(isAbsoluteUrl(node.properties.href) || node.properties.href.startsWith("#"))) {
|
||||
node.properties.href = transformLink(node.properties.href)
|
||||
}
|
||||
|
||||
// rewrite link internals if prettylinks is on
|
||||
if (this.opts.prettyLinks && node.children.length === 1 && node.children[0].type === 'text') {
|
||||
node.children[0].value = path.basename(node.children[0].value)
|
||||
}
|
||||
}
|
||||
|
||||
// transform all images
|
||||
if (
|
||||
node.tagName === 'img' &&
|
||||
node.properties &&
|
||||
typeof node.properties.src === 'string'
|
||||
) {
|
||||
if (!isAbsoluteUrl(node.properties.src)) {
|
||||
const ext = path.extname(node.properties.src)
|
||||
node.properties.src = transformLink(path.join("assets", node.properties.src)) + ext
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,174 +89,168 @@ const capitalize = (s: string): string => {
|
|||
return s.substring(0, 1).toUpperCase() + s.substring(1);
|
||||
}
|
||||
|
||||
export class ObsidianFlavoredMarkdown extends QuartzTransformerPlugin {
|
||||
name = "ObsidianFlavoredMarkdown"
|
||||
opts: Options
|
||||
export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
|
||||
const opts = { ...defaultOptions, ...userOpts }
|
||||
return {
|
||||
name: "ObsidianFlavoredMarkdown",
|
||||
markdownPlugins() {
|
||||
const plugins: PluggableList = []
|
||||
if (opts.wikilinks) {
|
||||
plugins.push(() => {
|
||||
// Match wikilinks
|
||||
// !? -> optional embedding
|
||||
// \[\[ -> open brace
|
||||
// ([^\[\]\|\#]+) -> one or more non-special characters ([,],|, or #) (name)
|
||||
// (#[^\[\]\|\#]+)? -> # then one or more non-special characters (heading link)
|
||||
// (|[^\[\]\|\#]+)? -> | then one or more non-special characters (alias)
|
||||
const backlinkRegex = new RegExp(/!?\[\[([^\[\]\|\#]+)(#[^\[\]\|\#]+)?(\|[^\[\]\|\#]+)?\]\]/, "g")
|
||||
return (tree: Root, _file) => {
|
||||
findAndReplace(tree, backlinkRegex, (value: string, ...capture: string[]) => {
|
||||
const [fp, rawHeader, rawAlias] = capture
|
||||
const anchor = rawHeader?.trim() ?? ""
|
||||
const alias = rawAlias?.slice(1).trim()
|
||||
|
||||
constructor(opts?: Partial<Options>) {
|
||||
super()
|
||||
this.opts = { ...defaultOptions, ...opts }
|
||||
}
|
||||
|
||||
markdownPlugins(): PluggableList {
|
||||
const plugins: PluggableList = []
|
||||
|
||||
if (this.opts.wikilinks) {
|
||||
plugins.push(() => {
|
||||
// Match wikilinks
|
||||
// !? -> optional embedding
|
||||
// \[\[ -> open brace
|
||||
// ([^\[\]\|\#]+) -> one or more non-special characters ([,],|, or #) (name)
|
||||
// (#[^\[\]\|\#]+)? -> # then one or more non-special characters (heading link)
|
||||
// (|[^\[\]\|\#]+)? -> | then one or more non-special characters (alias)
|
||||
const backlinkRegex = new RegExp(/!?\[\[([^\[\]\|\#]+)(#[^\[\]\|\#]+)?(\|[^\[\]\|\#]+)?\]\]/, "g")
|
||||
return (tree: Root, _file) => {
|
||||
findAndReplace(tree, backlinkRegex, (value: string, ...capture: string[]) => {
|
||||
const [fp, rawHeader, rawAlias] = capture
|
||||
const anchor = rawHeader?.trim() ?? ""
|
||||
const alias = rawAlias?.slice(1).trim()
|
||||
|
||||
// embed cases
|
||||
if (value.startsWith("!")) {
|
||||
const ext = path.extname(fp).toLowerCase()
|
||||
const url = slugify(fp.trim()) + ext
|
||||
if ([".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg"].includes(ext)) {
|
||||
const dims = alias ?? ""
|
||||
let [width, height] = dims.split("x", 2)
|
||||
width ||= "auto"
|
||||
height ||= "auto"
|
||||
return {
|
||||
type: 'image',
|
||||
url,
|
||||
data: {
|
||||
hProperties: {
|
||||
width, height
|
||||
// embed cases
|
||||
if (value.startsWith("!")) {
|
||||
const ext = path.extname(fp).toLowerCase()
|
||||
const url = slugify(fp.trim()) + ext
|
||||
if ([".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg"].includes(ext)) {
|
||||
const dims = alias ?? ""
|
||||
let [width, height] = dims.split("x", 2)
|
||||
width ||= "auto"
|
||||
height ||= "auto"
|
||||
return {
|
||||
type: 'image',
|
||||
url,
|
||||
data: {
|
||||
hProperties: {
|
||||
width, height
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ([".mp4", ".webm", ".ogv", ".mov", ".mkv"].includes(ext)) {
|
||||
return {
|
||||
type: 'html',
|
||||
value: `<video src="${url}" controls></video>`
|
||||
}
|
||||
} else if ([".mp3", ".webm", ".wav", ".m4a", ".ogg", ".3gp", ".flac"].includes(ext)) {
|
||||
return {
|
||||
type: 'html',
|
||||
value: `<audio src="${url}" controls></audio>`
|
||||
}
|
||||
} else if ([".pdf"].includes(ext)) {
|
||||
return {
|
||||
type: 'html',
|
||||
value: `<iframe src="${url}"></iframe>`
|
||||
}
|
||||
}
|
||||
} else if ([".mp4", ".webm", ".ogv", ".mov", ".mkv"].includes(ext)) {
|
||||
return {
|
||||
type: 'html',
|
||||
value: `<video src="${url}" controls></video>`
|
||||
}
|
||||
} else if ([".mp3", ".webm", ".wav", ".m4a", ".ogg", ".3gp", ".flac"].includes(ext)) {
|
||||
return {
|
||||
type: 'html',
|
||||
value: `<audio src="${url}" controls></audio>`
|
||||
}
|
||||
} else if ([".pdf"].includes(ext)) {
|
||||
return {
|
||||
type: 'html',
|
||||
value: `<iframe src="${url}"></iframe>`
|
||||
}
|
||||
// otherwise, fall through to regular link
|
||||
}
|
||||
// otherwise, fall through to regular link
|
||||
}
|
||||
|
||||
// internal link
|
||||
const url = slugify(fp.trim() + anchor)
|
||||
return {
|
||||
type: 'link',
|
||||
url,
|
||||
children: [{
|
||||
type: 'text',
|
||||
value: alias ?? fp
|
||||
}]
|
||||
}
|
||||
})
|
||||
// internal link
|
||||
const url = slugify(fp.trim() + anchor)
|
||||
return {
|
||||
type: 'link',
|
||||
url,
|
||||
children: [{
|
||||
type: 'text',
|
||||
value: alias ?? fp
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (this.opts.highlight) {
|
||||
plugins.push(() => {
|
||||
// Match highlights
|
||||
const highlightRegex = new RegExp(/==(.+)==/, "g")
|
||||
return (tree: Root, _file) => {
|
||||
findAndReplace(tree, highlightRegex, (_value: string, ...capture: string[]) => {
|
||||
const [inner] = capture
|
||||
return {
|
||||
type: 'html',
|
||||
value: `<span class="text-highlight">${inner}</span>`
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
if (opts.highlight) {
|
||||
plugins.push(() => {
|
||||
// Match highlights
|
||||
const highlightRegex = new RegExp(/==(.+)==/, "g")
|
||||
return (tree: Root, _file) => {
|
||||
findAndReplace(tree, highlightRegex, (_value: string, ...capture: string[]) => {
|
||||
const [inner] = capture
|
||||
return {
|
||||
type: 'html',
|
||||
value: `<span class="text-highlight">${inner}</span>`
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (this.opts.callouts) {
|
||||
plugins.push(() => {
|
||||
// from https://github.com/escwxyz/remark-obsidian-callout/blob/main/src/index.ts
|
||||
const calloutRegex = new RegExp(/^\[\!(\w+)\]([+-]?)/)
|
||||
return (tree: Root, _file) => {
|
||||
visit(tree, "blockquote", (node) => {
|
||||
if (node.children.length === 0) {
|
||||
return
|
||||
}
|
||||
if (opts.callouts) {
|
||||
plugins.push(() => {
|
||||
// from https://github.com/escwxyz/remark-obsidian-callout/blob/main/src/index.ts
|
||||
const calloutRegex = new RegExp(/^\[\!(\w+)\]([+-]?)/)
|
||||
return (tree: Root, _file) => {
|
||||
visit(tree, "blockquote", (node) => {
|
||||
if (node.children.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// find first line
|
||||
const firstChild = node.children[0]
|
||||
if (firstChild.type !== "paragraph" || firstChild.children[0]?.type !== "text") {
|
||||
return
|
||||
}
|
||||
// find first line
|
||||
const firstChild = node.children[0]
|
||||
if (firstChild.type !== "paragraph" || firstChild.children[0]?.type !== "text") {
|
||||
return
|
||||
}
|
||||
|
||||
const text = firstChild.children[0].value
|
||||
const [firstLine, ...remainingLines] = text.split("\n")
|
||||
const remainingText = remainingLines.join("\n")
|
||||
const text = firstChild.children[0].value
|
||||
const [firstLine, ...remainingLines] = text.split("\n")
|
||||
const remainingText = remainingLines.join("\n")
|
||||
|
||||
const match = firstLine.match(calloutRegex)
|
||||
if (match && match.input) {
|
||||
const [calloutDirective, typeString, collapseChar] = match
|
||||
const calloutType = typeString.toLowerCase() as keyof typeof callouts
|
||||
const collapse = collapseChar === "+" || collapseChar === "-"
|
||||
const defaultState = collapseChar === "-" ? "collapsed" : "expanded"
|
||||
const title = match.input.slice(calloutDirective.length).trim() || capitalize(calloutType)
|
||||
const match = firstLine.match(calloutRegex)
|
||||
if (match && match.input) {
|
||||
const [calloutDirective, typeString, collapseChar] = match
|
||||
const calloutType = typeString.toLowerCase() as keyof typeof callouts
|
||||
const collapse = collapseChar === "+" || collapseChar === "-"
|
||||
const defaultState = collapseChar === "-" ? "collapsed" : "expanded"
|
||||
const title = match.input.slice(calloutDirective.length).trim() || capitalize(calloutType)
|
||||
|
||||
const titleNode: HTML = {
|
||||
type: "html",
|
||||
value: `<div
|
||||
const titleNode: HTML = {
|
||||
type: "html",
|
||||
value: `<div
|
||||
class="callout-title"
|
||||
>
|
||||
<div class="callout-icon">${callouts[canonicalizeCallout(calloutType)]}</div>
|
||||
<div class="callout-title-inner">${title}</div>
|
||||
</div>`
|
||||
}
|
||||
}
|
||||
|
||||
const blockquoteContent: (BlockContent | DefinitionContent)[] = [titleNode]
|
||||
if (remainingText.length > 0) {
|
||||
blockquoteContent.push({
|
||||
type: 'paragraph',
|
||||
children: [{
|
||||
type: 'text',
|
||||
value: remainingText,
|
||||
}]
|
||||
const blockquoteContent: (BlockContent | DefinitionContent)[] = [titleNode]
|
||||
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)
|
||||
|
||||
// add properties to base blockquote
|
||||
node.data = {
|
||||
hProperties: {
|
||||
...(node.data?.hProperties ?? {}),
|
||||
className: `callout ${collapse ? "is-collapsible" : ""} ${defaultState === "collapsed" ? "is-collapsed" : ""}`,
|
||||
"data-callout": calloutType,
|
||||
"data-callout-fold": collapse,
|
||||
// add properties to base blockquote
|
||||
node.data = {
|
||||
hProperties: {
|
||||
...(node.data?.hProperties ?? {}),
|
||||
className: `callout ${collapse ? "is-collapsible" : ""} ${defaultState === "collapsed" ? "is-collapsed" : ""}`,
|
||||
"data-callout": calloutType,
|
||||
"data-callout-fold": collapse,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
return plugins
|
||||
},
|
||||
|
||||
htmlPlugins() {
|
||||
return [rehypeRaw]
|
||||
}
|
||||
|
||||
return plugins
|
||||
}
|
||||
|
||||
htmlPlugins(): PluggableList {
|
||||
return [rehypeRaw]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
import { PluggableList } from "unified"
|
||||
import { QuartzTransformerPlugin } from "../types"
|
||||
import rehypePrettyCode, { Options as CodeOptions } from "rehype-pretty-code"
|
||||
|
||||
export class SyntaxHighlighting extends QuartzTransformerPlugin {
|
||||
name = "SyntaxHighlighting"
|
||||
|
||||
markdownPlugins(): PluggableList {
|
||||
export const SyntaxHighlighting: QuartzTransformerPlugin = () => ({
|
||||
name: "SyntaxHighlighting",
|
||||
markdownPlugins() {
|
||||
return []
|
||||
}
|
||||
|
||||
htmlPlugins(): PluggableList {
|
||||
},
|
||||
htmlPlugins() {
|
||||
return [[rehypePrettyCode, {
|
||||
theme: 'css-variables',
|
||||
onVisitLine(node) {
|
||||
|
@ -25,4 +22,4 @@ export class SyntaxHighlighting extends QuartzTransformerPlugin {
|
|||
},
|
||||
} satisfies Partial<CodeOptions>]]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { PluggableList } from "unified"
|
||||
import { QuartzTransformerPlugin } from "../types"
|
||||
import { Root } from "mdast"
|
||||
import { visit } from "unist-util-visit"
|
||||
|
@ -23,44 +22,39 @@ interface TocEntry {
|
|||
slug: string
|
||||
}
|
||||
|
||||
export class TableOfContents extends QuartzTransformerPlugin {
|
||||
name = "TableOfContents"
|
||||
opts: Options
|
||||
export const TableOfContents: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
|
||||
const opts = { ...defaultOptions, ...userOpts }
|
||||
return {
|
||||
name: "TableOfContents",
|
||||
markdownPlugins() {
|
||||
return [() => {
|
||||
return async (tree: Root, file) => {
|
||||
const display = file.data.frontmatter?.enableToc ?? opts.showByDefault
|
||||
if (display) {
|
||||
const toc: TocEntry[] = []
|
||||
let highestDepth: number = opts.maxDepth
|
||||
visit(tree, 'heading', (node) => {
|
||||
if (node.depth <= opts.maxDepth) {
|
||||
const text = toString(node)
|
||||
highestDepth = Math.min(highestDepth, node.depth)
|
||||
toc.push({
|
||||
depth: node.depth,
|
||||
text,
|
||||
slug: slugAnchor.slug(text)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
constructor(opts?: Partial<Options>) {
|
||||
super()
|
||||
this.opts = { ...defaultOptions, ...opts }
|
||||
}
|
||||
|
||||
markdownPlugins(): PluggableList {
|
||||
return [() => {
|
||||
return async (tree: Root, file) => {
|
||||
const display = file.data.frontmatter?.enableToc ?? this.opts.showByDefault
|
||||
if (display) {
|
||||
const toc: TocEntry[] = []
|
||||
let highestDepth: number = this.opts.maxDepth
|
||||
visit(tree, 'heading', (node) => {
|
||||
if (node.depth <= this.opts.maxDepth) {
|
||||
const text = toString(node)
|
||||
highestDepth = Math.min(highestDepth, node.depth)
|
||||
toc.push({
|
||||
depth: node.depth,
|
||||
text,
|
||||
slug: slugAnchor.slug(text)
|
||||
})
|
||||
if (toc.length > opts.minEntries) {
|
||||
file.data.toc = toc.map(entry => ({ ...entry, depth: entry.depth - highestDepth }))
|
||||
}
|
||||
})
|
||||
|
||||
if (toc.length > this.opts.minEntries) {
|
||||
file.data.toc = toc.map(entry => ({ ...entry, depth: entry.depth - highestDepth }))
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
htmlPlugins(): PluggableList {
|
||||
return []
|
||||
}]
|
||||
},
|
||||
htmlPlugins() {
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue