feat(diarization-ui): re-add bulk-select to library after Bootstrap merge
Restore multi-row checkbox selection, select-all/deselect-all controls, bulk action bar (move/delete) and backend endpoints, adapted to Bootstrap 5. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
91
app.py
91
app.py
@@ -5,7 +5,7 @@ import threading
|
|||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
import markdown as md
|
import markdown as md
|
||||||
import requests
|
import requests
|
||||||
@@ -578,6 +578,7 @@ def library(project_id: Optional[str] = None, q_title: str = "", q_content: str
|
|||||||
rows = "".join(
|
rows = "".join(
|
||||||
[
|
[
|
||||||
f"<tr>"
|
f"<tr>"
|
||||||
|
f"<td style='width:36px'><input type='checkbox' class='form-check-input row-cb' value='{d['id']}'></td>"
|
||||||
f"<td>#{d['id']}</td>"
|
f"<td>#{d['id']}</td>"
|
||||||
f"<td>{d['title']}</td>"
|
f"<td>{d['title']}</td>"
|
||||||
f"<td>{d['kind']}</td>"
|
f"<td>{d['kind']}</td>"
|
||||||
@@ -619,20 +620,75 @@ def library(project_id: Optional[str] = None, q_title: str = "", q_content: str
|
|||||||
<div class='col-6 col-md-1 d-grid'><a class='btn btn-outline-secondary' href='/library'>Reset</a></div>
|
<div class='col-6 col-md-1 d-grid'><a class='btn btn-outline-secondary' href='/library'>Reset</a></div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<div id='bulkBar' class='alert alert-primary d-none d-flex align-items-center gap-2 py-2 mb-2' role='alert'>
|
||||||
|
<strong id='bulkCount'>0 ausgewählt</strong>
|
||||||
|
<button type='button' class='btn btn-sm btn-primary' onclick='bulkMove()'><i class='bi bi-folder-symlink'></i> Verschieben</button>
|
||||||
|
<button type='button' class='btn btn-sm btn-danger' onclick='bulkDelete()'><i class='bi bi-trash'></i> Löschen</button>
|
||||||
|
</div>
|
||||||
<div class='card'>
|
<div class='card'>
|
||||||
<div class='table-responsive'>
|
<div class='table-responsive'>
|
||||||
<table class='table table-sm table-striped align-middle mb-0'>
|
<table class='table table-sm table-striped align-middle mb-0'>
|
||||||
<thead><tr><th>ID</th><th>Titel</th><th>Typ</th><th>Projekt</th><th>Erstellt</th><th>Aktionen</th></tr></thead>
|
<thead><tr>
|
||||||
<tbody>{rows or "<tr><td colspan='6' class='text-secondary'>Keine Einträge.</td></tr>"}</tbody>
|
<th style='width:36px'><input type='checkbox' class='form-check-input' id='cbAll' title='Alle wählen'></th>
|
||||||
|
<th>ID</th><th>Titel</th><th>Typ</th><th>Projekt</th><th>Erstellt</th><th>Aktionen</th>
|
||||||
|
</tr></thead>
|
||||||
|
<tbody>{rows or "<tr><td colspan='7' class='text-secondary'>Keine Einträge.</td></tr>"}</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class='d-flex gap-2 mt-2'>
|
||||||
|
<button type='button' class='btn btn-sm btn-outline-secondary' onclick='selectAll()'><i class='bi bi-check2-all'></i> Alle wählen</button>
|
||||||
|
<button type='button' class='btn btn-sm btn-outline-secondary' onclick='selectNone()'><i class='bi bi-x-square'></i> Alle abwählen</button>
|
||||||
|
</div>
|
||||||
<script>
|
<script>
|
||||||
async function libPost(url, data) {{
|
async function libPost(url, data) {{
|
||||||
const r = await fetch(url, {{method:'POST', headers:{{'Content-Type':'application/x-www-form-urlencoded'}}, body:new URLSearchParams(data)}});
|
const r = await fetch(url, {{method:'POST', headers:{{'Content-Type':'application/x-www-form-urlencoded'}}, body:new URLSearchParams(data)}});
|
||||||
if(!r.ok) {{ alert('Fehler '+r.status); return; }}
|
if(!r.ok) {{ alert('Fehler '+r.status); return; }}
|
||||||
location.reload();
|
location.reload();
|
||||||
}}
|
}}
|
||||||
|
async function libPostMulti(url, params) {{
|
||||||
|
const body = new URLSearchParams();
|
||||||
|
for(const [k,v] of Object.entries(params)) {{
|
||||||
|
if(Array.isArray(v)) v.forEach(x => body.append(k, x));
|
||||||
|
else body.append(k, v);
|
||||||
|
}}
|
||||||
|
const r = await fetch(url, {{method:'POST', headers:{{'Content-Type':'application/x-www-form-urlencoded'}}, body}});
|
||||||
|
if(!r.ok) {{ alert('Fehler '+r.status); return; }}
|
||||||
|
location.reload();
|
||||||
|
}}
|
||||||
|
function getSelected() {{
|
||||||
|
return [...document.querySelectorAll('.row-cb:checked')].map(cb => cb.value);
|
||||||
|
}}
|
||||||
|
function updateBulkBar() {{
|
||||||
|
const ids = getSelected();
|
||||||
|
const all = document.querySelectorAll('.row-cb');
|
||||||
|
document.getElementById('bulkCount').textContent = ids.length + ' ausgewählt';
|
||||||
|
document.getElementById('bulkBar').classList.toggle('d-none', ids.length === 0);
|
||||||
|
const cbAll = document.getElementById('cbAll');
|
||||||
|
cbAll.indeterminate = ids.length > 0 && ids.length < all.length;
|
||||||
|
cbAll.checked = all.length > 0 && ids.length === all.length;
|
||||||
|
}}
|
||||||
|
document.querySelectorAll('.row-cb').forEach(cb => {{
|
||||||
|
cb.addEventListener('change', function() {{
|
||||||
|
this.closest('tr').classList.toggle('table-active', this.checked);
|
||||||
|
updateBulkBar();
|
||||||
|
}});
|
||||||
|
}});
|
||||||
|
document.getElementById('cbAll').addEventListener('change', function() {{
|
||||||
|
document.querySelectorAll('.row-cb').forEach(cb => {{
|
||||||
|
cb.checked = this.checked;
|
||||||
|
cb.closest('tr').classList.toggle('table-active', this.checked);
|
||||||
|
}});
|
||||||
|
updateBulkBar();
|
||||||
|
}});
|
||||||
|
function selectAll() {{
|
||||||
|
document.querySelectorAll('.row-cb').forEach(cb => {{ cb.checked=true; cb.closest('tr').classList.add('table-active'); }});
|
||||||
|
updateBulkBar();
|
||||||
|
}}
|
||||||
|
function selectNone() {{
|
||||||
|
document.querySelectorAll('.row-cb').forEach(cb => {{ cb.checked=false; cb.closest('tr').classList.remove('table-active'); }});
|
||||||
|
updateBulkBar();
|
||||||
|
}}
|
||||||
window.libRename = async function(id, current) {{
|
window.libRename = async function(id, current) {{
|
||||||
const v = await window.uiPrompt('Dokument umbenennen', current || '');
|
const v = await window.uiPrompt('Dokument umbenennen', current || '');
|
||||||
if(v===null) return;
|
if(v===null) return;
|
||||||
@@ -649,6 +705,21 @@ window.libDelete = async function(id) {{
|
|||||||
if(!ok) return;
|
if(!ok) return;
|
||||||
await libPost(`/document/${{id}}/delete`, {{}});
|
await libPost(`/document/${{id}}/delete`, {{}});
|
||||||
}};
|
}};
|
||||||
|
window.bulkMove = async function() {{
|
||||||
|
const ids = getSelected();
|
||||||
|
if(!ids.length) return;
|
||||||
|
const options = {project_js};
|
||||||
|
const v = await window.uiSelect(`${{ids.length}} Dokumente verschieben`, options, 'Projekt wählen');
|
||||||
|
if(v===null || v==='') return;
|
||||||
|
await libPostMulti('/documents/bulk-move', {{ids, project_id:v}});
|
||||||
|
}};
|
||||||
|
window.bulkDelete = async function() {{
|
||||||
|
const ids = getSelected();
|
||||||
|
if(!ids.length) return;
|
||||||
|
const ok = await window.uiConfirm(`${{ids.length}} Dokumente löschen?`);
|
||||||
|
if(!ok) return;
|
||||||
|
await libPostMulti('/documents/bulk-delete', {{ids}});
|
||||||
|
}};
|
||||||
</script>
|
</script>
|
||||||
"""
|
"""
|
||||||
return layout("Library", body)
|
return layout("Library", body)
|
||||||
@@ -755,6 +826,20 @@ def delete_document(doc_id: int):
|
|||||||
return HTMLResponse("<meta http-equiv='refresh' content='0; url=/library'>")
|
return HTMLResponse("<meta http-equiv='refresh' content='0; url=/library'>")
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/documents/bulk-move", response_class=HTMLResponse)
|
||||||
|
def bulk_move_documents(ids: List[int] = Form(...), project_id: int = Form(...)):
|
||||||
|
with db() as c:
|
||||||
|
c.executemany("UPDATE documents SET project_id=? WHERE id=?", [(project_id, i) for i in ids])
|
||||||
|
return HTMLResponse("<meta http-equiv='refresh' content='0; url=/library'>")
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/documents/bulk-delete", response_class=HTMLResponse)
|
||||||
|
def bulk_delete_documents(ids: List[int] = Form(...)):
|
||||||
|
with db() as c:
|
||||||
|
c.executemany("DELETE FROM documents WHERE id=?", [(i,) for i in ids])
|
||||||
|
return HTMLResponse("<meta http-equiv='refresh' content='0; url=/library'>")
|
||||||
|
|
||||||
|
|
||||||
@app.get("/prompts", response_class=HTMLResponse)
|
@app.get("/prompts", response_class=HTMLResponse)
|
||||||
def prompts_page():
|
def prompts_page():
|
||||||
with db() as c:
|
with db() as c:
|
||||||
|
|||||||
Reference in New Issue
Block a user