Jacky Zhao
2023-06-07 1cb4dadf13913009660685b6f2c163c939e2a51a
codeblock copy
3 files added
5 files modified
2 files renamed
108 ■■■■■ changed files
quartz.config.ts 4 ●●● patch | view | raw | blame | history
quartz/components/Body.tsx 18 ●●●●● patch | view | raw | blame | history
quartz/components/Darkmode.tsx 2 ●●● patch | view | raw | blame | history
quartz/components/Header.tsx 2 ●●● patch | view | raw | blame | history
quartz/components/scripts/clipboard.inline.ts 32 ●●●●● patch | view | raw | blame | history
quartz/components/styles/clipboard.scss 37 ●●●●● patch | view | raw | blame | history
quartz/components/styles/darkmode.scss patch | view | raw | blame | history
quartz/components/styles/header.scss patch | view | raw | blame | history
quartz/plugins/emitters/contentPage.tsx 11 ●●●● patch | view | raw | blame | history
quartz/plugins/index.ts 2 ●●● patch | view | raw | blame | history
quartz.config.ts
@@ -1,4 +1,5 @@
import { QuartzConfig } from "./quartz/cfg"
import Body from "./quartz/components/Body"
import Head from "./quartz/components/Head"
import Header from "./quartz/components/Header"
import {
@@ -68,7 +69,8 @@
    emitters: [
      new ContentPage({
        Head: Head,
        Header: Header
        Header: Header,
        Body: Body
      })
    ]
  },
quartz/components/Body.tsx
New file
@@ -0,0 +1,18 @@
import { ComponentChildren } from "preact"
import clipboardScript from './scripts/clipboard.inline'
import clipboardStyle from './styles/clipboard.scss'
export interface BodyProps {
  title?: string
  children: ComponentChildren
}
export default function Body({ title, children }: BodyProps) {
  return <article>
    {title && <h1>{title}</h1>}
    {children}
  </article>
}
Body.afterDOMLoaded = clipboardScript
Body.css = clipboardStyle
quartz/components/Darkmode.tsx
@@ -2,7 +2,7 @@
// modules are automatically deferred and we don't want that to happen for critical beforeDOMLoads
// see: https://v8.dev/features/modules#defer
import darkmodeScript from "./scripts/darkmode.inline"
import styles from '../styles/darkmode.scss'
import styles from './styles/darkmode.scss'
export default function Darkmode() {
  return <div class="darkmode">
quartz/components/Header.tsx
@@ -1,6 +1,6 @@
import { resolveToRoot } from "../path"
import Darkmode from "./Darkmode"
import style from '../styles/header.scss'
import style from './styles/header.scss'
export interface HeaderProps {
  title: string
quartz/components/scripts/clipboard.inline.ts
New file
@@ -0,0 +1,32 @@
const description = "Initialize copy for codeblocks"
export default description
const svgCopy =
  '<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true"><path fill-rule="evenodd" d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25v-7.5z"></path><path fill-rule="evenodd" d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0114.25 11h-7.5A1.75 1.75 0 015 9.25v-7.5zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25h-7.5z"></path></svg>'
const svgCheck =
  '<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true"><path fill-rule="evenodd" fill="rgb(63, 185, 80)" d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"></path></svg>'
const els = document.getElementsByTagName("pre")
for (let i = 0; i < els.length; i++) {
  const codeBlock = els[i].getElementsByTagName("code")[0]
  const source = codeBlock.innerText.replace(/\n\n/g, "\n")
  const button = document.createElement("button")
  button.className = "clipboard-button"
  button.type = "button"
  button.innerHTML = svgCopy
  button.ariaLabel = "Copy source"
  button.addEventListener("click", () => {
    navigator.clipboard.writeText(source).then(
      () => {
        button.blur()
        button.innerHTML = svgCheck
        setTimeout(() => {
          button.innerHTML = svgCopy
          button.style.borderColor = ""
        }, 2000)
      },
      (error) => console.error(error),
    )
  })
  els[i].prepend(button)
}
quartz/components/styles/clipboard.scss
New file
@@ -0,0 +1,37 @@
.clipboard-button {
  position: absolute;
  display: flex;
  float: right;
  right: 0;
  padding: 0.4rem;
  margin: -0.2rem 0.3rem;
  color: var(--gray);
  border-color: var(--dark);
  background-color: var(--light);
  border: 1px solid;
  border-radius: 5px;
  z-index: 1;
  opacity: 0;
  transition: 0.2s;
  & > svg {
    fill: var(--light);
    filter: contrast(0.3);
  }
  &:hover {
    cursor: pointer;
    border-color: var(--secondary);
  }
  &:focus {
    outline: 0;
  }
}
pre {
  &:hover > .clipboard-button {
    opacity: 1;
    transition: 0.2s;
  }
}
quartz/components/styles/darkmode.scss
quartz/components/styles/header.scss
quartz/plugins/emitters/contentPage.tsx
@@ -9,10 +9,12 @@
import { HeaderProps } from "../../components/Header"
import { QuartzComponent } from "../../components/types"
import { resolveToRoot } from "../../path"
import { BodyProps } from "../../components/Body"
interface Options {
  Head: QuartzComponent<HeadProps>
  Header: QuartzComponent<HeaderProps>
  Body: QuartzComponent<BodyProps>
}
export class ContentPage extends QuartzEmitterPlugin {
@@ -31,7 +33,7 @@
  async emit(cfg: GlobalConfiguration, content: ProcessedContent[], resources: StaticResources, emit: EmitCallback): Promise<string[]> {
    const fps: string[] = []
    const { Head, Header } = this.opts
    const { Head, Header, Body } = this.opts
    for (const [tree, file] of content) {
      // @ts-ignore (preact makes it angry)
      const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: 'html' })
@@ -42,7 +44,7 @@
        js: [
          { src: baseDir + "/prescript.js", loadTime: "beforeDOMReady" },
          ...resources.js,
          { src: baseDir + "/postscript.js", loadTime: "afterDOMReady" }
          { src: baseDir + "/postscript.js", loadTime: "afterDOMReady", type: 'module' }
        ]
      }
@@ -56,10 +58,7 @@
        <body>
          <div id="quartz-root" class="page">
            <Header title={cfg.siteTitle} slug={file.data.slug!} />
            <article>
              {file.data.slug !== "index" && <h1>{title}</h1>}
              {content}
            </article>
            <Body title={file.data.slug === "index" ? undefined : title}>{content}</Body>
          </div>
        </body>
        {pageResources.js.filter(resource => resource.loadTime === "afterDOMReady").map(resource => <script key={resource.src} {...resource} />)}
quartz/plugins/index.ts
@@ -40,7 +40,7 @@
      componentResources.beforeDOMLoaded.push(beforeDOMLoaded)
    }
    if (afterDOMLoaded) {
      componentResources.beforeDOMLoaded.push(afterDOMLoaded)
      componentResources.afterDOMLoaded.push(afterDOMLoaded)
    }
  }