# Description of Changes

- Made a recursive function that checks if a file is a zip, then scans
its contents. If the content is a zip, or an accepted file type
(non-folder, size > 0), add it and repeat the check for zips
- Change all convert fragment to accept application/zip
- Slightly modified the input file styling to include an ID for
appending the "Extracting" text
- Added language translation for the "Extracting..." text
- (Edit March 3) Removed recursive function, zip file inside target zip
file is excluded
- For decrypt function after uploading the file, i reused one webworker
to handle the decryption , since in the previous code the workers are
created but not detroyed for every single file, this caused a huge slow
down for uploading large files due to creation of threads and thus this
proposal.

- Closes #2951 

---

### 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/DeveloperGuide.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### UI Changes (if applicable)


![image](https://github.com/user-attachments/assets/ee4c6dc8-8740-45c9-8772-05fa7444ca6d)
Added extracting text (for all language).

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing)
for more details.

---------

Co-authored-by: Ludy <[email protected]>
Co-authored-by: reecebrowne <[email protected]>
Co-authored-by: Reece Browne <[email protected]>
Co-authored-by: Anthony Stirling <[email protected]>
Co-authored-by: swanemar <[email protected]>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
This commit is contained in:
Ryan Tang
2025-03-10 00:10:35 +00:00
committed by GitHub
co-authored by Ludy reecebrowne Reece Browne Anthony Stirling swanemar stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
parent b6b49762f6
commit b0836d0bd7
43 changed files with 192 additions and 21 deletions
+20 -3
View File
@@ -1,5 +1,11 @@
export class DecryptFile {
constructor(){
this.decryptWorker = null
}
async decryptFile(file, requiresPassword) {
try {
async function getCsrfToken() {
const cookieValue = document.cookie
@@ -81,12 +87,23 @@ export class DecryptFile {
}
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
const arrayBuffer = await file.arrayBuffer();
const arrayBufferForPdfLib = arrayBuffer.slice(0);
var loadingTask;
const loadingTask = pdfjsLib.getDocument({
data: arrayBuffer,
});
if(this.decryptWorker == null){
loadingTask = pdfjsLib.getDocument({
data: arrayBuffer,
});
this.decryptWorker = loadingTask._worker
}else {
loadingTask = pdfjsLib.getDocument({
data: arrayBuffer,
worker: this.decryptWorker
});
}
await loadingTask.promise;
+124 -13
View File
@@ -1,7 +1,8 @@
import FileIconFactory from './file-icon-factory.js';
import FileUtils from './file-utils.js';
import UUID from './uuid.js';
import {DecryptFile} from './DecryptFiles.js';
import { DecryptFile } from './DecryptFiles.js';
let isScriptExecuted = false;
if (!isScriptExecuted) {
isScriptExecuted = true;
@@ -11,6 +12,23 @@ if (!isScriptExecuted) {
}
let hasDroppedImage = false;
const zipTypes = [
'application/zip',
'multipart/x-zip',
'application/zip-compressed',
'application/x-zip-compressed',
];
const mimeTypes = {
"png": "image/png",
"jpg": "image/jpeg",
"jpeg": "image/jpeg",
"gif": "image/gif",
"bmp": "image/bmp",
"svg": "image/svg+xml",
"pdf": "application/pdf",
};
function setupFileInput(chooser) {
const elementId = chooser.getAttribute('data-bs-element-id');
const filesSelected = chooser.getAttribute('data-bs-files-selected');
@@ -55,6 +73,7 @@ function setupFileInput(chooser) {
overlay = false;
}
const dropListener = function (e) {
e.preventDefault();
// Drag and Drop shall only affect the target file chooser
@@ -83,7 +102,7 @@ function setupFileInput(chooser) {
dragCounter = 0;
fileInput.dispatchEvent(new CustomEvent('change', {bubbles: true, detail: {source: 'drag-drop'}}));
fileInput.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { source: 'drag-drop' } }));
};
function pushFileListTo(fileList, container) {
@@ -114,18 +133,44 @@ function setupFileInput(chooser) {
} else {
allFiles = Array.from(isDragAndDrop ? allFiles : [element.files[0]]);
}
async function checkZipFile() {
const promises = allFiles.map(async (file, index) => {
try {
if (zipTypes.includes(file.type)) {
await extractZipFiles(file, element.accept);
allFiles.splice(index, 1);
}
} catch (error) {
console.error(`Error extracting ZIP file (${file.name}):`, error);
allFiles.splice(index, 1);
}
});
await Promise.all(promises);
}
const originalText = inputContainer.querySelector('#fileInputText').innerHTML;
const decryptFile = new DecryptFile();
inputContainer.querySelector('#fileInputText').innerHTML = window.fileInput.extractPDF;
await checkZipFile();
allFiles = await Promise.all(
allFiles.map(async (file) => {
let decryptedFile = file;
try {
const decryptFile = new DecryptFile();
const {isEncrypted, requiresPassword} = await decryptFile.checkFileEncrypted(file);
const { isEncrypted, requiresPassword } = await decryptFile.checkFileEncrypted(file);
if (file.type === 'application/pdf' && isEncrypted) {
decryptedFile = await decryptFile.decryptFile(file, requiresPassword);
if (!decryptedFile) throw new Error('File decryption failed.');
}
decryptedFile.uniqueId = UUID.uuidv4();
return decryptedFile;
} catch (error) {
console.error(`Error decrypting file: ${file.name}`, error);
if (!file.uniqueId) file.uniqueId = UUID.uuidv4();
@@ -133,13 +178,15 @@ function setupFileInput(chooser) {
}
})
);
inputContainer.querySelector('#fileInputText').innerHTML = originalText;
if (!isDragAndDrop) {
let dataTransfer = toDataTransfer(allFiles);
element.files = dataTransfer.files;
}
handleFileInputChange(this);
this.dispatchEvent(new CustomEvent('file-input-change', {bubbles: true, detail: {elementId, allFiles}}));
this.dispatchEvent(new CustomEvent('file-input-change', { bubbles: true, detail: { elementId, allFiles } }));
});
function toDataTransfer(files) {
@@ -147,17 +194,79 @@ function setupFileInput(chooser) {
files.forEach((file) => dataTransfer.items.add(file));
return dataTransfer;
}
async function extractZipFiles(zipFile, acceptedFileType) {
const jszip = new JSZip();
var counter = 0;
// do an overall count, then proceed to make the pdf files
await jszip.loadAsync(zipFile)
.then(function (zip) {
zip.forEach(function (relativePath, zipEntry) {
counter+=1;
})
}
)
if (counter >= 1000) {
throw Error("Maximum file reached");
}
return jszip.loadAsync(zipFile)
.then(function (zip) {
var extractionPromises = [];
zip.forEach(function (relativePath, zipEntry) {
const promise = zipEntry.async('blob').then(function (content) {
// Assuming that folders have size zero
if (content.size > 0) {
const extension = zipEntry.name.split('.').pop().toLowerCase();
const mimeType = mimeTypes[extension];
// Check for file extension
if (mimeType && (mimeType.startsWith(acceptedFileType.split('/')[0]) || acceptedFileType === mimeType)) {
var file = new File([content], zipEntry.name, { type: mimeType });
file.uniqueId = UUID.uuidv4();
allFiles.push(file);
} else {
console.log(`File ${zipEntry.name} skipped. MIME type (${mimeType}) does not match accepted type (${acceptedFileType})`);
}
}
});
extractionPromises.push(promise);
});
return Promise.all(extractionPromises);
})
.catch(function (err) {
console.error("Error extracting ZIP file:", err);
throw err;
});
}
function handleFileInputChange(inputElement) {
const files = allFiles;
showOrHideSelectedFilesContainer(files);
const filesInfo = files.map((f) => ({
name: f.name,
size: f.size,
uniqueId: f.uniqueId,
type: f.type,
url: URL.createObjectURL(f),
}));
const filesInfo = files.map((f) => {
const url = URL.createObjectURL(f);
return {
name: f.name,
size: f.size,
uniqueId: f.uniqueId,
type: f.type,
url: url,
};
});
const selectedFilesContainer = $(inputContainer).siblings('.selected-files');
selectedFilesContainer.empty();
@@ -171,6 +280,8 @@ function setupFileInput(chooser) {
let fileIconContainer = document.createElement('div');
const isDragAndDropEnabled =
window.location.pathname.includes('add-image') || window.location.pathname.includes('sign');
// add image thumbnail to it
if (info.type.startsWith('image/')) {
let imgPreview = document.createElement('img');
imgPreview.src = info.url;
@@ -283,7 +394,7 @@ function setupFileInput(chooser) {
showOrHideSelectedFilesContainer(allFiles);
inputElement.dispatchEvent(new CustomEvent('file-input-change', {bubbles: true}));
inputElement.dispatchEvent(new CustomEvent('file-input-change', { bubbles: true }));
}
function removeFileById(fileId, inputElement) {