Jacky Zhao
2023-06-03 4bdc17d4a11f0ba517c6d9124d296458332c536b
inline scripts
2 files added
17 files modified
256 ■■■■ changed files
package-lock.json 12 ●●●●● patch | view | raw | blame | history
package.json 1 ●●●● patch | view | raw | blame | history
quartz/bootstrap.mjs 34 ●●●● patch | view | raw | blame | history
quartz/components/Head.tsx 14 ●●●● patch | view | raw | blame | history
quartz/components/Header.tsx 4 ●●●● patch | view | raw | blame | history
quartz/components/scripts/darkmode.inline.ts 3 ●●●●● patch | view | raw | blame | history
quartz/components/types.ts 8 ●●●●● patch | view | raw | blame | history
quartz/index.ts 2 ●●● patch | view | raw | blame | history
quartz/path.ts 10 ●●●● patch | view | raw | blame | history
quartz/plugins/emitters/contentPage.tsx 43 ●●●● patch | view | raw | blame | history
quartz/plugins/index.ts 70 ●●●●● patch | view | raw | blame | history
quartz/plugins/transformers/ofm.ts 6 ●●●● patch | view | raw | blame | history
quartz/plugins/types.ts 2 ●●●●● patch | view | raw | blame | history
quartz/processors/emit.ts 16 ●●●● patch | view | raw | blame | history
quartz/processors/filter.ts 15 ●●●● patch | view | raw | blame | history
quartz/processors/parse.ts 5 ●●●● patch | view | raw | blame | history
quartz/resources.ts 1 ●●●● patch | view | raw | blame | history
quartz/styles/base.scss 2 ●●●●● patch | view | raw | blame | history
quartz/theme.ts 8 ●●●●● patch | view | raw | blame | history
package-lock.json
@@ -13,6 +13,7 @@
        "@napi-rs/simple-git": "^0.1.8",
        "chalk": "^4.1.2",
        "cli-spinner": "^0.2.10",
        "env-paths": "^3.0.0",
        "esbuild-sass-plugin": "^2.9.0",
        "github-slugger": "^2.0.0",
        "globby": "^13.1.4",
