Something went wrong. Try again.
[mirror] A local-first desktop markdown writer with grammar checking, at proto integration, and more github.com/stormlightlabs/writer/releases
Something went wrong. Try again.
3.9 kB · 113 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114import type { DocMeta } from "$types";
export type DirectoryTreeNode = { type: "directory"; name: string; path: string; children: TreeNode[] };export type FileTreeNode = { type: "file"; name: string; path: string; doc: DocMeta };export type TreeNode = DirectoryTreeNode | FileTreeNode;
function splitPathSegments(relPath: string): string[] { return relPath.split(/[\\/]+/).filter(Boolean);}
function ensureDirectoryNode(parent: DirectoryTreeNode, name: string, path: string): DirectoryTreeNode { const existing = parent.children.find((node) => node.type === "directory" && node.path === path); if (existing && existing.type === "directory") { return existing; }
const directoryNode: DirectoryTreeNode = { type: "directory", name, path, children: [] }; parent.children.push(directoryNode); return directoryNode;}
function sortTreeNodes(children: TreeNode[], directoryOrderIndex: Map<string, number>): TreeNode[] { return children.toSorted((left, right) => { if (left.type !== right.type) { return left.type === "directory" ? -1 : 1; }
if (left.type === "directory" && right.type === "directory") { const leftOrder = directoryOrderIndex.get(left.path); const rightOrder = directoryOrderIndex.get(right.path); if (leftOrder !== undefined && rightOrder !== undefined && leftOrder !== rightOrder) { return leftOrder - rightOrder; } if (leftOrder !== undefined && rightOrder === undefined) { return -1; } if (leftOrder === undefined && rightOrder !== undefined) { return 1; } }
return left.name.localeCompare(right.name, void 0, { sensitivity: "base" }); });}
function normalizeTree(node: DirectoryTreeNode, directoryOrderIndex: Map<string, number>): DirectoryTreeNode { const normalizedChildren = sortTreeNodes(node.children, directoryOrderIndex).map((child) => child.type === "directory" ? normalizeTree(child, directoryOrderIndex) : child ); return { ...node, children: normalizedChildren };}
export function buildDocumentTree( documents: DocMeta[], directories: string[], directoryOrder: string[] = [],): DirectoryTreeNode { const root: DirectoryTreeNode = { type: "directory", name: "", path: "", children: [] }; const directoryOrderIndex = new Map<string, number>(); for (let index = 0; index < directoryOrder.length; index += 1) { directoryOrderIndex.set(directoryOrder[index], index); }
for (const directoryPath of directories) { const segments = splitPathSegments(directoryPath); if (segments.length === 0) { continue; }
let currentParent = root; let currentPath = ""; for (const segment of segments) { currentPath = currentPath ? `${currentPath}/${segment}` : segment; currentParent = ensureDirectoryNode(currentParent, segment, currentPath); } }
for (const doc of documents) { const segments = splitPathSegments(doc.rel_path); if (segments.length === 0) { continue; }
const fileName = segments.at(-1) ?? doc.rel_path; const parentSegments = segments.slice(0, -1);
let currentParent = root; let currentPath = "";
for (const segment of parentSegments) { currentPath = currentPath ? `${currentPath}/${segment}` : segment; currentParent = ensureDirectoryNode(currentParent, segment, currentPath); }
currentParent.children.push({ type: "file", name: fileName, path: doc.rel_path, doc }); }
return normalizeTree(root, directoryOrderIndex);}
export function parentDirectoryPaths(relPath: string): string[] { const parts = splitPathSegments(relPath); const directories = parts.slice(0, -1); const paths: string[] = []; let currentPath = "";
for (const directory of directories) { currentPath = currentPath ? `${currentPath}/${directory}` : directory; paths.push(currentPath); }
return paths;}