ultisuite-client/lib/sidebar-nav-folder-ids.ts
2026-05-15 17:40:17 +02:00

139 lines
3.9 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { FolderTreeNode } from "@/lib/sidebar-nav-maps"
/** Slug URL-safe pour un segment de chemin (dossier / partie de libellé). */
export function slugifyNavSegment(name: string): string {
const s = name
.trim()
.toLowerCase()
.normalize("NFD")
.replace(/\p{M}/gu, "")
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "")
return s || "dossier"
}
export function folderIdFromPathSlugs(slugs: string[]): string {
return `folder-${slugs.join("-")}`
}
export function findFolderPath(
nodes: FolderTreeNode[],
id: string,
acc: FolderTreeNode[] = []
): FolderTreeNode[] | null {
for (const n of nodes) {
const next = [...acc, n]
if (n.id === id) return next
if (n.children?.length) {
const hit = findFolderPath(n.children, id, next)
if (hit) return hit
}
}
return null
}
export function collectFolderIdsInTree(nodes: FolderTreeNode[]): string[] {
const out: string[] = []
const walk = (ns: FolderTreeNode[]) => {
for (const e of ns) {
out.push(e.id)
if (e.children?.length) walk(e.children)
}
}
walk(nodes)
return out
}
function collectSubtreeIdsFromNode(node: FolderTreeNode): Set<string> {
const s = new Set<string>([node.id])
if (node.children?.length) {
for (const c of node.children) {
for (const x of collectSubtreeIdsFromNode(c)) s.add(x)
}
}
return s
}
export function uniqueFolderPathId(
pathSlugs: string[],
usedIds: Set<string>
): string {
let base = folderIdFromPathSlugs(pathSlugs)
if (!usedIds.has(base)) return base
let n = 2
while (usedIds.has(`${base}-${n}`)) n += 1
return `${base}-${n}`
}
/** Slugs des ancêtres (noms affichés) pour un parent `parentId` dans larbre. */
export function ancestorLabelSlugsForParentId(
tree: FolderTreeNode[],
parentId: string | null
): string[] {
if (parentId === null) return []
const path = findFolderPath(tree, parentId)
if (!path) return []
return path.map((n) => slugifyNavSegment(n.label))
}
export function proposeNewFolderId(
tree: FolderTreeNode[],
labelRowIds: readonly string[],
parentId: string | null,
displayLabel: string
): string {
const used = new Set<string>([
...collectFolderIdsInTree(tree),
...labelRowIds,
])
const anc = ancestorLabelSlugsForParentId(tree, parentId)
const leaf = slugifyNavSegment(displayLabel)
return uniqueFolderPathId([...anc, leaf], used)
}
/**
* Recalcule les ids du sous-arbre enraciné à `rootId` (chemins slug à partir des libellés).
* `tree` doit déjà contenir les bons libellés pour ce sous-arbre.
*/
export function rekeyFolderSubtreeAt(
tree: FolderTreeNode[],
rootId: string,
labelRowIds: readonly string[]
): { tree: FolderTreeNode[]; idMap: Record<string, string> } | null {
const path = findFolderPath(tree, rootId)
if (!path) return null
const rootNode = path[path.length - 1]!
const ancestorSlugs = path.slice(0, -1).map((n) => slugifyNavSegment(n.label))
const subtreeOldIds = collectSubtreeIdsFromNode(rootNode)
const used = new Set<string>([...labelRowIds])
for (const id of collectFolderIdsInTree(tree)) {
if (!subtreeOldIds.has(id)) used.add(id)
}
const idMap: Record<string, string> = {}
function rebuild(node: FolderTreeNode, anc: string[]): FolderTreeNode {
const pathSlugs = [...anc, slugifyNavSegment(node.label)]
const newId = uniqueFolderPathId(pathSlugs, used)
used.add(newId)
if (newId !== node.id) idMap[node.id] = newId
const kids = node.children?.length
? node.children.map((c) => rebuild(c, pathSlugs))
: undefined
return { ...node, id: newId, children: kids }
}
const rebuiltRoot = rebuild(rootNode, ancestorSlugs)
function replaceAt(nodes: FolderTreeNode[]): FolderTreeNode[] {
return nodes.map((n) => {
if (n.id === rootId) return rebuiltRoot
if (n.children?.length) return { ...n, children: replaceAt(n.children) }
return n
})
}
return { tree: replaceAt(tree), idMap }
}