/* * Vencord, a modification for Discord's desktop app * Copyright (c) 2026 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ export type NestedFolderMap = Record; export interface GuildFolderLike { folderId?: string | number | null; guildIds?: string[]; } export function getChildFolderIds(map: NestedFolderMap, parentId: string | number): string[] { return Object.entries(map) .filter(([, id]) => id === String(parentId)) .map(([id]) => id); } export function hasParentInChain(map: NestedFolderMap, childId: string, parentId: string): boolean { const seen = new Set(); let current = map[childId]; while (current != null && !seen.has(current)) { if (current === parentId) return true; seen.add(current); current = map[current]; } return false; } export function areNestedRelated(map: NestedFolderMap, firstId: string, secondId: string): boolean { return hasParentInChain(map, firstId, secondId) || hasParentInChain(map, secondId, firstId); } export function getAncestorFolderIds(map: NestedFolderMap, childId: string | number): string[] { const ancestors: string[] = []; const seen = new Set([String(childId)]); let current = map[String(childId)]; while (current != null && !seen.has(current)) { ancestors.push(current); seen.add(current); current = map[current]; } return ancestors; } export function getDescendantFolderIds(map: NestedFolderMap, parentId: string | number): string[] { const descendants: string[] = []; const rootId = String(parentId); const seen = new Set([rootId]); const queue = [...getChildFolderIds(map, parentId)]; while (queue.length) { const current = queue.shift(); if (!current || seen.has(current)) continue; seen.add(current); descendants.push(current); queue.push(...getChildFolderIds(map, current)); } return descendants; } export function sanitizeNestedFolderMap(map: NestedFolderMap, validFolderIds: Set): NestedFolderMap { const sanitized: NestedFolderMap = {}; for (const [childId, parentId] of Object.entries(map)) { if (!validFolderIds.has(childId) || !validFolderIds.has(parentId)) continue; if (childId === parentId) continue; delete sanitized[childId]; if (hasParentInChain(sanitized, parentId, childId)) continue; sanitized[childId] = parentId; } return sanitized; } export function getGuildFolderId(folders: GuildFolderLike[], guildId: string | null | undefined): string | undefined { if (!guildId) return; return folders .find(folder => folder.guildIds?.includes(guildId)) ?.folderId ?.toString(); } export function getGuildNavigationExpandTargets( map: NestedFolderMap, folders: GuildFolderLike[], guildId: string | null | undefined, expandedFolderIds: Iterable ): string[] { const folderId = getGuildFolderId(folders, guildId); if (!folderId) return []; const expandedIds = new Set(Array.from(expandedFolderIds, id => String(id))); const targetFolderIds = [...getAncestorFolderIds(map, folderId).reverse(), folderId]; return targetFolderIds.filter(id => !expandedIds.has(id)); }