feat(diarization-ui): add inline document editor
Adds an edit mode to the document view: click "Bearbeiten" to switch
from rendered Markdown to a monospace textarea, save via POST
/document/{id}/edit, or cancel to discard changes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
54
app.py
54
app.py
@@ -754,22 +754,55 @@ def view_document(doc_id: int):
|
|||||||
rendered = md.markdown(d["content_md"] or "", extensions=["fenced_code", "tables", "nl2br"])
|
rendered = md.markdown(d["content_md"] or "", extensions=["fenced_code", "tables", "nl2br"])
|
||||||
projects = get_projects()
|
projects = get_projects()
|
||||||
|
|
||||||
|
content_escaped = (d['content_md'] or '').replace('`', '`').replace('</script>', '<\\/script>')
|
||||||
body = f"""
|
body = f"""
|
||||||
<div class='d-flex justify-content-between align-items-start flex-wrap gap-2 mb-2'>
|
<div class='d-flex justify-content-between align-items-start flex-wrap gap-2 mb-2'>
|
||||||
<div>
|
<div>
|
||||||
<h2 class='h4 mb-1'>Dokument #{d['id']} – {d['title']}</h2>
|
<h2 class='h4 mb-1'>Dokument #{d['id']} – {d['title']}</h2>
|
||||||
<div class='text-secondary small'>Projekt: {d['project']} · Typ: {d['kind']} · {d['created_at']}</div>
|
<div class='text-secondary small'>Projekt: {d['project']} · Typ: {d['kind']} · {d['created_at']}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class='btn-group btn-group-sm'>
|
<div class='d-flex gap-2 flex-wrap'>
|
||||||
<a class='btn btn-outline-secondary' title='Download .md' href='/document/{doc_id}/download.md'>⬇️</a>
|
<div class='btn-group btn-group-sm' id='view-actions'>
|
||||||
<a class='btn btn-outline-secondary' title='Umbenennen' href='#' onclick='renameDoc();return false;'>✏️</a>
|
<a class='btn btn-outline-secondary' title='Download .md' href='/document/{doc_id}/download.md'><i class='bi bi-download'></i></a>
|
||||||
<a class='btn btn-outline-secondary' title='Verschieben' href='#' onclick='moveDoc();return false;'>📁</a>
|
<button class='btn btn-outline-primary' title='Bearbeiten' onclick='startEdit()'><i class='bi bi-pencil'></i> Bearbeiten</button>
|
||||||
<a class='btn btn-outline-danger' title='Löschen' href='#' onclick='deleteDoc();return false;'>🗑️</a>
|
<a class='btn btn-outline-secondary' title='Umbenennen' href='#' onclick='renameDoc();return false;'><i class='bi bi-fonts'></i></a>
|
||||||
|
<a class='btn btn-outline-secondary' title='Verschieben' href='#' onclick='moveDoc();return false;'><i class='bi bi-folder-symlink'></i></a>
|
||||||
|
<a class='btn btn-outline-danger' title='Löschen' href='#' onclick='deleteDoc();return false;'><i class='bi bi-trash'></i></a>
|
||||||
|
</div>
|
||||||
|
<div class='btn-group btn-group-sm d-none' id='edit-actions'>
|
||||||
|
<button class='btn btn-success' onclick='saveEdit()'><i class='bi bi-check-lg'></i> Speichern</button>
|
||||||
|
<button class='btn btn-outline-secondary' onclick='cancelEdit()'><i class='bi bi-x-lg'></i> Abbrechen</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class='card mdview'>{rendered}</div>
|
<div class='card mdview' id='doc-view'>{rendered}</div>
|
||||||
|
<div class='d-none' id='doc-edit'>
|
||||||
|
<textarea id='doc-textarea' class='form-control' style='min-height:60vh;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.9rem;resize:vertical'></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
const _originalContent = {json.dumps(d['content_md'] or '')};
|
||||||
|
function startEdit() {{
|
||||||
|
document.getElementById('doc-textarea').value = _originalContent;
|
||||||
|
document.getElementById('doc-view').classList.add('d-none');
|
||||||
|
document.getElementById('doc-edit').classList.remove('d-none');
|
||||||
|
document.getElementById('view-actions').classList.add('d-none');
|
||||||
|
document.getElementById('edit-actions').classList.remove('d-none');
|
||||||
|
document.getElementById('doc-textarea').focus();
|
||||||
|
}}
|
||||||
|
function cancelEdit() {{
|
||||||
|
document.getElementById('doc-view').classList.remove('d-none');
|
||||||
|
document.getElementById('doc-edit').classList.add('d-none');
|
||||||
|
document.getElementById('view-actions').classList.remove('d-none');
|
||||||
|
document.getElementById('edit-actions').classList.add('d-none');
|
||||||
|
}}
|
||||||
|
async function saveEdit() {{
|
||||||
|
const content = document.getElementById('doc-textarea').value;
|
||||||
|
const body = new URLSearchParams({{content_md: content}});
|
||||||
|
const r = await fetch('/document/{doc_id}/edit', {{method:'POST', headers:{{'Content-Type':'application/x-www-form-urlencoded'}}, body}});
|
||||||
|
if(!r.ok) {{ alert('Fehler: '+r.status); return; }}
|
||||||
|
location.reload();
|
||||||
|
}}
|
||||||
window.postForm = async function(url, data) {{
|
window.postForm = async function(url, data) {{
|
||||||
const body = new URLSearchParams(data);
|
const body = new URLSearchParams(data);
|
||||||
const r = await fetch(url, {{method:'POST', headers:{{'Content-Type':'application/x-www-form-urlencoded'}}, body}});
|
const r = await fetch(url, {{method:'POST', headers:{{'Content-Type':'application/x-www-form-urlencoded'}}, body}});
|
||||||
@@ -782,7 +815,7 @@ window.renameDoc = async function() {{
|
|||||||
window.postForm('/document/{doc_id}/rename', {{title:v}});
|
window.postForm('/document/{doc_id}/rename', {{title:v}});
|
||||||
}};
|
}};
|
||||||
window.moveDoc = async function() {{
|
window.moveDoc = async function() {{
|
||||||
const options = {json.dumps([{"value": p['id'], "label": p['name']} for p in projects], ensure_ascii=False)};
|
const options = {json.dumps([{{"value": p['id'], "label": p['name']}} for p in projects], ensure_ascii=False)};
|
||||||
const v = await window.uiSelect('In Projekt verschieben', options, 'Projekt wählen');
|
const v = await window.uiSelect('In Projekt verschieben', options, 'Projekt wählen');
|
||||||
if (v===null || v==='') return;
|
if (v===null || v==='') return;
|
||||||
window.postForm('/document/{doc_id}/move', {{project_id:v}});
|
window.postForm('/document/{doc_id}/move', {{project_id:v}});
|
||||||
@@ -815,6 +848,13 @@ def download_md(doc_id: int):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/document/{doc_id}/edit", response_class=HTMLResponse)
|
||||||
|
def edit_document(doc_id: int, content_md: str = Form(...)):
|
||||||
|
with db() as c:
|
||||||
|
c.execute("UPDATE documents SET content_md=? WHERE id=?", (content_md, doc_id))
|
||||||
|
return HTMLResponse(f"<meta http-equiv='refresh' content='0; url=/document/{doc_id}'>")
|
||||||
|
|
||||||
|
|
||||||
@app.post("/document/{doc_id}/rename", response_class=HTMLResponse)
|
@app.post("/document/{doc_id}/rename", response_class=HTMLResponse)
|
||||||
def rename_document(doc_id: int, title: str = Form(...)):
|
def rename_document(doc_id: int, title: str = Form(...)):
|
||||||
with db() as c:
|
with db() as c:
|
||||||
|
|||||||
Reference in New Issue
Block a user