diff --git a/docs/features/explorer.md b/docs/features/explorer.md index 797d4f1ac..a61df2329 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 e4cbcabae..61b20407a 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) => { @@ -68,6 +70,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..8d401ad58 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 || 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)