From b5877824500a19c721c93eedc59704db94487a94 Mon Sep 17 00:00:00 2001
From: Jacky Zhao <j.zhao2k19@gmail.com>
Date: Sat, 17 Jun 2023 20:08:06 +0000
Subject: [PATCH] collapsible callout

---
 quartz/plugins/index.ts                     |    2 
 quartz/styles/callouts.scss                 |   16 +++++++
 quartz/plugins/transformers/ofm.ts          |   38 ++++++++++++++-----
 quartz/components/scripts/callout.inline.ts |   24 ++++++++++++
 4 files changed, 68 insertions(+), 12 deletions(-)

diff --git a/quartz/components/scripts/callout.inline.ts b/quartz/components/scripts/callout.inline.ts
new file mode 100644
index 0000000..5f35873
--- /dev/null
+++ b/quartz/components/scripts/callout.inline.ts
@@ -0,0 +1,24 @@
+function toggleCallout(this: HTMLElement) {
+  const outerBlock = this.parentElement!
+  this.classList.toggle(`is-collapsed`)
+  const collapsed = this.classList.contains(`is-collapsed`)
+  const height = collapsed ? this.scrollHeight : outerBlock.scrollHeight
+  outerBlock.style.maxHeight = height + `px`
+}
+
+function setupCallout(div: HTMLElement) {
+  const collapsed = div.classList.contains(`is-collapsed`)
+  const title = div.firstElementChild!
+  const height = collapsed ? title.scrollHeight : div.scrollHeight
+  div.style.maxHeight = height + `px`
+}
+
+document.addEventListener(`nav`, () => {
+  const collapsible = document.getElementsByClassName(`callout is-collapsible`) as HTMLCollectionOf<HTMLElement>
+  for (const div of collapsible) {
+    const title = div.firstElementChild
+    setupCallout(div)
+    title?.removeEventListener(`click`, toggleCallout)
+    title?.addEventListener(`click`, toggleCallout)
+  }
+})
diff --git a/quartz/plugins/index.ts b/quartz/plugins/index.ts
index 04de0d4..7e665fc 100644
--- a/quartz/plugins/index.ts
+++ b/quartz/plugins/index.ts
@@ -83,7 +83,7 @@
   }
 
   for (const transformer of plugins.transformers) {
-    const res = transformer.externalResources
+    const res = transformer.externalResources ? transformer.externalResources() : {}
     if (res?.js) {
       staticResources.js = staticResources.js.concat(res.js)
     }
diff --git a/quartz/plugins/transformers/ofm.ts b/quartz/plugins/transformers/ofm.ts
index aa83953..4ade476 100644
--- a/quartz/plugins/transformers/ofm.ts
+++ b/quartz/plugins/transformers/ofm.ts
@@ -7,6 +7,8 @@
 import { visit } from "unist-util-visit"
 import path from "path"
 import { JSResource } from "../../resources"
+// @ts-ignore
+import calloutScript from "../../components/scripts/callout.inline.ts"
 
 export interface Options {
   highlight: boolean
@@ -210,6 +212,10 @@
                 const defaultState = collapseChar === "-" ? "collapsed" : "expanded"
                 const title = match.input.slice(calloutDirective.length).trim() || capitalize(calloutType)
 
+                const toggleIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="fold">
+                  <polyline points="6 9 12 15 18 9"></polyline>
+                </svg>`
+
                 const titleNode: HTML = {
                   type: "html",
                   value: `<div 
@@ -217,6 +223,7 @@
                 >
                   <div class="callout-icon">${callouts[canonicalizeCallout(calloutType)]}</div>
                   <div class="callout-title-inner">${title}</div>
+                  ${collapse ? toggleIcon : ""}
                 </div>`
                 }
 
@@ -228,7 +235,6 @@
                       type: 'text',
                       value: remainingText,
                     }]
-
                   })
                 }
 
@@ -236,7 +242,6 @@
                 node.children.splice(0, 1, ...blockquoteContent)
 
                 // add properties to base blockquote
-                // TODO: add the js to actually support collapsing callout
                 node.data = {
                   hProperties: {
                     ...(node.data?.hProperties ?? {}),
@@ -273,18 +278,31 @@
       return [rehypeRaw]
     },
     externalResources() {
-      const mermaidScript: JSResource = {
-        script: `
+      const js: JSResource[] = []
+
+      if (opts.callouts) {
+        js.push({
+          script: calloutScript,
+          loadTime: 'afterDOMReady',
+          contentType: 'inline'
+        })
+      }
+
+      if (opts.mermaid) {
+        js.push({
+          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'
+          loadTime: 'afterDOMReady',
+          moduleType: 'module',
+          contentType: 'inline'
+        })
       }
-      return {
-        js: opts.mermaid ? [mermaidScript] : []
-      }
+
+      console.log(js)
+
+      return { js }
     }
   }
 }
diff --git a/quartz/styles/callouts.scss b/quartz/styles/callouts.scss
index 26cb0ba..84d70b4 100644
--- a/quartz/styles/callouts.scss
+++ b/quartz/styles/callouts.scss
@@ -5,6 +5,8 @@
 	background-color: var(--bg);
 	border-radius: 5px;
 	padding: 0 1rem;
+	overflow-y: hidden;
+  transition: max-height 0.3s ease;
 
 	&[data-callout="note"] {
 	  --color: #448aff;
@@ -71,8 +73,20 @@
 	display: flex;
 	align-items: center;
 	gap: 5px;
-	margin: 1rem 0;
+	padding: 1rem 0;
+	margin-bottom: -1rem;
 	color: var(--color);
+
+	& .fold {
+    margin-left: 0.5rem; 
+    transition: transform 0.3s ease;
+    opacity: 0.8;
+    cursor: pointer;
+  }
+
+  &.is-collapsed .fold {
+    transform: rotateZ(-90deg)
+  }
 }
 
 .callout-icon {

--
Gitblit v1.10.0