From f346a01296e5bdbd19de38fa69968b42629b883d Mon Sep 17 00:00:00 2001
From: Emile Bangma <github@emilebangma.com>
Date: Thu, 08 Jan 2026 01:54:41 +0000
Subject: [PATCH] feat(explorer): Add active class to current folder in explorer (#2196)
---
quartz/components/scripts/explorer.inline.ts | 98 +++++++++++++++++++++++++++++++------------------
1 files changed, 62 insertions(+), 36 deletions(-)
diff --git a/quartz/components/scripts/explorer.inline.ts b/quartz/components/scripts/explorer.inline.ts
index 68a20bb..3c6851c 100644
--- a/quartz/components/scripts/explorer.inline.ts
+++ b/quartz/components/scripts/explorer.inline.ts
@@ -21,13 +21,19 @@
let currentExplorerState: Array<FolderState>
function toggleExplorer(this: HTMLElement) {
- const explorers = document.querySelectorAll(".explorer")
- for (const explorer of explorers) {
- explorer.classList.toggle("collapsed")
- explorer.setAttribute(
- "aria-expanded",
- explorer.getAttribute("aria-expanded") === "true" ? "false" : "true",
- )
+ const nearestExplorer = this.closest(".explorer") as HTMLElement
+ if (!nearestExplorer) return
+ const explorerCollapsed = nearestExplorer.classList.toggle("collapsed")
+ nearestExplorer.setAttribute(
+ "aria-expanded",
+ nearestExplorer.getAttribute("aria-expanded") === "true" ? "false" : "true",
+ )
+
+ if (!explorerCollapsed) {
+ // Stop <html> from being scrollable when mobile explorer is open
+ document.documentElement.classList.add("mobile-no-scroll")
+ } else {
+ document.documentElement.classList.remove("mobile-no-scroll")
}
}
@@ -105,6 +111,10 @@
const folderPath = node.slug
folderContainer.dataset.folderpath = folderPath
+ if (currentSlug === folderPath) {
+ folderContainer.classList.add("active")
+ }
+
if (opts.folderClickBehavior === "link") {
// Replace button with link for link behavior
const button = titleContainer.querySelector(".folder-button") as HTMLElement
@@ -135,9 +145,9 @@
}
for (const child of node.children) {
- const childNode = child.data
- ? createFileNode(currentSlug, child)
- : createFolderNode(currentSlug, child, opts)
+ const childNode = child.isFolder
+ ? createFolderNode(currentSlug, child, opts)
+ : createFileNode(currentSlug, child)
ul.appendChild(childNode)
}
@@ -145,7 +155,7 @@
}
async function setupExplorer(currentSlug: FullSlug) {
- const allExplorers = document.querySelectorAll(".explorer") as NodeListOf<HTMLElement>
+ const allExplorers = document.querySelectorAll("div.explorer") as NodeListOf<HTMLElement>
for (const explorer of allExplorers) {
const dataFns = JSON.parse(explorer.dataset.dataFns || "{}")
@@ -162,7 +172,7 @@
// Get folder state from local storage
const storageTree = localStorage.getItem("fileTree")
const serializedExplorerState = storageTree && opts.useSavedState ? JSON.parse(storageTree) : []
- const oldIndex = new Map(
+ const oldIndex = new Map<string, boolean>(
serializedExplorerState.map((entry: FolderState) => [entry.path, entry.collapsed]),
)
@@ -187,12 +197,16 @@
// Get folder paths for state management
const folderPaths = trie.getFolderPaths()
- currentExplorerState = folderPaths.map((path) => ({
- path,
- collapsed: oldIndex.get(path) === true,
- }))
+ currentExplorerState = folderPaths.map((path) => {
+ const previousState = oldIndex.get(path)
+ return {
+ path,
+ collapsed:
+ previousState === undefined ? opts.folderDefaultState === "collapsed" : previousState,
+ }
+ })
- const explorerUl = document.getElementById("explorer-ul")
+ const explorerUl = explorer.querySelector(".explorer-ul")
if (!explorerUl) continue
// Create and insert new content
@@ -219,14 +233,12 @@
}
// Set up event handlers
- const explorerButtons = explorer.querySelectorAll(
- "button.explorer-toggle",
- ) as NodeListOf<HTMLElement>
- if (explorerButtons) {
- window.addCleanup(() =>
- explorerButtons.forEach((button) => button.removeEventListener("click", toggleExplorer)),
- )
- explorerButtons.forEach((button) => button.addEventListener("click", toggleExplorer))
+ const explorerButtons = explorer.getElementsByClassName(
+ "explorer-toggle",
+ ) as HTMLCollectionOf<HTMLElement>
+ for (const button of explorerButtons) {
+ button.addEventListener("click", toggleExplorer)
+ window.addCleanup(() => button.removeEventListener("click", toggleExplorer))
}
// Set up folder click handlers
@@ -235,8 +247,8 @@
"folder-button",
) as HTMLCollectionOf<HTMLElement>
for (const button of folderButtons) {
- window.addCleanup(() => button.removeEventListener("click", toggleFolder))
button.addEventListener("click", toggleFolder)
+ window.addCleanup(() => button.removeEventListener("click", toggleFolder))
}
}
@@ -244,15 +256,15 @@
"folder-icon",
) as HTMLCollectionOf<HTMLElement>
for (const icon of folderIcons) {
- window.addCleanup(() => icon.removeEventListener("click", toggleFolder))
icon.addEventListener("click", toggleFolder)
+ window.addCleanup(() => icon.removeEventListener("click", toggleFolder))
}
}
}
-document.addEventListener("prenav", async (e: CustomEventMap["prenav"]) => {
+document.addEventListener("prenav", async () => {
// save explorer scrollTop position
- const explorer = document.getElementById("explorer-ul")
+ const explorer = document.querySelector(".explorer-ul")
if (!explorer) return
sessionStorage.setItem("explorerScrollTop", explorer.scrollTop.toString())
})
@@ -262,16 +274,30 @@
await setupExplorer(currentSlug)
// if mobile hamburger is visible, collapse by default
- const mobileExplorer = document.getElementById("mobile-explorer")
- if (mobileExplorer && mobileExplorer.checkVisibility()) {
- for (const explorer of document.querySelectorAll(".explorer")) {
+ for (const explorer of document.getElementsByClassName("explorer")) {
+ const mobileExplorer = explorer.querySelector(".mobile-explorer")
+ if (!mobileExplorer) return
+
+ if (mobileExplorer.checkVisibility()) {
explorer.classList.add("collapsed")
explorer.setAttribute("aria-expanded", "false")
- }
- }
- const hiddenUntilDoneLoading = document.querySelector("#mobile-explorer")
- hiddenUntilDoneLoading?.classList.remove("hide-until-loaded")
+ // Allow <html> to be scrollable when mobile explorer is collapsed
+ document.documentElement.classList.remove("mobile-no-scroll")
+ }
+
+ mobileExplorer.classList.remove("hide-until-loaded")
+ }
+})
+
+window.addEventListener("resize", function () {
+ // Desktop explorer opens by default, and it stays open when the window is resized
+ // to mobile screen size. Applies `no-scroll` to <html> in this edge case.
+ const explorer = document.querySelector(".explorer")
+ if (explorer && !explorer.classList.contains("collapsed")) {
+ document.documentElement.classList.add("mobile-no-scroll")
+ return
+ }
})
function setFolderState(folderElement: HTMLElement, collapsed: boolean) {
--
Gitblit v1.10.0