new file: QUICKSTART.md

new file:   README_INSTALL.md
	new file:   requirements.txt
	new file:   static/css/style.css
	new file:   static/js/images-to-pdf.js
	new file:   static/js/main.js
	new file:   static/js/pdf-tools.js
	new file:   templates/pdf_tools.html
This commit is contained in:
SimolZimol
2025-10-12 22:13:29 +02:00
parent b6ed71346e
commit 97fd0762d7
8 changed files with 1831 additions and 0 deletions

414
static/css/style.css Normal file
View File

@@ -0,0 +1,414 @@
/* PDF Editor Web App - Custom Styles */
:root {
--primary-color: #007bff;
--secondary-color: #6c757d;
--success-color: #28a745;
--warning-color: #ffc107;
--danger-color: #dc3545;
--info-color: #17a2b8;
--light-color: #f8f9fa;
--dark-color: #343a40;
--border-radius: 0.5rem;
--box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--box-shadow-lg: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}
/* General Styles */
body {
background-color: #f5f7fa;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.container {
max-width: 1200px;
}
/* Navigation */
.navbar-brand {
font-weight: 600;
font-size: 1.5rem;
}
.navbar-nav .nav-link {
font-weight: 500;
transition: all 0.3s ease;
}
.navbar-nav .nav-link:hover {
transform: translateY(-1px);
}
/* Hero Section */
.jumbotron {
background: linear-gradient(135deg, var(--primary-color), #0056b3);
border-radius: var(--border-radius);
box-shadow: var(--box-shadow-lg);
}
/* Cards */
.card {
border: none;
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
transition: all 0.3s ease;
}
.card:hover {
transform: translateY(-2px);
box-shadow: var(--box-shadow-lg);
}
.feature-card {
height: 100%;
}
.feature-card .card-body {
padding: 2rem;
}
.feature-card .icon-wrapper {
display: inline-block;
padding: 1rem;
background-color: rgba(0, 123, 255, 0.1);
border-radius: 50%;
}
.step-icon {
display: inline-block;
padding: 1rem;
background-color: rgba(0, 0, 0, 0.05);
border-radius: 50%;
}
/* Upload Areas */
.upload-area {
border: 2px dashed #dee2e6;
border-radius: var(--border-radius);
background-color: #fafbfc;
transition: all 0.3s ease;
cursor: pointer;
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
}
.upload-area:hover {
border-color: var(--primary-color);
background-color: rgba(0, 123, 255, 0.05);
}
.upload-area.dragover {
border-color: var(--success-color);
background-color: rgba(40, 167, 69, 0.1);
transform: scale(1.02);
}
.upload-content {
text-align: center;
padding: 1rem;
}
/* File List */
.list-group-item {
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: var(--border-radius);
margin-bottom: 0.5rem;
transition: all 0.3s ease;
}
.list-group-item:hover {
background-color: #f8f9fa;
transform: translateX(5px);
}
.sortable-list .list-group-item {
cursor: move;
}
.sortable-list .list-group-item:hover {
background-color: rgba(0, 123, 255, 0.05);
}
.file-item {
display: flex;
align-items: center;
padding: 0.75rem;
}
.file-icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0, 123, 255, 0.1);
border-radius: var(--border-radius);
margin-right: 1rem;
}
.file-info {
flex: 1;
}
.file-name {
font-weight: 500;
margin-bottom: 0.25rem;
}
.file-details {
font-size: 0.875rem;
color: var(--secondary-color);
}
.file-actions {
display: flex;
gap: 0.5rem;
}
/* Progress Bars */
.progress {
height: 8px;
border-radius: 4px;
background-color: #e9ecef;
}
.progress-bar {
background: linear-gradient(90deg, var(--primary-color), #0056b3);
transition: width 0.3s ease;
}
/* Buttons */
.btn {
border-radius: var(--border-radius);
font-weight: 500;
transition: all 0.3s ease;
border: none;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.15);
}
.btn-primary {
background: linear-gradient(135deg, var(--primary-color), #0056b3);
}
.btn-success {
background: linear-gradient(135deg, var(--success-color), #1e7e34);
}
.btn-warning {
background: linear-gradient(135deg, var(--warning-color), #e0a800);
color: #212529;
}
.btn-danger {
background: linear-gradient(135deg, var(--danger-color), #bd2130);
}
.btn-outline-secondary {
border: 2px solid var(--secondary-color);
color: var(--secondary-color);
}
.btn-outline-secondary:hover {
background-color: var(--secondary-color);
color: white;
}
/* Alerts */
.alert {
border: none;
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
}
.alert-success {
background-color: rgba(40, 167, 69, 0.1);
color: #155724;
border-left: 4px solid var(--success-color);
}
.alert-danger {
background-color: rgba(220, 53, 69, 0.1);
color: #721c24;
border-left: 4px solid var(--danger-color);
}
.alert-info {
background-color: rgba(23, 162, 184, 0.1);
color: #0c5460;
border-left: 4px solid var(--info-color);
}
/* Tabs */
.nav-pills .nav-link {
border-radius: var(--border-radius);
margin: 0 0.25rem;
transition: all 0.3s ease;
}
.nav-pills .nav-link.active {
background: linear-gradient(135deg, var(--primary-color), #0056b3);
}
/* Page Headers */
.page-header {
border-bottom: 2px solid #e9ecef;
padding-bottom: 1rem;
}
.page-header h2 {
color: var(--dark-color);
font-weight: 600;
}
/* Forms */
.form-control, .form-select {
border-radius: var(--border-radius);
border: 2px solid #e9ecef;
transition: all 0.3s ease;
}
.form-control:focus, .form-select:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
.form-check-input:checked {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
/* Loading Spinner */
.spinner-border {
width: 3rem;
height: 3rem;
}
/* Modal */
.modal-content {
border: none;
border-radius: var(--border-radius);
box-shadow: var(--box-shadow-lg);
}
/* Footer */
footer {
background-color: white !important;
border-top: 1px solid #e9ecef;
}
/* Responsive Adjustments */
@media (max-width: 768px) {
.jumbotron {
padding: 2rem 1rem !important;
}
.jumbotron .display-4 {
font-size: 2rem;
}
.feature-card .card-body {
padding: 1.5rem;
}
.upload-area {
min-height: 150px;
}
.file-item {
flex-direction: column;
text-align: center;
}
.file-icon {
margin-right: 0;
margin-bottom: 0.5rem;
}
.file-actions {
margin-top: 0.5rem;
justify-content: center;
}
}
/* Dark mode support */
@media (prefers-color-scheme: dark) {
:root {
--light-color: #343a40;
--dark-color: #f8f9fa;
}
body {
background-color: #1a1d20;
color: #f8f9fa;
}
.card {
background-color: #2d3338;
color: #f8f9fa;
}
.upload-area {
background-color: #343a40;
border-color: #495057;
}
.list-group-item {
background-color: #2d3338;
border-color: #495057;
color: #f8f9fa;
}
}
/* Animation Classes */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slideIn {
from { opacity: 0; transform: translateX(-20px); }
to { opacity: 1; transform: translateX(0); }
}
.fade-in {
animation: fadeIn 0.5s ease-out;
}
.slide-in {
animation: slideIn 0.5s ease-out;
}
/* Utility Classes */
.border-dashed {
border-style: dashed !important;
}
.bg-gradient {
background: linear-gradient(135deg, var(--primary-color), #0056b3);
}
.text-gradient {
background: linear-gradient(135deg, var(--primary-color), #0056b3);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.shadow-hover {
transition: box-shadow 0.3s ease;
}
.shadow-hover:hover {
box-shadow: var(--box-shadow-lg);
}
/* File Type Icons */
.file-type-pdf { color: #dc3545; }
.file-type-image { color: #28a745; }
.file-type-zip { color: #ffc107; }

270
static/js/images-to-pdf.js Normal file
View File

@@ -0,0 +1,270 @@
// Images to PDF - JavaScript Functionality
let imagesToPdfFiles = [];
let imagesToPdfSortable = null;
// DOM elements
let uploadArea, fileInput, fileList, filesContainer, convertBtn, clearBtn;
let progressBar, progressText, uploadProgress, resultArea, errorArea;
let downloadLink;
// Initialize when page loads
document.addEventListener('DOMContentLoaded', function() {
// Get DOM elements
uploadArea = document.getElementById('upload-area');
fileInput = document.getElementById('file-input');
fileList = document.getElementById('file-list');
filesContainer = document.getElementById('files-container');
convertBtn = document.getElementById('convert-btn');
clearBtn = document.getElementById('clear-btn');
progressBar = document.getElementById('progress-bar');
progressText = document.getElementById('progress-text');
uploadProgress = document.getElementById('upload-progress');
resultArea = document.getElementById('result-area');
errorArea = document.getElementById('error-area');
downloadLink = document.getElementById('download-link');
// Setup event listeners
setupEventListeners();
// Setup drag and drop
setupDragAndDrop(uploadArea, handleFiles);
});
function setupEventListeners() {
// File input change
fileInput.addEventListener('change', function(e) {
handleFiles(Array.from(e.target.files));
});
// Convert button
convertBtn.addEventListener('click', convertToPdf);
// Clear button
clearBtn.addEventListener('click', clearFiles);
// Upload area click
uploadArea.addEventListener('click', function() {
fileInput.click();
});
}
function handleFiles(files) {
// Hide previous results/errors
hideResults();
if (!files || files.length === 0) {
showError('Keine Dateien ausgewählt.');
return;
}
// Filter valid image files
const validFiles = files.filter(file => {
const isValid = file.type.startsWith('image/');
if (!isValid) {
showNotification(`${file.name} ist keine gültige Bilddatei.`, 'warning');
}
return isValid;
});
if (validFiles.length === 0) {
showError('Keine gültigen Bilddateien gefunden.');
return;
}
// Show upload progress
showUploadProgress();
// Upload files
uploadImages(validFiles);
}
async function uploadImages(files) {
try {
const response = await uploadFiles(files, '/api/upload-images', updateProgress);
if (response.success) {
imagesToPdfFiles = response.files;
displayFiles();
showNotification(response.message, 'success');
} else {
throw new Error(response.error || 'Upload fehlgeschlagen');
}
} catch (error) {
showError(`Upload-Fehler: ${error.message}`);
} finally {
hideUploadProgress();
}
}
function updateProgress(percent) {
progressBar.style.width = percent + '%';
progressText.textContent = Math.round(percent) + '%';
}
function displayFiles() {
filesContainer.innerHTML = '';
imagesToPdfFiles.forEach((file, index) => {
const fileItem = createFileItem(file, index);
filesContainer.appendChild(fileItem);
});
// Show file list
fileList.style.display = 'block';
// Enable convert button
convertBtn.disabled = false;
// Setup sortable
setupSortableList();
}
function createFileItem(file, index) {
const div = document.createElement('div');
div.className = 'list-group-item file-item';
div.dataset.index = index;
div.innerHTML = `
<div class="file-icon">
<i class="${getFileIcon(file.original_name)}"></i>
</div>
<div class="file-info">
<div class="file-name">${file.original_name}</div>
<div class="file-details">${formatFileSize(file.size)}</div>
</div>
<div class="file-actions">
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="moveFileUp(${index})" ${index === 0 ? 'disabled' : ''}>
<i class="fas fa-arrow-up"></i>
</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="moveFileDown(${index})" ${index === imagesToPdfFiles.length - 1 ? 'disabled' : ''}>
<i class="fas fa-arrow-down"></i>
</button>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeFile(${index})">
<i class="fas fa-trash"></i>
</button>
</div>
`;
return div;
}
function setupSortableList() {
if (imagesToPdfSortable) {
imagesToPdfSortable.destroy();
}
imagesToPdfSortable = setupSortable(filesContainer, function(evt) {
// Update array order
const item = imagesToPdfFiles.splice(evt.oldIndex, 1)[0];
imagesToPdfFiles.splice(evt.newIndex, 0, item);
// Refresh display
displayFiles();
});
}
function moveFileUp(index) {
if (index > 0) {
[imagesToPdfFiles[index], imagesToPdfFiles[index - 1]] = [imagesToPdfFiles[index - 1], imagesToPdfFiles[index]];
displayFiles();
}
}
function moveFileDown(index) {
if (index < imagesToPdfFiles.length - 1) {
[imagesToPdfFiles[index], imagesToPdfFiles[index + 1]] = [imagesToPdfFiles[index + 1], imagesToPdfFiles[index]];
displayFiles();
}
}
function removeFile(index) {
imagesToPdfFiles.splice(index, 1);
if (imagesToPdfFiles.length === 0) {
clearFiles();
} else {
displayFiles();
}
showNotification('Datei entfernt.', 'info');
}
async function convertToPdf() {
if (imagesToPdfFiles.length === 0) {
showError('Keine Dateien zum Konvertieren vorhanden.');
return;
}
hideResults();
setLoadingState(convertBtn, true);
try {
const filenames = imagesToPdfFiles.map(file => file.filename);
const response = await makeRequest('/api/convert-images-to-pdf', {
method: 'POST',
body: JSON.stringify({ filenames })
});
if (response.success) {
showResult(response.filename, response.message);
} else {
throw new Error(response.error || 'Konvertierung fehlgeschlagen');
}
} catch (error) {
showError(`Konvertierungs-Fehler: ${error.message}`);
} finally {
setLoadingState(convertBtn, false);
}
}
function clearFiles() {
imagesToPdfFiles = [];
filesContainer.innerHTML = '';
fileList.style.display = 'none';
convertBtn.disabled = true;
fileInput.value = '';
hideResults();
if (imagesToPdfSortable) {
imagesToPdfSortable.destroy();
imagesToPdfSortable = null;
}
}
function showUploadProgress() {
uploadProgress.style.display = 'block';
progressBar.style.width = '0%';
progressText.textContent = '0%';
}
function hideUploadProgress() {
uploadProgress.style.display = 'none';
}
function showResult(filename, message) {
resultArea.style.display = 'block';
downloadLink.href = `/download/${filename}`;
downloadLink.querySelector('.me-2').nextSibling.textContent = 'PDF herunterladen';
// Update message if provided
if (message) {
resultArea.querySelector('p').textContent = message;
}
}
function showError(message) {
errorArea.style.display = 'block';
document.getElementById('error-message').textContent = message;
}
function hideResults() {
resultArea.style.display = 'none';
errorArea.style.display = 'none';
}
// Global functions for button actions
window.moveFileUp = moveFileUp;
window.moveFileDown = moveFileDown;
window.removeFile = removeFile;

190
static/js/main.js Normal file
View File

@@ -0,0 +1,190 @@
// PDF Editor - Main JavaScript Functions
// Global variables
let uploadedFiles = [];
let sortableInstance = null;
// Utility functions
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function getFileIcon(filename) {
const extension = filename.toLowerCase().split('.').pop();
const iconMap = {
'pdf': 'fas fa-file-pdf text-danger',
'jpg': 'fas fa-file-image text-success',
'jpeg': 'fas fa-file-image text-success',
'png': 'fas fa-file-image text-success',
'gif': 'fas fa-file-image text-success',
'bmp': 'fas fa-file-image text-success',
'tiff': 'fas fa-file-image text-success',
'zip': 'fas fa-file-archive text-warning'
};
return iconMap[extension] || 'fas fa-file text-muted';
}
// Show notification
function showNotification(message, type = 'info') {
// Create notification element
const notification = document.createElement('div');
notification.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
notification.style.cssText = 'top: 20px; right: 20px; z-index: 9999; max-width: 400px;';
notification.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(notification);
// Auto remove after 5 seconds
setTimeout(() => {
if (notification.parentNode) {
notification.remove();
}
}, 5000);
}
// Show/hide loading state
function setLoadingState(element, loading = true) {
if (loading) {
element.disabled = true;
const originalText = element.innerHTML;
element.dataset.originalText = originalText;
element.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Verarbeitung...';
} else {
element.disabled = false;
element.innerHTML = element.dataset.originalText || element.innerHTML;
}
}
// AJAX helper function
async function makeRequest(url, options = {}) {
try {
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options.headers
},
...options
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Request failed:', error);
throw error;
}
}
// File upload with progress
async function uploadFiles(files, endpoint, progressCallback) {
const formData = new FormData();
if (Array.isArray(files)) {
files.forEach(file => formData.append('files', file));
} else {
formData.append('file', files);
}
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
// Upload progress
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable && progressCallback) {
const percentComplete = (e.loaded / e.total) * 100;
progressCallback(percentComplete);
}
});
xhr.addEventListener('load', () => {
if (xhr.status === 200) {
try {
const response = JSON.parse(xhr.responseText);
resolve(response);
} catch (error) {
reject(new Error('Invalid JSON response'));
}
} else {
reject(new Error(`Upload failed with status: ${xhr.status}`));
}
});
xhr.addEventListener('error', () => {
reject(new Error('Upload failed'));
});
xhr.open('POST', endpoint);
xhr.send(formData);
});
}
// Drag and drop functionality
function setupDragAndDrop(element, callback) {
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
element.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
element.addEventListener(eventName, () => {
element.classList.add('dragover');
}, false);
});
['dragleave', 'drop'].forEach(eventName => {
element.addEventListener(eventName, () => {
element.classList.remove('dragover');
}, false);
});
element.addEventListener('drop', (e) => {
const files = Array.from(e.dataTransfer.files);
callback(files);
}, false);
}
// Sortable list functionality
function setupSortable(container, onUpdate) {
if (typeof Sortable !== 'undefined') {
return Sortable.create(container, {
animation: 150,
ghostClass: 'sortable-ghost',
chosenClass: 'sortable-chosen',
dragClass: 'sortable-drag',
onUpdate: onUpdate
});
}
return null;
}
// Initialize tooltips
function initializeTooltips() {
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
}
// Initialize on page load
document.addEventListener('DOMContentLoaded', function() {
initializeTooltips();
// Add fade-in animation to main content
const mainContent = document.querySelector('main');
if (mainContent) {
mainContent.classList.add('fade-in');
}
});

342
static/js/pdf-tools.js Normal file
View File

@@ -0,0 +1,342 @@
// 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';
div.dataset.index = index;
div.innerHTML = `
<div class="d-flex align-items-center">
<div class="file-icon me-3">
<i class="fas fa-file-pdf text-danger fa-2x"></i>
</div>
<div class="flex-grow-1">
<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>
<div class="file-actions">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeMergeFile(${index})">
<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');
}
async function mergePdfs() {
if (pdfToolsFiles.length < 2) {
showError('Mindestens 2 PDF-Dateien erforderlich.');
return;
}
hideAllResults();
processingModal.show();
try {
const filenames = pdfToolsFiles.map(file => file.filename);
const response = await makeRequest('/api/merge-pdfs', {
method: 'POST',
body: JSON.stringify({ filenames })
});
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)}`;
splitFileInfo.style.display = 'block';
}
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;