diff --git a/QUICKSTART.md b/QUICKSTART.md new file mode 100644 index 0000000..077d7d7 --- /dev/null +++ b/QUICKSTART.md @@ -0,0 +1,69 @@ +# Schnellstart-Anleitung für PDF Editor Web App + +## Sofort loslegen (Windows PowerShell) + +1. **Navigieren Sie zum Projektordner:** + ```powershell + cd "c:\Users\Simon.Speedy\Documents\dev projekte\Test\pdf web app" + ``` + +2. **Virtuelle Umgebung erstellen:** + ```powershell + python -m venv venv + .\venv\Scripts\Activate.ps1 + ``` + +3. **Abhängigkeiten installieren:** + ```powershell + pip install -r requirements.txt + ``` + +4. **Poppler für Windows installieren:** + - Downloaden Sie: https://github.com/oschwartz10612/poppler-windows/releases + - Extrahieren Sie nach C:\poppler + - Fügen Sie C:\poppler\Library\bin zu den Umgebungsvariablen hinzu + + **ODER per Scoop (einfacher):** + ```powershell + # Falls Scoop nicht installiert ist: + # Set-ExecutionPolicy RemoteSigned -Scope CurrentUser + # irm get.scoop.sh | iex + + scoop install poppler + ``` + +5. **App starten:** + ```powershell + python app.py + ``` + +6. **Browser öffnen:** + - Gehen Sie zu: http://127.0.0.1:5000 + +## Fertig! 🎉 + +Die App läuft jetzt lokal auf Ihrem Computer. Sie können: + +- ✅ Bilder zu PDF konvertieren +- ✅ PDFs zusammenführen +- ✅ PDFs zu Bildern konvertieren +- ✅ Drag & Drop verwenden +- ✅ Vollständig offline arbeiten + +## Bei Problemen: + +**Fehler bei pdf2image?** +```powershell +# Poppler-Pfad manuell setzen (falls automatisch nicht funktioniert) +$env:PATH += ";C:\poppler\Library\bin" +``` + +**Port bereits belegt?** +- Ändern Sie in app.py die Zeile: `app.run(debug=True, host='127.0.0.1', port=5001)` + +**Importfehler?** +```powershell +# Virtuelle Umgebung erneut aktivieren +.\venv\Scripts\Activate.ps1 +pip install --upgrade -r requirements.txt +``` \ No newline at end of file diff --git a/README_INSTALL.md b/README_INSTALL.md new file mode 100644 index 0000000..ef72ffb --- /dev/null +++ b/README_INSTALL.md @@ -0,0 +1,243 @@ +# PDF Editor Web App + +Eine vollständige PDF-Bearbeitungs-Web-Anwendung, die als lokaler Flask-Server läuft. Bietet Funktionen zum Konvertieren von Bildern zu PDF, Zusammenführen von PDFs, Konvertieren von PDFs zu Bildern und vieles mehr. + +## ✨ Features + +### 🖼️ Bilder zu PDF +- **Mehrere Bilder auswählen**: Unterstützt JPG, PNG, GIF, BMP, TIFF +- **Drag & Drop Interface**: Einfaches Hochladen durch Ziehen und Ablegen +- **Sortierbare Reihenfolge**: Bilder durch Ziehen neu anordnen +- **Automatische Größenanpassung**: Bilder werden optimal auf PDF-Seiten angepasst +- **Progress Tracking**: Echtzeit-Upload-Fortschritt + +### 🔧 PDF Tools +- **PDFs zusammenführen**: Mehrere PDFs zu einer Datei kombinieren +- **PDF zu Bildern**: Jede PDF-Seite als PNG/JPEG exportieren +- **Sortierbare PDF-Liste**: Reihenfolge beim Zusammenführen anpassen +- **ZIP-Export**: Alle extrahierten Bilder in einer ZIP-Datei + +### 🛡️ Sicherheit & Datenschutz +- **100% Lokal**: Alle Verarbeitung erfolgt auf Ihrem Computer +- **Keine Cloud-Uploads**: Dateien verlassen nie Ihr System +- **Automatische Bereinigung**: Temporäre Dateien werden nach 1 Stunde gelöscht +- **Sichere Dateinamen**: Automatische Bereinigung und Eindeutigkeit + +### 📱 Benutzerfreundlichkeit +- **Responsive Design**: Funktioniert auf Desktop, Tablet und Smartphone +- **Moderne UI**: Bootstrap 5 mit Custom CSS +- **Intuitive Bedienung**: Selbsterklärende Benutzeroberfläche +- **Echtzeit-Feedback**: Progress Bars und Notifications + +## 🚀 Installation + +### Voraussetzungen +- Python 3.8 oder höher +- pip (Python Package Manager) + +### Schritt-für-Schritt Installation + +1. **Repository klonen oder herunterladen** + ```bash + # Falls Sie Git verwenden + git clone + cd pdf-web-app + ``` + +2. **Virtuelle Umgebung erstellen (empfohlen)** + ```bash + python -m venv venv + + # Windows + venv\Scripts\activate + + # macOS/Linux + source venv/bin/activate + ``` + +3. **Abhängigkeiten installieren** + ```bash + pip install -r requirements.txt + ``` + +4. **Poppler installieren (für pdf2image)** + + **Windows:** + - Laden Sie Poppler von [hier](https://github.com/oschwartz10612/poppler-windows/releases) herunter + - Extrahieren Sie es nach `C:\poppler` + - Fügen Sie `C:\poppler\Library\bin` zu Ihren Umgebungsvariablen hinzu + + **macOS:** + ```bash + brew install poppler + ``` + + **Ubuntu/Debian:** + ```bash + sudo apt-get update + sudo apt-get install poppler-utils + ``` + +5. **Anwendung starten** + ```bash + python app.py + ``` + +6. **Browser öffnen** + - Öffnen Sie Ihren Browser und gehen Sie zu: `http://127.0.0.1:5000` + +## 📁 Projektstruktur + +``` +pdf-web-app/ +├── app.py # Haupt-Flask-Anwendung +├── requirements.txt # Python-Abhängigkeiten +├── README.md # Diese Datei +├── templates/ # HTML-Templates +│ ├── base.html # Basis-Template +│ ├── index.html # Startseite +│ ├── images_to_pdf.html # Bilder zu PDF Seite +│ └── pdf_tools.html # PDF Tools Seite +├── static/ # Statische Dateien +│ ├── css/ +│ │ └── style.css # Custom CSS +│ └── js/ +│ ├── main.js # Haupt-JavaScript +│ ├── images-to-pdf.js # Bilder zu PDF Funktionalität +│ └── pdf-tools.js # PDF Tools Funktionalität +├── uploads/ # Temporäre Upload-Dateien +└── output/ # Generierte Ausgabedateien +``` + +## 🔧 Konfiguration + +### Anpassbare Einstellungen in `app.py`: + +```python +# Maximale Dateigröße (Standard: 16MB) +MAX_FILE_SIZE = 16 * 1024 * 1024 + +# Unterstützte Bildformate +ALLOWED_IMAGE_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'bmp', 'tiff'} + +# Server-Konfiguration +app.run(debug=True, host='127.0.0.1', port=5000) +``` + +## 📖 Verwendung + +### Bilder zu PDF konvertieren + +1. Gehen Sie zur "Bilder zu PDF" Seite +2. Ziehen Sie Bilder in den Upload-Bereich oder klicken Sie zum Auswählen +3. Sortieren Sie die Bilder durch Ziehen in die gewünschte Reihenfolge +4. Klicken Sie auf "Zu PDF konvertieren" +5. Laden Sie die fertige PDF-Datei herunter + +### PDFs zusammenführen + +1. Gehen Sie zur "PDF Tools" Seite +2. Wählen Sie den "PDFs zusammenführen" Tab +3. Laden Sie mindestens 2 PDF-Dateien hoch +4. Sortieren Sie die Reihenfolge durch Ziehen +5. Klicken Sie auf "PDFs zusammenführen" +6. Laden Sie die zusammengeführte PDF herunter + +### PDF zu Bildern konvertieren + +1. Gehen Sie zur "PDF Tools" Seite +2. Wählen Sie den "PDF zu Bildern" Tab +3. Laden Sie eine PDF-Datei hoch +4. Wählen Sie die gewünschten Export-Einstellungen +5. Klicken Sie auf "Zu Bildern konvertieren" +6. Laden Sie die ZIP-Datei mit allen Bildern herunter + +## 🛠️ Entwicklung + +### Lokale Entwicklung + +```bash +# Debug-Modus aktivieren +export FLASK_DEBUG=1 # Linux/macOS +set FLASK_DEBUG=1 # Windows + +# Anwendung starten +python app.py +``` + +### Neue Features hinzufügen + +1. Backend-Logik in `app.py` hinzufügen +2. API-Endpunkte erstellen +3. Frontend-Funktionalität in entsprechende JS-Datei einfügen +4. Templates bei Bedarf erweitern + +## 🐛 Fehlerbehebung + +### Häufige Probleme + +**Problem**: `ImportError: No module named 'pdf2image'` +**Lösung**: Stellen Sie sicher, dass Poppler installiert ist (siehe Installation) + +**Problem**: `Permission denied` beim Dateizugriff +**Lösung**: Stellen Sie sicher, dass die Upload- und Output-Ordner beschreibbar sind + +**Problem**: Große Dateien werden nicht hochgeladen +**Lösung**: Erhöhen Sie `MAX_CONTENT_LENGTH` in der Flask-Konfiguration + +**Problem**: PDF-Konvertierung schlägt fehl +**Lösung**: Überprüfen Sie, ob alle Abhängigkeiten korrekt installiert sind + +### Debug-Informationen aktivieren + +```python +# In app.py +import logging +logging.basicConfig(level=logging.DEBUG) +``` + +## 📝 Lizenz + +Dieses Projekt steht unter der MIT-Lizenz. Sie können es frei verwenden, modifizieren und verteilen. + +## 🤝 Beitragen + +Beiträge sind willkommen! Bitte: + +1. Forken Sie das Repository +2. Erstellen Sie einen Feature-Branch +3. Committen Sie Ihre Änderungen +4. Pushen Sie zum Branch +5. Erstellen Sie einen Pull Request + +## 📞 Support + +Bei Fragen oder Problemen: + +1. Überprüfen Sie die Fehlerbehebung oben +2. Erstellen Sie ein Issue im Repository +3. Überprüfen Sie die Console-Logs im Browser (F12) + +## 🔮 Geplante Features + +- [ ] PDF-Passwort-Schutz +- [ ] PDF-Metadaten bearbeiten +- [ ] Wasserzeichen hinzufügen +- [ ] OCR-Texterkennung +- [ ] PDF-Komprimierung +- [ ] Batch-Verarbeitung +- [ ] Dark Mode +- [ ] Mehrsprachige Unterstützung + +## 📊 Technologie-Stack + +- **Backend**: Python Flask +- **Frontend**: HTML5, CSS3, JavaScript (ES6+) +- **UI Framework**: Bootstrap 5 +- **PDF Processing**: PyPDF2, reportlab, pdf2image +- **Image Processing**: Pillow (PIL) +- **Icons**: Font Awesome + +--- + +**Erstellt mit ❤️ für lokale PDF-Bearbeitung** \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..07d011b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,11 @@ +Flask==2.3.3 +Werkzeug==2.3.7 +Pillow==10.0.1 +PyPDF2==3.0.1 +reportlab==4.0.4 +pdf2image==1.16.3 +Jinja2==3.1.2 +MarkupSafe==2.1.3 +click==8.1.7 +itsdangerous==2.1.2 +blinker==1.6.3 \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 0000000..7e5f2ad --- /dev/null +++ b/static/css/style.css @@ -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; } \ No newline at end of file diff --git a/static/js/images-to-pdf.js b/static/js/images-to-pdf.js new file mode 100644 index 0000000..2739dba --- /dev/null +++ b/static/js/images-to-pdf.js @@ -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 = ` +
+ +
+
+
${file.original_name}
+
${formatFileSize(file.size)}
+
+
+ + + +
+ `; + + 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; \ No newline at end of file diff --git a/static/js/main.js b/static/js/main.js new file mode 100644 index 0000000..66a07bc --- /dev/null +++ b/static/js/main.js @@ -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} + + `; + + 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 = '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'); + } +}); \ No newline at end of file diff --git a/static/js/pdf-tools.js b/static/js/pdf-tools.js new file mode 100644 index 0000000..2bd41a2 --- /dev/null +++ b/static/js/pdf-tools.js @@ -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 = ` +
+
+ +
+
+
${file.original_name}
+
+ ${file.page_count} Seiten • ${formatFileSize(file.size)} +
+
+
+ +
+
+ `; + + 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; \ No newline at end of file diff --git a/templates/pdf_tools.html b/templates/pdf_tools.html new file mode 100644 index 0000000..754d658 --- /dev/null +++ b/templates/pdf_tools.html @@ -0,0 +1,292 @@ +{% extends "base.html" %} + +{% block title %}PDF Tools - PDF Editor{% endblock %} + +{% block content %} +
+
+ +
+
+ + +
+
+
+
+ +
+
+
+
+ +
+ +
+
+
+
+
+
+ PDFs zusammenführen +
+
+
+
+ +
PDF-Dateien hier hinziehen oder auswählen
+

