51 lines
1.8 KiB
TypeScript
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)];
|
|
}
|