1 files deleted
2 files added
9 files modified
| | |
| | | Plugin.ContentPage({ |
| | | head: Component.Head(), |
| | | 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()] |
| | | }) |
| | | ] |
| | | }, |
| | |
| | | import { resolveToRoot } from "../path" |
| | | import { JSResourceToScriptElement } from "../resources" |
| | | import { QuartzComponentConstructor, QuartzComponentProps } from "./types" |
| | | |
| | | function Head({ fileData, externalResources }: QuartzComponentProps) { |
| | |
| | | <link rel="preconnect" href="https://fonts.googleapis.com" /> |
| | | <link rel="preconnect" href="https://fonts.gstatic.com" /> |
| | | {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> |
| | | } |
| | | |
| | |
| | | import { QuartzComponentConstructor, QuartzComponentProps } from "./types" |
| | | import style from "./styles/toc.scss" |
| | | |
| | | 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> |
| | | interface Options { |
| | | layout: 'modern' | 'quartz-3' |
| | | } |
| | | |
| | | 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 |
| New file |
| | |
| | | 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 |
| | |
| | | import ReadingTime from "./ReadingTime" |
| | | import Spacer from "./Spacer" |
| | | import TableOfContents from "./TableOfContents" |
| | | import TagList from "./TagList" |
| | | |
| | | export { |
| | | ArticleTitle, |
| | |
| | | PageTitle, |
| | | ReadingTime, |
| | | Spacer, |
| | | TableOfContents |
| | | TableOfContents, |
| | | TagList |
| | | } |
| | |
| | | import path from 'path' |
| | | import SlugAnchor from 'github-slugger' |
| | | |
| | | export const slugAnchor = new SlugAnchor() |
| | | import { slug as slugAnchor } from 'github-slugger' |
| | | |
| | | function slugSegment(s: string): string { |
| | | return s.replace(/\s/g, '-') |
| | |
| | | |
| | | export function slugify(s: string): string { |
| | | 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 rawSlugSegments = withoutFileExt.split(path.sep) |
| | | const slugParts: string = rawSlugSegments |
| | |
| | | import { StaticResources } from "../../resources" |
| | | import { JSResourceToScriptElement, StaticResources } from "../../resources" |
| | | import { EmitCallback, QuartzEmitterPlugin } from "../types" |
| | | import { ProcessedContent } from "../vfile" |
| | | import { render } from "preact-render-to-string" |
| | |
| | | const pageResources: StaticResources = { |
| | | css: [baseDir + "/index.css", ...resources.css], |
| | | js: [ |
| | | { src: baseDir + "/prescript.js", loadTime: "beforeDOMReady" }, |
| | | { src: baseDir + "/prescript.js", loadTime: "beforeDOMReady", contentType: "external" }, |
| | | ...resources.js, |
| | | { src: baseDir + "/postscript.js", loadTime: "afterDOMReady", type: 'module' } |
| | | { src: baseDir + "/postscript.js", loadTime: "afterDOMReady", moduleType: 'module', contentType: "external" } |
| | | ] |
| | | } |
| | | |
| | |
| | | </Body> |
| | | </div> |
| | | </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> |
| | | |
| | | const fp = file.data.slug + ".html" |
| | |
| | | { |
| | | // 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", |
| | | loadTime: "afterDOMReady" |
| | | loadTime: "afterDOMReady", |
| | | contentType: 'external' |
| | | } |
| | | ] |
| | | } |
| | |
| | | import { PluggableList } from "unified" |
| | | 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 { slugify } from "../../path" |
| | | import rehypeRaw from "rehype-raw" |
| | |
| | | highlight: boolean |
| | | wikilinks: boolean |
| | | callouts: boolean |
| | | mermaid: boolean |
| | | } |
| | | |
| | | const defaultOptions: Options = { |
| | | highlight: true, |
| | | wikilinks: true, |
| | | callouts: true |
| | | callouts: true, |
| | | mermaid: false, |
| | | } |
| | | |
| | | const icons = { |
| | |
| | | } |
| | | }) |
| | | } |
| | | |
| | | 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 |
| | | }, |
| | | |
| | | htmlPlugins() { |
| | | 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' |
| | | }] |
| | | } |
| | | } |
| | | } |
| | |
| | | import { Root } from "mdast" |
| | | import { visit } from "unist-util-visit" |
| | | import { toString } from "mdast-util-to-string" |
| | | import { slugAnchor } from "../../path" |
| | | import { slug as slugAnchor } from 'github-slugger' |
| | | |
| | | export interface Options { |
| | | maxDepth: 1 | 2 | 3 | 4 | 5 | 6, |
| | |
| | | toc.push({ |
| | | depth: node.depth, |
| | | text, |
| | | slug: slugAnchor.slug(text) |
| | | slug: slugAnchor(text) |
| | | }) |
| | | } |
| | | }) |
| New file |
| | |
| | | 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[] |
| | | } |