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)