wiki/quartz/components/ExplorerNode.tsx

226 lines
6.5 KiB
TypeScript
Raw Normal View History

feat: implement file explorer component (closes #201) (#452) * feat: add basic explorer structure„ * feat: integrate new component/plugin * feat: add basic explorer structure * feat: add sort to FileNodes * style: improve style for explorer * refactor: remove unused explorer plugin * refactor: clean explorer structure, fix base (toc) * refactor: clean css, respect displayClass * style: add styling to chevron * refactor: clean up debug statements * refactor: remove unused import * fix: clicking folder icon sometimes turns invisible * refactor: clean css * feat(explorer): add config for title * feat: add config for folder click behavior * fix: `no-pointer` not being set for all elements new approach, have one `no-pointer` class, that removes pointer events and one `clickable` class on the svg and button (everything that can normally be clicked). then, find all children with `clickable` and toggle `no-pointer` * fix: bug where nested folders got incorrect height this fixes the bug where nested folders weren't calculating their total height correctly. done by adding class to main container of all children and calculating total * feat: introduce `folderDefaultState` config * feat: store depth for explorer nodes * feat: implement option for collapsed state + bug fixes folderBehavior: "link" still has bad styling, but major bugs with pointers fixed (not clean yet, but working) * fix: default folder icon rotation * fix: hitbox problem with folder links, fix style * fix: redirect url for nested folders * fix: inconsistent behavior with 'collapseFolders' opt * chore: add comments to `ExplorerNode` * feat: save explorer state to local storage (not clean) * feat: rework `getFolders()`, fix localstorage read + write * feat: set folder state from localStorage needs serious refactoring but functional (except folder icon orientation) * fix: folder icon orientation after local storage * feat: add config for `useSavedState` * refactor: clean `explorer.inline.ts` remove unused functions, comments, unused code, add types to EventHandler * refactor: clean explorer merge `isSvg` paths, remove console logs * refactor: add documentation, remove unused funcs * feat: rework folder collapse logic use grids instead of jank scuffed solution with calculating total heights * refactor: remove depth arg from insert * feat: restore collapse functionality to clicks allow folder icon + folder label to collapse folders again * refactor: remove `pointer-event` jank * feat: improve svg viewbox + remove unused props * feat: use css selector to toggle icon rework folder icon to work purely with css instead of JS manipulation * refactor: remove unused cfg * feat: move TOC to right sidebar * refactor: clean css * style: fix overflow + overflow margin * fix: use `resolveRelative` to resolve file paths * fix: `defaultFolderState` config option * refactor: rename import, rename `folderLi` + ul * fix: use `QuartzPluginData` type * docs: add explorer documentation
2023-09-15 16:39:16 +00:00
// @ts-ignore
import { QuartzPluginData } from "../plugins/vfile"
feat: implement file explorer component (closes #201) (#452) * feat: add basic explorer structure„ * feat: integrate new component/plugin * feat: add basic explorer structure * feat: add sort to FileNodes * style: improve style for explorer * refactor: remove unused explorer plugin * refactor: clean explorer structure, fix base (toc) * refactor: clean css, respect displayClass * style: add styling to chevron * refactor: clean up debug statements * refactor: remove unused import * fix: clicking folder icon sometimes turns invisible * refactor: clean css * feat(explorer): add config for title * feat: add config for folder click behavior * fix: `no-pointer` not being set for all elements new approach, have one `no-pointer` class, that removes pointer events and one `clickable` class on the svg and button (everything that can normally be clicked). then, find all children with `clickable` and toggle `no-pointer` * fix: bug where nested folders got incorrect height this fixes the bug where nested folders weren't calculating their total height correctly. done by adding class to main container of all children and calculating total * feat: introduce `folderDefaultState` config * feat: store depth for explorer nodes * feat: implement option for collapsed state + bug fixes folderBehavior: "link" still has bad styling, but major bugs with pointers fixed (not clean yet, but working) * fix: default folder icon rotation * fix: hitbox problem with folder links, fix style * fix: redirect url for nested folders * fix: inconsistent behavior with 'collapseFolders' opt * chore: add comments to `ExplorerNode` * feat: save explorer state to local storage (not clean) * feat: rework `getFolders()`, fix localstorage read + write * feat: set folder state from localStorage needs serious refactoring but functional (except folder icon orientation) * fix: folder icon orientation after local storage * feat: add config for `useSavedState` * refactor: clean `explorer.inline.ts` remove unused functions, comments, unused code, add types to EventHandler * refactor: clean explorer merge `isSvg` paths, remove console logs * refactor: add documentation, remove unused funcs * feat: rework folder collapse logic use grids instead of jank scuffed solution with calculating total heights * refactor: remove depth arg from insert * feat: restore collapse functionality to clicks allow folder icon + folder label to collapse folders again * refactor: remove `pointer-event` jank * feat: improve svg viewbox + remove unused props * feat: use css selector to toggle icon rework folder icon to work purely with css instead of JS manipulation * refactor: remove unused cfg * feat: move TOC to right sidebar * refactor: clean css * style: fix overflow + overflow margin * fix: use `resolveRelative` to resolve file paths * fix: `defaultFolderState` config option * refactor: rename import, rename `folderLi` + ul * fix: use `QuartzPluginData` type * docs: add explorer documentation
2023-09-15 16:39:16 +00:00
import { resolveRelative } from "../util/path"
type OrderEntries = "sort" | "filter" | "map"
feat: implement file explorer component (closes #201) (#452) * feat: add basic explorer structure„ * feat: integrate new component/plugin * feat: add basic explorer structure * feat: add sort to FileNodes * style: improve style for explorer * refactor: remove unused explorer plugin * refactor: clean explorer structure, fix base (toc) * refactor: clean css, respect displayClass * style: add styling to chevron * refactor: clean up debug statements * refactor: remove unused import * fix: clicking folder icon sometimes turns invisible * refactor: clean css * feat(explorer): add config for title * feat: add config for folder click behavior * fix: `no-pointer` not being set for all elements new approach, have one `no-pointer` class, that removes pointer events and one `clickable` class on the svg and button (everything that can normally be clicked). then, find all children with `clickable` and toggle `no-pointer` * fix: bug where nested folders got incorrect height this fixes the bug where nested folders weren't calculating their total height correctly. done by adding class to main container of all children and calculating total * feat: introduce `folderDefaultState` config * feat: store depth for explorer nodes * feat: implement option for collapsed state + bug fixes folderBehavior: "link" still has bad styling, but major bugs with pointers fixed (not clean yet, but working) * fix: default folder icon rotation * fix: hitbox problem with folder links, fix style * fix: redirect url for nested folders * fix: inconsistent behavior with 'collapseFolders' opt * chore: add comments to `ExplorerNode` * feat: save explorer state to local storage (not clean) * feat: rework `getFolders()`, fix localstorage read + write * feat: set folder state from localStorage needs serious refactoring but functional (except folder icon orientation) * fix: folder icon orientation after local storage * feat: add config for `useSavedState` * refactor: clean `explorer.inline.ts` remove unused functions, comments, unused code, add types to EventHandler * refactor: clean explorer merge `isSvg` paths, remove console logs * refactor: add documentation, remove unused funcs * feat: rework folder collapse logic use grids instead of jank scuffed solution with calculating total heights * refactor: remove depth arg from insert * feat: restore collapse functionality to clicks allow folder icon + folder label to collapse folders again * refactor: remove `pointer-event` jank * feat: improve svg viewbox + remove unused props * feat: use css selector to toggle icon rework folder icon to work purely with css instead of JS manipulation * refactor: remove unused cfg * feat: move TOC to right sidebar * refactor: clean css * style: fix overflow + overflow margin * fix: use `resolveRelative` to resolve file paths * fix: `defaultFolderState` config option * refactor: rename import, rename `folderLi` + ul * fix: use `QuartzPluginData` type * docs: add explorer documentation
2023-09-15 16:39:16 +00:00
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[]
feat: implement file explorer component (closes #201) (#452) * feat: add basic explorer structure„ * feat: integrate new component/plugin * feat: add basic explorer structure * feat: add sort to FileNodes * style: improve style for explorer * refactor: remove unused explorer plugin * refactor: clean explorer structure, fix base (toc) * refactor: clean css, respect displayClass * style: add styling to chevron * refactor: clean up debug statements * refactor: remove unused import * fix: clicking folder icon sometimes turns invisible * refactor: clean css * feat(explorer): add config for title * feat: add config for folder click behavior * fix: `no-pointer` not being set for all elements new approach, have one `no-pointer` class, that removes pointer events and one `clickable` class on the svg and button (everything that can normally be clicked). then, find all children with `clickable` and toggle `no-pointer` * fix: bug where nested folders got incorrect height this fixes the bug where nested folders weren't calculating their total height correctly. done by adding class to main container of all children and calculating total * feat: introduce `folderDefaultState` config * feat: store depth for explorer nodes * feat: implement option for collapsed state + bug fixes folderBehavior: "link" still has bad styling, but major bugs with pointers fixed (not clean yet, but working) * fix: default folder icon rotation * fix: hitbox problem with folder links, fix style * fix: redirect url for nested folders * fix: inconsistent behavior with 'collapseFolders' opt * chore: add comments to `ExplorerNode` * feat: save explorer state to local storage (not clean) * feat: rework `getFolders()`, fix localstorage read + write * feat: set folder state from localStorage needs serious refactoring but functional (except folder icon orientation) * fix: folder icon orientation after local storage * feat: add config for `useSavedState` * refactor: clean `explorer.inline.ts` remove unused functions, comments, unused code, add types to EventHandler * refactor: clean explorer merge `isSvg` paths, remove console logs * refactor: add documentation, remove unused funcs * feat: rework folder collapse logic use grids instead of jank scuffed solution with calculating total heights * refactor: remove depth arg from insert * feat: restore collapse functionality to clicks allow folder icon + folder label to collapse folders again * refactor: remove `pointer-event` jank * feat: improve svg viewbox + remove unused props * feat: use css selector to toggle icon rework folder icon to work purely with css instead of JS manipulation * refactor: remove unused cfg * feat: move TOC to right sidebar * refactor: clean css * style: fix overflow + overflow margin * fix: use `resolveRelative` to resolve file paths * fix: `defaultFolderState` config option * refactor: rename import, rename `folderLi` + ul * fix: use `QuartzPluginData` type * docs: add explorer documentation
2023-09-15 16:39:16 +00:00
}
type DataWrapper = {
file: QuartzPluginData
path: string[]
}
export type FolderState = {
path: string
collapsed: boolean
}
// Structure to add all files into a tree
export class FileNode {
children: FileNode[]
name: string
file: QuartzPluginData | null
depth: number
constructor(name: string, file?: QuartzPluginData, depth?: number) {
this.children = []
this.name = name
this.file = file ? structuredClone(file) : null
feat: implement file explorer component (closes #201) (#452) * feat: add basic explorer structure„ * feat: integrate new component/plugin * feat: add basic explorer structure * feat: add sort to FileNodes * style: improve style for explorer * refactor: remove unused explorer plugin * refactor: clean explorer structure, fix base (toc) * refactor: clean css, respect displayClass * style: add styling to chevron * refactor: clean up debug statements * refactor: remove unused import * fix: clicking folder icon sometimes turns invisible * refactor: clean css * feat(explorer): add config for title * feat: add config for folder click behavior * fix: `no-pointer` not being set for all elements new approach, have one `no-pointer` class, that removes pointer events and one `clickable` class on the svg and button (everything that can normally be clicked). then, find all children with `clickable` and toggle `no-pointer` * fix: bug where nested folders got incorrect height this fixes the bug where nested folders weren't calculating their total height correctly. done by adding class to main container of all children and calculating total * feat: introduce `folderDefaultState` config * feat: store depth for explorer nodes * feat: implement option for collapsed state + bug fixes folderBehavior: "link" still has bad styling, but major bugs with pointers fixed (not clean yet, but working) * fix: default folder icon rotation * fix: hitbox problem with folder links, fix style * fix: redirect url for nested folders * fix: inconsistent behavior with 'collapseFolders' opt * chore: add comments to `ExplorerNode` * feat: save explorer state to local storage (not clean) * feat: rework `getFolders()`, fix localstorage read + write * feat: set folder state from localStorage needs serious refactoring but functional (except folder icon orientation) * fix: folder icon orientation after local storage * feat: add config for `useSavedState` * refactor: clean `explorer.inline.ts` remove unused functions, comments, unused code, add types to EventHandler * refactor: clean explorer merge `isSvg` paths, remove console logs * refactor: add documentation, remove unused funcs * feat: rework folder collapse logic use grids instead of jank scuffed solution with calculating total heights * refactor: remove depth arg from insert * feat: restore collapse functionality to clicks allow folder icon + folder label to collapse folders again * refactor: remove `pointer-event` jank * feat: improve svg viewbox + remove unused props * feat: use css selector to toggle icon rework folder icon to work purely with css instead of JS manipulation * refactor: remove unused cfg * feat: move TOC to right sidebar * refactor: clean css * style: fix overflow + overflow margin * fix: use `resolveRelative` to resolve file paths * fix: `defaultFolderState` config option * refactor: rename import, rename `folderLi` + ul * fix: use `QuartzPluginData` type * docs: add explorer documentation
2023-09-15 16:39:16 +00:00
this.depth = depth ?? 0
}
private insert(file: DataWrapper) {
if (file.path.length === 1) {
this.children.push(new FileNode(file.file.frontmatter!.title, file.file, this.depth + 1))
} else {
const next = file.path[0]
file.path = file.path.splice(1)
for (const child of this.children) {
if (child.name === next) {
child.insert(file)
return
}
}
const newChild = new FileNode(next, undefined, this.depth + 1)
newChild.insert(file)
this.children.push(newChild)
}
}
// Add new file to tree
add(file: QuartzPluginData, splice: number = 0) {
this.insert({ file, path: file.filePath!.split("/").splice(splice) })
}
// Print tree structure (for debugging)
print(depth: number = 0) {
let folderChar = ""
if (!this.file) folderChar = "|"
console.log("-".repeat(depth), folderChar, this.name, this.depth)
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) {
const filteredNodes: FileNode[] = []
const traverse = (node: FileNode) => {
if (filterFn(node)) {
filteredNodes.push(node)
}
node.children.forEach(traverse)
}
traverse(this)
this.children = filteredNodes
}
/**
* Filter FileNode tree. Behaves similar to `Array.prototype.map()`, but modifies tree in place
* @param mapFn function to filter tree with
*/
map(mapFn: (node: FileNode) => void) {
mapFn(this)
this.children.forEach((child) => child.map(mapFn))
}
feat: implement file explorer component (closes #201) (#452) * feat: add basic explorer structure„ * feat: integrate new component/plugin * feat: add basic explorer structure * feat: add sort to FileNodes * style: improve style for explorer * refactor: remove unused explorer plugin * refactor: clean explorer structure, fix base (toc) * refactor: clean css, respect displayClass * style: add styling to chevron * refactor: clean up debug statements * refactor: remove unused import * fix: clicking folder icon sometimes turns invisible * refactor: clean css * feat(explorer): add config for title * feat: add config for folder click behavior * fix: `no-pointer` not being set for all elements new approach, have one `no-pointer` class, that removes pointer events and one `clickable` class on the svg and button (everything that can normally be clicked). then, find all children with `clickable` and toggle `no-pointer` * fix: bug where nested folders got incorrect height this fixes the bug where nested folders weren't calculating their total height correctly. done by adding class to main container of all children and calculating total * feat: introduce `folderDefaultState` config * feat: store depth for explorer nodes * feat: implement option for collapsed state + bug fixes folderBehavior: "link" still has bad styling, but major bugs with pointers fixed (not clean yet, but working) * fix: default folder icon rotation * fix: hitbox problem with folder links, fix style * fix: redirect url for nested folders * fix: inconsistent behavior with 'collapseFolders' opt * chore: add comments to `ExplorerNode` * feat: save explorer state to local storage (not clean) * feat: rework `getFolders()`, fix localstorage read + write * feat: set folder state from localStorage needs serious refactoring but functional (except folder icon orientation) * fix: folder icon orientation after local storage * feat: add config for `useSavedState` * refactor: clean `explorer.inline.ts` remove unused functions, comments, unused code, add types to EventHandler * refactor: clean explorer merge `isSvg` paths, remove console logs * refactor: add documentation, remove unused funcs * feat: rework folder collapse logic use grids instead of jank scuffed solution with calculating total heights * refactor: remove depth arg from insert * feat: restore collapse functionality to clicks allow folder icon + folder label to collapse folders again * refactor: remove `pointer-event` jank * feat: improve svg viewbox + remove unused props * feat: use css selector to toggle icon rework folder icon to work purely with css instead of JS manipulation * refactor: remove unused cfg * feat: move TOC to right sidebar * refactor: clean css * style: fix overflow + overflow margin * fix: use `resolveRelative` to resolve file paths * fix: `defaultFolderState` config option * refactor: rename import, rename `folderLi` + ul * fix: use `QuartzPluginData` type * docs: add explorer documentation
2023-09-15 16:39:16 +00:00
/**
* Get folder representation with state of tree.
* Intended to only be called on root node before changes to the tree are made
* @param collapsed default state of folders (collapsed by default or not)
* @returns array containing folder state for tree
*/
getFolderPaths(collapsed: boolean): FolderState[] {
const folderPaths: FolderState[] = []
const traverse = (node: FileNode, currentPath: string) => {
if (!node.file) {
const folderPath = currentPath + (currentPath ? "/" : "") + node.name
if (folderPath !== "") {
folderPaths.push({ path: folderPath, collapsed })
}
node.children.forEach((child) => traverse(child, folderPath))
}
}
traverse(this, "")
return folderPaths
}
// Sort order: folders first, then files. Sort folders and files alphabetically
/**
* 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))
feat: implement file explorer component (closes #201) (#452) * feat: add basic explorer structure„ * feat: integrate new component/plugin * feat: add basic explorer structure * feat: add sort to FileNodes * style: improve style for explorer * refactor: remove unused explorer plugin * refactor: clean explorer structure, fix base (toc) * refactor: clean css, respect displayClass * style: add styling to chevron * refactor: clean up debug statements * refactor: remove unused import * fix: clicking folder icon sometimes turns invisible * refactor: clean css * feat(explorer): add config for title * feat: add config for folder click behavior * fix: `no-pointer` not being set for all elements new approach, have one `no-pointer` class, that removes pointer events and one `clickable` class on the svg and button (everything that can normally be clicked). then, find all children with `clickable` and toggle `no-pointer` * fix: bug where nested folders got incorrect height this fixes the bug where nested folders weren't calculating their total height correctly. done by adding class to main container of all children and calculating total * feat: introduce `folderDefaultState` config * feat: store depth for explorer nodes * feat: implement option for collapsed state + bug fixes folderBehavior: "link" still has bad styling, but major bugs with pointers fixed (not clean yet, but working) * fix: default folder icon rotation * fix: hitbox problem with folder links, fix style * fix: redirect url for nested folders * fix: inconsistent behavior with 'collapseFolders' opt * chore: add comments to `ExplorerNode` * feat: save explorer state to local storage (not clean) * feat: rework `getFolders()`, fix localstorage read + write * feat: set folder state from localStorage needs serious refactoring but functional (except folder icon orientation) * fix: folder icon orientation after local storage * feat: add config for `useSavedState` * refactor: clean `explorer.inline.ts` remove unused functions, comments, unused code, add types to EventHandler * refactor: clean explorer merge `isSvg` paths, remove console logs * refactor: add documentation, remove unused funcs * feat: rework folder collapse logic use grids instead of jank scuffed solution with calculating total heights * refactor: remove depth arg from insert * feat: restore collapse functionality to clicks allow folder icon + folder label to collapse folders again * refactor: remove `pointer-event` jank * feat: improve svg viewbox + remove unused props * feat: use css selector to toggle icon rework folder icon to work purely with css instead of JS manipulation * refactor: remove unused cfg * feat: move TOC to right sidebar * refactor: clean css * style: fix overflow + overflow margin * fix: use `resolveRelative` to resolve file paths * fix: `defaultFolderState` config option * refactor: rename import, rename `folderLi` + ul * fix: use `QuartzPluginData` type * docs: add explorer documentation
2023-09-15 16:39:16 +00:00
}
}
type ExplorerNodeProps = {
node: FileNode
opts: Options
fileData: QuartzPluginData
fullPath?: string
}
export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodeProps) {
// Get options
const folderBehavior = opts.folderClickBehavior
const isDefaultOpen = opts.folderDefaultState === "open"
// Calculate current folderPath
let pathOld = fullPath ? fullPath : ""
let folderPath = ""
if (node.name !== "") {
folderPath = `${pathOld}/${node.name}`
}
return (
<div>
{node.file ? (
// 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}
</a>
</li>
) : (
<div>
{node.name !== "" && (
// Node with entire folder
// Render svg button + folder name, then children
<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>
{/* render <a> tag if folderBehavior is "link", otherwise render <button> with collapse click event */}
<li key={node.name} data-folderpath={folderPath}>
{folderBehavior === "link" ? (
<a href={`${folderPath}`} data-for={node.name} class="folder-title">
{node.name}
</a>
) : (
<button class="folder-button">
<h3 class="folder-title">{node.name}</h3>
</button>
)}
</li>
</div>
)}
{/* Recursively render children of folder */}
<div class={`folder-outer ${node.depth === 0 || isDefaultOpen ? "open" : ""}`}>
<ul
// Inline style for left folder paddings
style={{
paddingLeft: node.name !== "" ? "1.4rem" : "0",
}}
class="content"
data-folderul={folderPath}
>
{node.children.map((childNode, i) => (
<ExplorerNode
node={childNode}
key={i}
opts={opts}
fullPath={folderPath}
fileData={fileData}
/>
))}
</ul>
</div>
</div>
)}
</div>
)
}