Jacky Zhao
2023-06-13 9d2024b11c7c24ec8112b5019504fc44b4e1a297
taglist, mermaid
1 files deleted
2 files added
9 files modified
190 ■■■■ changed files
quartz.config.ts 2 ●●● patch | view | raw | blame | history
quartz/components/Head.tsx 3 ●●●● patch | view | raw | blame | history
quartz/components/TableOfContents.tsx 45 ●●●●● patch | view | raw | blame | history
quartz/components/TagList.tsx 42 ●●●●● patch | view | raw | blame | history
quartz/components/index.ts 4 ●●● patch | view | raw | blame | history
quartz/path.ts 6 ●●●●● patch | view | raw | blame | history
quartz/plugins/emitters/contentPage.tsx 8 ●●●● patch | view | raw | blame | history
quartz/plugins/transformers/latex.ts 3 ●●●● patch | view | raw | blame | history
quartz/plugins/transformers/ofm.ts 35 ●●●●● patch | view | raw | blame | history
quartz/plugins/transformers/toc.ts 4 ●●●● patch | view | raw | blame | history
quartz/resources.ts 10 ●●●●● patch | view | raw | blame | history
quartz/resources.tsx 28 ●●●●● patch | view | raw | blame | history
quartz.config.ts
@@ -58,7 +58,7 @@
      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()]
      })
    ]
  },
quartz/components/Head.tsx
@@ -1,4 +1,5 @@
import { resolveToRoot } from "../path"
import { JSResourceToScriptElement } from "../resources"
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
function Head({ fileData, externalResources }: QuartzComponentProps) {
@@ -25,7 +26,7 @@
    <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>
}
quartz/components/TableOfContents.tsx
@@ -1,21 +1,38 @@
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
quartz/components/TagList.tsx
New 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
quartz/components/index.ts
@@ -6,6 +6,7 @@
import ReadingTime from "./ReadingTime"
import Spacer from "./Spacer"
import TableOfContents from "./TableOfContents"
import TagList from "./TagList"
export {
  ArticleTitle,
@@ -15,5 +16,6 @@
  PageTitle,
  ReadingTime,
  Spacer,
  TableOfContents
  TableOfContents,
  TagList
quartz/path.ts
@@ -1,7 +1,5 @@
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, '-')
@@ -9,7 +7,7 @@
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
quartz/plugins/emitters/contentPage.tsx
@@ -1,4 +1,4 @@
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"
@@ -37,9 +37,9 @@
        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" }
          ]
        }
@@ -63,7 +63,7 @@
              </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"
quartz/plugins/transformers/latex.ts
@@ -23,7 +23,8 @@
      {
        // 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'
      }
    ]
  }
quartz/plugins/transformers/ofm.ts
@@ -1,6 +1,6 @@
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"
@@ -11,12 +11,14 @@
  highlight: boolean
  wikilinks: boolean
  callouts: boolean
  mermaid: boolean
}
const defaultOptions: Options = {
  highlight: true,
  wikilinks: true,
  callouts: true
  callouts: true,
  mermaid: false,
}
const icons = {
@@ -246,11 +248,38 @@
          }
        })
      }
      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'
      }]
    }
  }
}
quartz/plugins/transformers/toc.ts
@@ -2,7 +2,7 @@
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,
@@ -40,7 +40,7 @@
                toc.push({
                  depth: node.depth,
                  text,
                  slug: slugAnchor.slug(text)
                  slug: slugAnchor(text)
                })
              }
            })
quartz/resources.ts
File was deleted
quartz/resources.tsx
New 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[]
}