taglist, mermaid

This commit is contained in:
Jacky Zhao 2023-06-12 22:41:42 -07:00
parent 397f4f7d3a
commit 9c6046a1f8
12 changed files with 149 additions and 41 deletions

View File

@ -58,7 +58,7 @@ const config: QuartzConfig = {
Plugin.ContentPage({ Plugin.ContentPage({
head: Component.Head(), head: Component.Head(),
header: [Component.PageTitle(), Component.Spacer(), Component.Darkmode()], header: [Component.PageTitle(), Component.Spacer(), Component.Darkmode()],
body: [Component.ArticleTitle(), Component.ReadingTime(), Component.TableOfContents(), Component.Content()] body: [Component.ArticleTitle(), Component.ReadingTime(), Component.TagList(), Component.TableOfContents(), Component.Content()]
}) })
] ]
}, },

View File

@ -1,4 +1,5 @@
import { resolveToRoot } from "../path" import { resolveToRoot } from "../path"
import { JSResourceToScriptElement } from "../resources"
import { QuartzComponentConstructor, QuartzComponentProps } from "./types" import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
function Head({ fileData, externalResources }: QuartzComponentProps) { function Head({ fileData, externalResources }: QuartzComponentProps) {
@ -25,7 +26,7 @@ function Head({ fileData, externalResources }: QuartzComponentProps) {
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" /> <link rel="preconnect" href="https://fonts.gstatic.com" />
{css.map(href => <link key={href} href={href} rel="stylesheet" type="text/css" spa-preserve />)} {css.map(href => <link key={href} href={href} rel="stylesheet" type="text/css" spa-preserve />)}
{js.filter(resource => resource.loadTime === "beforeDOMReady").map(resource => <script key={resource.src} {...resource} spa-preserve />)} {js.filter(resource => resource.loadTime === "beforeDOMReady").map(res => JSResourceToScriptElement(res, true))}
</head> </head>
} }

View File

@ -1,21 +1,38 @@
import { QuartzComponentConstructor, QuartzComponentProps } from "./types" import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import style from "./styles/toc.scss" import style from "./styles/toc.scss"
function TableOfContents({ fileData }: QuartzComponentProps) {
if (!fileData.toc) {
return null
}
return <details class="toc" open> interface Options {
<summary><h3>Table of Contents</h3></summary> layout: 'modern' | 'quartz-3'
<ul>
{fileData.toc.map(tocEntry => <li key={tocEntry.slug} class={`depth-${tocEntry.depth}`}>
<a href={`#${tocEntry.slug}`}>{tocEntry.text}</a>
</li>)}
</ul>
</details>
} }
TableOfContents.css = style const defaultOptions: Options = {
layout: 'quartz-3'
}
export default (() => TableOfContents) satisfies QuartzComponentConstructor export default ((opts?: Partial<Options>) => {
const layout = opts?.layout ?? defaultOptions.layout
if (layout === "modern") {
return function() {
return null // TODO (make this look like nextra)
}
} else {
function TableOfContents({ fileData }: QuartzComponentProps) {
if (!fileData.toc) {
return null
}
return <details class="toc" open>
<summary><h3>Table of Contents</h3></summary>
<ul>
{fileData.toc.map(tocEntry => <li key={tocEntry.slug} class={`depth-${tocEntry.depth}`}>
<a href={`#${tocEntry.slug}`}>{tocEntry.text}</a>
</li>)}
</ul>
</details>
}
TableOfContents.css = style
return TableOfContents
}
}) satisfies QuartzComponentConstructor

View File

@ -0,0 +1,42 @@
import { resolveToRoot } from "../path"
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { slug as slugAnchor } from 'github-slugger'
function TagList({ fileData }: QuartzComponentProps) {
const tags = fileData.frontmatter?.tags
const slug = fileData.slug!
const baseDir = resolveToRoot(slug)
if (tags) {
return <ul class="tags">{tags.map(tag => {
const display = `#${tag}`
const linkDest = baseDir + `/tags/${slugAnchor(tag)}`
return <li>
<a href={linkDest}>{display}</a>
</li>
})}</ul>
} else {
return null
}
}
TagList.css = `
.tags {
list-style: none;
display: flex;
padding-left: 0;
gap: 0.4rem;
& > li {
display: inline-block;
margin: 0;
& > a {
border-radius: 8px;
border: var(--lightgray) 1px solid;
padding: 0.2rem 0.5rem;
}
}
}
`
export default (() => TagList) satisfies QuartzComponentConstructor

View File

@ -6,6 +6,7 @@ import PageTitle from "./PageTitle"
import ReadingTime from "./ReadingTime" import ReadingTime from "./ReadingTime"
import Spacer from "./Spacer" import Spacer from "./Spacer"
import TableOfContents from "./TableOfContents" import TableOfContents from "./TableOfContents"
import TagList from "./TagList"
export { export {
ArticleTitle, ArticleTitle,
@ -15,5 +16,6 @@ export {
PageTitle, PageTitle,
ReadingTime, ReadingTime,
Spacer, Spacer,
TableOfContents TableOfContents,
TagList
} }

View File

@ -1,7 +1,5 @@
import path from 'path' import path from 'path'
import SlugAnchor from 'github-slugger' import { slug as slugAnchor } from 'github-slugger'
export const slugAnchor = new SlugAnchor()
function slugSegment(s: string): string { function slugSegment(s: string): string {
return s.replace(/\s/g, '-') return s.replace(/\s/g, '-')
@ -9,7 +7,7 @@ function slugSegment(s: string): string {
export function slugify(s: string): string { export function slugify(s: string): string {
const [fp, anchor] = s.split("#", 2) const [fp, anchor] = s.split("#", 2)
const sluggedAnchor = anchor === undefined ? "" : "#" + slugAnchor.slug(anchor) const sluggedAnchor = anchor === undefined ? "" : "#" + slugAnchor(anchor)
const withoutFileExt = fp.replace(new RegExp(path.extname(fp) + '$'), '') const withoutFileExt = fp.replace(new RegExp(path.extname(fp) + '$'), '')
const rawSlugSegments = withoutFileExt.split(path.sep) const rawSlugSegments = withoutFileExt.split(path.sep)
const slugParts: string = rawSlugSegments const slugParts: string = rawSlugSegments

View File

@ -1,4 +1,4 @@
import { StaticResources } from "../../resources" import { JSResourceToScriptElement, StaticResources } from "../../resources"
import { EmitCallback, QuartzEmitterPlugin } from "../types" import { EmitCallback, QuartzEmitterPlugin } from "../types"
import { ProcessedContent } from "../vfile" import { ProcessedContent } from "../vfile"
import { render } from "preact-render-to-string" import { render } from "preact-render-to-string"
@ -37,9 +37,9 @@ export const ContentPage: QuartzEmitterPlugin<Options> = (opts) => {
const pageResources: StaticResources = { const pageResources: StaticResources = {
css: [baseDir + "/index.css", ...resources.css], css: [baseDir + "/index.css", ...resources.css],
js: [ js: [
{ src: baseDir + "/prescript.js", loadTime: "beforeDOMReady" }, { src: baseDir + "/prescript.js", loadTime: "beforeDOMReady", contentType: "external" },
...resources.js, ...resources.js,
{ src: baseDir + "/postscript.js", loadTime: "afterDOMReady", type: 'module' } { src: baseDir + "/postscript.js", loadTime: "afterDOMReady", moduleType: 'module', contentType: "external" }
] ]
} }
@ -63,7 +63,7 @@ export const ContentPage: QuartzEmitterPlugin<Options> = (opts) => {
</Body> </Body>
</div> </div>
</body> </body>
{pageResources.js.filter(resource => resource.loadTime === "afterDOMReady").map(resource => <script key={resource.src} {...resource} />)} {pageResources.js.filter(resource => resource.loadTime === "afterDOMReady").map(res => JSResourceToScriptElement(res))}
</html> </html>
const fp = file.data.slug + ".html" const fp = file.data.slug + ".html"

View File

@ -23,7 +23,8 @@ export const Katex: QuartzTransformerPlugin = () => ({
{ {
// fix copy behaviour: https://github.com/KaTeX/KaTeX/blob/main/contrib/copy-tex/README.md // fix copy behaviour: https://github.com/KaTeX/KaTeX/blob/main/contrib/copy-tex/README.md
src: "https://cdn.jsdelivr.net/npm/katex@0.16.7/dist/contrib/copy-tex.min.js", src: "https://cdn.jsdelivr.net/npm/katex@0.16.7/dist/contrib/copy-tex.min.js",
loadTime: "afterDOMReady" loadTime: "afterDOMReady",
contentType: 'external'
} }
] ]
} }

View File

@ -1,6 +1,6 @@
import { PluggableList } from "unified" import { PluggableList } from "unified"
import { QuartzTransformerPlugin } from "../types" import { QuartzTransformerPlugin } from "../types"
import { Root, HTML, BlockContent, DefinitionContent } from 'mdast' import { Root, HTML, BlockContent, DefinitionContent, Code } from 'mdast'
import { findAndReplace } from "mdast-util-find-and-replace" import { findAndReplace } from "mdast-util-find-and-replace"
import { slugify } from "../../path" import { slugify } from "../../path"
import rehypeRaw from "rehype-raw" import rehypeRaw from "rehype-raw"
@ -11,12 +11,14 @@ export interface Options {
highlight: boolean highlight: boolean
wikilinks: boolean wikilinks: boolean
callouts: boolean callouts: boolean
mermaid: boolean
} }
const defaultOptions: Options = { const defaultOptions: Options = {
highlight: true, highlight: true,
wikilinks: true, wikilinks: true,
callouts: true callouts: true,
mermaid: false,
} }
const icons = { const icons = {
@ -246,11 +248,38 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
} }
}) })
} }
if (opts.mermaid) {
plugins.push(() => {
return (tree: Root, _file) => {
visit(tree, 'code', (node: Code) => {
if (node.lang === 'mermaid') {
node.data = {
hProperties: {
className: 'mermaid'
}
}
}
})
}
})
}
return plugins return plugins
}, },
htmlPlugins() { htmlPlugins() {
return [rehypeRaw] return [rehypeRaw]
},
externalResources: {
js: [{
script: `
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.mjs';
mermaid.initialize({ startOnLoad: true });
`,
loadTime: 'afterDOMReady',
moduleType: 'module',
contentType: 'inline'
}]
} }
} }
} }

