/** * Pure helpers for manual page reordering in the sidebar (issue #45). The * server recomputes the moved page's `sort_key` between two neighbours, so the * client only has to name them. Given the current order and where the page * should land, these compute the `afterId`/`beforeId` the position endpoint * expects — kept separate from the component so they are unit-testable. */ /** The moved page's new neighbours when it lands at `newIndex` in the list with * itself removed. `afterId` is its predecessor (null at the top), `beforeId` * its successor (null at the bottom). */ export function neighborsForMove( orderedIds: string[], movedId: string, newIndex: number, ): { afterId: string | null; beforeId: string | null } { const without = orderedIds.filter((id) => id !== movedId); const clamped = Math.max(0, Math.min(newIndex, without.length)); return { afterId: clamped > 0 ? without[clamped - 1]! : null, beforeId: clamped < without.length ? without[clamped]! : null, }; } /** Target index for a drop onto `targetId`: before it, or after it when the * pointer is over the item's lower half (so the very bottom is reachable). */ export function dropIndex( orderedIds: string[], movedId: string, targetId: string, after: boolean, ): number { const without = orderedIds.filter((id) => id !== movedId); const base = without.indexOf(targetId); if (base === -1) return without.length; return after ? base + 1 : base; }