fix: Word-Upload-Feld wiederhergestellt + beide Formate parallel
- Excel (.xlsx) für Fakten/Wertung/Pro/Kontra/Tags - Word (.docx) für Artikeltext (Spielerklärung) - Beide optional — Text aus Word wird zwischen Infobox und Fazit eingefügt - Formular zeigt beide Upload-Zonen nebeneinander
This commit is contained in:
+61
-23
@@ -1903,11 +1903,21 @@ textarea{{min-height:100px;resize:vertical}}
|
|||||||
<h1>Review Builder</h1>
|
<h1>Review Builder</h1>
|
||||||
<p style="color:var(--muted)">Word-Datei + Bilder hochladen. Inhalte werden automatisch zugeordnet.</p>
|
<p style="color:var(--muted)">Word-Datei + Bilder hochladen. Inhalte werden automatisch zugeordnet.</p>
|
||||||
<form id="reviewForm" enctype="multipart/form-data">
|
<form id="reviewForm" enctype="multipart/form-data">
|
||||||
<div class="card"><h2>1. Excel-Datei hochladen</h2>
|
<div class="card"><h2>1. Dateien hochladen</h2>
|
||||||
<div class="drop-zone" id="excelDrop">
|
<div class="row-2">
|
||||||
<div class="drop-zone-label"><strong>Bewertungs-Excel hier ablegen</strong> oder klicken (.xlsx)</div>
|
<div>
|
||||||
|
<div class="drop-zone" id="excelDrop" style="padding:20px">
|
||||||
|
<div class="drop-zone-label"><strong>Excel mit Fakten</strong><br>(.xlsx)</div>
|
||||||
<input type="file" id="excelInput" accept=".xlsx" style="display:none">
|
<input type="file" id="excelInput" accept=".xlsx" style="display:none">
|
||||||
</div><ul class="file-list" id="excelList"></ul></div>
|
</div><ul class="file-list" id="excelList"></ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="drop-zone" id="wordDrop" style="padding:20px">
|
||||||
|
<div class="drop-zone-label"><strong>Word mit Artikeltext</strong><br>(.docx)</div>
|
||||||
|
<input type="file" id="wordInput" accept=".docx" style="display:none">
|
||||||
|
</div><ul class="file-list" id="wordList"></ul>
|
||||||
|
</div>
|
||||||
|
</div></div>
|
||||||
<div class="card"><h2>2. Bilder hochladen</h2>
|
<div class="card"><h2>2. Bilder hochladen</h2>
|
||||||
<div class="drop-zone" id="imageDrop">
|
<div class="drop-zone" id="imageDrop">
|
||||||
<div class="drop-zone-label"><strong>Bilder hier ablegen</strong> oder klicken</div>
|
<div class="drop-zone-label"><strong>Bilder hier ablegen</strong> oder klicken</div>
|
||||||
@@ -1944,8 +1954,9 @@ textarea{{min-height:100px;resize:vertical}}
|
|||||||
<div class="toast" id="toast"></div>
|
<div class="toast" id="toast"></div>
|
||||||
<script>
|
<script>
|
||||||
const excelDrop=document.getElementById('excelDrop'),excelInput=document.getElementById('excelInput'),excelList=document.getElementById('excelList');
|
const excelDrop=document.getElementById('excelDrop'),excelInput=document.getElementById('excelInput'),excelList=document.getElementById('excelList');
|
||||||
|
const wordDrop=document.getElementById('wordDrop'),wordInput=document.getElementById('wordInput'),wordList=document.getElementById('wordList');
|
||||||
const imageDrop=document.getElementById('imageDrop'),imageInput=document.getElementById('imageInput'),imageList=document.getElementById('imageList');
|
const imageDrop=document.getElementById('imageDrop'),imageInput=document.getElementById('imageInput'),imageList=document.getElementById('imageList');
|
||||||
let excelFile=null,imageFiles=[];
|
let excelFile=null,wordFile=null,imageFiles=[];
|
||||||
function st(msg,type){{var t=document.getElementById('toast');t.textContent=msg;t.className='toast '+type+' show';clearTimeout(t._t);t._t=setTimeout(function(){{t.classList.remove('show')}},3000)}}
|
function st(msg,type){{var t=document.getElementById('toast');t.textContent=msg;t.className='toast '+type+' show';clearTimeout(t._t);t._t=setTimeout(function(){{t.classList.remove('show')}},3000)}}
|
||||||
excelDrop.addEventListener('click',function(){{excelInput.click()}});
|
excelDrop.addEventListener('click',function(){{excelInput.click()}});
|
||||||
excelDrop.addEventListener('dragover',function(e){{e.preventDefault();excelDrop.classList.add('dragover')}});
|
excelDrop.addEventListener('dragover',function(e){{e.preventDefault();excelDrop.classList.add('dragover')}});
|
||||||
@@ -1953,13 +1964,19 @@ excelDrop.addEventListener('dragleave',function(){{excelDrop.classList.remove('d
|
|||||||
excelDrop.addEventListener('drop',function(e){{e.preventDefault();excelDrop.classList.remove('dragover');if(e.dataTransfer.files[0])aef(e.dataTransfer.files[0])}});
|
excelDrop.addEventListener('drop',function(e){{e.preventDefault();excelDrop.classList.remove('dragover');if(e.dataTransfer.files[0])aef(e.dataTransfer.files[0])}});
|
||||||
excelInput.addEventListener('change',function(){{if(excelInput.files[0])aef(excelInput.files[0])}});
|
excelInput.addEventListener('change',function(){{if(excelInput.files[0])aef(excelInput.files[0])}});
|
||||||
function aef(f){{excelFile=f;excelList.innerHTML='<li>'+f.name+' ('+(f.size/1024).toFixed(1)+' KB) <span class="remove" onclick="excelFile=null;excelList.innerHTML=\\'\\';">X</span></li>'}}
|
function aef(f){{excelFile=f;excelList.innerHTML='<li>'+f.name+' ('+(f.size/1024).toFixed(1)+' KB) <span class="remove" onclick="excelFile=null;excelList.innerHTML=\\'\\';">X</span></li>'}}
|
||||||
|
wordDrop.addEventListener('click',function(){{wordInput.click()}});
|
||||||
|
wordDrop.addEventListener('dragover',function(e){{e.preventDefault();wordDrop.classList.add('dragover')}});
|
||||||
|
wordDrop.addEventListener('dragleave',function(){{wordDrop.classList.remove('dragover')}});
|
||||||
|
wordDrop.addEventListener('drop',function(e){{e.preventDefault();wordDrop.classList.remove('dragover');if(e.dataTransfer.files[0])awf(e.dataTransfer.files[0])}});
|
||||||
|
wordInput.addEventListener('change',function(){{if(wordInput.files[0])awf(wordInput.files[0])}});
|
||||||
|
function awf(f){{wordFile=f;wordList.innerHTML='<li>'+f.name+' ('+(f.size/1024).toFixed(1)+' KB) <span class="remove" onclick="wordFile=null;wordList.innerHTML=\\'\\';">X</span></li>'}}
|
||||||
imageDrop.addEventListener('click',function(){{imageInput.click()}});
|
imageDrop.addEventListener('click',function(){{imageInput.click()}});
|
||||||
imageDrop.addEventListener('dragover',function(e){{e.preventDefault();imageDrop.classList.add('dragover')}});
|
imageDrop.addEventListener('dragover',function(e){{e.preventDefault();imageDrop.classList.add('dragover')}});
|
||||||
imageDrop.addEventListener('dragleave',function(){{imageDrop.classList.remove('dragover')}});
|
imageDrop.addEventListener('dragleave',function(){{imageDrop.classList.remove('dragover')}});
|
||||||
imageDrop.addEventListener('drop',function(e){{e.preventDefault();imageDrop.classList.remove('dragover');aif(e.dataTransfer.files)}});
|
imageDrop.addEventListener('drop',function(e){{e.preventDefault();imageDrop.classList.remove('dragover');aif(e.dataTransfer.files)}});
|
||||||
imageInput.addEventListener('change',function(){{aif(imageInput.files)}});
|
imageInput.addEventListener('change',function(){{aif(imageInput.files)}});
|
||||||
function aif(files){{for(var i=0;i<files.length;i++){{(function(f){{imageFiles.push(f);var li=document.createElement('li');li.innerHTML=f.name+' ('+(f.size/1024).toFixed(1)+' KB) <span class="remove">X</span>';li.querySelector('.remove').onclick=function(){{imageFiles=imageFiles.filter(function(x){{return x!==f}});li.remove()}};imageList.appendChild(li)}})(files[i])}}}}
|
function aif(files){{for(var i=0;i<files.length;i++){{(function(f){{imageFiles.push(f);var li=document.createElement('li');li.innerHTML=f.name+' ('+(f.size/1024).toFixed(1)+' KB) <span class="remove">X</span>';li.querySelector('.remove').onclick=function(){{imageFiles=imageFiles.filter(function(x){{return x!==f}});li.remove()}};imageList.appendChild(li)}})(files[i])}}}}
|
||||||
async function submitForm(publish){{var s=document.getElementById('spielname').value.trim();if(!excelFile){{st('Excel-Datei fehlt','err');return}}var fd=new FormData();fd.append('excel',excelFile);imageFiles.forEach(function(f){{fd.append('images',f)}});fd.append('spielname',s);fd.append('autor',document.getElementById('autor').value);fd.append('rubrik',document.getElementById('rubrik').value);fd.append('komplexitaet',document.getElementById('komplexitaet').value);fd.append('disclosure',document.getElementById('disclosure').value);fd.append('verlag',document.getElementById('verlag').value.trim());fd.append('fantasywelt_url',document.getElementById('fantasywelt_url').value.trim());fd.append('so_url',document.getElementById('so_url').value.trim());fd.append('amazon_url',document.getElementById('amazon_url').value.trim());fd.append('allgames4you_url',document.getElementById('allgames4you_url').value.trim());fd.append('publish',publish?'1':'0');st(publish?'Veroffentliche...':'Baue Vorschau...','info');try{{var resp=await fetch('/review-builder/build?key={access_token}',{{method:'POST',body:fd}});var data=await resp.json();if(!data.ok){{st(data.error||'Fehler','err');return}}var box=document.getElementById('resultBox');box.classList.add('visible');document.getElementById('resultTitle').textContent=data.title;document.getElementById('resultLink').innerHTML=data.published?'<a href="'+data.article_url+'" target="_blank">Artikel online: '+data.article_url+'</a>':'<a href="/'+data.filename+'?key={access_token}" target="_blank">Vorschau: '+data.filename+'</a>';st(data.published?'Veroffentlicht!':'Vorschau bereit','ok')}}catch(e){{st('Fehler: '+e.message,'err')}}}}
|
async function submitForm(publish){{var s=document.getElementById('spielname').value.trim();var fd=new FormData();if(excelFile)fd.append('excel',excelFile);if(wordFile)fd.append('word',wordFile);imageFiles.forEach(function(f){{fd.append('images',f)}});fd.append('spielname',s);fd.append('autor',document.getElementById('autor').value);fd.append('rubrik',document.getElementById('rubrik').value);fd.append('komplexitaet',document.getElementById('komplexitaet').value);fd.append('disclosure',document.getElementById('disclosure').value);fd.append('verlag',document.getElementById('verlag').value.trim());fd.append('fantasywelt_url',document.getElementById('fantasywelt_url').value.trim());fd.append('so_url',document.getElementById('so_url').value.trim());fd.append('amazon_url',document.getElementById('amazon_url').value.trim());fd.append('allgames4you_url',document.getElementById('allgames4you_url').value.trim());fd.append('publish',publish?'1':'0');st(publish?'Veroffentliche...':'Baue Vorschau...','info');try{{var resp=await fetch('/review-builder/build?key={access_token}',{{method:'POST',body:fd}});var data=await resp.json();if(!data.ok){{st(data.error||'Fehler','err');return}}var box=document.getElementById('resultBox');box.classList.add('visible');document.getElementById('resultTitle').textContent=data.title;document.getElementById('resultLink').innerHTML=data.published?'<a href="'+data.article_url+'" target="_blank">Artikel online: '+data.article_url+'</a>':'<a href="/'+data.filename+'?key={access_token}" target="_blank">Vorschau: '+data.filename+'</a>';st(data.published?'Veroffentlicht!':'Vorschau bereit','ok')}}catch(e){{st('Fehler: '+e.message,'err')}}}}
|
||||||
document.getElementById('previewBtn').addEventListener('click',function(){{submitForm(false)}});
|
document.getElementById('previewBtn').addEventListener('click',function(){{submitForm(false)}});
|
||||||
document.getElementById('publishBtn').addEventListener('click',function(){{submitForm(true)}});
|
document.getElementById('publishBtn').addEventListener('click',function(){{submitForm(true)}});
|
||||||
</script></body></html>"""
|
</script></body></html>"""
|
||||||
@@ -1995,24 +2012,36 @@ document.getElementById('publishBtn').addEventListener('click',function(){{submi
|
|||||||
boundary = boundary[1:-1]
|
boundary = boundary[1:-1]
|
||||||
parts = _parse_multipart(raw, boundary)
|
parts = _parse_multipart(raw, boundary)
|
||||||
excel_data = parts.get("excel")
|
excel_data = parts.get("excel")
|
||||||
|
word_data = parts.get("word")
|
||||||
image_datas = [v for k, v in parts.items() if k.startswith("images_")]
|
image_datas = [v for k, v in parts.items() if k.startswith("images_")]
|
||||||
meta = {k: v for k, v in parts.items() if k not in ("excel",) and not k.startswith("images_")}
|
meta = {k: v for k, v in parts.items() if k not in ("excel", "word") and not k.startswith("images_")}
|
||||||
if not excel_data:
|
|
||||||
self._json_reply(400, {"ok": False, "error": "Keine Excel-Datei"})
|
# Excel parsen (Fakten, Wertung, Pro/Kontra, Tags)
|
||||||
return
|
xl = {}
|
||||||
try:
|
if excel_data:
|
||||||
import openpyxl
|
try:
|
||||||
wb = openpyxl.load_workbook(io.BytesIO(excel_data), data_only=True)
|
import openpyxl
|
||||||
ws = wb["EINGABE"]
|
wb = openpyxl.load_workbook(io.BytesIO(excel_data), data_only=True)
|
||||||
xl = {}
|
ws = wb["EINGABE"]
|
||||||
for row in ws.iter_rows(min_row=1, values_only=True):
|
for row in ws.iter_rows(min_row=1, values_only=True):
|
||||||
key = str(row[0]).strip() if row[0] else ""
|
key = str(row[0]).strip() if row[0] else ""
|
||||||
val = str(row[1]).strip() if row[1] else ""
|
val = str(row[1]).strip() if row[1] else ""
|
||||||
if key and val:
|
if key and val:
|
||||||
xl[key] = val
|
xl[key] = val
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._json_reply(400, {"ok": False, "error": f"Excel-Fehler: {e}"})
|
self._json_reply(400, {"ok": False, "error": f"Excel-Fehler: {e}"})
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Word parsen (Artikeltext)
|
||||||
|
word_text = ""
|
||||||
|
if word_data:
|
||||||
|
try:
|
||||||
|
from docx import Document
|
||||||
|
doc = Document(io.BytesIO(word_data))
|
||||||
|
word_text = "\n".join(p.text for p in doc.paragraphs if p.text.strip())
|
||||||
|
except Exception as e:
|
||||||
|
self._json_reply(400, {"ok": False, "error": f"Word-Fehler: {e}"})
|
||||||
|
return
|
||||||
|
|
||||||
spielname = xl.get("Spieltitel", meta.get("spielname", "Unbekannt")).strip()
|
spielname = xl.get("Spieltitel", meta.get("spielname", "Unbekannt")).strip()
|
||||||
zusatz = xl.get("Zusatztitel", "").strip()
|
zusatz = xl.get("Zusatztitel", "").strip()
|
||||||
@@ -2148,6 +2177,14 @@ document.getElementById('publishBtn').addEventListener('click',function(){{submi
|
|||||||
tags_str = ", ".join(tags) if tags else f"{spielname}, Brettspiel, Review, Bewertung, {autor_name}"
|
tags_str = ", ".join(tags) if tags else f"{spielname}, Brettspiel, Review, Bewertung, {autor_name}"
|
||||||
title_full = f"{spielname} {zusatz}".strip()
|
title_full = f"{spielname} {zusatz}".strip()
|
||||||
|
|
||||||
|
# Artikeltext aus Word (zwischen Infobox und Fazit)
|
||||||
|
artikel_html = ""
|
||||||
|
if word_text:
|
||||||
|
if "<" not in word_text[:100]:
|
||||||
|
artikel_html = f"<p>{word_text.replace(chr(10)+chr(10), '</p><p>')}</p>"
|
||||||
|
else:
|
||||||
|
artikel_html = word_text
|
||||||
|
|
||||||
body = f"""<p>{spielname} — {komplex or 'Brettspiel'} von {autoren or verlag or 'unbekannt'}</p>
|
body = f"""<p>{spielname} — {komplex or 'Brettspiel'} von {autoren or verlag or 'unbekannt'}</p>
|
||||||
<hr id="system-readmore">
|
<hr id="system-readmore">
|
||||||
<p> </p>
|
<p> </p>
|
||||||
@@ -2155,6 +2192,7 @@ document.getElementById('publishBtn').addEventListener('click',function(){{submi
|
|||||||
{fakten_html}
|
{fakten_html}
|
||||||
{disc_html}
|
{disc_html}
|
||||||
{mech_html}
|
{mech_html}
|
||||||
|
{artikel_html}
|
||||||
{pro_html}
|
{pro_html}
|
||||||
{kontra_html}
|
{kontra_html}
|
||||||
<h2><img src="images/banners/redaktion/{autor_banner}" alt="Meine Meinung"></h2>
|
<h2><img src="images/banners/redaktion/{autor_banner}" alt="Meine Meinung"></h2>
|
||||||
|
|||||||
Reference in New Issue
Block a user