From abd1ae1bf2d72f1d628f8cda640890159ff41aeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Sz=C3=BCcs?= <127139797+balazs-szucs@users.noreply.github.com> Date: Fri, 7 Nov 2025 14:35:18 +0100 Subject: [PATCH] feat(sort): enhance file sorting and order handling (#4813) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description of Changes - Added async support to sorting functions for improved UI responsiveness in merge.js. - Enhanced logging for debugging file orders in both JavaScript and Java. - Improved file order validation and handling in the backend, ensuring consistent sorting and upload order. - Refactored file order processing with better trimming, handling for empty entries, and logging unmatched filenames. Closes: #4810 --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --------- Signed-off-by: Balázs Szücs --- .../SPDF/controller/api/MergeController.java | 18 +++++++-- .../main/resources/static/js/downloader.js | 6 +++ .../src/main/resources/static/js/merge.js | 40 ++++++++++++------- 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/MergeController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/MergeController.java index 569c58f5e..b1132ec94 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/MergeController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/MergeController.java @@ -72,19 +72,29 @@ public class MergeController { // fileOrder is newline-delimited original filenames in the desired order. private static MultipartFile[] reorderFilesByProvidedOrder( MultipartFile[] files, String fileOrder) { - String[] desired = fileOrder.split("\n", -1); + // Split by various line endings and trim each entry + String[] desired = + stirling.software.common.util.RegexPatternUtils.getInstance() + .getNewlineSplitPattern() + .split(fileOrder); + List remaining = new ArrayList<>(Arrays.asList(files)); List ordered = new ArrayList<>(files.length); for (String name : desired) { - if (name == null || name.isEmpty()) continue; + name = name.trim(); + if (name.isEmpty()) { + log.debug("Skipping empty entry"); + continue; + } int idx = indexOfByOriginalFilename(remaining, name); if (idx >= 0) { ordered.add(remaining.remove(idx)); + } else { + log.debug("Filename from order list not found in uploaded files: {}", name); } } - // Append any files not explicitly listed, preserving their relative order ordered.addAll(remaining); return ordered.toArray(new MultipartFile[0]); } @@ -252,8 +262,10 @@ public class MergeController { // If front-end provided explicit visible order, honor it and override backend sorting if (fileOrder != null && !fileOrder.isBlank()) { + log.info("Reordering files based on fileOrder parameter"); files = reorderFilesByProvidedOrder(files, fileOrder); } else { + log.info("Sorting files based on sortType: {}", request.getSortType()); Arrays.sort( files, getSortComparator( diff --git a/app/core/src/main/resources/static/js/downloader.js b/app/core/src/main/resources/static/js/downloader.js index 9e074be5e..070fd90af 100644 --- a/app/core/src/main/resources/static/js/downloader.js +++ b/app/core/src/main/resources/static/js/downloader.js @@ -74,6 +74,12 @@ showGameBtn.style.display = 'none'; } + // Log fileOrder for debugging + const fileOrderValue = formData.get('fileOrder'); + if (fileOrderValue) { + console.log('FormData fileOrder:', fileOrderValue); + } + // Remove empty file entries for (let [key, value] of formData.entries()) { if (value instanceof File && !value.name) { diff --git a/app/core/src/main/resources/static/js/merge.js b/app/core/src/main/resources/static/js/merge.js index 01d7d97d9..09a4fe2fc 100644 --- a/app/core/src/main/resources/static/js/merge.js +++ b/app/core/src/main/resources/static/js/merge.js @@ -123,39 +123,38 @@ function attachMoveButtons() { } } -document.getElementById("sortByNameBtn").addEventListener("click", function () { +document.getElementById("sortByNameBtn").addEventListener("click", async function () { if (currentSort.field === "name" && !currentSort.descending) { currentSort.descending = true; - sortFiles((a, b) => b.name.localeCompare(a.name)); + await sortFiles((a, b) => b.name.localeCompare(a.name)); } else { currentSort.field = "name"; currentSort.descending = false; - sortFiles((a, b) => a.name.localeCompare(b.name)); + await sortFiles((a, b) => a.name.localeCompare(b.name)); } }); -document.getElementById("sortByDateBtn").addEventListener("click", function () { +document.getElementById("sortByDateBtn").addEventListener("click", async function () { if (currentSort.field === "lastModified" && !currentSort.descending) { currentSort.descending = true; - sortFiles((a, b) => b.lastModified - a.lastModified); + await sortFiles((a, b) => b.lastModified - a.lastModified); } else { currentSort.field = "lastModified"; currentSort.descending = false; - sortFiles((a, b) => a.lastModified - b.lastModified); + await sortFiles((a, b) => a.lastModified - b.lastModified); } }); -function sortFiles(comparator) { +async function sortFiles(comparator) { // Convert FileList to array and sort const sortedFilesArray = Array.from(document.getElementById("fileInput-input").files).sort(comparator); - // Refresh displayed list - displayFiles(sortedFilesArray); + // Refresh displayed list (wait for it to complete since it's async) + await displayFiles(sortedFilesArray); - // Update the files property - const dataTransfer = new DataTransfer(); - sortedFilesArray.forEach((file) => dataTransfer.items.add(file)); - document.getElementById("fileInput-input").files = dataTransfer.files; + // Update the file input and fileOrder based on the current display order + // This ensures consistency between display and file input + updateFiles(); } function updateFiles() { @@ -163,25 +162,36 @@ function updateFiles() { var liElements = document.querySelectorAll("#selectedFiles li"); const files = document.getElementById("fileInput-input").files; + console.log("updateFiles: found", liElements.length, "LI elements and", files.length, "files"); + for (var i = 0; i < liElements.length; i++) { var fileNameFromList = liElements[i].querySelector(".filename").innerText; - var fileFromFiles; + var found = false; for (var j = 0; j < files.length; j++) { var file = files[j]; if (file.name === fileNameFromList) { dataTransfer.items.add(file); + found = true; break; } } + if (!found) { + console.warn("updateFiles: Could not find file:", fileNameFromList); + } } + document.getElementById("fileInput-input").files = dataTransfer.files; + console.log("updateFiles: Updated file input with", dataTransfer.files.length, "files"); // Also populate hidden fileOrder to preserve visible order const order = Array.from(liElements) .map((li) => li.querySelector(".filename").innerText) .join("\n"); const orderInput = document.getElementById("fileOrder"); - if (orderInput) orderInput.value = order; + if (orderInput) { + orderInput.value = order; + console.log("Updated fileOrder:", order); + } } document.querySelector("#resetFileInputBtn").addEventListener("click", ()=>{