@@ -1346,6 +1347,17 @@
        "url": "https://github.com/fb55/entities?sponsor=1"
      }
    },
    "node_modules/env-paths": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
      "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==",
      "engines": {
        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/esbuild": {
      "version": "0.17.19",
      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
package.json
@@ -28,6 +28,7 @@
    "@napi-rs/simple-git": "^0.1.8",
    "chalk": "^4.1.2",
    "cli-spinner": "^0.2.10",
    "env-paths": "^3.0.0",
    "esbuild-sass-plugin": "^2.9.0",
    "github-slugger": "^2.0.0",
    "globby": "^13.1.4",
quartz/bootstrap.mjs
@@ -1,6 +1,7 @@
#!/usr/bin/env node
import { readFileSync } from 'fs'
import { promises, readFileSync } from 'fs'
import yargs from 'yargs'
import path from 'path'
import { hideBin } from 'yargs/helpers'
import esbuild from 'esbuild'
import chalk from 'chalk'
@@ -61,9 +62,34 @@
      jsx: "automatic",
      jsxImportSource: "preact",
      external: ["@napi-rs/simple-git", "shiki"],
      plugins: [sassPlugin({
        type: 'css-text'
      })]
      plugins: [
        sassPlugin({
          type: 'css-text'
        }),
        {
          name: 'inline-script-loader',
          setup(build) {
            build.onLoad({ filter: /\.inline\.(ts|js)$/ }, async (args) => {
              let text = await promises.readFile(args.path, 'utf8')
              const transpiled = await esbuild.build({
                stdin: {
                  contents: text,
                  sourcefile: path.relative(path.resolve('.'), args.path),
                },
                write: false,
                bundle: true,
                platform: "browser",
                format: "esm",
              })
              const rawMod = transpiled.outputFiles[0].text
              return {
                contents: rawMod,
                loader: 'text',
              }
            })
          }
        }
      ]
    }).catch(err => {
      console.error(`${chalk.red("Couldn't parse Quartz configuration:")} ${fp}`)
      console.log(`Reason: ${chalk.grey(err)}`)
quartz/components/Head.tsx
@@ -8,10 +8,9 @@
  externalResources: StaticResources
}
export default function({ title, description, slug, externalResources }: HeadProps) {
export function Component({ title, description, slug, externalResources }: HeadProps) {
  const { css, js } = externalResources
  const baseDir = resolveToRoot(slug)
  const stylePath = baseDir + "/index.css"
  const iconPath = baseDir + "/static/icon.png"
  const ogImagePath = baseDir + "/static/og-image.png"
  return <head>
@@ -28,16 +27,7 @@
    <meta name="generator" content="Quartz" />
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" />
    <link rel="stylesheet" type="text/css" href={stylePath} />
    {css.map(href => <link key={href} href={href} rel="stylesheet" type="text/css" />)}
    {js.filter(resource => resource.loadTime === "beforeDOMReady").map(resource => <script key={resource.src} src={resource.src} />)}
    {js.filter(resource => resource.loadTime === "beforeDOMReady").map(resource => <script key={resource.src} {...resource} />)}
  </head>
}
export function beforeDOMLoaded() {
}
export function onDOMLoaded() {
}
quartz/components/Header.tsx
@@ -5,10 +5,10 @@
  slug: string
}
export default function({ title, slug }: HeaderProps) {
export function Component({ title, slug }: HeaderProps) {
  const baseDir = resolveToRoot(slug)
  return <header>
    <h1><a href={baseDir}>{title}</a></h1>
  </header>
}
quartz/components/scripts/darkmode.inline.ts
New file
@@ -0,0 +1,3 @@
export default "Darkmode"
console.log("HELLOOOO FROM CONSOLE")
quartz/components/types.ts
New file
@@ -0,0 +1,8 @@
import { ComponentType } from "preact"
export type QuartzComponent<Props> = {
  Component: ComponentType<Props>
  css?: string,
  beforeDOMLoaded?: string,
  afterDOMLoaded?: string,
}
quartz/index.ts
@@ -60,7 +60,7 @@
    const parsedFiles = await parseMarkdown(processor, argv.directory, filePaths, argv.verbose)
    const filteredContent = filterContent(cfg.plugins.filters, parsedFiles, argv.verbose)
    await emitContent(argv.directory, output, cfg, filteredContent, argv.verbose)
    console.log(chalk.green(`Done in ${perf.timeSince()}`))
    console.log(chalk.green(`Done processing ${fps.length} files in ${perf.timeSince()}`))
    if (argv.serve) {
      const server = http.createServer(async (req, res) => {
quartz/path.ts
@@ -22,11 +22,15 @@
// resolve /a/b/c to ../../
export function resolveToRoot(slug: string): string {
  let fp = slug
  if (fp.endsWith("/index")) {
    fp = fp.slice(0, -"/index".length)
  if (fp.endsWith("index")) {
    fp = fp.slice(0, -"index".length)
  }
  return fp
  if (fp === "") {
    return "."
  }
  return "./" + fp
    .split('/')
    .filter(x => x !== '')
    .map(_ => '..')
quartz/plugins/emitters/contentPage.tsx
@@ -4,17 +4,15 @@
import { ProcessedContent } from "../vfile"
import { Fragment, jsx, jsxs } from 'preact/jsx-runtime'
import { render } from "preact-render-to-string"
import { ComponentType } from "preact"
import { HeadProps } from "../../components/Head"
import { googleFontHref, templateThemeStyles } from "../../theme"
import { GlobalConfiguration } from "../../cfg"
import { HeaderProps } from "../../components/Header"
import styles from '../../styles/base.scss'
import { QuartzComponent } from "../../components/types"
import { resolveToRoot } from "../../path"
interface Options {
  Head: ComponentType<HeadProps>
  Header: ComponentType<HeaderProps>
  Head: QuartzComponent<HeadProps>
  Header: QuartzComponent<HeaderProps>
}
export class ContentPage extends QuartzEmitterPlugin {
@@ -26,40 +24,45 @@
    this.opts = opts
  }
  getQuartzComponents(): QuartzComponent<any>[] {
    return [...Object.values(this.opts)]
  }
  async emit(cfg: GlobalConfiguration, content: ProcessedContent[], resources: StaticResources, emit: EmitCallback): Promise<string[]> {
    const fps: string[] = []
    // emit styles
    emit({
      slug: "index",
      ext: ".css",
      content: templateThemeStyles(cfg.theme, styles)
    })
    fps.push("index.css")
    resources.css.push(googleFontHref(cfg.theme))
    const { Head, Header } = this.opts
    for (const [tree, file] of content) {
      // @ts-ignore (preact makes it angry)
      const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: 'html' })
      const baseDir = resolveToRoot(file.data.slug!)
      const pageResources: StaticResources = {
        css: [baseDir + "/index.css", ...resources.css,],
        js: [
          { src: baseDir + "/prescript.js", loadTime: "beforeDOMReady", type: 'module' },
          ...resources.js,
          { src: baseDir + "/postscript.js", loadTime: "afterDOMReady", type: 'module' }
        ]
      }
      const title = file.data.frontmatter?.title
      const { Head, Header } = this.opts
      const doc = <html>
        <Head
        <Head.Component
          title={title ?? "Untitled"}
          description={file.data.description ?? "No description provided"}
          slug={file.data.slug!}
          externalResources={resources} />
          externalResources={pageResources} />
        <body>
          <div id="quartz-root" class="page">
            <Header title={cfg.siteTitle} slug={file.data.slug!} />
            <Header.Component title={cfg.siteTitle} slug={file.data.slug!} />
            <article>
              {file.data.slug !== "index" && <h1>{title}</h1>}
              {content}
            </article>
          </div>
        </body>
        {resources.js.filter(resource => resource.loadTime === "afterDOMReady").map(resource => <script key={resource.src} src={resource.src} />)}
        {pageResources.js.filter(resource => resource.loadTime === "afterDOMReady").map(resource => <script key={resource.src} {...resource} />)}
      </html>
      const fp = file.data.slug + ".html"
quartz/plugins/index.ts
@@ -1,5 +1,69 @@
import { GlobalConfiguration } from '../cfg'
import { QuartzComponent } from '../components/types'
import { StaticResources } from '../resources'
import { PluginTypes } from './types'
import { googleFontHref, joinStyles } from '../theme'
import { EmitCallback, PluginTypes } from './types'
import styles from '../styles/base.scss'
export type ComponentResources = {
  css: string[],
  beforeDOMLoaded: string[],
  afterDOMLoaded: string[]
}
function joinScripts(scripts: string[]): string {
  return scripts.join("\n")
}
export function emitComponentResources(cfg: GlobalConfiguration, resources: StaticResources, plugins: PluginTypes, emit: EmitCallback) {
  const fps: string[] = []
  const allComponents: Set<QuartzComponent<any>> = new Set()
  for (const emitter of plugins.emitters) {
    const components = emitter.getQuartzComponents()
    for (const component of components) {
      allComponents.add(component)
    }
  }
  const componentResources: ComponentResources = {
    css: [],
    beforeDOMLoaded: [],
    afterDOMLoaded: []
  }
  for (const component of allComponents) {
    const { css, beforeDOMLoaded, afterDOMLoaded } = component
    if (css) {
      componentResources.css.push(css)
    }
    if (beforeDOMLoaded) {
      componentResources.beforeDOMLoaded.push(beforeDOMLoaded)
    }
    if (afterDOMLoaded) {
      componentResources.beforeDOMLoaded.push(afterDOMLoaded)
    }
  }
  emit({
    slug: "index",
    ext: ".css",
    content: joinStyles(cfg.theme, styles, ...componentResources.css)
  })
  emit({
    slug: "prescript",
    ext: ".js",
    content: joinScripts(componentResources.beforeDOMLoaded)
  })
  emit({
    slug: "postscript",
    ext: ".js",
    content: joinScripts(componentResources.afterDOMLoaded)
  })
  fps.push("index.css", "prescript.js", "postscript.js")
  resources.css.push(googleFontHref(cfg.theme))
  return fps
}
export function getStaticResourcesFromPlugins(plugins: PluginTypes) {
  const staticResources: StaticResources = {
@@ -7,8 +71,8 @@
    js: [],
  }
  for (const plugin of plugins.transformers) {
    const res = plugin.externalResources
  for (const transformer of plugins.transformers) {
    const res = transformer.externalResources
    if (res?.js) {
      staticResources.js = staticResources.js.concat(res.js)
    }
quartz/plugins/transformers/ofm.ts
@@ -12,7 +12,7 @@
const defaultOptions: Options = {
  highlight: true,
  wikilinks: true
  wikilinks: true,
}
export class ObsidianFlavoredMarkdown extends QuartzTransformerPlugin {
@@ -39,10 +39,10 @@
        return (tree: Root, _file) => {
          findAndReplace(tree, backlinkRegex, (value: string, ...capture: string[]) => {
            if (value.startsWith("!")) {
              // TODO: handle embeds
            } else {
              const [path, rawHeader, rawAlias] = capture
              const anchor = rawHeader?.slice(1).trim() ?? ""
              const anchor = rawHeader?.trim() ?? ""
              const alias = rawAlias?.slice(1).trim() ?? path
              const url = slugify(path.trim() + anchor)
              return {
quartz/plugins/types.ts
@@ -2,6 +2,7 @@
import { StaticResources } from "../resources"
import { ProcessedContent } from "./vfile"
import { GlobalConfiguration } from "../cfg"
import { QuartzComponent } from "../components/types"
export abstract class QuartzTransformerPlugin {
  abstract name: string
@@ -25,6 +26,7 @@
export abstract class QuartzEmitterPlugin {
  abstract name: string
  abstract emit(cfg: GlobalConfiguration, content: ProcessedContent[], resources: StaticResources, emitCallback: EmitCallback): Promise<string[]>
  abstract getQuartzComponents(): QuartzComponent<any>[]
}
export interface PluginTypes {
quartz/processors/emit.ts
@@ -2,7 +2,7 @@
import fs from "fs"
import { QuartzConfig } from "../cfg"
import { PerfTimer } from "../perf"
import { getStaticResourcesFromPlugins } from "../plugins"
import { emitComponentResources, getStaticResourcesFromPlugins } from "../plugins"
import { EmitCallback } from "../plugins/types"
import { ProcessedContent } from "../plugins/vfile"
import { QUARTZ, slugify } from "../path"
@@ -10,9 +10,6 @@
export async function emitContent(contentFolder: string, output: string, cfg: QuartzConfig, content: ProcessedContent[], verbose: boolean) {
  const perf = new PerfTimer()
  const staticResources = getStaticResourcesFromPlugins(cfg.plugins)
  const emit: EmitCallback = async ({ slug, ext, content }) => {
    const pathToPage = path.join(output, slug + ext)
    const dir = path.dirname(pathToPage)
@@ -21,6 +18,9 @@
    return pathToPage
  }
  const staticResources = getStaticResourcesFromPlugins(cfg.plugins)
  emitComponentResources(cfg.configuration, staticResources, cfg.plugins, emit)
  let emittedFiles = 0
  for (const emitter of cfg.plugins.emitters) {
    const emitted = await emitter.emit(cfg.configuration, content, staticResources, emit)
@@ -35,6 +35,9 @@
  const staticPath = path.join(QUARTZ, "static")
  await fs.promises.cp(staticPath, path.join(output, "static"), { recursive: true })
  if (verbose) {
    console.log(`[emit:Static] ${path.join(output, "static", "**")}`)
  }
  // glob all non MD/MDX/HTML files in content folder and copy it over
  const assetsPath = path.join("public", "assets")
@@ -54,8 +57,5 @@
    }
  }
  if (verbose) {
    console.log(`[emit:Static] ${path.join(output, "static", "**")}`)
    console.log(`Emitted ${emittedFiles} files to \`${output}\` in ${perf.timeSince()}`)
  }
  console.log(`Emitted ${emittedFiles} files to \`${output}\` in ${perf.timeSince()}`)
}
quartz/processors/filter.ts
@@ -6,11 +6,18 @@
  const perf = new PerfTimer()
  const initialLength = content.length
  for (const plugin of plugins) {
    content = content.filter(plugin.shouldPublish)
    const updatedContent = content.filter(plugin.shouldPublish)
    if (verbose) {
      const diff = content.filter(x => !updatedContent.includes(x))
      for (const file of diff) {
        console.log(`[filter:${plugin.name}] ${file[1].data.slug}`)
      }
    }
    content = updatedContent
  }
  if (verbose) {
    console.log(`Filtered out ${initialLength - content.length} files in ${perf.timeSince()}`)
  }
  console.log(`Filtered out ${initialLength - content.length} files in ${perf.timeSince()}`)
  return content
}
quartz/processors/parse.ts
@@ -50,9 +50,6 @@
    }
  }
  if (verbose) {
    console.log(`Parsed and transformed ${res.length} Markdown files in ${perf.timeSince()}`)
  }
  console.log(`Parsed and transformed ${res.length} Markdown files in ${perf.timeSince()}`)
  return res
}
quartz/resources.ts
@@ -1,6 +1,7 @@
export interface JSResource {
  src: string
  loadTime: 'beforeDOMReady' | 'afterDOMReady'
  type?: 'module'
}
export interface StaticResources {
quartz/styles/base.scss
@@ -98,6 +98,8 @@
    margin: 0 0.5rem;
    opacity: 0;
    transition: opacity 0.2s ease;
    transform: translateY(-0.1rem);
    display: inline-block;
    font-family: var(--codeFont);
    user-select: none;
  }
quartz/theme.ts
@@ -26,9 +26,8 @@
  return `https://fonts.googleapis.com/css2?family=${code}&family=${header}:wght@400;700&family=${body}:ital,wght@0,400;0,600;1,400;1,600&display=swap`
}
export function templateThemeStyles(theme: Theme, stylesheet: string) {
  return `
:root {
export function joinStyles(theme: Theme, ...stylesheet: string[]) {
  return `:root {
  --light: ${theme.colors.lightMode.light};
  --lightgray: ${theme.colors.lightMode.lightgray};
  --gray: ${theme.colors.lightMode.gray};
@@ -54,6 +53,5 @@
  --highlight: ${theme.colors.darkMode.highlight};
}
${stylesheet}
`
${stylesheet.join("\n\n")}`
}