feat: add drag-and-drop reorder to list detail page items
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -406,3 +406,17 @@ async def reorder_list_item(
|
|||||||
filters["parent_item_id"] = None
|
filters["parent_item_id"] = None
|
||||||
await repo.move_in_order(item_id, direction, filters=filters)
|
await repo.move_in_order(item_id, direction, filters=filters)
|
||||||
return RedirectResponse(url=f"/lists/{list_id}", status_code=303)
|
return RedirectResponse(url=f"/lists/{list_id}", status_code=303)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{list_id}/items/reorder-all")
|
||||||
|
async def reorder_all_list_items(
|
||||||
|
list_id: str,
|
||||||
|
request: Request,
|
||||||
|
item_ids: str = Form(...),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
|
repo = BaseRepository("list_items", db)
|
||||||
|
ids = [i.strip() for i in item_ids.split(",") if i.strip()]
|
||||||
|
if ids:
|
||||||
|
await repo.reorder(ids)
|
||||||
|
return RedirectResponse(url=f"/lists/{list_id}", status_code=303)
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
{% if list_items %}
|
{% if list_items %}
|
||||||
<div class="card mt-2">
|
<div class="card mt-2">
|
||||||
{% for li in list_items %}
|
{% for li in list_items %}
|
||||||
<div class="list-row {{ 'completed' if li.completed }}">
|
<div class="list-row li-drag-row {{ 'completed' if li.completed }}" draggable="true" data-id="{{ li.id }}">
|
||||||
{% with reorder_url="/lists/" ~ item.id ~ "/items/reorder", item_id=li.id %}
|
{% with reorder_url="/lists/" ~ item.id ~ "/items/reorder", item_id=li.id %}
|
||||||
{% include 'partials/reorder_arrows.html' %}
|
{% include 'partials/reorder_arrows.html' %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
@@ -133,6 +133,9 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
<form id="li-reorder-form" action="/lists/{{ item.id }}/items/reorder-all" method="post" style="display:none;">
|
||||||
|
<input type="hidden" name="item_ids" id="li-reorder-ids">
|
||||||
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="empty-state mt-3">
|
<div class="empty-state mt-3">
|
||||||
<div class="empty-state-icon">☐</div>
|
<div class="empty-state-icon">☐</div>
|
||||||
@@ -175,6 +178,52 @@
|
|||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
|
// Drag-and-drop reorder for top-level list items
|
||||||
|
var dragRow = null;
|
||||||
|
var container = document.querySelector('.card.mt-2');
|
||||||
|
if (container) {
|
||||||
|
container.querySelectorAll('.li-drag-row').forEach(function(row) {
|
||||||
|
row.addEventListener('dragstart', function(e) {
|
||||||
|
dragRow = row;
|
||||||
|
row.classList.add('dragging');
|
||||||
|
e.dataTransfer.effectAllowed = 'move';
|
||||||
|
e.dataTransfer.setData('text/plain', row.dataset.id);
|
||||||
|
});
|
||||||
|
row.addEventListener('dragend', function() {
|
||||||
|
row.classList.remove('dragging');
|
||||||
|
container.querySelectorAll('.li-drag-row.drag-over').forEach(function(el) { el.classList.remove('drag-over'); });
|
||||||
|
if (dragRow) {
|
||||||
|
var allIds = [];
|
||||||
|
container.querySelectorAll('.li-drag-row').forEach(function(r) { allIds.push(r.dataset.id); });
|
||||||
|
document.getElementById('li-reorder-ids').value = allIds.join(',');
|
||||||
|
document.getElementById('li-reorder-form').submit();
|
||||||
|
}
|
||||||
|
dragRow = null;
|
||||||
|
});
|
||||||
|
row.addEventListener('dragover', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!dragRow || row === dragRow) return;
|
||||||
|
e.dataTransfer.dropEffect = 'move';
|
||||||
|
container.querySelectorAll('.li-drag-row.drag-over').forEach(function(el) { el.classList.remove('drag-over'); });
|
||||||
|
row.classList.add('drag-over');
|
||||||
|
});
|
||||||
|
row.addEventListener('dragleave', function() { row.classList.remove('drag-over'); });
|
||||||
|
row.addEventListener('drop', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!dragRow || row === dragRow) return;
|
||||||
|
row.classList.remove('drag-over');
|
||||||
|
var rows = Array.from(container.querySelectorAll('.li-drag-row'));
|
||||||
|
var dragIdx = rows.indexOf(dragRow);
|
||||||
|
var targetIdx = rows.indexOf(row);
|
||||||
|
if (dragIdx < targetIdx) {
|
||||||
|
container.insertBefore(dragRow, row.nextSibling);
|
||||||
|
} else {
|
||||||
|
container.insertBefore(dragRow, row);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Link picker: insert URL at cursor position in target input
|
// Link picker: insert URL at cursor position in target input
|
||||||
document.querySelectorAll('.link-picker').forEach(function(sel) {
|
document.querySelectorAll('.link-picker').forEach(function(sel) {
|
||||||
sel.addEventListener('change', function() {
|
sel.addEventListener('change', function() {
|
||||||
|
|||||||
Reference in New Issue
Block a user