View File

@ -2,7 +2,7 @@ import { QuartzTransformerPlugin } from "../types"
import { Root } from "mdast" import { Root } from "mdast"
import { visit } from "unist-util-visit" import { visit } from "unist-util-visit"
import { toString } from "mdast-util-to-string" import { toString } from "mdast-util-to-string"
import { slugAnchor } from "../../path" import { slug as slugAnchor } from 'github-slugger'
export interface Options { export interface Options {
maxDepth: 1 | 2 | 3 | 4 | 5 | 6, maxDepth: 1 | 2 | 3 | 4 | 5 | 6,
@ -40,7 +40,7 @@ export const TableOfContents: QuartzTransformerPlugin<Partial<Options> | undefin
toc.push({ toc.push({
depth: node.depth, depth: node.depth,
text, text,
slug: slugAnchor.slug(text) slug: slugAnchor(text)
}) })
} }
}) })

View File

@ -1,10 +0,0 @@
export interface JSResource {
src: string
loadTime: 'beforeDOMReady' | 'afterDOMReady'
type?: 'module'
}
export interface StaticResources {
css: string[],
js: JSResource[]
}

28
quartz/resources.tsx Normal file
View File

@ -0,0 +1,28 @@
import { randomUUID } from "crypto"
import { JSX } from "preact/jsx-runtime"
export type JSResource = {
loadTime: 'beforeDOMReady' | 'afterDOMReady'
moduleType?: 'module'
} & ({
src: string
contentType: 'external'
} | {
script: string
contentType: 'inline'
})
export function JSResourceToScriptElement(resource: JSResource, preserve?: boolean): JSX.Element {
const scriptType = resource.moduleType ?? 'application/javascript'
if (resource.contentType === 'external') {
return <script key={resource.src} src={resource.src} type={scriptType} spa-preserve={preserve} />
} else {
const content = resource.script
return <script key={randomUUID()} type={scriptType} spa-preserve={preserve}>{content}</script>
}
}
export interface StaticResources {
css: string[],
js: JSResource[]
}