modified: app.py deleted: output/.gitkeep modified: static/css/style.css modified: static/js/pdf-tools.js modified: templates/pdf_tools.html deleted: uploads/.gitkeep
511 lines
17 KiB
JavaScript
511 lines
17 KiB
JavaScript
// PDF Tools - JavaScript Functionality
|
|
|
|
let pdfToolsFiles = [];
|
|
let mergeSortable = null;
|
|
let currentPdfFile = null;
|
|
|
|
// DOM elements
|
|
let mergeUploadArea, mergeFileInput, mergeFileList, mergeFilesContainer, mergePdfsBtn;
|
|
let splitUploadArea, splitFileInput, splitFileInfo, convertToImagesBtn;
|
|
let mergeResult, splitResult, mergeDownloadLink, splitDownloadLink;
|
|
let processingModal, errorArea, errorMessage;
|
|
|
|
// Initialize when page loads
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
initializeDOMElements();
|
|
setupEventListeners();
|
|
setupDragAndDrop();
|
|
});
|
|
|
|
function initializeDOMElements() {
|
|
// Merge elements
|
|
mergeUploadArea = document.getElementById('merge-upload-area');
|
|
mergeFileInput = document.getElementById('merge-file-input');
|
|
mergeFileList = document.getElementById('merge-file-list');
|
|
mergeFilesContainer = document.getElementById('merge-files-container');
|
|
mergePdfsBtn = document.getElementById('merge-pdfs-btn');
|
|
mergeResult = document.getElementById('merge-result');
|
|
mergeDownloadLink = document.getElementById('merge-download-link');
|
|
|
|
// Split elements
|
|
splitUploadArea = document.getElementById('split-upload-area');
|
|
splitFileInput = document.getElementById('split-file-input');
|
|
splitFileInfo = document.getElementById('split-file-info');
|
|
convertToImagesBtn = document.getElementById('convert-to-images-btn');
|
|
splitResult = document.getElementById('split-result');
|
|
splitDownloadLink = document.getElementById('split-download-link');
|
|
|
|
// Common elements
|
|
processingModal = new bootstrap.Modal(document.getElementById('processing-modal'));
|
|
errorArea = document.getElementById('pdf-tools-error');
|
|
errorMessage = document.getElementById('pdf-tools-error-message');
|
|
}
|
|
|
|
function setupEventListeners() {
|
|
// Merge PDF events
|
|
mergeFileInput.addEventListener('change', function(e) {
|
|
handleMergeFiles(Array.from(e.target.files));
|
|
});
|
|
|
|
mergeUploadArea.addEventListener('click', function() {
|
|
mergeFileInput.click();
|
|
});
|
|
|
|
mergePdfsBtn.addEventListener('click', mergePdfs);
|
|
|
|
// Split PDF events
|
|
splitFileInput.addEventListener('change', function(e) {
|
|
if (e.target.files.length > 0) {
|
|
handleSplitFile(e.target.files[0]);
|
|
}
|
|
});
|
|
|
|
splitUploadArea.addEventListener('click', function() {
|
|
splitFileInput.click();
|
|
});
|
|
|
|
convertToImagesBtn.addEventListener('click', convertPdfToImages);
|
|
|
|
// Tab change events
|
|
document.querySelectorAll('[data-bs-toggle="pill"]').forEach(tab => {
|
|
tab.addEventListener('shown.bs.tab', function(e) {
|
|
hideAllResults();
|
|
});
|
|
});
|
|
}
|
|
|
|
function setupDragAndDrop() {
|
|
// Merge area drag and drop
|
|
setupDragAndDrop(mergeUploadArea, handleMergeFiles);
|
|
|
|
// Split area drag and drop
|
|
setupDragAndDrop(splitUploadArea, function(files) {
|
|
if (files.length > 0) {
|
|
handleSplitFile(files[0]);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Merge PDF functions
|
|
async function handleMergeFiles(files) {
|
|
hideAllResults();
|
|
|
|
if (!files || files.length === 0) {
|
|
showError('Keine Dateien ausgewählt.');
|
|
return;
|
|
}
|
|
|
|
// Filter PDF files
|
|
const pdfFiles = files.filter(file => file.type === 'application/pdf');
|
|
|
|
if (pdfFiles.length === 0) {
|
|
showError('Keine PDF-Dateien gefunden.');
|
|
return;
|
|
}
|
|
|
|
if (pdfFiles.length < 2) {
|
|
showError('Mindestens 2 PDF-Dateien erforderlich.');
|
|
return;
|
|
}
|
|
|
|
// Upload files one by one
|
|
pdfToolsFiles = [];
|
|
|
|
for (const file of pdfFiles) {
|
|
try {
|
|
const response = await uploadSinglePdf(file);
|
|
if (response.success) {
|
|
pdfToolsFiles.push(response);
|
|
}
|
|
} catch (error) {
|
|
showError(`Fehler beim Upload von ${file.name}: ${error.message}`);
|
|
return;
|
|
}
|
|
}
|
|
|
|
displayMergeFiles();
|
|
showNotification(`${pdfToolsFiles.length} PDF-Dateien erfolgreich hochgeladen.`, 'success');
|
|
}
|
|
|
|
async function uploadSinglePdf(file) {
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
|
|
const response = await fetch('/api/upload-pdf', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
function displayMergeFiles() {
|
|
mergeFilesContainer.innerHTML = '';
|
|
|
|
pdfToolsFiles.forEach((file, index) => {
|
|
const fileItem = createMergeFileItem(file, index);
|
|
mergeFilesContainer.appendChild(fileItem);
|
|
});
|
|
|
|
mergeFileList.style.display = 'block';
|
|
mergePdfsBtn.disabled = pdfToolsFiles.length < 2;
|
|
|
|
// Setup sortable
|
|
setupMergeSortable();
|
|
}
|
|
|
|
function createMergeFileItem(file, index) {
|
|
const div = document.createElement('div');
|
|
div.className = 'list-group-item pdf-item-enhanced';
|
|
div.dataset.index = index;
|
|
|
|
// Initialize rotation if not set
|
|
if (file.rotation === undefined) {
|
|
file.rotation = 0;
|
|
}
|
|
|
|
// Create preview element
|
|
const previewElement = file.preview ?
|
|
`<img src="/uploads/${file.preview}" class="pdf-preview size-medium rotate-${file.rotation}"
|
|
alt="${file.original_name}" onclick="showPdfModal('${file.preview}', '${file.original_name}', ${index})">` :
|
|
`<div class="pdf-preview pdf-no-preview size-medium rotate-${file.rotation}">
|
|
<i class="fas fa-file-pdf"></i>
|
|
</div>`;
|
|
|
|
div.innerHTML = `
|
|
<div class="d-flex align-items-center">
|
|
<div class="pdf-preview-container me-3">
|
|
${previewElement}
|
|
</div>
|
|
<div class="pdf-info">
|
|
<div class="file-name fw-bold">${file.original_name}</div>
|
|
<div class="file-details text-muted">
|
|
${file.page_count} Seiten • ${formatFileSize(file.size)}
|
|
</div>
|
|
<div class="rotation-controls">
|
|
<button type="button" class="btn btn-sm btn-outline-danger rotation-btn"
|
|
onclick="rotatePdf(${index}, -90)" title="Links drehen">
|
|
<i class="fas fa-undo"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-sm btn-outline-danger rotation-btn"
|
|
onclick="rotatePdf(${index}, 90)" title="Rechts drehen">
|
|
<i class="fas fa-redo"></i>
|
|
</button>
|
|
<small class="text-muted ms-2">${file.rotation}°</small>
|
|
</div>
|
|
</div>
|
|
<div class="pdf-controls">
|
|
<div class="d-flex gap-1 mb-2">
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="movePdfUp(${index})"
|
|
${index === 0 ? 'disabled' : ''} title="Nach oben">
|
|
<i class="fas fa-arrow-up"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="movePdfDown(${index})"
|
|
${index === pdfToolsFiles.length - 1 ? 'disabled' : ''} title="Nach unten">
|
|
<i class="fas fa-arrow-down"></i>
|
|
</button>
|
|
</div>
|
|
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeMergeFile(${index})" title="Entfernen">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
return div;
|
|
}
|
|
|
|
function setupMergeSortable() {
|
|
if (mergeSortable) {
|
|
mergeSortable.destroy();
|
|
}
|
|
|
|
mergeSortable = setupSortable(mergeFilesContainer, function(evt) {
|
|
const item = pdfToolsFiles.splice(evt.oldIndex, 1)[0];
|
|
pdfToolsFiles.splice(evt.newIndex, 0, item);
|
|
displayMergeFiles();
|
|
});
|
|
}
|
|
|
|
function removeMergeFile(index) {
|
|
pdfToolsFiles.splice(index, 1);
|
|
|
|
if (pdfToolsFiles.length === 0) {
|
|
mergeFileList.style.display = 'none';
|
|
mergePdfsBtn.disabled = true;
|
|
} else {
|
|
displayMergeFiles();
|
|
}
|
|
|
|
showNotification('PDF-Datei entfernt.', 'info');
|
|
}
|
|
|
|
function rotatePdf(index, degrees) {
|
|
if (index >= 0 && index < pdfToolsFiles.length) {
|
|
pdfToolsFiles[index].rotation = (pdfToolsFiles[index].rotation + degrees) % 360;
|
|
if (pdfToolsFiles[index].rotation < 0) {
|
|
pdfToolsFiles[index].rotation += 360;
|
|
}
|
|
displayMergeFiles();
|
|
showNotification(`PDF um ${degrees}° gedreht.`, 'info');
|
|
}
|
|
}
|
|
|
|
function movePdfUp(index) {
|
|
if (index > 0) {
|
|
[pdfToolsFiles[index], pdfToolsFiles[index - 1]] = [pdfToolsFiles[index - 1], pdfToolsFiles[index]];
|
|
displayMergeFiles();
|
|
}
|
|
}
|
|
|
|
function movePdfDown(index) {
|
|
if (index < pdfToolsFiles.length - 1) {
|
|
[pdfToolsFiles[index], pdfToolsFiles[index + 1]] = [pdfToolsFiles[index + 1], pdfToolsFiles[index]];
|
|
displayMergeFiles();
|
|
}
|
|
}
|
|
|
|
function showPdfModal(previewFilename, originalName, index) {
|
|
// Create modal if it doesn't exist
|
|
let modal = document.getElementById('pdf-modal');
|
|
if (!modal) {
|
|
modal = document.createElement('div');
|
|
modal.className = 'modal fade image-modal';
|
|
modal.id = 'pdf-modal';
|
|
modal.setAttribute('tabindex', '-1');
|
|
modal.setAttribute('aria-hidden', 'true');
|
|
|
|
modal.innerHTML = `
|
|
<div class="modal-dialog modal-lg modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="pdf-modal-title">${originalName}</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<img id="modal-pdf-preview" src="/uploads/${previewFilename}"
|
|
class="img-fluid rotate-${pdfToolsFiles[index].rotation}" alt="${originalName}">
|
|
<p class="text-muted mt-2 mb-0">Vorschau der ersten Seite</p>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<div class="btn-group me-auto">
|
|
<button type="button" class="btn btn-outline-danger" onclick="rotatePdfInModal(${index}, -90)">
|
|
<i class="fas fa-undo me-1"></i>Links drehen
|
|
</button>
|
|
<button type="button" class="btn btn-outline-danger" onclick="rotatePdfInModal(${index}, 90)">
|
|
<i class="fas fa-redo me-1"></i>Rechts drehen
|
|
</button>
|
|
</div>
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(modal);
|
|
} else {
|
|
// Update existing modal
|
|
document.getElementById('pdf-modal-title').textContent = originalName;
|
|
const modalPreview = document.getElementById('modal-pdf-preview');
|
|
modalPreview.src = `/uploads/${previewFilename}`;
|
|
modalPreview.alt = originalName;
|
|
modalPreview.className = `img-fluid rotate-${pdfToolsFiles[index].rotation}`;
|
|
|
|
// Update rotation buttons
|
|
const rotateButtons = modal.querySelectorAll('.btn-outline-danger');
|
|
rotateButtons[0].onclick = () => rotatePdfInModal(index, -90);
|
|
rotateButtons[1].onclick = () => rotatePdfInModal(index, 90);
|
|
}
|
|
|
|
// Show modal
|
|
const bootstrapModal = new bootstrap.Modal(modal);
|
|
bootstrapModal.show();
|
|
}
|
|
|
|
function rotatePdfInModal(index, degrees) {
|
|
rotatePdf(index, degrees);
|
|
|
|
// Update modal preview
|
|
const modalPreview = document.getElementById('modal-pdf-preview');
|
|
modalPreview.className = `img-fluid rotate-${pdfToolsFiles[index].rotation}`;
|
|
}
|
|
|
|
function updatePdfPreviewSize() {
|
|
const previewSize = document.getElementById('pdf-preview-size')?.value || 'medium';
|
|
const previews = document.querySelectorAll('.pdf-preview');
|
|
|
|
previews.forEach(preview => {
|
|
// Remove existing size classes
|
|
preview.classList.remove('size-small', 'size-medium', 'size-large');
|
|
// Add new size class
|
|
preview.classList.add('size-' + previewSize);
|
|
});
|
|
}
|
|
|
|
async function mergePdfs() {
|
|
if (pdfToolsFiles.length < 2) {
|
|
showError('Mindestens 2 PDF-Dateien erforderlich.');
|
|
return;
|
|
}
|
|
|
|
hideAllResults();
|
|
processingModal.show();
|
|
|
|
try {
|
|
// Include rotation data for each file
|
|
const filesWithRotation = pdfToolsFiles.map(file => ({
|
|
filename: file.filename,
|
|
rotation: file.rotation || 0,
|
|
original_name: file.original_name
|
|
}));
|
|
|
|
const response = await makeRequest('/api/merge-pdfs', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ files: filesWithRotation })
|
|
});
|
|
|
|
if (response.success) {
|
|
showMergeResult(response.filename, response.message);
|
|
showNotification(response.message, 'success');
|
|
} else {
|
|
throw new Error(response.error || 'Zusammenführung fehlgeschlagen');
|
|
}
|
|
} catch (error) {
|
|
showError(`Fehler beim Zusammenführen: ${error.message}`);
|
|
} finally {
|
|
processingModal.hide();
|
|
}
|
|
}
|
|
|
|
function showMergeResult(filename, message) {
|
|
mergeResult.style.display = 'block';
|
|
mergeDownloadLink.href = `/download/${filename}`;
|
|
|
|
if (message) {
|
|
mergeResult.querySelector('p').textContent = message;
|
|
}
|
|
}
|
|
|
|
// Split PDF functions
|
|
async function handleSplitFile(file) {
|
|
hideAllResults();
|
|
|
|
if (!file) {
|
|
showError('Keine Datei ausgewählt.');
|
|
return;
|
|
}
|
|
|
|
if (file.type !== 'application/pdf') {
|
|
showError('Nur PDF-Dateien sind erlaubt.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await uploadSinglePdf(file);
|
|
|
|
if (response.success) {
|
|
currentPdfFile = response;
|
|
displaySplitFile();
|
|
showNotification('PDF-Datei erfolgreich hochgeladen.', 'success');
|
|
} else {
|
|
throw new Error(response.error || 'Upload fehlgeschlagen');
|
|
}
|
|
} catch (error) {
|
|
showError(`Upload-Fehler: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
function displaySplitFile() {
|
|
document.getElementById('split-filename').textContent = currentPdfFile.original_name;
|
|
document.getElementById('split-file-details').textContent =
|
|
`${currentPdfFile.page_count} Seiten • ${formatFileSize(currentPdfFile.size)}`;
|
|
|
|
// PDF-Vorschau anzeigen falls verfügbar
|
|
const previewContainer = document.getElementById('split-preview-container');
|
|
if (currentPdfFile.preview) {
|
|
previewContainer.innerHTML = `
|
|
<img src="/uploads/${currentPdfFile.preview}" class="pdf-preview size-medium"
|
|
alt="${currentPdfFile.original_name}" onclick="showSplitPdfModal()">
|
|
`;
|
|
} else {
|
|
previewContainer.innerHTML = `
|
|
<div class="pdf-preview pdf-no-preview size-medium">
|
|
<i class="fas fa-file-pdf"></i>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
splitFileInfo.style.display = 'block';
|
|
}
|
|
|
|
function showSplitPdfModal() {
|
|
if (currentPdfFile && currentPdfFile.preview) {
|
|
showPdfModal(currentPdfFile.preview, currentPdfFile.original_name, 0);
|
|
}
|
|
}
|
|
|
|
async function convertPdfToImages() {
|
|
if (!currentPdfFile) {
|
|
showError('Keine PDF-Datei ausgewählt.');
|
|
return;
|
|
}
|
|
|
|
hideAllResults();
|
|
processingModal.show();
|
|
|
|
try {
|
|
const response = await makeRequest('/api/pdf-to-images', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ filename: currentPdfFile.filename })
|
|
});
|
|
|
|
if (response.success) {
|
|
showSplitResult(response.filename, response.message);
|
|
showNotification(response.message, 'success');
|
|
} else {
|
|
throw new Error(response.error || 'Konvertierung fehlgeschlagen');
|
|
}
|
|
} catch (error) {
|
|
showError(`Konvertierungs-Fehler: ${error.message}`);
|
|
} finally {
|
|
processingModal.hide();
|
|
}
|
|
}
|
|
|
|
function showSplitResult(filename, message) {
|
|
splitResult.style.display = 'block';
|
|
splitDownloadLink.href = `/download/${filename}`;
|
|
|
|
if (message) {
|
|
splitResult.querySelector('p').textContent = message;
|
|
}
|
|
}
|
|
|
|
// Utility functions
|
|
function hideAllResults() {
|
|
if (mergeResult) mergeResult.style.display = 'none';
|
|
if (splitResult) splitResult.style.display = 'none';
|
|
if (errorArea) errorArea.style.display = 'none';
|
|
}
|
|
|
|
function showError(message) {
|
|
errorArea.style.display = 'block';
|
|
errorMessage.textContent = message;
|
|
|
|
// Scroll to error
|
|
errorArea.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
}
|
|
|
|
// Global functions
|
|
window.removeMergeFile = removeMergeFile;
|
|
window.rotatePdf = rotatePdf;
|
|
window.movePdfUp = movePdfUp;
|
|
window.movePdfDown = movePdfDown;
|
|
window.showPdfModal = showPdfModal;
|
|
window.rotatePdfInModal = rotatePdfInModal;
|
|
window.updatePdfPreviewSize = updatePdfPreviewSize;
|
|
window.showSplitPdfModal = showSplitPdfModal; |