From 4d3579ca9876d3ca6612589d116c3a300a60b446 Mon Sep 17 00:00:00 2001
From: Jacky Zhao <j.zhao2k19@gmail.com>
Date: Tue, 6 Jun 2023 19:48:37 -0700
Subject: [PATCH] darkmode scripts

---
 quartz.config.ts                             |  4 +-
 quartz/bootstrap-cli.mjs                     |  1 +
 quartz/build.ts                              |  2 +-
 quartz/components/Darkmode.tsx               | 47 ++++++++++++++++++++
 quartz/components/Head.tsx                   |  2 +-
 quartz/components/Header.tsx                 |  8 +++-
 quartz/components/scripts/darkmode.inline.ts | 26 ++++++++++-
 quartz/components/types.ts                   |  3 +-
 quartz/plugins/emitters/contentPage.tsx      |  4 +-
 quartz/styles/base.scss                      |  6 +--
 quartz/styles/callouts.scss                  |  2 +-
 quartz/styles/darkmode.scss                  | 45 +++++++++++++++++++
 quartz/styles/header.scss                    | 10 +++++
 13 files changed, 145 insertions(+), 15 deletions(-)
 create mode 100644 quartz/components/Darkmode.tsx
 create mode 100644 quartz/styles/darkmode.scss
 create mode 100644 quartz/styles/header.scss

diff --git a/quartz.config.ts b/quartz.config.ts
index 6808d93..25518d5 100644
--- a/quartz.config.ts
+++ b/quartz.config.ts
@@ -1,6 +1,6 @@
 import { QuartzConfig } from "./quartz/cfg"
