Compare commits
1 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
042d00292b |
|
|
@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "forgejo-blog-manager"
|
name = "forgejo-blog-manager"
|
||||||
version = "0.1.4"
|
version = "0.1.5"
|
||||||
description = "Blog-Manager für Uberspace 8 mit Marvin API und Pelican sowie DOCX-Konvertierung"
|
description = "Blog-Manager für Uberspace 8 mit Marvin API, Pelican und DOCX-Konvertierung"
|
||||||
authors = [{name = "Ihre Autorin", email = "autorin@example.com"}]
|
authors = [{name = "Ihre Autorin", email = "autorin@example.com"}]
|
||||||
license = {text = "MIT"}
|
license = {text = "MIT"}
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
@ -30,4 +30,7 @@ package-dir = {"" = "src"}
|
||||||
packages = ["dlw"]
|
packages = ["dlw"]
|
||||||
|
|
||||||
[tool.setuptools.package-data]
|
[tool.setuptools.package-data]
|
||||||
"dlw" = ["ssg_config/*", "ssg_config/templates/*"]
|
"dlw" = [
|
||||||
|
"ssg_config/*",
|
||||||
|
"templates/**/*.j2"
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
"""
|
||||||
|
DLW Frontend - Web-Interface für die DOCX-zu-PDF Konvertierung.
|
||||||
|
|
||||||
|
Dieses Modul stellt eine Flask-App bereit, die es Autorinnen ermöglicht,
|
||||||
|
Word-Dokumente hochzuladen und direkt als PDF im Browser zurückzuerhalten.
|
||||||
|
Genutzt werden dafür Mammoth (Struktur) und WeasyPrint (Layout).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask import Flask, render_template, request, send_file
|
||||||
|
from dlw.convert import convert_docx_to_pdf_stream
|
||||||
|
import os
|
||||||
|
|
||||||
|
# --- Konfiguration der Verzeichnisstruktur ---
|
||||||
|
# Wir ermitteln den absoluten Pfad zum zentralen Template-Ordner,
|
||||||
|
# damit Flask die Jinja2-Dateien auch dann findet, wenn die App
|
||||||
|
# aus einem anderen Verzeichnis gestartet wird.
|
||||||
|
base_dir = os.path.dirname(__file__)
|
||||||
|
template_dir = os.path.abspath(os.path.join(base_dir, 'templates'))
|
||||||
|
|
||||||
|
app = Flask(__name__, template_folder=template_dir)
|
||||||
|
|
||||||
|
# Begrenzung der Upload-Größe auf 16 Megabyte, um den Server zu schützen.
|
||||||
|
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
|
||||||
|
|
||||||
|
@app.route('/', methods=['GET', 'POST'])
|
||||||
|
def index():
|
||||||
|
"""
|
||||||
|
Haupt-Route für den Konverter.
|
||||||
|
|
||||||
|
GET: Zeigt das Upload-Formular an.
|
||||||
|
POST: Verarbeitet das hochgeladene DOCX, konvertiert es in ein PDF-Stream
|
||||||
|
und sendet dieses als Download an den Client zurück.
|
||||||
|
"""
|
||||||
|
if request.method == 'POST':
|
||||||
|
# 1. Validierung: Prüfen, ob überhaupt ein Datei-Teil im Request ist
|
||||||
|
if 'file' not in request.files:
|
||||||
|
return "Fehler: Kein Datei-Feld im Formular gefunden.", 400
|
||||||
|
|
||||||
|
file = request.files['file']
|
||||||
|
|
||||||
|
# 2. Validierung: Prüfen, ob eine Datei ausgewählt wurde
|
||||||
|
if file.filename == '':
|
||||||
|
return "Fehler: Es wurde keine Datei zum Hochladen ausgewählt.", 400
|
||||||
|
|
||||||
|
# 3. Verarbeitung: Nur .docx Dateien zulassen
|
||||||
|
if file and file.filename.endswith('.docx'):
|
||||||
|
try:
|
||||||
|
# Wir lesen die Datei direkt in den Arbeitsspeicher (Bytes)
|
||||||
|
docx_bytes = file.read()
|
||||||
|
|
||||||
|
# Konvertierung via Mammoth & WeasyPrint (gibt BytesIO Stream zurück)
|
||||||
|
pdf_stream = convert_docx_to_pdf_stream(docx_bytes)
|
||||||
|
|
||||||
|
# Dynamische Generierung des Dateinamens (aus .docx wird .pdf)
|
||||||
|
download_name = file.filename.replace('.docx', '.pdf')
|
||||||
|
|
||||||
|
# PDF-Stream als Download an den Browser senden
|
||||||
|
return send_file(
|
||||||
|
pdf_stream,
|
||||||
|
mimetype='application/pdf',
|
||||||
|
as_attachment=True,
|
||||||
|
download_name=download_name
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
# Grobe Fehlerbehandlung für den MVP-Status
|
||||||
|
return f"Interner Fehler bei der Konvertierung: {str(e)}", 500
|
||||||
|
|
||||||
|
# Falls GET-Request oder fehlerhafter Dateityp: Zeige das Formular
|
||||||
|
# Der Pfad 'app/converter.html.j2' bezieht sich auf den template_folder
|
||||||
|
return render_template('app/converter.html.j2')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Startet den lokalen Entwicklungs-Server.
|
||||||
|
# 'debug=True' sorgt für automatisches Neuladen bei Code-Änderungen.
|
||||||
|
app.run(debug=True, port=5000)
|
||||||
|
|
@ -1,3 +1,14 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
# --- Dynamische Pfadberechnung ---
|
||||||
|
# Ermittelt den absoluten Pfad zum Verzeichnis dieser Datei (src/dlw/ssg_config)
|
||||||
|
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
# Setzt den Theme-Pfad auf den neuen zentralen Ort (src/dlw/templates/ssg)
|
||||||
|
# Wir gehen eine Ebene hoch (..) und dann in den templates/ssg Ordner
|
||||||
|
THEME = os.path.join(CURRENT_DIR, '..', 'templates', 'ssg')
|
||||||
|
|
||||||
|
# --- Allgemeine Website-Einstellungen ---
|
||||||
AUTHOR = 'Ihre Autorin'
|
AUTHOR = 'Ihre Autorin'
|
||||||
SITENAME = 'Forgejo Blog'
|
SITENAME = 'Forgejo Blog'
|
||||||
SITEURL = ''
|
SITEURL = ''
|
||||||
|
|
@ -5,15 +16,16 @@ PATH = 'content'
|
||||||
TIMEZONE = 'Europe/Berlin'
|
TIMEZONE = 'Europe/Berlin'
|
||||||
DEFAULT_LANG = 'de'
|
DEFAULT_LANG = 'de'
|
||||||
|
|
||||||
|
# --- URL-Struktur ---
|
||||||
ARTICLE_URL = 'blog/{slug}/'
|
ARTICLE_URL = 'blog/{slug}/'
|
||||||
ARTICLE_SAVE_AS = 'blog/{slug}/index.html'
|
ARTICLE_SAVE_AS = 'blog/{slug}/index.html'
|
||||||
STATIC_PATHS = ['downloads', 'images']
|
STATIC_PATHS = ['downloads', 'images']
|
||||||
THEME = 'ssg_config/templates'
|
|
||||||
|
|
||||||
|
# --- Markdown-Konfiguration ---
|
||||||
MARKDOWN = {
|
MARKDOWN = {
|
||||||
'extension_configs': {
|
'extension_configs': {
|
||||||
'markdown.extensions.extra': {},
|
'markdown.extensions.extra': {},
|
||||||
'markdown.extensions.meta': {},
|
'markdown.extensions.meta': {},
|
||||||
},
|
},
|
||||||
'output_format': 'html5',
|
'output_format': 'html5',
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>DLW - DOCX zu PDF</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, system-ui, sans-serif;
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 100px auto;
|
||||||
|
background: #f4f7f6;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
background: white;
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 10px 25px rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
h1 { font-size: 1.25rem; margin-bottom: 20px; text-align: center; }
|
||||||
|
.upload-area {
|
||||||
|
border: 2px dashed #cbd5e0;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
background: #4a90e2;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
button:hover { background: #357abd; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Word-Konverter für Autorinnen</h1>
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
<div class="upload-area">
|
||||||
|
<input type="file" name="file" accept=".docx" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit">PDF generieren</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in New Issue