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:
270
static/js/images-to-pdf.js
Normal file
270
static/js/images-to-pdf.js
Normal 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
190
static/js/main.js
Normal 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
342
static/js/pdf-tools.js
Normal 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;
|
||||
Reference in New Issue
Block a user