-import * as Head from "./quartz/components/Head"
-import * as Header from "./quartz/components/Header"
+import Head from "./quartz/components/Head"
+import Header from "./quartz/components/Header"
 import {
   ContentPage,
   CreatedModifiedDate,
diff --git a/quartz/bootstrap-cli.mjs b/quartz/bootstrap-cli.mjs
index be6270e..f9b5304 100755
--- a/quartz/bootstrap-cli.mjs
+++ b/quartz/bootstrap-cli.mjs
@@ -74,6 +74,7 @@ yargs(hideBin(process.argv))
               const transpiled = await esbuild.build({
                 stdin: {
                   contents: text,
+                  loader: 'ts',
                   sourcefile: path.relative(path.resolve('.'), args.path),
                 },
                 write: false,
diff --git a/quartz/build.ts b/quartz/build.ts
index 26e354c..60a1a51 100644
--- a/quartz/build.ts
+++ b/quartz/build.ts
@@ -59,7 +59,7 @@ export default async function buildQuartz(argv: Argv, version: string) {
     const server = http.createServer(async (req, res) => {
       return serveHandler(req, res, {
         public: output,
-        directoryListing: false
+        directoryListing: false,
       })
     })
     server.listen(argv.port)
diff --git a/quartz/components/Darkmode.tsx b/quartz/components/Darkmode.tsx
new file mode 100644
index 0000000..0161e0a
--- /dev/null
+++ b/quartz/components/Darkmode.tsx
@@ -0,0 +1,47 @@
+import darkmodeScript from "./scripts/darkmode.inline"
+import styles from '../styles/darkmode.scss'
+
+export default function Darkmode() {
+  return <div class="darkmode">
+    <input class="toggle" id="darkmode-toggle" type="checkbox" tabIndex={-1} />
+    <label id="toggle-label-light" for="darkmode-toggle" tabIndex={-1}>
+      <svg
+        xmlns="http://www.w3.org/2000/svg"
+        xmlnsXlink="http://www.w3.org/1999/xlink"
+        version="1.1"
+        id="dayIcon"
+        x="0px"
+        y="0px"
+        viewBox="0 0 35 35"
+        style="enable-background:new 0 0 35 35;"
+        xmlSpace="preserve"
+      >
+        <title>Light mode</title>
+        <path
+          d="M6,17.5C6,16.672,5.328,16,4.5,16h-3C0.672,16,0,16.672,0,17.5    S0.672,19,1.5,19h3C5.328,19,6,18.328,6,17.5z M7.5,26c-0.414,0-0.789,0.168-1.061,0.439l-2,2C4.168,28.711,4,29.086,4,29.5    C4,30.328,4.671,31,5.5,31c0.414,0,0.789-0.168,1.06-0.44l2-2C8.832,28.289,9,27.914,9,27.5C9,26.672,8.329,26,7.5,26z M17.5,6    C18.329,6,19,5.328,19,4.5v-3C19,0.672,18.329,0,17.5,0S16,0.672,16,1.5v3C16,5.328,16.671,6,17.5,6z M27.5,9    c0.414,0,0.789-0.168,1.06-0.439l2-2C30.832,6.289,31,5.914,31,5.5C31,4.672,30.329,4,29.5,4c-0.414,0-0.789,0.168-1.061,0.44    l-2,2C26.168,6.711,26,7.086,26,7.5C26,8.328,26.671,9,27.5,9z M6.439,8.561C6.711,8.832,7.086,9,7.5,9C8.328,9,9,8.328,9,7.5    c0-0.414-0.168-0.789-0.439-1.061l-2-2C6.289,4.168,5.914,4,5.5,4C4.672,4,4,4.672,4,5.5c0,0.414,0.168,0.789,0.439,1.06    L6.439,8.561z M33.5,16h-3c-0.828,0-1.5,0.672-1.5,1.5s0.672,1.5,1.5,1.5h3c0.828,0,1.5-0.672,1.5-1.5S34.328,16,33.5,16z     M28.561,26.439C28.289,26.168,27.914,26,27.5,26c-0.828,0-1.5,0.672-1.5,1.5c0,0.414,0.168,0.789,0.439,1.06l2,2    C28.711,30.832,29.086,31,29.5,31c0.828,0,1.5-0.672,1.5-1.5c0-0.414-0.168-0.789-0.439-1.061L28.561,26.439z M17.5,29    c-0.829,0-1.5,0.672-1.5,1.5v3c0,0.828,0.671,1.5,1.5,1.5s1.5-0.672,1.5-1.5v-3C19,29.672,18.329,29,17.5,29z M17.5,7    C11.71,7,7,11.71,7,17.5S11.71,28,17.5,28S28,23.29,28,17.5S23.29,7,17.5,7z M17.5,25c-4.136,0-7.5-3.364-7.5-7.5    c0-4.136,3.364-7.5,7.5-7.5c4.136,0,7.5,3.364,7.5,7.5C25,21.636,21.636,25,17.5,25z"
+        ></path>
+      </svg>
+    </label>
+    <label id="toggle-label-dark" for="darkmode-toggle" tabIndex={-1}>
+      <svg
+        xmlns="http://www.w3.org/2000/svg"
+        xmlnsXlink="http://www.w3.org/1999/xlink"
+        version="1.1"
+        id="nightIcon"
+        x="0px"
+        y="0px"
+        viewBox="0 0 100 100"
+        style="enable-background='new 0 0 100 100'"
+        xmlSpace="preserve"
+      >
+        <title>Dark mode</title>
+        <path
+          d="M96.76,66.458c-0.853-0.852-2.15-1.064-3.23-0.534c-6.063,2.991-12.858,4.571-19.655,4.571  C62.022,70.495,50.88,65.88,42.5,57.5C29.043,44.043,25.658,23.536,34.076,6.47c0.532-1.08,0.318-2.379-0.534-3.23  c-0.851-0.852-2.15-1.064-3.23-0.534c-4.918,2.427-9.375,5.619-13.246,9.491c-9.447,9.447-14.65,22.008-14.65,35.369  c0,13.36,5.203,25.921,14.65,35.368s22.008,14.65,35.368,14.65c13.361,0,25.921-5.203,35.369-14.65  c3.872-3.871,7.064-8.328,9.491-13.246C97.826,68.608,97.611,67.309,96.76,66.458z"
+        ></path>
+      </svg>
+    </label>
+  </div>
+}
+
+Darkmode.beforeDOMLoaded = darkmodeScript
+Darkmode.css = styles
diff --git a/quartz/components/Head.tsx b/quartz/components/Head.tsx
index 29050a7..9e182c7 100644
--- a/quartz/components/Head.tsx
+++ b/quartz/components/Head.tsx
@@ -8,7 +8,7 @@ export interface HeadProps {
   externalResources: StaticResources
 }
 
-export function Component({ title, description, slug, externalResources }: HeadProps) {
+export default function Head({ title, description, slug, externalResources }: HeadProps) {
   const { css, js } = externalResources
   const baseDir = resolveToRoot(slug)
   const iconPath = baseDir + "/static/icon.png"
diff --git a/quartz/components/Header.tsx b/quartz/components/Header.tsx
index 229e210..cb2fae8 100644
--- a/quartz/components/Header.tsx
+++ b/quartz/components/Header.tsx
@@ -1,14 +1,20 @@
 import { resolveToRoot } from "../path"
+import Darkmode from "./Darkmode"
+import style from '../styles/header.scss'
 
 export interface HeaderProps {
   title: string
   slug: string
 }
 
-export function Component({ title, slug }: HeaderProps) {
+export default function Header({ title, slug }: HeaderProps) {
   const baseDir = resolveToRoot(slug)
   return <header>
     <h1><a href={baseDir}>{title}</a></h1>
+    <div class="spacer"></div>
+    <Darkmode />
   </header>
 }
 
+Header.beforeDOMLoaded = Darkmode.beforeDOMLoaded
+Header.css = style + Darkmode.css
diff --git a/quartz/components/scripts/darkmode.inline.ts b/quartz/components/scripts/darkmode.inline.ts
index c17013a..d2b6e06 100644
--- a/quartz/components/scripts/darkmode.inline.ts
+++ b/quartz/components/scripts/darkmode.inline.ts
@@ -1,3 +1,25 @@
-export default "Darkmode" 
+export default "Darkmode"
 
-console.log("HELLOOOO FROM CONSOLE")
+const currentTheme = localStorage.getItem("theme")
+const theme =
+  currentTheme ??
+  (window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark")
+
+document.documentElement.setAttribute("saved-theme", theme)
+
+window.addEventListener("DOMContentLoaded", () => {
+  const toggleSwitch = document.querySelector("#darkmode-toggle") as HTMLInputElement
+  toggleSwitch.addEventListener("change", (e: any) => {
+    if (e.target.checked) {
+      document.documentElement.setAttribute("saved-theme", "dark")
+      localStorage.setItem("theme", "dark")
+    } else {
+      document.documentElement.setAttribute("saved-theme", "light")
+      localStorage.setItem("theme", "light")
+    }
+  })
+
+  if (theme === "dark") {
+    toggleSwitch.checked = true
+  }
+})
diff --git a/quartz/components/types.ts b/quartz/components/types.ts
index e1bdebc..96ead5f 100644
--- a/quartz/components/types.ts
+++ b/quartz/components/types.ts
@@ -1,7 +1,6 @@
 import { ComponentType } from "preact"
 
-export type QuartzComponent<Props> = {
-  Component: ComponentType<Props>
+export type QuartzComponent<Props> = ComponentType<Props> & {
   css?: string,
   beforeDOMLoaded?: string,
   afterDOMLoaded?: string,
diff --git a/quartz/plugins/emitters/contentPage.tsx b/quartz/plugins/emitters/contentPage.tsx
index 05c4c4b..9cca0af 100644
--- a/quartz/plugins/emitters/contentPage.tsx
+++ b/quartz/plugins/emitters/contentPage.tsx
@@ -48,14 +48,14 @@ export class ContentPage extends QuartzEmitterPlugin {
 
       const title = file.data.frontmatter?.title
       const doc = <html>
-        <Head.Component
+        <Head
           title={title ?? "Untitled"}
           description={file.data.description ?? "No description provided"}
           slug={file.data.slug!}
           externalResources={pageResources} />
         <body>
           <div id="quartz-root" class="page">
-            <Header.Component title={cfg.siteTitle} slug={file.data.slug!} />
+            <Header title={cfg.siteTitle} slug={file.data.slug!} />
             <article>
               {file.data.slug !== "index" && <h1>{title}</h1>}
               {content}
diff --git a/quartz/styles/base.scss b/quartz/styles/base.scss
index f039035..d7a1891 100644
--- a/quartz/styles/base.scss
+++ b/quartz/styles/base.scss
@@ -24,7 +24,7 @@ body {
   border-radius: 5px;
 }
 
-p, ul, text, a, tr, td, li, ol, ul {
+p, ul, text, a, tr, td, li, ol, ul, .katex {
   color: var(--darkgray);
   fill: var(--darkgray);
 }
@@ -67,7 +67,7 @@ a {
 }
 
 blockquote {
-  margin-left: 0;
+  margin: 1rem 0;
   border-left: 3px solid var(--secondary);
   padding-left: 1rem;
   transition: border-color 0.2s ease;
@@ -134,7 +134,7 @@ pre {
   & > code {
     background: none;
     padding: 0;
-    font-size: 0.9rem;
+    font-size: 0.85rem;
     counter-reset: line;
     counter-increment: line 0;
     display: grid;
diff --git a/quartz/styles/callouts.scss b/quartz/styles/callouts.scss
index 9e2ff2d..26cb0ba 100644
--- a/quartz/styles/callouts.scss
+++ b/quartz/styles/callouts.scss
@@ -4,7 +4,7 @@
 	border: 1px solid var(--border);
 	background-color: var(--bg);
 	border-radius: 5px;
-	padding: 0 0.7rem;
+	padding: 0 1rem;
 
 	&[data-callout="note"] {
 	  --color: #448aff;
diff --git a/quartz/styles/darkmode.scss b/quartz/styles/darkmode.scss
new file mode 100644
index 0000000..561a83c
--- /dev/null
+++ b/quartz/styles/darkmode.scss
@@ -0,0 +1,45 @@
+.darkmode {
+  float: right;
+  padding: 1rem;
+  min-width: 30px;
+  position: relative;
+
+  @media all and (max-width: 450px) {
+    padding: 1rem;
+  }
+
+  & > .toggle {
+    display: none;
+    box-sizing: border-box;
+  }
+
+  & svg {
+    cursor: pointer;
+    opacity: 0;
+    position: absolute;
+    width: 20px;
+    height: 20px;
+    top: calc(50% - 10px);
+    margin: 0 7px;
+    fill: var(--darkgray);
+    transition: opacity 0.1s ease;
+  }
+}
+
+:root[saved-theme="dark"] .toggle ~ label {
+  & > #dayIcon {
+    opacity: 0;
+  }
+  & > #nightIcon {
+    opacity: 1;
+  }
+}
+
+:root .toggle ~ label {
+  & > #dayIcon {
+    opacity: 1;
+  }
+  & > #nightIcon {
+    opacity: 0;
+  }
+}
diff --git a/quartz/styles/header.scss b/quartz/styles/header.scss
new file mode 100644
index 0000000..c3ea487
--- /dev/null
+++ b/quartz/styles/header.scss
@@ -0,0 +1,10 @@
+header {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  margin: 1em 0 2em 0;
+  & > h1 {
+    margin: 0;
+    flex: auto;
+  }
+}