docs + various polish
This commit is contained in:
		@@ -54,21 +54,16 @@ export default async function buildQuartz(argv: Argv, version: string) {
 | 
			
		||||
 | 
			
		||||
  if (argv.serve) {
 | 
			
		||||
    const server = http.createServer(async (req, res) => {
 | 
			
		||||
      let status = 200
 | 
			
		||||
      const result = await serveHandler(req, res, {
 | 
			
		||||
      await serveHandler(req, res, {
 | 
			
		||||
        public: output,
 | 
			
		||||
        directoryListing: false,
 | 
			
		||||
      }, {
 | 
			
		||||
        async sendError() {
 | 
			
		||||
          status = 404
 | 
			
		||||
        },
 | 
			
		||||
      })
 | 
			
		||||
      const status = res.statusCode
 | 
			
		||||
      const statusString = status === 200 ? chalk.green(`[${status}]`) : chalk.red(`[${status}]`)
 | 
			
		||||
      console.log(statusString + chalk.grey(` ${req.url}`))
 | 
			
		||||
      return result
 | 
			
		||||
    })
 | 
			
		||||
    server.listen(argv.port)
 | 
			
		||||
    console.log(`Started a Quartz server listening at http://localhost:${argv.port}`)
 | 
			
		||||
    console.log(chalk.cyan(`Started a Quartz server listening at http://localhost:${argv.port}`))
 | 
			
		||||
    console.log('hint: exit with ctrl+c')
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,8 @@ import { QuartzComponent } from "./components/types"
 | 
			
		||||
import { PluginTypes } from "./plugins/types"
 | 
			
		||||
import { Theme } from "./theme"
 | 
			
		||||
 | 
			
		||||
export type Analytics = null
 | 
			
		||||
export type Analytics =
 | 
			
		||||
  | null
 | 
			
		||||
  | {
 | 
			
		||||
    provider: 'plausible'
 | 
			
		||||
  }
 | 
			
		||||
@@ -18,7 +19,7 @@ export interface GlobalConfiguration {
 | 
			
		||||
  /** Whether to display Wikipedia-style popovers when hovering over links */
 | 
			
		||||
  enablePopovers: boolean,
 | 
			
		||||
  /** Analytics mode */
 | 
			
		||||
  analytics: Analytics 
 | 
			
		||||
  analytics: Analytics
 | 
			
		||||
  /** Glob patterns to not search */
 | 
			
		||||
  ignorePatterns: string[],
 | 
			
		||||
  /** Base URL to use for CNAME files, sitemaps, and RSS feeds that require an absolute URL.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,18 @@
 | 
			
		||||
import { QuartzComponentConstructor } from "./types"
 | 
			
		||||
import style from "./styles/footer.scss"
 | 
			
		||||
import {version} from "../../package.json"
 | 
			
		||||
 | 
			
		||||
interface Options {
 | 
			
		||||
  authorName: string,
 | 
			
		||||
  links: Record<string, string>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default ((opts?: Options) => {
 | 
			
		||||
  function Footer() {
 | 
			
		||||
    const year = new Date().getFullYear()
 | 
			
		||||
    const name = opts?.authorName ?? "someone"
 | 
			
		||||
    const links = opts?.links ?? []
 | 
			
		||||
    return <footer>
 | 
			
		||||
      <hr />
 | 
			
		||||
      <p>Made by {name} using <a href="https://quartz.jzhao.xyz/">Quartz</a>, © {year}</p>
 | 
			
		||||
      <p>Created with <a href="https://quartz.jzhao.xyz/">Quartz v{version}</a>, © {year}</p>
 | 
			
		||||
      <ul>{Object.entries(links).map(([text, link]) => <li>
 | 
			
		||||
        <a href={link}>{text}</a>
 | 
			
		||||
      </li>)}</ul>
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ export interface D3Config {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface GraphOptions {
 | 
			
		||||
  localGraph: Partial<D3Config>,
 | 
			
		||||
  localGraph: Partial<D3Config> | undefined,
 | 
			
		||||
  globalGraph: Partial<D3Config> | undefined
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -50,7 +50,7 @@ export default ((opts?: GraphOptions) => {
 | 
			
		||||
    const localGraph = { ...opts?.localGraph, ...defaultOptions.localGraph }
 | 
			
		||||
    const globalGraph = { ...opts?.globalGraph, ...defaultOptions.globalGraph }
 | 
			
		||||
    return <div class="graph">
 | 
			
		||||
      <h3>Site Graph</h3>
 | 
			
		||||
      <h3>Graph View</h3>
 | 
			
		||||
      <div class="graph-outer">
 | 
			
		||||
        <div id="graph-container" data-cfg={JSON.stringify(localGraph)}></div>
 | 
			
		||||
        <svg version="1.1" id="global-graph-icon" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
 | 
			
		||||
 
 | 
			
		||||
@@ -5,10 +5,11 @@ import path from "path"
 | 
			
		||||
 | 
			
		||||
import style from '../styles/listPage.scss'
 | 
			
		||||
import { PageList } from "../PageList"
 | 
			
		||||
import { clientSideSlug } from "../../path"
 | 
			
		||||
 | 
			
		||||
function FolderContent(props: QuartzComponentProps) {
 | 
			
		||||
  const { tree, fileData, allFiles } = props
 | 
			
		||||
  const folderSlug = fileData.slug!
 | 
			
		||||
  const folderSlug = clientSideSlug(fileData.slug!)
 | 
			
		||||
  const allPagesInFolder = allFiles.filter(file => {
 | 
			
		||||
    const fileSlug = file.slug ?? ""
 | 
			
		||||
    const prefixed = fileSlug.startsWith(folderSlug)
 | 
			
		||||
@@ -23,12 +24,9 @@ function FolderContent(props: QuartzComponentProps) {
 | 
			
		||||
    allFiles: allPagesInFolder
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const desc = props.fileData.description
 | 
			
		||||
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: 'html' })
 | 
			
		||||
  return <div class="popover-hint">
 | 
			
		||||
    {desc && <p>{desc}</p>}
 | 
			
		||||
    <article>{content}</article>
 | 
			
		||||
    <p>{allPagesInFolder.length} items under this folder.</p>
 | 
			
		||||
    <div>
 | 
			
		||||
 
 | 
			
		||||
@@ -17,12 +17,9 @@ function TagContent(props: QuartzComponentProps) {
 | 
			
		||||
      allFiles: allPagesWithTag
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const desc = props.fileData.description
 | 
			
		||||
 | 
			
		||||
    // @ts-ignore
 | 
			
		||||
    const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: 'html' })
 | 
			
		||||
    return <div class="popover-hint">
 | 
			
		||||
      {desc && <p>{desc}</p>}
 | 
			
		||||
      <article>{content}</article>
 | 
			
		||||
      <p>{allPagesWithTag.length} items with this tag.</p>
 | 
			
		||||
      <div>
 | 
			
		||||
 
 | 
			
		||||
@@ -72,7 +72,7 @@ async function renderGraph(container: string, slug: string) {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    links.flatMap(l => [l.source, l.target]).forEach((id) => neighbourhood.add(id))
 | 
			
		||||
    Object.keys(data).forEach(id => neighbourhood.add(id))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const graphData: { nodes: NodeData[], links: LinkData[] } = {
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ interface Item {
 | 
			
		||||
let index: Document<Item> | undefined = undefined
 | 
			
		||||
 | 
			
		||||
const contextWindowWords = 30
 | 
			
		||||
const numSearchResults = 5
 | 
			
		||||
function highlight(searchTerm: string, text: string, trim?: boolean) {
 | 
			
		||||
  // try to highlight longest tokens first
 | 
			
		||||
  const tokenizedTerms = searchTerm.split(/\s+/).filter(t => t !== "").sort((a, b) => b.length - a.length)
 | 
			
		||||
@@ -134,7 +135,7 @@ document.addEventListener("nav", async (e: unknown) => {
 | 
			
		||||
 | 
			
		||||
  function onType(e: HTMLElementEventMap["input"]) {
 | 
			
		||||
    const term = (e.target as HTMLInputElement).value
 | 
			
		||||
    const searchResults = index?.search(term, 5) ?? []
 | 
			
		||||
    const searchResults = index?.search(term, numSearchResults) ?? []
 | 
			
		||||
    const getByField = (field: string): string[] => {
 | 
			
		||||
      const results = searchResults.filter((x) => x.field === field)
 | 
			
		||||
      return results.length === 0 ? [] : [...results[0].result] as string[]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
footer {
 | 
			
		||||
  text-align: left;
 | 
			
		||||
  margin-bottom: 4rem;
 | 
			
		||||
  opacity: 0.7;
 | 
			
		||||
 | 
			
		||||
  & ul {
 | 
			
		||||
    list-style: none;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
@use "../../styles/variables.scss" as *;
 | 
			
		||||
 | 
			
		||||
.graph {
 | 
			
		||||
  & > h3 {
 | 
			
		||||
    font-size: 1rem;
 | 
			
		||||
@@ -59,6 +61,10 @@
 | 
			
		||||
      transform: translate(-50%, -50%);
 | 
			
		||||
      height: 60vh;
 | 
			
		||||
      width: 50vw;
 | 
			
		||||
 | 
			
		||||
      @media all and (max-width: $fullPageWidth) {
 | 
			
		||||
        width: 90%;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,8 +23,8 @@
 | 
			
		||||
  & > .popover-inner {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    width: 30rem;
 | 
			
		||||
    height: 20rem;
 | 
			
		||||
    padding: 0 1rem 1rem 1rem;
 | 
			
		||||
    max-height: 20rem;
 | 
			
		||||
    padding: 0 1rem 2rem 1rem;
 | 
			
		||||
    font-weight: initial;
 | 
			
		||||
    line-height: normal;
 | 
			
		||||
    font-size: initial;
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,10 @@ button#toc {
 | 
			
		||||
  max-height: none;
 | 
			
		||||
  transition: max-height 0.5s ease;
 | 
			
		||||
 | 
			
		||||
  &.collapsed > .overflow::after {
 | 
			
		||||
    opacity: 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  & ul {
 | 
			
		||||
    list-style: none;
 | 
			
		||||
    margin: 0.5rem 0;
 | 
			
		||||
 
 | 
			
		||||
@@ -100,6 +100,7 @@ declare module 'vfile' {
 | 
			
		||||
  // inserted in processors.ts
 | 
			
		||||
  interface DataMap {
 | 
			
		||||
    slug: string
 | 
			
		||||
    allSlugs: string[]
 | 
			
		||||
    filePath: string
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ import matter from "gray-matter"
 | 
			
		||||
import remarkFrontmatter from 'remark-frontmatter'
 | 
			
		||||
import { QuartzTransformerPlugin } from "../types"
 | 
			
		||||
import yaml from 'js-yaml'
 | 
			
		||||
import { slug as slugAnchor } from 'github-slugger'
 | 
			
		||||
 | 
			
		||||
export interface Options {
 | 
			
		||||
  language: 'yaml' | 'toml',
 | 
			
		||||
@@ -29,10 +30,18 @@ export const FrontMatter: QuartzTransformerPlugin<Partial<Options> | undefined>
 | 
			
		||||
              }
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            // tag is an alias for tags
 | 
			
		||||
            if (data.tag) {
 | 
			
		||||
              data.tags = data.tag
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (data.tags && !Array.isArray(data.tags)) {
 | 
			
		||||
              data.tags = data.tags.toString().split(",").map((tag: string) => tag.trim())
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // slug them all!!
 | 
			
		||||
            data.tags = data.tags?.map((tag: string) => slugAnchor(tag)) ?? []
 | 
			
		||||
 | 
			
		||||
            // fill in frontmatter
 | 
			
		||||
            file.data.frontmatter = {
 | 
			
		||||
              title: file.stem ?? "Untitled",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
export { FrontMatter } from './frontmatter'
 | 
			
		||||
export { GitHubFlavoredMarkdown } from './gfm'
 | 
			
		||||
export { CreatedModifiedDate } from './lastmod'
 | 
			
		||||
export { Katex } from './latex'
 | 
			
		||||
export { Latex } from './latex'
 | 
			
		||||
export { Description } from './description'
 | 
			
		||||
export { CrawlLinks } from './links'
 | 
			
		||||
export { ObsidianFlavoredMarkdown } from './ofm'
 | 
			
		||||
 
 | 
			
		||||
@@ -1,33 +1,43 @@
 | 
			
		||||
import remarkMath from "remark-math"
 | 
			
		||||
import rehypeKatex from 'rehype-katex'
 | 
			
		||||
import rehypeMathjax from 'rehype-mathjax/svg.js'
 | 
			
		||||
import { QuartzTransformerPlugin } from "../types"
 | 
			
		||||
 | 
			
		||||
export const Katex: QuartzTransformerPlugin = () => ({
 | 
			
		||||
  name: "Katex",
 | 
			
		||||
  markdownPlugins() {
 | 
			
		||||
    return [remarkMath]
 | 
			
		||||
  },
 | 
			
		||||
  htmlPlugins() {
 | 
			
		||||
    return [
 | 
			
		||||
      [rehypeKatex, {
 | 
			
		||||
        output: 'html',
 | 
			
		||||
      }]
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  externalResources() {
 | 
			
		||||
    return {
 | 
			
		||||
      css: [
 | 
			
		||||
        // base css
 | 
			
		||||
        "https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css",
 | 
			
		||||
      ],
 | 
			
		||||
      js: [
 | 
			
		||||
        {
 | 
			
		||||
          // 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",
 | 
			
		||||
          contentType: 'external'
 | 
			
		||||
        }
 | 
			
		||||
interface Options {
 | 
			
		||||
  renderEngine: 'katex' | 'mathjax'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const Latex: QuartzTransformerPlugin<Options> = (opts?: Options) => {
 | 
			
		||||
  const engine = opts?.renderEngine ?? 'katex'
 | 
			
		||||
  return {
 | 
			
		||||
    name: "Latex",
 | 
			
		||||
    markdownPlugins() {
 | 
			
		||||
      return [remarkMath]
 | 
			
		||||
    },
 | 
			
		||||
    htmlPlugins() {
 | 
			
		||||
      return [
 | 
			
		||||
        engine === 'katex'
 | 
			
		||||
          ? [rehypeKatex, { output: 'html' }]
 | 
			
		||||
          : [rehypeMathjax]
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    externalResources() {
 | 
			
		||||
      return engine === 'katex'
 | 
			
		||||
        ? {
 | 
			
		||||
          css: [
 | 
			
		||||
            // base css
 | 
			
		||||
            "https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css",
 | 
			
		||||
          ],
 | 
			
		||||
          js: [
 | 
			
		||||
            {
 | 
			
		||||
              // 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",
 | 
			
		||||
              contentType: 'external'
 | 
			
		||||
            }
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
        : {}
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -29,13 +29,30 @@ export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> =
 | 
			
		||||
        return (tree, file) => {
 | 
			
		||||
          const curSlug = clientSideSlug(file.data.slug!)
 | 
			
		||||
          const transformLink = (target: string) => {
 | 
			
		||||
            const targetSlug = slugify(decodeURI(target).trim())
 | 
			
		||||
            const targetSlug = clientSideSlug(slugify(decodeURI(target).trim()))
 | 
			
		||||
            if (opts.markdownLinkResolution === 'relative' && !path.isAbsolute(targetSlug)) {
 | 
			
		||||
              return './' + relative(curSlug, targetSlug)
 | 
			
		||||
            } else {
 | 
			
		||||
              return './' + relativeToRoot(curSlug, targetSlug)
 | 
			
		||||
            } else if (opts.markdownLinkResolution === 'shortest') {
 | 
			
		||||
              // https://forum.obsidian.md/t/settings-new-link-format-what-is-shortest-path-when-possible/6748/5
 | 
			
		||||
              const allSlugs = file.data.allSlugs!
 | 
			
		||||
 | 
			
		||||
              // if the file name is unique, then it's just the filename
 | 
			
		||||
              const matchingFileNames = allSlugs.filter(slug => {
 | 
			
		||||
                const parts = clientSideSlug(slug).split(path.posix.sep)
 | 
			
		||||
                const fileName = parts.at(-1)
 | 
			
		||||
                return targetSlug === fileName
 | 
			
		||||
              })
 | 
			
		||||
 | 
			
		||||
              if (matchingFileNames.length === 1) {
 | 
			
		||||
                const targetSlug = clientSideSlug(matchingFileNames[0])
 | 
			
		||||
                return './' + relativeToRoot(curSlug, targetSlug)
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              // if it's not unique, then it's the absolute path from the vault root
 | 
			
		||||
              // (fall-through case)
 | 
			
		||||
            }
 | 
			
		||||
            // todo: handle shortest path
 | 
			
		||||
            // treat as absolute
 | 
			
		||||
            return './' + relativeToRoot(curSlug, targetSlug)
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          const outgoing: Set<string> = new Set()
 | 
			
		||||
@@ -53,7 +70,7 @@ export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> =
 | 
			
		||||
              if (!(isAbsoluteUrl(dest) || dest.startsWith("#"))) {
 | 
			
		||||
                node.properties.href = transformLink(dest)
 | 
			
		||||
              }
 | 
			
		||||
              
 | 
			
		||||
 | 
			
		||||
              dest = node.properties.href
 | 
			
		||||
              if (dest.startsWith(".")) {
 | 
			
		||||
                const normalizedPath = path.normalize(path.join(curSlug, dest))
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ import { JSResource } from "../../resources"
 | 
			
		||||
import calloutScript from "../../components/scripts/callout.inline.ts"
 | 
			
		||||
 | 
			
		||||
export interface Options {
 | 
			
		||||
  comments: boolean
 | 
			
		||||
  highlight: boolean
 | 
			
		||||
  wikilinks: boolean
 | 
			
		||||
  callouts: boolean
 | 
			
		||||
@@ -19,6 +20,7 @@ export interface Options {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const defaultOptions: Options = {
 | 
			
		||||
  comments: true,
 | 
			
		||||
  highlight: true,
 | 
			
		||||
  wikilinks: true,
 | 
			
		||||
  callouts: true,
 | 
			
		||||
@@ -101,11 +103,14 @@ const capitalize = (s: string): string => {
 | 
			
		||||
// ([^\[\]\|\#]+)   -> 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")
 | 
			
		||||
const wikilinkRegex = new RegExp(/!?\[\[([^\[\]\|\#]+)(#[^\[\]\|\#]+)?(\|[^\[\]\|\#]+)?\]\]/, "g")
 | 
			
		||||
 | 
			
		||||
// Match highlights 
 | 
			
		||||
const highlightRegex = new RegExp(/==(.+)==/, "g")
 | 
			
		||||
 | 
			
		||||
// Match comments 
 | 
			
		||||
const commentRegex = new RegExp(/%%(.+)%%/, "g")
 | 
			
		||||
 | 
			
		||||
// from https://github.com/escwxyz/remark-obsidian-callout/blob/main/src/index.ts
 | 
			
		||||
const calloutRegex = new RegExp(/^\[\!(\w+)\]([+-]?)/)
 | 
			
		||||
 | 
			
		||||
@@ -117,7 +122,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
 | 
			
		||||
      // pre-transform wikilinks (fix anchors to things that may contain illegal syntax e.g. codeblocks, latex)
 | 
			
		||||
      if (opts.wikilinks) {
 | 
			
		||||
        src = src.toString()
 | 
			
		||||
        return src.replaceAll(backlinkRegex, (value, ...capture) => {
 | 
			
		||||
        return src.replaceAll(wikilinkRegex, (value, ...capture) => {
 | 
			
		||||
          const [fp, rawHeader, rawAlias] = capture
 | 
			
		||||
          const anchor = rawHeader?.trim().slice(1)
 | 
			
		||||
          const displayAnchor = anchor ? `#${slugAnchor(anchor)}` : ""
 | 
			
		||||
@@ -133,7 +138,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
 | 
			
		||||
      if (opts.wikilinks) {
 | 
			
		||||
        plugins.push(() => {
 | 
			
		||||
          return (tree: Root, _file) => {
 | 
			
		||||
            findAndReplace(tree, backlinkRegex, (value: string, ...capture: string[]) => {
 | 
			
		||||
            findAndReplace(tree, wikilinkRegex, (value: string, ...capture: string[]) => {
 | 
			
		||||
              const [fp, rawHeader, rawAlias] = capture
 | 
			
		||||
              const anchor = rawHeader?.trim() ?? ""
 | 
			
		||||
              const alias = rawAlias?.slice(1).trim()
 | 
			
		||||
@@ -204,6 +209,19 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      if (opts.comments) {
 | 
			
		||||
        plugins.push(() => {
 | 
			
		||||
          return (tree: Root, _file) => {
 | 
			
		||||
            findAndReplace(tree, commentRegex, (_value: string, ..._capture: string[]) => {
 | 
			
		||||
              return {
 | 
			
		||||
                type: 'text',
 | 
			
		||||
                value: ''
 | 
			
		||||
              }
 | 
			
		||||
            })
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (opts.callouts) {
 | 
			
		||||
        plugins.push(() => {
 | 
			
		||||
 
 | 
			
		||||
@@ -6,19 +6,6 @@ export const SyntaxHighlighting: QuartzTransformerPlugin = () => ({
 | 
			
		||||
  htmlPlugins() {
 | 
			
		||||
    return [[rehypePrettyCode, {
 | 
			
		||||
      theme: 'css-variables',
 | 
			
		||||
      onVisitLine(node) {
 | 
			
		||||
        if (node.children.length === 0) {
 | 
			
		||||
          node.children = [{ type: 'text', value: ' ' }]
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      onVisitHighlightedLine(node) {
 | 
			
		||||
        node.properties.className ??= []
 | 
			
		||||
        node.properties.className.push('highlighted')
 | 
			
		||||
      },
 | 
			
		||||
      onVisitHighlightedWord(node) {
 | 
			
		||||
        node.properties.className ??= []
 | 
			
		||||
        node.properties.className.push('word')
 | 
			
		||||
      },
 | 
			
		||||
    } satisfies Partial<CodeOptions>]]
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -73,7 +73,7 @@ async function transpileWorkerScript() {
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createFileParser(transformers: QuartzTransformerPluginInstance[], baseDir: string, fps: string[], verbose: boolean) {
 | 
			
		||||
export function createFileParser(transformers: QuartzTransformerPluginInstance[], baseDir: string, fps: string[], allSlugs: string[], verbose: boolean) {
 | 
			
		||||
  return async (processor: QuartzProcessor) => {
 | 
			
		||||
    const res: ProcessedContent[] = []
 | 
			
		||||
    for (const fp of fps) {
 | 
			
		||||
@@ -90,6 +90,7 @@ export function createFileParser(transformers: QuartzTransformerPluginInstance[]
 | 
			
		||||
 | 
			
		||||
        // base data properties that plugins may use
 | 
			
		||||
        file.data.slug = slugify(path.relative(baseDir, file.path))
 | 
			
		||||
        file.data.allSlugs = allSlugs
 | 
			
		||||
        file.data.filePath = fp
 | 
			
		||||
 | 
			
		||||
        const ast = processor.parse(file)
 | 
			
		||||
@@ -115,12 +116,16 @@ export async function parseMarkdown(transformers: QuartzTransformerPluginInstanc
 | 
			
		||||
 | 
			
		||||
  const CHUNK_SIZE = 128
 | 
			
		||||
  let concurrency = fps.length < CHUNK_SIZE ? 1 : os.availableParallelism()
 | 
			
		||||
  let res: ProcessedContent[] = []
 | 
			
		||||
 | 
			
		||||
  // get all slugs ahead of time as each thread needs a copy
 | 
			
		||||
  // const slugs: string[] = fps.map(fp => slugify(path))
 | 
			
		||||
  const allSlugs = fps.map(fp => slugify(path.relative(baseDir, path.resolve(fp))))
 | 
			
		||||
 | 
			
		||||
  let res: ProcessedContent[] = []
 | 
			
		||||
  log.start(`Parsing input files using ${concurrency} threads`)
 | 
			
		||||
  if (concurrency === 1) {
 | 
			
		||||
    const processor = createProcessor(transformers)
 | 
			
		||||
    const parse = createFileParser(transformers, baseDir, fps, verbose)
 | 
			
		||||
    const parse = createFileParser(transformers, baseDir, fps, allSlugs, verbose)
 | 
			
		||||
    res = await parse(processor)
 | 
			
		||||
  } else {
 | 
			
		||||
    await transpileWorkerScript()
 | 
			
		||||
@@ -135,7 +140,7 @@ export async function parseMarkdown(transformers: QuartzTransformerPluginInstanc
 | 
			
		||||
 | 
			
		||||
    const childPromises: WorkerPromise<ProcessedContent[]>[] = []
 | 
			
		||||
    for (const chunk of chunks(fps, CHUNK_SIZE)) {
 | 
			
		||||
      childPromises.push(pool.exec('parseFiles', [baseDir, chunk, verbose]))
 | 
			
		||||
      childPromises.push(pool.exec('parseFiles', [baseDir, chunk, allSlugs, verbose]))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const results: ProcessedContent[][] = await WorkerPromise.all(childPromises)
 | 
			
		||||
 
 | 
			
		||||
@@ -15,16 +15,23 @@ body {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.text-highlight {
 | 
			
		||||
  background-color: #fff236aa;
 | 
			
		||||
  background-color: #fff23688;
 | 
			
		||||
  padding: 0 0.1rem;
 | 
			
		||||
  border-radius: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
p, ul, text, a, tr, td, li, ol, ul, .katex {
 | 
			
		||||
p, ul, text, a, tr, td, li, ol, ul, .katex, .math {
 | 
			
		||||
  color: var(--darkgray);
 | 
			
		||||
  fill: var(--darkgray);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.math {
 | 
			
		||||
  font-size: 1.1rem;
 | 
			
		||||
  &.math-display {
 | 
			
		||||
    text-align: center;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a {
 | 
			
		||||
  font-weight: 600;
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
@@ -76,6 +83,10 @@ a {
 | 
			
		||||
      padding-left: 0;
 | 
			
		||||
      margin-left: -1.4rem;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    & li > * {
 | 
			
		||||
      margin: 0;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  & > #quartz-body {
 | 
			
		||||
@@ -144,6 +155,11 @@ a {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.footnotes {
 | 
			
		||||
  margin-top: 2rem;
 | 
			
		||||
  border-top: 1px solid var(--lightgray);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input[type="checkbox"] {
 | 
			
		||||
  transform: translateY(2px);
 | 
			
		||||
  color: var(--secondary);
 | 
			
		||||
@@ -168,7 +184,7 @@ thead {
 | 
			
		||||
  font-family: var(--headerFont);
 | 
			
		||||
  color: var(--dark);
 | 
			
		||||
  font-weight: revert;
 | 
			
		||||
  margin: 2rem 0 0;
 | 
			
		||||
  margin-bottom: 0;
 | 
			
		||||
 | 
			
		||||
  article > & > a {
 | 
			
		||||
    color: var(--dark);
 | 
			
		||||
@@ -178,6 +194,8 @@ thead {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
h1, h2, h3, h4, h5, h6 {
 | 
			
		||||
  &[id] > a[href^="#"] {
 | 
			
		||||
    margin: 0 0.5rem;
 | 
			
		||||
@@ -200,13 +218,17 @@ div[data-rehype-pretty-code-fragment] {
 | 
			
		||||
  & > div[data-rehype-pretty-code-title] {
 | 
			
		||||
    font-family: var(--codeFont);
 | 
			
		||||
    font-size: 0.9rem;
 | 
			
		||||
    padding: 0.1rem 0.8rem;
 | 
			
		||||
    padding: 0.1rem 0.5rem;
 | 
			
		||||
    border: 1px solid var(--lightgray);
 | 
			
		||||
    width: max-content;
 | 
			
		||||
    border-radius: 5px;
 | 
			
		||||
    margin-bottom: -0.8rem;
 | 
			
		||||
    margin-bottom: -0.5rem;
 | 
			
		||||
    color: var(--darkgray);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  & > pre {
 | 
			
		||||
    padding: 0.5rem 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pre {
 | 
			
		||||
@@ -228,12 +250,17 @@ pre {
 | 
			
		||||
    counter-increment: line 0;
 | 
			
		||||
    display: grid;
 | 
			
		||||
 | 
			
		||||
    & .line {
 | 
			
		||||
    & [data-highlighted-chars] {
 | 
			
		||||
      background-color: var(--highlight);
 | 
			
		||||
      border-radius: 5px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    & > [data-line] {
 | 
			
		||||
      padding: 0 0.25rem;
 | 
			
		||||
      box-sizing: border-box;
 | 
			
		||||
      border-left: 3px solid transparent;
 | 
			
		||||
 | 
			
		||||
      &.highlighted {
 | 
			
		||||
      &[data-highlighted-line] {
 | 
			
		||||
        background-color: var(--highlight);
 | 
			
		||||
        border-left: 3px solid var(--secondary);
 | 
			
		||||
      }
 | 
			
		||||
@@ -245,9 +272,17 @@ pre {
 | 
			
		||||
        margin-right: 1rem;
 | 
			
		||||
        display: inline-block;
 | 
			
		||||
        text-align: right;
 | 
			
		||||
        color: rgba(115, 138, 148, 0.4);
 | 
			
		||||
        color: rgba(115, 138, 148, 0.6);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &[data-line-numbers-max-digits='2'] > [data-line]::before {
 | 
			
		||||
      width: 2rem;
 | 
			
		||||
    }
 | 
			
		||||
     
 | 
			
		||||
    &[data-line-numbers-max-digits='3'] > [data-line]::before {
 | 
			
		||||
      width: 3rem;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -265,6 +300,7 @@ tbody, li, p {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table {
 | 
			
		||||
  margin: 1rem 0;
 | 
			
		||||
  border: 1px solid var(--gray);
 | 
			
		||||
  padding: 1.5rem;
 | 
			
		||||
  border-collapse: collapse;
 | 
			
		||||
@@ -294,21 +330,6 @@ hr {
 | 
			
		||||
  background-color: var(--lightgray);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
section {
 | 
			
		||||
  margin: 2rem auto;
 | 
			
		||||
  border-top: 1px solid var(--lightgray);
 | 
			
		||||
 | 
			
		||||
  & > #footnote-label {
 | 
			
		||||
    & > a {
 | 
			
		||||
      color: var(--dark);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  & ol, & ul {
 | 
			
		||||
    padding: 0 1em
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
audio, video {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  border-radius: 5px;
 | 
			
		||||
@@ -322,6 +343,10 @@ ul.overflow, ol.overflow {
 | 
			
		||||
  height: 400px;
 | 
			
		||||
  overflow-y: scroll;
 | 
			
		||||
 | 
			
		||||
  // clearfix
 | 
			
		||||
  content: "";
 | 
			
		||||
  clear: both;
 | 
			
		||||
 | 
			
		||||
  & > li:last-of-type {
 | 
			
		||||
    margin-bottom: 50px;
 | 
			
		||||
  }
 | 
			
		||||
@@ -334,6 +359,8 @@ ul.overflow, ol.overflow {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
    opacity: 1;
 | 
			
		||||
    transition: opacity 0.3s ease;
 | 
			
		||||
    background: linear-gradient(transparent 0px, var(--light));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ const transformers = config.plugins.transformers
 | 
			
		||||
const processor = createProcessor(transformers)
 | 
			
		||||
 | 
			
		||||
// only called from worker thread
 | 
			
		||||
export async function parseFiles(baseDir: string, fps: string[], verbose: boolean) {
 | 
			
		||||
  const parse = createFileParser(transformers, baseDir, fps, verbose)
 | 
			
		||||
export async function parseFiles(baseDir: string, fps: string[], allSlugs: string[], verbose: boolean) {
 | 
			
		||||
  const parse = createFileParser(transformers, baseDir, fps, allSlugs, verbose)
 | 
			
		||||
  return parse(processor)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user