From 69004df27d5f882b65beb10c2b7f95ba5411cc61 Mon Sep 17 00:00:00 2001 From: Alexey Chernyavskiy Date: Mon, 21 Jul 2025 01:45:51 +0500 Subject: [PATCH 1/4] feat(explorer): add maxExpandLevel option to control folder expansion depth in Explorer --- docs/features/explorer.md | 4 ++++ quartz/components/Explorer.tsx | 3 +++ quartz/components/scripts/explorer.inline.ts | 19 ++++++++++++------- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/docs/features/explorer.md b/docs/features/explorer.md index 9f06d0cef..921d00d24 100644 --- a/docs/features/explorer.md +++ b/docs/features/explorer.md @@ -26,6 +26,7 @@ Component.Explorer({ title: "Explorer", // title of the explorer component folderClickBehavior: "collapse", // what happens when you click a folder ("link" to navigate to folder page on click or "collapse" to collapse folder on click) folderDefaultState: "collapsed", // default state of folders ("collapsed" or "open") + maxExpandLevel: 0, // number of folder levels expanded initially when folders are open; 0 means no limit (all levels expanded) useSavedState: true, // whether to use local storage to save "state" (which folders are opened) of explorer // omitted but shown later sortFn: ..., @@ -36,6 +37,9 @@ Component.Explorer({ }) ``` +- When `folderDefaultState` is `"collapsed"`, folders are collapsed by default, and `maxExpandLevel` does not affect initial folder expansion. +- When `folderDefaultState` is `"open"`, `maxExpandLevel` controls how many folder levels are expanded initially. + When passing in your own options, you can omit any or all of these fields if you'd like to keep the default value for that field. Want to customize it even more? diff --git a/quartz/components/Explorer.tsx b/quartz/components/Explorer.tsx index 56784f132..fff518af5 100644 --- a/quartz/components/Explorer.tsx +++ b/quartz/components/Explorer.tsx @@ -14,6 +14,7 @@ type OrderEntries = "sort" | "filter" | "map" export interface Options { title?: string folderDefaultState: "collapsed" | "open" + maxExpandLevel?: number folderClickBehavior: "collapse" | "link" useSavedState: boolean sortFn: (a: FileTrieNode, b: FileTrieNode) => number @@ -24,6 +25,7 @@ export interface Options { const defaultOptions: Options = { folderDefaultState: "collapsed", + maxExpandLevel: 0, folderClickBehavior: "link", useSavedState: true, mapFn: (node) => { @@ -65,6 +67,7 @@ export default ((userOpts?: Partial) => { class={classNames(displayClass, "explorer")} data-behavior={opts.folderClickBehavior} data-collapsed={opts.folderDefaultState} + data-maxexpandlevel={opts.maxExpandLevel} data-savestate={opts.useSavedState} data-data-fns={JSON.stringify({ order: opts.order, diff --git a/quartz/components/scripts/explorer.inline.ts b/quartz/components/scripts/explorer.inline.ts index 9c8341169..dc5d19849 100644 --- a/quartz/components/scripts/explorer.inline.ts +++ b/quartz/components/scripts/explorer.inline.ts @@ -7,6 +7,7 @@ type MaybeHTMLElement = HTMLElement | undefined interface ParsedOptions { folderClickBehavior: "collapse" | "link" folderDefaultState: "collapsed" | "open" + maxExpandLevel?: number useSavedState: boolean sortFn: (a: FileTrieNode, b: FileTrieNode) => number filterFn: (node: FileTrieNode) => boolean @@ -99,6 +100,7 @@ function createFolderNode( currentSlug: FullSlug, node: FileTrieNode, opts: ParsedOptions, + level: number = 1, ): HTMLLIElement { const template = document.getElementById("template-folder") as HTMLTemplateElement const clone = template.content.cloneNode(true) as DocumentFragment @@ -125,10 +127,8 @@ function createFolderNode( span.textContent = node.displayName } - // if the saved state is collapsed or the default state is collapsed - const isCollapsed = - currentExplorerState.find((item) => item.path === folderPath)?.collapsed ?? - opts.folderDefaultState === "collapsed" + const savedState = currentExplorerState.find((item) => item.path === folderPath)?.collapsed + const isCollapsedByState = savedState ?? opts.folderDefaultState === "collapsed" // if this folder is a prefix of the current path we // want to open it anyways @@ -136,13 +136,17 @@ function createFolderNode( const folderIsPrefixOfCurrentSlug = simpleFolderPath === currentSlug.slice(0, simpleFolderPath.length) - if (!isCollapsed || folderIsPrefixOfCurrentSlug) { + const maxLevel = opts.maxExpandLevel ?? 0 + + if (!isCollapsedByState || folderIsPrefixOfCurrentSlug || maxLevel === 0 || level <= maxLevel) { folderOuter.classList.add("open") + } else { + folderOuter.classList.remove("open") } for (const child of node.children) { const childNode = child.isFolder - ? createFolderNode(currentSlug, child, opts) + ? createFolderNode(currentSlug, child, opts, level + 1) : createFileNode(currentSlug, child) ul.appendChild(childNode) } @@ -158,6 +162,7 @@ async function setupExplorer(currentSlug: FullSlug) { const opts: ParsedOptions = { folderClickBehavior: (explorer.dataset.behavior || "collapse") as "collapse" | "link", folderDefaultState: (explorer.dataset.collapsed || "collapsed") as "collapsed" | "open", + maxExpandLevel: parseInt(explorer.dataset.maxexpandlevel || "0"), useSavedState: explorer.dataset.savestate === "true", order: dataFns.order || ["filter", "map", "sort"], sortFn: new Function("return " + (dataFns.sortFn || "undefined"))(), @@ -209,7 +214,7 @@ async function setupExplorer(currentSlug: FullSlug) { const fragment = document.createDocumentFragment() for (const child of trie.children) { const node = child.isFolder - ? createFolderNode(currentSlug, child, opts) + ? createFolderNode(currentSlug, child, opts, 1) : createFileNode(currentSlug, child) fragment.appendChild(node) From ceadf9eb360d4bbeb22f4036bf5d716a66f0b8d9 Mon Sep 17 00:00:00 2001 From: Alexey Chernyavskiy Date: Mon, 21 Jul 2025 09:30:09 +0500 Subject: [PATCH 2/4] fix(explorer): explorer tree ignores maxExpandLevel on folder pages --- quartz/components/scripts/explorer.inline.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quartz/components/scripts/explorer.inline.ts b/quartz/components/scripts/explorer.inline.ts index dc5d19849..e98b2035e 100644 --- a/quartz/components/scripts/explorer.inline.ts +++ b/quartz/components/scripts/explorer.inline.ts @@ -138,7 +138,7 @@ function createFolderNode( const maxLevel = opts.maxExpandLevel ?? 0 - if (!isCollapsedByState || folderIsPrefixOfCurrentSlug || maxLevel === 0 || level <= maxLevel) { + if (!isCollapsedByState || folderIsPrefixOfCurrentSlug || level <= maxLevel) { folderOuter.classList.add("open") } else { folderOuter.classList.remove("open") From a8e5eb0afc29655ee323baa39a52f34dbecfb11c Mon Sep 17 00:00:00 2001 From: Alexey Chernyavskiy Date: Mon, 21 Jul 2025 09:35:30 +0500 Subject: [PATCH 3/4] fix(explorer): make maxExpandLevel non-nullable with 0 as default --- quartz/components/scripts/explorer.inline.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quartz/components/scripts/explorer.inline.ts b/quartz/components/scripts/explorer.inline.ts index e98b2035e..8d401ad58 100644 --- a/quartz/components/scripts/explorer.inline.ts +++ b/quartz/components/scripts/explorer.inline.ts @@ -7,7 +7,7 @@ type MaybeHTMLElement = HTMLElement | undefined interface ParsedOptions { folderClickBehavior: "collapse" | "link" folderDefaultState: "collapsed" | "open" - maxExpandLevel?: number + maxExpandLevel: number useSavedState: boolean sortFn: (a: FileTrieNode, b: FileTrieNode) => number filterFn: (node: FileTrieNode) => boolean From de2612d4d5b0e036f6276c8e10db74a0f00d27bf Mon Sep 17 00:00:00 2001 From: Alexey Chernyavskiy Date: Mon, 21 Jul 2025 09:42:47 +0500 Subject: [PATCH 4/4] fix(explorer): correct missed maxExpandLevel non-nullable update in one place --- quartz/components/Explorer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quartz/components/Explorer.tsx b/quartz/components/Explorer.tsx index fff518af5..f0b29cd10 100644 --- a/quartz/components/Explorer.tsx +++ b/quartz/components/Explorer.tsx @@ -14,7 +14,7 @@ type OrderEntries = "sort" | "filter" | "map" export interface Options { title?: string folderDefaultState: "collapsed" | "open" - maxExpandLevel?: number + maxExpandLevel: number folderClickBehavior: "collapse" | "link" useSavedState: boolean sortFn: (a: FileTrieNode, b: FileTrieNode) => number