Mindestens 2 PDF-Dateien erforderlich

+ + +
+ + + + +
+
+
+ +
+
+
+
+ Anleitung +
+
+
+
    +
  1. Wählen Sie mindestens 2 PDF-Dateien aus
  2. +
  3. Sortieren Sie die Reihenfolge durch Ziehen
  4. +
  5. Klicken Sie auf "PDFs zusammenführen"
  6. +
  7. Laden Sie die zusammengeführte PDF herunter
  8. +
+
+
+
+
+
+ + +
+
+
+
+
+
+ PDF zu Bildern konvertieren +
+
+
+
+ +
PDF-Datei hier hinziehen oder auswählen
+

Jede Seite wird als separates Bild exportiert

+ + +
+ + + + +
+
+
+ +
+
+
+
+ Export-Einstellungen +
+
+
+
+ + +
+ +
+ + +
+ +
+ + + Höhere DPI-Werte ergeben bessere Qualität, aber größere Dateien. + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ Seiten extrahieren +
+
+
+
+ + Diese Funktion wird in einer zukünftigen Version verfügbar sein. + Verwenden Sie vorerst die "PDF zu Bildern" Funktion, um einzelne Seiten zu extrahieren. +
+
+
+
+ +
+
+
+
+ Geplante Features +
+
+
+
    +
  • + + Seitenbereiche extrahieren +
  • +
  • + + PDF-Seiten neu ordnen +
  • +
  • + + Passwort-geschützte PDFs +
  • +
  • + + PDF-Metadaten bearbeiten +
  • +
+
+
+
+
+
+
+ + + + + + +{% endblock %} + +{% block scripts %} + +{% endblock %} \ No newline at end of file