UNPKG

@firecms/core

Version:

Awesome Firebase/Firestore-based headless open-source CMS

228 lines (195 loc) 8.97 kB
import { EntityCollection, NavigationController, SideEntityController } from "../types"; export function removeInitialAndTrailingSlashes(s: string): string { return removeInitialSlash(removeTrailingSlash(s)); } export function removeInitialSlash(s: string) { if (s.startsWith("/")) return s.slice(1); else return s; } export function removeTrailingSlash(s: string) { if (s.endsWith("/")) return s.slice(0, -1); else return s; } export function addInitialSlash(s: string) { if (s.startsWith("/")) return s; else return `/${s}`; } export function getLastSegment(path: string) { const cleanPath = removeInitialAndTrailingSlashes(path); if (cleanPath.includes("/")) { const segments = cleanPath.split("/"); return segments[segments.length - 1]; } return cleanPath; } export function resolveCollectionPathIds(path: string, allCollections: EntityCollection[]): string { let remainingPath = removeInitialAndTrailingSlashes(path); if (!remainingPath) { return ""; } let currentCollections: EntityCollection[] | undefined = allCollections; const resolvedPathParts: string[] = []; while (remainingPath.length > 0) { if (!currentCollections || currentCollections.length === 0) { // We have remaining path segments but no more collections to match against console.warn(`resolveCollectionPathIds: Path structure implies subcollections, but none found before segment starting with "${remainingPath}" in original path "${path}". Appending remaining original path.`); resolvedPathParts.push(remainingPath); remainingPath = ""; // Stop processing break; } let foundMatch = false; // Sort potential matches by length descending to prioritize longer matches (e.g., "a/b" over "a") const potentialMatches: { col: EntityCollection; match: string; }[] = currentCollections .flatMap(col => [{ col, match: col.path }, { col, match: col.id }]) .filter(p => p.match && remainingPath.startsWith(p.match)) .sort((a, b) => b.match.length - a.match.length); if (potentialMatches.length > 0) { const { col: foundCollection, match: matchString } = potentialMatches[0]; resolvedPathParts.push(foundCollection.path); // Use the defined path remainingPath = removeInitialSlash(remainingPath.substring(matchString.length)); // Check if we are at the end of the path if (remainingPath.length === 0) { foundMatch = true; break; // Path ends with a collection segment } // The next segment must be an entity ID const idSeparatorIndex = remainingPath.indexOf("/"); let entityId: string; if (idSeparatorIndex > -1) { entityId = remainingPath.substring(0, idSeparatorIndex); remainingPath = remainingPath.substring(idSeparatorIndex + 1); } else { // This should not happen if the original path is valid (odd segments) // but handle it defensively: assume the rest is the ID entityId = remainingPath; remainingPath = ""; console.warn(`resolveCollectionPathIds: Path seems to end with an entity ID "${entityId}" instead of a collection segment in original path "${path}". This might indicate an invalid input path.`); // Even if it ends here, we still need to push the ID } resolvedPathParts.push(entityId); // Append entity ID currentCollections = foundCollection.subcollections; // Move to subcollections foundMatch = true; if (!currentCollections && remainingPath.length > 0) { // Warn if the path continues but no subcollections were defined console.warn(`resolveCollectionPathIds: Path continues after entity ID "${entityId}", but no subcollections are defined for the preceding collection "${foundCollection.path}" in path "${path}". Appending remaining original path.`); resolvedPathParts.push(remainingPath); // Append the rest remainingPath = ""; // Stop processing break; } } if (!foundMatch) { // Collection definition not found for the start of the remaining path console.warn(`resolveCollectionPathIds: Collection definition not found for segment starting with "${remainingPath}" in original path "${path}". Appending remaining original path.`); resolvedPathParts.push(remainingPath); // Append the rest remainingPath = ""; // Stop processing break; } } return resolvedPathParts.join("/"); } /** * Find the corresponding view at any depth for a given path. * Note that path or segments of the paths can be collection aliases. * @param pathOrId * @param collections */ export function getCollectionByPathOrId(pathOrId: string, collections: EntityCollection[]): EntityCollection | undefined { const subpaths = removeInitialAndTrailingSlashes(pathOrId).split("/"); if (subpaths.length % 2 === 0) { throw Error(`getCollectionByPathOrId: Collection paths must have an odd number of segments: ${pathOrId}`); } const subpathCombinations = getCollectionPathsCombinations(subpaths); let result: EntityCollection | undefined; for (let i = 0; i < subpathCombinations.length; i++) { const subpathCombination = subpathCombinations[i]; const navigationEntry = collections && collections .sort((a, b) => (a.id ?? "").localeCompare(b.id ?? "")) .find((entry) => entry.id === subpathCombination || entry.path === subpathCombination); if (navigationEntry) { if (subpathCombination === pathOrId) { result = navigationEntry; } else if (navigationEntry.subcollections) { const newPath = pathOrId.replace(subpathCombination, "").split("/").slice(2).join("/"); if (newPath.length > 0) result = getCollectionByPathOrId(newPath, navigationEntry.subcollections); } } if (result) break; } return result; } /** * Get the subcollection combinations from a path: * "sites/es/locales" => ["sites/es/locales", "sites"] * @param subpaths */ export function getCollectionPathsCombinations(subpaths: string[]): string[] { const entries = subpaths.length > 0 && subpaths.length % 2 === 0 ? subpaths.splice(0, subpaths.length - 1) : subpaths; const length = entries.length; const result: string[] = []; for (let i = length; i > 0; i = i - 2) { result.push(entries.slice(0, i).join("/")); } return result; } export function navigateToEntity({ openEntityMode, collection, entityId, copy, path, fullIdPath, selectedTab, sideEntityController, onClose, navigation }: { openEntityMode: "side_panel" | "full_screen"; collection?: EntityCollection; entityId?: string; selectedTab?: string; copy?: boolean; path: string; fullIdPath?: string; sideEntityController: SideEntityController; onClose?: () => void; navigation: NavigationController }) { if (openEntityMode === "side_panel") { sideEntityController.open({ entityId, path, fullIdPath, copy, selectedTab, collection, updateUrl: true, onClose }); } else { let to = navigation.buildUrlCollectionPath(entityId ? `${fullIdPath ?? path}/${entityId}` : fullIdPath ?? path); if (entityId && selectedTab) { to += `/${selectedTab}`; } if (!entityId) { to += "#new"; } if (copy) { to += "#copy"; } navigation.navigate(to); } }