mirror of
https://github.com/alrayyes/wiki.git
synced 2025-05-03 07:18:14 +00:00
Merge pull request #479 from benschlegel/explorer-config
feat(explorer): add config for custom sort/map/filter functions
This commit is contained in:
commit
e67f409ec1
3 changed files with 264 additions and 21 deletions
quartz/components
|
@ -11,6 +11,18 @@ const defaultOptions = (): Options => ({
|
|||
folderClickBehavior: "collapse",
|
||||
folderDefaultState: "collapsed",
|
||||
useSavedState: true,
|
||||
// Sort order: folders first, then files. Sort folders and files alphabetically
|
||||
sortFn: (a, b) => {
|
||||
if ((!a.file && !b.file) || (a.file && b.file)) {
|
||||
return a.name.localeCompare(b.name)
|
||||
}
|
||||
if (a.file && !b.file) {
|
||||
return 1
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
},
|
||||
order: ["filter", "map", "sort"],
|
||||
})
|
||||
export default ((userOpts?: Partial<Options>) => {
|
||||
function Explorer({ allFiles, displayClass, fileData }: QuartzComponentProps) {
|
||||
|
@ -21,8 +33,34 @@ export default ((userOpts?: Partial<Options>) => {
|
|||
const fileTree = new FileNode("")
|
||||
allFiles.forEach((file) => fileTree.add(file, 1))
|
||||
|
||||
// Sort tree (folders first, then files (alphabetic))
|
||||
fileTree.sort()
|
||||
/**
|
||||
* Keys of this object must match corresponding function name of `FileNode`,
|
||||
* while values must be the argument that will be passed to the function.
|
||||
*
|
||||
* e.g. entry for FileNode.sort: `sort: opts.sortFn` (value is sort function from options)
|
||||
*/
|
||||
const functions = {
|
||||
map: opts.mapFn,
|
||||
sort: opts.sortFn,
|
||||
filter: opts.filterFn,
|
||||
}
|
||||
|
||||
// Execute all functions (sort, filter, map) that were provided (if none were provided, only default "sort" is applied)
|
||||
if (opts.order) {
|
||||
// Order is important, use loop with index instead of order.map()
|
||||
for (let i = 0; i < opts.order.length; i++) {
|
||||
const functionName = opts.order[i]
|
||||
if (functions[functionName]) {
|
||||
// for every entry in order, call matching function in FileNode and pass matching argument
|
||||
// e.g. i = 0; functionName = "filter"
|
||||
// converted to: (if opts.filterFn) => fileTree.filter(opts.filterFn)
|
||||
|
||||
// @ts-ignore
|
||||
// typescript cant statically check these dynamic references, so manually make sure reference is valid and ignore warning
|
||||
fileTree[functionName].call(fileTree, functions[functionName])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get all folders of tree. Initialize with collapsed state
|
||||
const folders = fileTree.getFolderPaths(opts.folderDefaultState === "collapsed")
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
// @ts-ignore
|
||||
import { QuartzPluginData } from "vfile"
|
||||
import { QuartzPluginData } from "../plugins/vfile"
|
||||
import { resolveRelative } from "../util/path"
|
||||
|
||||
type OrderEntries = "sort" | "filter" | "map"
|
||||
|
||||
export interface Options {
|
||||
title: string
|
||||
folderDefaultState: "collapsed" | "open"
|
||||
folderClickBehavior: "collapse" | "link"
|
||||
useSavedState: boolean
|
||||
sortFn: (a: FileNode, b: FileNode) => number
|
||||
filterFn?: (node: FileNode) => boolean
|
||||
mapFn?: (node: FileNode) => void
|
||||
order?: OrderEntries[]
|
||||
}
|
||||
|
||||
type DataWrapper = {
|
||||
|
@ -29,7 +35,7 @@ export class FileNode {
|
|||
constructor(name: string, file?: QuartzPluginData, depth?: number) {
|
||||
this.children = []
|
||||
this.name = name
|
||||
this.file = file ?? null
|
||||
this.file = file ? structuredClone(file) : null
|
||||
this.depth = depth ?? 0
|
||||
}
|
||||
|
||||
|
@ -65,6 +71,25 @@ export class FileNode {
|
|||
this.children.forEach((e) => e.print(depth + 1))
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter FileNode tree. Behaves similar to `Array.prototype.filter()`, but modifies tree in place
|
||||
* @param filterFn function to filter tree with
|
||||
*/
|
||||
filter(filterFn: (node: FileNode) => boolean) {
|
||||
this.children = this.children.filter(filterFn)
|
||||
this.children.forEach((child) => child.filter(filterFn))
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter FileNode tree. Behaves similar to `Array.prototype.map()`, but modifies tree in place
|
||||
* @param mapFn function to use for mapping over tree
|
||||
*/
|
||||
map(mapFn: (node: FileNode) => void) {
|
||||
mapFn(this)
|
||||
|
||||
this.children.forEach((child) => child.map(mapFn))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get folder representation with state of tree.
|
||||
* Intended to only be called on root node before changes to the tree are made
|
||||
|
@ -90,19 +115,13 @@ export class FileNode {
|
|||
}
|
||||
|
||||
// Sort order: folders first, then files. Sort folders and files alphabetically
|
||||
sort() {
|
||||
this.children = this.children.sort((a, b) => {
|
||||
if ((!a.file && !b.file) || (a.file && b.file)) {
|
||||
return a.name.localeCompare(b.name)
|
||||
}
|
||||
if (a.file && !b.file) {
|
||||
return 1
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
})
|
||||
|
||||
this.children.forEach((e) => e.sort())
|
||||
/**
|
||||
* Sorts tree according to sort/compare function
|
||||
* @param sortFn compare function used for `.sort()`, also used recursively for children
|
||||
*/
|
||||
sort(sortFn: (a: FileNode, b: FileNode) => number) {
|
||||
this.children = this.children.sort(sortFn)
|
||||
this.children.forEach((e) => e.sort(sortFn))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,7 +150,7 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro
|
|||
// Single file node
|
||||
<li key={node.file.slug}>
|
||||
<a href={resolveRelative(fileData.slug!, node.file.slug!)} data-for={node.file.slug}>
|
||||
{node.file.frontmatter?.title}
|
||||
{node.name}
|
||||
</a>
|
||||
</li>
|
||||
) : (
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue