Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This project is a comprehensive full‑stack web app for doing simple, local fil
**PDF Tools:**

- Convert PDF pages to PNG (single page, range, or all pages)
- Batch-convert multiple PDFs to PNG with unique ZIP folders for same-named files
- Merge multiple PDF files into one document, including files with the same name
- Split a PDF by extracting a page range into a new document
- Convert PDF to DOCX
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/pages/PdfPngBatch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
import { clsx } from "clsx";
import { twMerge } from "tailwind-merge";
import PrimaryButton from "../components/UI/PrimaryButton";
import { uniqueZipFolderName } from "../utils/uniqueZipFolderName";

pdfjsLib.GlobalWorkerOptions.workerSrc = pdfWorker;

Expand Down Expand Up @@ -120,6 +121,7 @@ export default function PdfPngBatch() {

const zip = new JSZip();
let done = 0;
const usedFolderNames = new Set<string>();

try {
for (let i = 0; i < files.length; i++) {
Expand All @@ -129,7 +131,8 @@ export default function PdfPngBatch() {
setFileProgress(Math.round((page / total) * 100));
});
// If a batch contains multiple files, namespace PNGs into a folder per file.
const folder = files.length > 1 ? zip.folder(result.name) : zip;
const folderName = uniqueZipFolderName(result.name, usedFolderNames);
const folder = files.length > 1 ? zip.folder(folderName) : zip;
for (const p of result.pages) {
folder.file(p.name, p.blob);
}
Expand Down
20 changes: 20 additions & 0 deletions frontend/src/utils/uniqueZipFolderName.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { describe, expect, it } from "vitest";

import { uniqueZipFolderName } from "./uniqueZipFolderName";

describe("uniqueZipFolderName", () => {
it("keeps the first name and suffixes later collisions", () => {
const used = new Set<string>();

expect(uniqueZipFolderName("report", used)).toBe("report");
expect(uniqueZipFolderName("report", used)).toBe("report-2");
expect(uniqueZipFolderName("report", used)).toBe("report-3");
expect(uniqueZipFolderName("invoice", used)).toBe("invoice");
});

it("skips suffixes that are already claimed", () => {
const used = new Set<string>(["report", "report-2"]);

expect(uniqueZipFolderName("report", used)).toBe("report-3");
});
});
22 changes: 22 additions & 0 deletions frontend/src/utils/uniqueZipFolderName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Pick a ZIP folder name that does not collide with names already claimed
* in the current archive. First use keeps the base name; later collisions
* get a numeric suffix (`report`, `report-2`, ...).
*/
export function uniqueZipFolderName(baseName: string, usedNames: Set<string>) {
const normalized = baseName.trim() || "document";
if (!usedNames.has(normalized)) {
usedNames.add(normalized);
return normalized;
}

let suffix = 2;
let candidate = `${normalized}-${suffix}`;
while (usedNames.has(candidate)) {
suffix += 1;
candidate = `${normalized}-${suffix}`;
}

usedNames.add(candidate);
return candidate;
}
Loading