From c538c151c7462ad0395ff2c15c5e11e89e362aa8 Mon Sep 17 00:00:00 2001
From: Striven <sg.striven@cutecat.club>
Date: Sat, 04 Apr 2026 19:47:16 +0000
Subject: [PATCH] Initial commit
---
quartz/components/Explorer.tsx | 169 +++++++++++++++++++++++++++++++++++++++++---------------
1 files changed, 124 insertions(+), 45 deletions(-)
diff --git a/quartz/components/Explorer.tsx b/quartz/components/Explorer.tsx
index efc9f6a..e4cbcab 100644
--- a/quartz/components/Explorer.tsx
+++ b/quartz/components/Explorer.tsx
@@ -1,62 +1,109 @@
-import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
-import explorerStyle from "./styles/explorer.scss"
+import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
+import style from "./styles/explorer.scss"
// @ts-ignore
import script from "./scripts/explorer.inline"
-import { ExplorerNode, FileNode, Options } from "./ExplorerNode"
+import { classNames } from "../util/lang"
+import { i18n } from "../i18n"
+import { FileTrieNode } from "../util/fileTrie"
+import OverflowListFactory from "./OverflowList"
+import { concatenateResources } from "../util/resources"
-// Options interface defined in `ExplorerNode` to avoid circular dependency
-const defaultOptions = (): Options => ({
- title: "Explorer",
- folderClickBehavior: "collapse",
+type OrderEntries = "sort" | "filter" | "map"
+
+export interface Options {
+ title?: string
+ folderDefaultState: "collapsed" | "open"
+ folderClickBehavior: "collapse" | "link"
+ useSavedState: boolean
+ sortFn: (a: FileTrieNode, b: FileTrieNode) => number
+ filterFn: (node: FileTrieNode) => boolean
+ mapFn: (node: FileTrieNode) => void
+ order: OrderEntries[]
+}
+
+const defaultOptions: Options = {
folderDefaultState: "collapsed",
+ folderClickBehavior: "link",
useSavedState: true,
- // Sort order: folders first, then files. Sort folders and files alphabetically
+ mapFn: (node) => {
+ return node
+ },
sortFn: (a, b) => {
- if ((!a.file && !b.file) || (a.file && b.file)) {
- return a.name.localeCompare(b.name)
+ // Sort order: folders first, then files. Sort folders and files alphabeticall
+ if ((!a.isFolder && !b.isFolder) || (a.isFolder && b.isFolder)) {
+ // numeric: true: Whether numeric collation should be used, such that "1" < "2" < "10"
+ // sensitivity: "base": Only strings that differ in base letters compare as unequal. Examples: a ≠ b, a = á, a = A
+ return a.displayName.localeCompare(b.displayName, undefined, {
+ numeric: true,
+ sensitivity: "base",
+ })
}
- if (a.file && !b.file) {
+
+ if (!a.isFolder && b.isFolder) {
return 1
} else {
return -1
}
},
-})
+ filterFn: (node) => node.slugSegment !== "tags",
+ order: ["filter", "map", "sort"],
+}
+
+export type FolderState = {
+ path: string
+ collapsed: boolean
+}
+
+let numExplorers = 0
export default ((userOpts?: Partial<Options>) => {
- function Explorer({ allFiles, displayClass, fileData }: QuartzComponentProps) {
- // Parse config
- const opts: Options = { ...defaultOptions(), ...userOpts }
+ const opts: Options = { ...defaultOptions, ...userOpts }
+ const { OverflowList, overflowListAfterDOMLoaded } = OverflowListFactory()
- // Construct tree from allFiles
- const fileTree = new FileNode("")
- allFiles.forEach((file) => fileTree.add(file, 1))
-
- // Sort tree (folders first, then files (alphabetic))
- fileTree.sort(opts.sortFn!)
-
- // If provided, apply filter function to fileTree
- if (opts.filterFn) {
- fileTree.filter(opts.filterFn)
- }
-
- // Get all folders of tree. Initialize with collapsed state
- const folders = fileTree.getFolderPaths(opts.folderDefaultState === "collapsed")
-
- // Stringify to pass json tree as data attribute ([data-tree])
- const jsonTree = JSON.stringify(folders)
+ const Explorer: QuartzComponent = ({ cfg, displayClass }: QuartzComponentProps) => {
+ const id = `explorer-${numExplorers++}`
return (
- <div class={`explorer ${displayClass}`}>
+ <div
+ class={classNames(displayClass, "explorer")}
+ data-behavior={opts.folderClickBehavior}
+ data-collapsed={opts.folderDefaultState}
+ data-savestate={opts.useSavedState}
+ data-data-fns={JSON.stringify({
+ order: opts.order,
+ sortFn: opts.sortFn.toString(),
+ filterFn: opts.filterFn.toString(),
+ mapFn: opts.mapFn.toString(),
+ })}
+ >
<button
type="button"
- id="explorer"
- data-behavior={opts.folderClickBehavior}
- data-collapsed={opts.folderDefaultState}
- data-savestate={opts.useSavedState}
- data-tree={jsonTree}
+ class="explorer-toggle mobile-explorer hide-until-loaded"
+ data-mobile={true}
+ aria-controls={id}
>
- <h3>{opts.title}</h3>
+ <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-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"
+ class="title-button explorer-toggle desktop-explorer"
+ data-mobile={false}
+ aria-expanded={true}
+ >
+ <h2>{opts.title ?? i18n(cfg.locale).components.explorer.title}</h2>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
@@ -72,15 +119,47 @@
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</button>
- <div id="explorer-content">
- <ul class="overflow">
- <ExplorerNode node={fileTree} opts={opts} fileData={fileData} />
- </ul>
+ <div id={id} class="explorer-content" aria-expanded={false} role="group">
+ <OverflowList class="explorer-ul" />
</div>
+ <template id="template-file">
+ <li>
+ <a href="#"></a>
+ </li>
+ </template>
+ <template id="template-folder">
+ <li>
+ <div class="folder-container">
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="12"
+ height="12"
+ viewBox="5 8 14 8"
+ fill="none"
+ stroke="currentColor"
+ stroke-width="2"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ class="folder-icon"
+ >
+ <polyline points="6 9 12 15 18 9"></polyline>
+ </svg>
+ <div>
+ <button class="folder-button">
+ <span class="folder-title"></span>
+ </button>
+ </div>
+ </div>
+ <div class="folder-outer">
+ <ul class="content"></ul>
+ </div>
+ </li>
+ </template>
</div>
)
}
- Explorer.css = explorerStyle
- Explorer.afterDOMLoaded = script
+
+ Explorer.css = style
+ Explorer.afterDOMLoaded = concatenateResources(script, overflowListAfterDOMLoaded)
return Explorer
}) satisfies QuartzComponentConstructor
--
Gitblit v1.10.0