Files
lifeos-dev/templates/lists.html
Michael 590f019ca7 feat: focus priority, focus links, task list assignment, lists drag-and-drop, URL display fixes
- Focus page: turn sequence number into persistent editable priority (focus_priority column)
- Focus detail: add links section (add existing, create new, unlink) via focus_links junction table
- Focus detail: add copy and inline edit for checklist items
- Task detail lists tab: add existing list assignment and unlink actions
- Lists page: add drag-and-drop reorder support
- Links/bookmarks pages: remove artificial URL truncation, use CSS ellipsis

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 22:19:06 +00:00

147 lines
6.5 KiB
HTML

{% extends "base.html" %}
{% block content %}
<div class="page-header">
<h1 class="page-title">Lists<span class="page-count">{{ items|length }}</span></h1>
<a href="/lists/create" class="btn btn-primary">+ New List</a>
</div>
<!-- Filters -->
<form class="filters-bar" method="get" action="/lists" id="list-filters">
<select name="domain_id" class="filter-select" id="domain-filter" onchange="this.form.submit()">
<option value="">All Domains</option>
{% for d in domains %}
<option value="{{ d.id }}" {{ 'selected' if current_domain_id == d.id|string }}>{{ d.name }}</option>
{% endfor %}
</select>
<select name="project_id" class="filter-select" id="project-filter" onchange="this.form.submit()">
<option value="">All Projects</option>
{% for p in projects %}
<option value="{{ p.id }}" {{ 'selected' if current_project_id == p.id|string }}>{{ p.name }}</option>
{% endfor %}
</select>
</form>
{% if items %}
<div class="card mt-3">
{% for item in items %}
<div class="list-row lists-drag-row" draggable="true" data-id="{{ item.id }}">
{% with reorder_url="/lists/reorder", item_id=item.id %}
{% include 'partials/reorder_arrows.html' %}
{% endwith %}
<span class="row-title"><a href="/lists/{{ item.id }}">{{ item.name }}</a></span>
<span class="row-meta">
{{ item.completed_count }}/{{ item.item_count }} items
</span>
{% if item.item_count > 0 %}
<div class="progress-bar" style="width: 80px;">
<div class="progress-fill" style="width: {{ (item.completed_count / item.item_count * 100) if item.item_count > 0 else 0 }}%"></div>
</div>
{% endif %}
<span class="row-tag">{{ item.list_type }}</span>
{% if item.domain_name %}
<span class="row-domain-tag" style="background: {{ item.domain_color or '#4F6EF7' }}22; color: {{ item.domain_color or '#4F6EF7' }}">{{ item.domain_name }}</span>
{% endif %}
{% if item.project_name %}
<span class="row-tag">{{ item.project_name }}</span>
{% endif %}
<div class="row-actions">
<a href="/lists/{{ item.id }}/edit" class="btn btn-ghost btn-xs">Edit</a>
<form action="/lists/{{ item.id }}/delete" method="post" data-confirm="Delete this list?" style="display:inline">
<button type="submit" class="btn btn-ghost btn-xs" style="color: var(--red)">Del</button>
</form>
</div>
</div>
{% endfor %}
</div>
<form id="lists-reorder-form" action="/lists/reorder-all" method="post" style="display:none;">
<input type="hidden" name="item_ids" id="lists-reorder-ids">
</form>
{% else %}
<div class="empty-state mt-3">
<div class="empty-state-icon">&#9776;</div>
<div class="empty-state-text">No lists yet</div>
<a href="/lists/create" class="btn btn-primary">Create First List</a>
</div>
{% endif %}
<script>
(function() {
// Drag-and-drop reorder
var dragRow = null;
var container = document.querySelector('.card.mt-3');
if (container) {
container.querySelectorAll('.lists-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('.lists-drag-row.drag-over').forEach(function(el) { el.classList.remove('drag-over'); });
if (dragRow) {
var allIds = [];
container.querySelectorAll('.lists-drag-row').forEach(function(r) { allIds.push(r.dataset.id); });
document.getElementById('lists-reorder-ids').value = allIds.join(',');
document.getElementById('lists-reorder-form').submit();
}
dragRow = null;
});
row.addEventListener('dragover', function(e) {
e.preventDefault();
if (!dragRow || row === dragRow) return;
e.dataTransfer.dropEffect = 'move';
container.querySelectorAll('.lists-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('.lists-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);
}
});
});
}
var domainSel = document.getElementById('domain-filter');
var projectSel = document.getElementById('project-filter');
var currentProjectId = '{{ current_project_id }}';
domainSel.addEventListener('change', function() {
var did = domainSel.value;
if (!did) {
// No domain filter - submit immediately, server returns all projects
document.getElementById('list-filters').submit();
return;
}
// Fetch projects for this domain
fetch('/projects/api/by-domain?domain_id=' + encodeURIComponent(did))
.then(function(r) { return r.json(); })
.then(function(projects) {
projectSel.innerHTML = '<option value="">All Projects</option>';
projects.forEach(function(p) {
var opt = document.createElement('option');
opt.value = p.id;
opt.textContent = p.name;
if (p.id === currentProjectId) opt.selected = true;
projectSel.appendChild(opt);
});
document.getElementById('list-filters').submit();
})
.catch(function() {
document.getElementById('list-filters').submit();
});
});
})();
</script>
{% endblock %}