Files
gites/src/lib/tree.ts
T

51 lines
1.8 KiB
TypeScript

import type { Ref } from "./types";
export interface TreeNode {
/** One `/`-separated part of the name. */
label: string;
/** The name up to and including this part; unique within one tree. */
path: string;
children: TreeNode[];
/** Set when a ref ends here. A node can be both a ref and a folder (`a` and `a/b`). */
ref?: Ref;
}
/**
* Splits short names by `/` into a tree. Folders come before refs, each sorted by name,
* or, with `keepOrder`, in the order of `items` (a folder takes the place of its first item).
*/
export function buildTree(items: { short: string; ref: Ref }[], keepOrder = false): TreeNode[] {
const root: TreeNode = { label: "", path: "", children: [] };
for (const { short, ref } of items) {
let node = root;
for (const part of short.split("/")) {
const path = node.path ? `${node.path}/${part}` : part;
let child = node.children.find((c) => c.label === part);
if (!child) {
child = { label: part, path, children: [] };
node.children.push(child);
}
node = child;
}
node.ref = ref;
}
const sort = (nodes: TreeNode[]) => {
nodes.sort(
(a, b) => Number(b.children.length > 0) - Number(a.children.length > 0) || a.label.localeCompare(b.label),
);
nodes.forEach((n) => sort(n.children));
};
const foldersFirst = (nodes: TreeNode[]) => {
// Array.prototype.sort is stable, so items keep their order within each group.
nodes.sort((a, b) => Number(b.children.length > 0) - Number(a.children.length > 0));
nodes.forEach((n) => foldersFirst(n.children));
};
(keepOrder ? foldersFirst : sort)(root.children);
return root.children;
}
/** Every ref in the subtree. */
export function refsUnder(node: TreeNode): Ref[] {
return [...(node.ref ? [node.ref] : []), ...node.children.flatMap(refsUnder)];
}