taglist, mermaid
This commit is contained in:
parent
2bfe90b7e6
commit
9d2024b11c
@ -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()]
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -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>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
42
quartz/components/TagList.tsx
Normal file
42
quartz/components/TagList.tsx
Normal 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
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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"
|
||||||
|
@ -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'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -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'
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -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
28
quartz/resources.tsx
Normal 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[]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user