Emile Bangma
2026-01-08 f346a01296e5bdbd19de38fa69968b42629b883d
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")
  }
}
@@ -78,11 +84,11 @@
  const clone = template.content.cloneNode(true) as DocumentFragment
  const li = clone.querySelector("li") as HTMLLIElement
  const a = li.querySelector("a") as HTMLAnchorElement
  a.href = resolveRelative(currentSlug, node.data?.slug!)
  a.dataset.for = node.data?.slug
  a.href = resolveRelative(currentSlug, node.slug)
  a.dataset.for = node.slug
  a.textContent = node.displayName
  if (currentSlug === node.data?.slug) {
  if (currentSlug === node.slug) {
    a.classList.add("active")
  }
@@ -102,15 +108,19 @@
  const folderOuter = li.querySelector(".folder-outer") as HTMLElement
  const ul = folderOuter.querySelector("ul") as HTMLUListElement
  const folderPath = node.data?.slug!
  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
    const a = document.createElement("a")
    a.href = resolveRelative(currentSlug, folderPath)
    a.dataset.for = node.data?.slug
    a.dataset.for = folderPath
    a.className = "folder-title"
    a.textContent = node.displayName
    button.replaceWith(a)
@@ -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) {