diff --git a/backend/app/api/office.py b/backend/app/api/office.py index 0e63b22..bb45906 100644 --- a/backend/app/api/office.py +++ b/backend/app/api/office.py @@ -1,4 +1,7 @@ import io +import os +import hashlib +from datetime import datetime, timezone from pathlib import Path from flask import request, jsonify, current_app, send_file @@ -168,3 +171,146 @@ def _convert_pptx(filepath): 'html': '\n'.join(content_parts) if content_parts else '

(Leere Folie)

', }) return slides + + +# ========== Save (write back edited documents) ========== + +@api_bp.route('/files//save', methods=['POST']) +@token_required +def save_file(file_id): + """Save edited content back to the original file format.""" + user = request.current_user + f, err = _get_file_or_403(file_id, user, 'write') + if err: + return err + + if f.is_folder: + return jsonify({'error': 'Ordner koennen nicht gespeichert werden'}), 400 + + data = request.get_json() + save_type = data.get('type', '') + filepath = Path(current_app.config['UPLOAD_PATH']) / str(f.owner_id) / f.storage_path + + try: + if save_type == 'html' and f.name.endswith('.docx'): + _save_html_to_docx(filepath, data.get('content', '')) + elif save_type == 'spreadsheet' and (f.name.endswith('.xlsx') or f.name.endswith('.xls')): + _save_sheets_to_xlsx(filepath, data.get('sheets', [])) + elif save_type == 'text': + filepath.write_text(data.get('content', ''), encoding='utf-8') + else: + return jsonify({'error': f'Speichern fuer diesen Typ nicht unterstuetzt'}), 400 + + # Update file metadata + f.size = os.path.getsize(str(filepath)) + h = hashlib.sha256() + with open(str(filepath), 'rb') as fh: + for chunk in iter(lambda: fh.read(8192), b''): + h.update(chunk) + f.checksum = h.hexdigest() + f.updated_at = datetime.now(timezone.utc) + db.session.commit() + + return jsonify({'message': 'Gespeichert', 'size': f.size}), 200 + except Exception as e: + return jsonify({'error': f'Speichern fehlgeschlagen: {str(e)}'}), 500 + + +def _save_html_to_docx(filepath, html_content): + """Convert HTML content back to DOCX.""" + from docx import Document + from docx.shared import Pt + import re + + doc = Document() + + # Simple HTML to DOCX conversion + # Strip tags and convert basic elements + html = html_content.replace('\r\n', '\n').replace('\r', '\n') + + # Process block elements + blocks = re.split(r'<(?:p|h[1-3]|br\s*/?)(?:\s[^>]*)?>|', html) + + # Find tag types + tags = re.findall(r'<(/?(?:p|h[1-3]|br\s*/?)(?:\s[^>]*)?)>', html) + + current_tag = 'p' + for i, block in enumerate(blocks): + text = re.sub(r'<[^>]+>', '', block).strip() + if not text: + if i < len(tags): + tag = tags[i] if i < len(tags) else '' + if tag.startswith('h1'): + current_tag = 'h1' + elif tag.startswith('h2'): + current_tag = 'h2' + elif tag.startswith('h3'): + current_tag = 'h3' + else: + current_tag = 'p' + continue + + if current_tag == 'h1': + doc.add_heading(text, level=1) + elif current_tag == 'h2': + doc.add_heading(text, level=2) + elif current_tag == 'h3': + doc.add_heading(text, level=3) + else: + # Check for bold/italic in remaining inline tags + para = doc.add_paragraph() + # Simple inline parsing + parts = re.split(r'()', block) + bold = False + italic = False + underline = False + for part in parts: + if part in ('', ''): + bold = True + elif part in ('', ''): + bold = False + elif part in ('', ''): + italic = True + elif part in ('', ''): + italic = False + elif part in ('',): + underline = True + elif part in ('',): + underline = False + else: + clean = re.sub(r'<[^>]+>', '', part) + if clean: + run = para.add_run(clean) + run.bold = bold + run.italic = italic + run.underline = underline + + current_tag = 'p' + + doc.save(str(filepath)) + + +def _save_sheets_to_xlsx(filepath, sheets_data): + """Save spreadsheet data back to XLSX.""" + from openpyxl import Workbook + + wb = Workbook() + # Remove default sheet + wb.remove(wb.active) + + for sheet_data in sheets_data: + ws = wb.create_sheet(title=sheet_data.get('name', 'Sheet')) + for ri, row in enumerate(sheet_data.get('rows', []), 1): + for ci, cell_value in enumerate(row, 1): + val = cell_value + # Try to convert to number + try: + if '.' in str(val): + val = float(val) + else: + val = int(val) + except (ValueError, TypeError): + pass + ws.cell(row=ri, column=ci, value=val if val != '' else None) + + wb.save(str(filepath)) diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 8d7945b..04fc5c4 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -28,6 +28,11 @@ const routes = [ name: 'Files', component: () => import('../views/FilesView.vue'), }, + { + path: 'preview/:fileId', + name: 'Preview', + component: () => import('../views/PreviewView.vue'), + }, { path: 'trash', name: 'Trash', diff --git a/frontend/src/views/FilesView.vue b/frontend/src/views/FilesView.vue index 410ddca..4ff5b78 100644 --- a/frontend/src/views/FilesView.vue +++ b/frontend/src/views/FilesView.vue @@ -74,9 +74,16 @@ - + + + + +