diff --git a/frontend/src/components/pageEditor/DragDropGrid.tsx b/frontend/src/components/pageEditor/DragDropGrid.tsx index a13108079..6c7d1ec09 100644 --- a/frontend/src/components/pageEditor/DragDropGrid.tsx +++ b/frontend/src/components/pageEditor/DragDropGrid.tsx @@ -511,7 +511,7 @@ const DragDropGrid = ({ const sourcePageNumber = activeData.pageNumber; const overData = over?.data.current; - const targetIndex = resolveTargetIndex( + let targetIndex = resolveTargetIndex( hoveredItemId, finalDropSide, visibleItems, @@ -521,6 +521,8 @@ const DragDropGrid = ({ ); if (targetIndex === null) return; + if (targetIndex < 0) targetIndex = 0; + if (targetIndex > items.length) targetIndex = items.length; // Check if this page is box-selected const isBoxSelected = boxSelectedPageIds.includes(active.id as string); diff --git a/frontend/src/components/pageEditor/PageEditor.tsx b/frontend/src/components/pageEditor/PageEditor.tsx index 4b6afcfc7..9254f7342 100644 --- a/frontend/src/components/pageEditor/PageEditor.tsx +++ b/frontend/src/components/pageEditor/PageEditor.tsx @@ -200,6 +200,7 @@ const PageEditor = ({ break; } } + if (!hasAdditions && !hasRemovals) return; setEditedDocument(prev => { @@ -213,22 +214,18 @@ const PageEditor = ({ // Insert new pages while preserving current interleaving if (hasAdditions) { - // Insert file-by-file at the correct anchors - for (const [, additions] of newByFile) { - // Check if any page has insertAfterPageId (specific insertion point) - const hasSpecificInsertPoint = additions.some(p => (p as any).insertAfterPageId); + // Rebuild pages array respecting the order from mergedPdfDocument + // This handles file selection/deselection correctly (placeholder positioning) + const mergedOrder = mergedPdfDocument.pages.map(p => p.id); + const pageById = new Map(pages.map(p => [p.id, p])); - if (hasSpecificInsertPoint) { - // Insert after specific page (ignores file order) - const insertAfterPageId = (additions[0] as any).insertAfterPageId; - const insertAfterIndex = pages.findIndex(p => p.id === insertAfterPageId); - const insertAt = insertAfterIndex >= 0 ? insertAfterIndex + 1 : pages.length; - pages.splice(insertAt, 0, ...additions); - } else { - // Normal add: append to end - pages.push(...additions); - } + // Add new pages to the map + for (const [, additions] of newByFile) { + additions.forEach(p => pageById.set(p.id, p)); } + + // Rebuild in mergedPdfDocument order + pages = mergedOrder.map(id => pageById.get(id)!).filter(Boolean); } // Renumber without reordering @@ -350,17 +347,28 @@ const PageEditor = ({ }, [displayDocument, setSelectedPageIds, setSelectionMode]); // Automatically include newly added pages in the current selection + const previousPageIdsRef = useRef>(new Set()); useEffect(() => { - if (!displayDocument || displayDocument.pages.length === 0) return; + if (!displayDocument || displayDocument.pages.length === 0) { + previousPageIdsRef.current = new Set(); + return; + } - const currentSelection = new Set(selectedPageIds); - const newlyAddedPageIds = displayDocument.pages - .map(page => page.id) - .filter(pageId => !currentSelection.has(pageId)); + const currentIds = new Set(displayDocument.pages.map(page => page.id)); + const newlyAddedPageIds: string[] = []; + currentIds.forEach(id => { + if (!previousPageIdsRef.current.has(id)) { + newlyAddedPageIds.push(id); + } + }); if (newlyAddedPageIds.length > 0) { - setSelectedPageIds([...selectedPageIds, ...newlyAddedPageIds]); + const next = new Set(selectedPageIds); + newlyAddedPageIds.forEach(id => next.add(id)); + setSelectedPageIds(Array.from(next)); } + + previousPageIdsRef.current = currentIds; }, [displayDocument, selectedPageIds, setSelectedPageIds]); // DOM-first command handlers diff --git a/frontend/src/components/pageEditor/PageThumbnail.tsx b/frontend/src/components/pageEditor/PageThumbnail.tsx index fc7198696..5aadc2238 100644 --- a/frontend/src/components/pageEditor/PageThumbnail.tsx +++ b/frontend/src/components/pageEditor/PageThumbnail.tsx @@ -445,7 +445,7 @@ const PageThumbnail: React.FC = ({ backgroundColor: 'white', border: '1px solid #e9ecef', borderRadius: 2 - }}> + }} /> ) : thumbnailUrl ? ( [id, index])); + + const sortedOriginalFileIds = [...originalFileIds].sort((a, b) => { + const posA = fileOrderMap.get(a) ?? Number.MAX_SAFE_INTEGER; + const posB = fileOrderMap.get(b) ?? Number.MAX_SAFE_INTEGER; + return posA - posB; + }); + const originalFilePages: PDFPage[] = []; - originalFileIds.forEach(fileId => { + sortedOriginalFileIds.forEach(fileId => { const isSelected = selectedFileIdsSet.has(fileId); const filePages = createPagesFromFile(fileId, 1, isSelected); // Temporary numbering originalFilePages.push(...filePages); @@ -184,6 +195,13 @@ export function usePageDocument(): PageDocumentHook { return null; } + // Pages are already in the correct order from the sorted assembly above + // Just ensure page numbers are sequential + pages = pages.map((page, index) => ({ + ...page, + pageNumber: index + 1, + })); + const mergedDoc: PDFDocument = { id: activeFileIds.join('-'), name, @@ -193,7 +211,7 @@ export function usePageDocument(): PageDocumentHook { }; return mergedDoc; - }, [activeFileIds, primaryFileId, primaryStirlingFileStub, processedFilePages, processedFileTotalPages, selectors, activeFilesSignature, selectedFileIdsKey, state.ui.selectedFileIds]); + }, [activeFileIds, primaryFileId, primaryStirlingFileStub, processedFilePages, processedFileTotalPages, selectors, activeFilesSignature, selectedFileIdsKey, state.ui.selectedFileIds, allFileIds]); // Large document detection for smart loading const isVeryLargeDocument = useMemo(() => {