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"])
|
||||
projects = get_projects()
|
||||
|
||||
content_escaped = (d['content_md'] or '').replace('`', '`').replace('</script>', '<\\/script>')
|
||||
body = f"""
|
||||
<div class='d-flex justify-content-between align-items-start flex-wrap gap-2 mb-2'>
|
||||
<div>
|
||||
<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>
|
||||
<div class='btn-group btn-group-sm'>
|
||||
<a class='btn btn-outline-secondary' title='Download .md' href='/document/{doc_id}/download.md'>⬇️</a>
|
||||
<a class='btn btn-outline-secondary' title='Umbenennen' href='#' onclick='renameDoc();return false;'>✏️</a>
|
||||
<a class='btn btn-outline-secondary' title='Verschieben' href='#' onclick='moveDoc();return false;'>📁</a>
|
||||
<a class='btn btn-outline-danger' title='Löschen' href='#' onclick='deleteDoc();return false;'>🗑️</a>
|
||||
<div class='d-flex gap-2 flex-wrap'>
|
||||
<div class='btn-group btn-group-sm' id='view-actions'>
|
||||
<a class='btn btn-outline-secondary' title='Download .md' href='/document/{doc_id}/download.md'><i class='bi bi-download'></i></a>
|
||||
<button class='btn btn-outline-primary' title='Bearbeiten' onclick='startEdit()'><i class='bi bi-pencil'></i> Bearbeiten</button>
|
||||
<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 class='card mdview'>{rendered}</div>
|
||||
</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>
|
||||
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) {{
|
||||
const body = new URLSearchParams(data);
|
||||
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.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');
|
||||
if (v===null || v==='') return;
|
||||
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)
|
||||
def rename_document(doc_id: int, title: str = Form(...)):
|
||||
with db() as c:
|
||||
|
||||
Reference in New Issue
Block a user