| | |
| | | import { FolderState } from "../ExplorerNode" |
| | | |
| | | // Current state of folders |
| | | 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 explorerUl = document.getElementById("explorer-ul") |
| | |
| | | }) |
| | | |
| | | function toggleExplorer(this: HTMLElement) { |
| | | // Toggle collapsed state of entire explorer |
| | | this.classList.toggle("collapsed") |
| | | const content = this.nextElementSibling as MaybeHTMLElement |
| | | if (!content) return |
| | | |
| | | // Toggle collapsed aria state of entire explorer |
| | | this.setAttribute( |
| | | "aria-expanded", |
| | | this.getAttribute("aria-expanded") === "true" ? "false" : "true", |
| | | ) |
| | | |
| | | const content = ( |
| | | this.nextElementSibling?.nextElementSibling |
| | | ? this.nextElementSibling.nextElementSibling |
| | | : this.nextElementSibling |
| | | ) as MaybeHTMLElement |
| | | if (!content) return |
| | | content.classList.toggle("collapsed") |
| | | content.style.maxHeight = content.style.maxHeight === "0px" ? content.scrollHeight + "px" : "0px" |
| | | content.classList.toggle("explorer-viewmode") |
| | | |
| | | // Prevent scroll under |
| | | if (document.querySelector("#mobile-explorer")) { |
| | | // Disable scrolling on the page when the explorer is opened on mobile |
| | | const bodySelector = document.querySelector("#quartz-body") |
| | | if (bodySelector) bodySelector.classList.toggle("lock-scroll") |
| | | } |
| | | } |
| | | |
| | | function toggleFolder(evt: MouseEvent) { |
| | | evt.stopPropagation() |
| | | |
| | | // Element that was clicked |
| | | const target = evt.target as MaybeHTMLElement |
| | | if (!target) return |
| | | |
| | | // Check if target was svg icon or button |
| | | const isSvg = target.nodeName === "svg" |
| | | |
| | | // corresponding <ul> element relative to clicked button/folder |
| | | const childFolderContainer = ( |
| | | isSvg |
| | | ? target.parentElement?.nextSibling |
| | |
| | | isSvg ? target.nextElementSibling : target.parentElement |
| | | ) as MaybeHTMLElement |
| | | if (!(childFolderContainer && currentFolderParent)) return |
| | | |
| | | // <li> element of folder (stores folder-path dataset) |
| | | childFolderContainer.classList.toggle("open") |
| | | |
| | | // Collapse folder container |
| | | const isCollapsed = childFolderContainer.classList.contains("open") |
| | | setFolderState(childFolderContainer, !isCollapsed) |
| | | |
| | | // Save folder state to localStorage |
| | | const fullFolderPath = currentFolderParent.dataset.folderpath as string |
| | | toggleCollapsedByPath(currentExplorerState, fullFolderPath) |
| | | const stringifiedFileTree = JSON.stringify(currentExplorerState) |
| | |
| | | } |
| | | |
| | | function setupExplorer() { |
| | | const explorer = document.getElementById("explorer") |
| | | if (!explorer) return |
| | | // Set click handler for collapsing entire explorer |
| | | const allExplorers = document.querySelectorAll(".explorer > button") as NodeListOf<HTMLElement> |
| | | |
| | | if (explorer.dataset.behavior === "collapse") { |
| | | for (const item of document.getElementsByClassName( |
| | | "folder-button", |
| | | ) as HTMLCollectionOf<HTMLElement>) { |
| | | item.removeEventListener("click", toggleFolder) |
| | | item.addEventListener("click", toggleFolder) |
| | | for (const explorer of allExplorers) { |
| | | // Get folder state from local storage |
| | | const storageTree = localStorage.getItem("fileTree") |
| | | |
| | | // Convert to bool |
| | | const useSavedFolderState = explorer?.dataset.savestate === "true" |
| | | |
| | | if (explorer) { |
| | | // Get config |
| | | const collapseBehavior = explorer.dataset.behavior |
| | | |
| | | // Add click handlers for all folders (click handler on folder "label") |
| | | if (collapseBehavior === "collapse") { |
| | | for (const item of document.getElementsByClassName( |
| | | "folder-button", |
| | | ) as HTMLCollectionOf<HTMLElement>) { |
| | | window.addCleanup(() => explorer.removeEventListener("click", toggleExplorer)) |
| | | item.addEventListener("click", toggleFolder) |
| | | } |
| | | } |
| | | |
| | | // Add click handler to main explorer |
| | | window.addCleanup(() => 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.addEventListener("click", toggleFolder) |
| | | window.addCleanup(() => item.removeEventListener("click", toggleFolder)) |
| | | } |
| | | |
| | | // Get folder state from local storage |
| | | 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, |
| | | }) |
| | | } |
| | | |
| | | currentExplorerState.map((folderState) => { |
| | | const folderLi = document.querySelector( |
| | | `[data-folderpath='${folderState.path.replace("'", "-")}']`, |
| | | ) as MaybeHTMLElement |
| | | const folderUl = folderLi?.parentElement?.nextElementSibling as MaybeHTMLElement |
| | | if (folderUl) { |
| | | setFolderState(folderUl, folderState.collapsed) |
| | | } |
| | | }) |
| | | } |
| | | } |
| | | |
| | | explorer.removeEventListener("click", toggleExplorer) |
| | | explorer.addEventListener("click", toggleExplorer) |
| | | function toggleExplorerFolders() { |
| | | const currentFile = (document.querySelector("body")?.getAttribute("data-slug") ?? "").replace( |
| | | /\/index$/g, |
| | | "", |
| | | ) |
| | | const allFolders = document.querySelectorAll(".folder-outer") |
| | | |
| | | // 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") |
| | | 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 }) |
| | | } |
| | | |
| | | currentExplorerState.map((folderState) => { |
| | | const folderLi = document.querySelector( |
| | | `[data-folderpath='${folderState.path}']`, |
| | | ) as MaybeHTMLElement |
| | | const folderUl = folderLi?.parentElement?.nextElementSibling as MaybeHTMLElement |
| | | allFolders.forEach((element) => { |
| | | const folderUl = Array.from(element.children).find((child) => |
| | | child.matches("ul[data-folderul]"), |
| | | ) |
| | | if (folderUl) { |
| | | setFolderState(folderUl, folderState.collapsed) |
| | | if (currentFile.includes(folderUl.getAttribute("data-folderul") ?? "")) { |
| | | if (!element.classList.contains("open")) { |
| | | element.classList.add("open") |
| | | } |
| | | } |
| | | } |
| | | }) |
| | | } |
| | | |
| | | window.addEventListener("resize", setupExplorer) |
| | | |
| | | document.addEventListener("nav", () => { |
| | | const explorer = document.querySelector("#mobile-explorer") |
| | | if (explorer) { |
| | | explorer.classList.add("collapsed") |
| | | const content = explorer.nextElementSibling?.nextElementSibling as HTMLElement |
| | | if (content) { |
| | | content.classList.add("collapsed") |
| | | content.classList.toggle("explorer-viewmode") |
| | | } |
| | | } |
| | | setupExplorer() |
| | | |
| | | observer.disconnect() |
| | | |
| | | // select pseudo element at end of list |
| | |
| | | if (lastItem) { |
| | | observer.observe(lastItem) |
| | | } |
| | | |
| | | // Hide explorer on mobile until it is requested |
| | | const hiddenUntilDoneLoading = document.querySelector("#mobile-explorer") |
| | | hiddenUntilDoneLoading?.classList.remove("hide-until-loaded") |
| | | |
| | | toggleExplorerFolders() |
| | | }) |
| | | |
| | | /** |