From 76be137283a497c88b7da445cd9f4b8533a04f35 Mon Sep 17 00:00:00 2001
From: Jacky Zhao <j.zhao2k19@gmail.com>
Date: Mon, 29 Jan 2024 08:56:20 +0000
Subject: [PATCH] fix: attempt to merge cached folder state between builds (closes #691)
---
quartz/components/Explorer.tsx | 3
quartz/components/scripts/explorer.inline.ts | 156 +++++++++++++++++++++-------------------------------
quartz/components/scripts/toc.inline.ts | 6 +
3 files changed, 68 insertions(+), 97 deletions(-)
diff --git a/quartz/components/Explorer.tsx b/quartz/components/Explorer.tsx
index e3ed9b1..fdfff23 100644
--- a/quartz/components/Explorer.tsx
+++ b/quartz/components/Explorer.tsx
@@ -69,9 +69,8 @@
}
// Get all folders of tree. Initialize with collapsed state
- const folders = fileTree.getFolderPaths(opts.folderDefaultState === "collapsed")
-
// Stringify to pass json tree as data attribute ([data-tree])
+ const folders = fileTree.getFolderPaths(opts.folderDefaultState === "collapsed")
jsonTree = JSON.stringify(folders)
}
diff --git a/quartz/components/scripts/explorer.inline.ts b/quartz/components/scripts/explorer.inline.ts
index 8e79d20..12546bb 100644
--- a/quartz/components/scripts/explorer.inline.ts
+++ b/quartz/components/scripts/explorer.inline.ts
@@ -1,132 +1,106 @@
import { FolderState } from "../ExplorerNode"
-// Current state of folders
-let explorerState: FolderState[]
-
+type MaybeHTMLElement = HTMLElement | undefined
+let currentExplorerState: FolderState[]
const observer = new IntersectionObserver((entries) => {
// If last element is observed, remove gradient of "overflow" class so element is visible
- const explorer = document.getElementById("explorer-ul")
+ const explorerUl = document.getElementById("explorer-ul")
+ if (!explorerUl) return
for (const entry of entries) {
if (entry.isIntersecting) {
- explorer?.classList.add("no-background")
+ explorerUl.classList.add("no-background")
} else {
- explorer?.classList.remove("no-background")
+ explorerUl.classList.remove("no-background")
}
}
})
function toggleExplorer(this: HTMLElement) {
- // Toggle collapsed state of entire explorer
this.classList.toggle("collapsed")
- const content = this.nextElementSibling as HTMLElement
+ const content = this.nextElementSibling as MaybeHTMLElement
+ if (!content) return
+
content.classList.toggle("collapsed")
content.style.maxHeight = content.style.maxHeight === "0px" ? content.scrollHeight + "px" : "0px"
}
function toggleFolder(evt: MouseEvent) {
evt.stopPropagation()
+ const target = evt.target as MaybeHTMLElement
+ if (!target) return
- // Element that was clicked
- const target = evt.target as HTMLElement
-
- // Check if target was svg icon or button
const isSvg = target.nodeName === "svg"
+ const childFolderContainer = (
+ isSvg
+ ? target.parentElement?.nextSibling
+ : target.parentElement?.parentElement?.nextElementSibling
+ ) as MaybeHTMLElement
+ const currentFolderParent = (
+ isSvg ? target.nextElementSibling : target.parentElement
+ ) as MaybeHTMLElement
+ if (!(childFolderContainer && currentFolderParent)) return
- // corresponding <ul> element relative to clicked button/folder
- let childFolderContainer: HTMLElement
-
- // <li> element of folder (stores folder-path dataset)
- let currentFolderParent: HTMLElement
-
- // Get correct relative container and toggle collapsed class
- if (isSvg) {
- childFolderContainer = target.parentElement?.nextSibling as HTMLElement
- currentFolderParent = target.nextElementSibling as HTMLElement
-
- childFolderContainer.classList.toggle("open")
- } else {
- childFolderContainer = target.parentElement?.parentElement?.nextElementSibling as HTMLElement
- currentFolderParent = target.parentElement as HTMLElement
-
- childFolderContainer.classList.toggle("open")
- }
- if (!childFolderContainer) return
-
- // Collapse folder container
+ childFolderContainer.classList.toggle("open")
const isCollapsed = childFolderContainer.classList.contains("open")
setFolderState(childFolderContainer, !isCollapsed)
-
- // Save folder state to localStorage
- const clickFolderPath = currentFolderParent.dataset.folderpath as string
-
- const fullFolderPath = clickFolderPath
- toggleCollapsedByPath(explorerState, fullFolderPath)
-
- const stringifiedFileTree = JSON.stringify(explorerState)
+ const fullFolderPath = currentFolderParent.dataset.folderpath as string
+ toggleCollapsedByPath(currentExplorerState, fullFolderPath)
+ const stringifiedFileTree = JSON.stringify(currentExplorerState)
localStorage.setItem("fileTree", stringifiedFileTree)
}
function setupExplorer() {
- // Set click handler for collapsing entire explorer
const explorer = document.getElementById("explorer")
+ if (!explorer) return
+
+ if (explorer.dataset.behavior === "collapse") {
+ for (const item of document.getElementsByClassName(
+ "folder-button",
+ ) as HTMLCollectionOf<HTMLElement>) {
+ item.removeEventListener("click", toggleFolder)
+ item.addEventListener("click", toggleFolder)
+ }
+ }
+
+ explorer.removeEventListener("click", toggleExplorer)
+ explorer.addEventListener("click", toggleExplorer)
+
+ // Set up click handlers for each folder (click handler on folder "icon")
+ for (const item of document.getElementsByClassName(
+ "folder-icon",
+ ) as HTMLCollectionOf<HTMLElement>) {
+ item.removeEventListener("click", toggleFolder)
+ item.addEventListener("click", toggleFolder)
+ }
// Get folder state from local storage
const storageTree = localStorage.getItem("fileTree")
-
- // Convert to bool
const useSavedFolderState = explorer?.dataset.savestate === "true"
+ const oldExplorerState: FolderState[] =
+ storageTree && useSavedFolderState ? JSON.parse(storageTree) : []
+ const oldIndex = new Map(oldExplorerState.map((entry) => [entry.path, entry.collapsed]))
+ const newExplorerState: FolderState[] = explorer.dataset.tree
+ ? JSON.parse(explorer.dataset.tree)
+ : []
+ currentExplorerState = []
+ for (const { path, collapsed } of newExplorerState) {
+ currentExplorerState.push({ path, collapsed: oldIndex.get(path) ?? collapsed })
+ }
- if (explorer) {
- // Get config
- const collapseBehavior = explorer.dataset.behavior
-
- // Add click handlers for all folders (click handler on folder "label")
- if (collapseBehavior === "collapse") {
- Array.prototype.forEach.call(
- document.getElementsByClassName("folder-button"),
- function (item) {
- item.removeEventListener("click", toggleFolder)
- item.addEventListener("click", toggleFolder)
- },
- )
+ currentExplorerState.map((folderState) => {
+ const folderLi = document.querySelector(
+ `[data-folderpath='${folderState.path}']`,
+ ) as MaybeHTMLElement
+ const folderUl = folderLi?.parentElement?.nextElementSibling as MaybeHTMLElement
+ if (folderUl) {
+ setFolderState(folderUl, folderState.collapsed)
}
-
- // Add click handler to main explorer
- explorer.removeEventListener("click", toggleExplorer)
- explorer.addEventListener("click", toggleExplorer)
- }
-
- // Set up click handlers for each folder (click handler on folder "icon")
- Array.prototype.forEach.call(document.getElementsByClassName("folder-icon"), function (item) {
- item.removeEventListener("click", toggleFolder)
- item.addEventListener("click", toggleFolder)
})
-
- if (storageTree && useSavedFolderState) {
- // Get state from localStorage and set folder state
- explorerState = JSON.parse(storageTree)
- explorerState.map((folderUl) => {
- // grab <li> element for matching folder path
- const folderLi = document.querySelector(`[data-folderpath='${folderUl.path}']`) as HTMLElement
-
- // Get corresponding content <ul> tag and set state
- if (folderLi) {
- const folderUL = folderLi.parentElement?.nextElementSibling
- if (folderUL) {
- setFolderState(folderUL as HTMLElement, folderUl.collapsed)
- }
- }
- })
- } else if (explorer?.dataset.tree) {
- // If tree is not in localStorage or config is disabled, use tree passed from Explorer as dataset
- explorerState = JSON.parse(explorer.dataset.tree)
- }
}
window.addEventListener("resize", setupExplorer)
document.addEventListener("nav", () => {
setupExplorer()
-
observer.disconnect()
// select pseudo element at end of list
@@ -142,11 +116,7 @@
* @param collapsed if folder should be set to collapsed or not
*/
function setFolderState(folderElement: HTMLElement, collapsed: boolean) {
- if (collapsed) {
- folderElement?.classList.remove("open")
- } else {
- folderElement?.classList.add("open")
- }
+ return collapsed ? folderElement.classList.remove("open") : folderElement.classList.add("open")
}
/**
diff --git a/quartz/components/scripts/toc.inline.ts b/quartz/components/scripts/toc.inline.ts
index f3da52c..2e1e52b 100644
--- a/quartz/components/scripts/toc.inline.ts
+++ b/quartz/components/scripts/toc.inline.ts
@@ -16,7 +16,8 @@
function toggleToc(this: HTMLElement) {
this.classList.toggle("collapsed")
- const content = this.nextElementSibling as HTMLElement
+ const content = this.nextElementSibling as HTMLElement | undefined
+ if (!content) return
content.classList.toggle("collapsed")
content.style.maxHeight = content.style.maxHeight === "0px" ? content.scrollHeight + "px" : "0px"
}
@@ -25,7 +26,8 @@
const toc = document.getElementById("toc")
if (toc) {
const collapsed = toc.classList.contains("collapsed")
- const content = toc.nextElementSibling as HTMLElement
+ const content = toc.nextElementSibling as HTMLElement | undefined
+ if (!content) return
content.style.maxHeight = collapsed ? "0px" : content.scrollHeight + "px"
toc.removeEventListener("click", toggleToc)
toc.addEventListener("click", toggleToc)
--
Gitblit v1.10.0