Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
zelestis committed Dec 26, 2024
1 parent 2e01888 commit 4c336b5
Show file tree
Hide file tree
Showing 5 changed files with 308 additions and 65 deletions.
2 changes: 1 addition & 1 deletion quartz.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as Plugin from "./quartz/plugins"
*/
const config: QuartzConfig = {
configuration: {
pageTitle: "zelestis",
pageTitle: "zelestis 🟪",
pageTitleSuffix: "",
enableSPA: true,
enablePopovers: true,
Expand Down
4 changes: 2 additions & 2 deletions quartz.layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const defaultContentPageLayout: PageLayout = {
Component.MobileOnly(Component.Spacer()),
Component.Search(),
Component.Darkmode(),
Component.DesktopOnly(Component.Explorer()),
Component.Explorer(),
],
right: [
Component.Graph(),
Expand All @@ -44,7 +44,7 @@ export const defaultListPageLayout: PageLayout = {
Component.MobileOnly(Component.Spacer()),
Component.Search(),
Component.Darkmode(),
Component.DesktopOnly(Component.Explorer()),
Component.Explorer(),
],
right: [],
}
50 changes: 39 additions & 11 deletions quartz/components/Explorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default ((userOpts?: Partial<Options>) => {
let jsonTree: string
let lastBuildId: string = ""

function constructFileTree(allFiles: QuartzPluginData[]) {
function constructFileTree(allFiles: QuartzPluginData[], currentFilePath: string) {
// Construct tree from allFiles
fileTree = new FileNode("")
allFiles.forEach((file) => fileTree.add(file))
Expand All @@ -73,28 +73,56 @@ export default ((userOpts?: Partial<Options>) => {
}

const Explorer: QuartzComponent = ({
ctx,
cfg,
allFiles,
displayClass,
fileData,
}: QuartzComponentProps) => {
ctx,
cfg,
allFiles,
displayClass,
fileData,
}: QuartzComponentProps) => {
if (ctx.buildId !== lastBuildId) {
lastBuildId = ctx.buildId
constructFileTree(allFiles)
constructFileTree(allFiles, (fileData.filePath ?? "").replaceAll(" ", "-"))
}

return (
<div class={classNames(displayClass, "explorer")}>
<button
type="button"
id="explorer"
id="mobile-explorer"
class="collapsed hide-until-loaded"
data-behavior={opts.folderClickBehavior}
data-collapsed={opts.folderDefaultState}
data-savestate={opts.useSavedState}
data-tree={jsonTree}
data-mobile={true}
aria-controls="explorer-content"
aria-expanded={false}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-menu"
>
<line x1="4" x2="20" y1="12" y2="12" />
<line x1="4" x2="20" y1="6" y2="6" />
<line x1="4" x2="20" y1="18" y2="18" />
</svg>
</button>
<button
type="button"
id="desktop-explorer"
class="title-button"
data-behavior={opts.folderClickBehavior}
data-collapsed={opts.folderDefaultState}
data-savestate={opts.useSavedState}
data-tree={jsonTree}
data-mobile={false}
aria-controls="explorer-content"
aria-expanded={opts.folderDefaultState === "open"}
aria-expanded={true}
>
<h2>{opts.title ?? i18n(cfg.locale).components.explorer.title}</h2>
<svg
Expand Down
152 changes: 115 additions & 37 deletions quartz/components/scripts/explorer.inline.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
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")
Expand All @@ -16,23 +18,43 @@ const observer = new IntersectionObserver((entries) => {
})

function toggleExplorer(this: HTMLElement) {
// Toggle collapsed state of entire explorer
this.classList.toggle("collapsed")

// Toggle collapsed aria state of entire explorer
this.setAttribute(
"aria-expanded",
this.getAttribute("aria-expanded") === "true" ? "false" : "true",
)
const content = this.nextElementSibling as MaybeHTMLElement
if (!content) return

const content = (
this.nextElementSibling?.nextElementSibling
? this.nextElementSibling.nextElementSibling
: this.nextElementSibling
) as MaybeHTMLElement
if (!content) return
content.classList.toggle("collapsed")
content.classList.toggle("explorer-viewmode")

// Prevent scroll under
if (document.querySelector("#mobile-explorer")) {
// Disable scrolling one 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
Expand All @@ -42,75 +64,131 @@ function toggleFolder(evt: MouseEvent) {
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)
localStorage.setItem("fileTree", stringifiedFileTree)
}

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>

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)
}

if (explorer.dataset.behavior === "collapse") {
// Set up click handlers for each folder (click handler on folder "icon")
for (const item of document.getElementsByClassName(
"folder-button",
"folder-icon",
) as HTMLCollectionOf<HTMLElement>) {
item.addEventListener("click", toggleFolder)
window.addCleanup(() => item.removeEventListener("click", toggleFolder))
}
}

explorer.addEventListener("click", toggleExplorer)
window.addCleanup(() => explorer.removeEventListener("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,
})
}

// 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.replace("'", "-")}']`,
) as MaybeHTMLElement
const folderUl = folderLi?.parentElement?.nextElementSibling as MaybeHTMLElement
if (folderUl) {
setFolderState(folderUl, folderState.collapsed)
}
})
}
}

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)
function toggleExplorerFolders() {
const currentFile = (document.querySelector("body")?.getAttribute("data-slug") ?? "").replace(
/\/index$/g,
"",
)
const listToReplace = document.querySelectorAll(".folder-outer:has(> ul[data-folderul]")

listToReplace.forEach((element) => {
if (element.children.length > 0) {
if (currentFile.includes(element.firstElementChild?.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
const lastItem = document.getElementById("explorer-end")
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()
})

/**
Expand Down
Loading

0 comments on commit 4c336b5

Please sign in to comment.