bak
This commit is contained in:
1 parent
e85b5203cd
commit
0d4d369870
19 files changed
+2258
-1814
No files matched your search
@@ -0,0 +1,114 @@
|
||||
// ── Global tasks view ──
|
||||
|
||||
import { $, esc, api } from './util.js';
|
||||
import { state } from './state.js';
|
||||
|
||||
const overlay = $('tasks');
|
||||
const btn = $('tasks-btn');
|
||||
const closeBtn = $('tasks-close');
|
||||
const statusFilter = $('tasks-status-filter');
|
||||
const bucketFilter = $('tasks-bucket-filter');
|
||||
const list = $('tasks-list');
|
||||
const summary = $('tasks-summary');
|
||||
const countBadge = $('tasks-open-count');
|
||||
|
||||
function collectTasks() {
|
||||
const all = [];
|
||||
for (const card of state.cards) {
|
||||
const re = /- \[([ xX])\][ \t]*([^\n\r]*)/g;
|
||||
let m, idx = 0;
|
||||
while ((m = re.exec(card.body || '')) !== null) {
|
||||
all.push({ cardId: card.id, cardTitle: card.title || 'untitled', cardColor: card.color || '#94a3b8',
|
||||
bucketId: card.bucket_id, bucketName: card.bucket_name || '', done: m[1].toLowerCase() === 'x',
|
||||
text: (m[2] || '').trim() || '(empty task)', todoIndex: idx++ });
|
||||
}
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
function filter(tasks) {
|
||||
const s = statusFilter?.value || 'open', b = bucketFilter?.value || '';
|
||||
return tasks.filter(t => {
|
||||
if (s === 'open' && t.done) return false;
|
||||
if (s === 'done' && !t.done) return false;
|
||||
if (b === 'none') return !t.bucketId;
|
||||
if (b && t.bucketId != b) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
export function refreshTasksView() {
|
||||
if (!countBadge) return;
|
||||
const tasks = collectTasks();
|
||||
const open = tasks.filter(t => !t.done).length;
|
||||
countBadge.textContent = String(open);
|
||||
|
||||
if (!overlay?.classList.contains('open') || !list) return;
|
||||
const filtered = filter(tasks);
|
||||
if (summary) summary.textContent = `open: ${open} | done: ${tasks.length - open} | showing: ${filtered.length}`;
|
||||
|
||||
if (!filtered.length) {
|
||||
list.innerHTML = `<div class="px-4 py-10 text-center text-t-dim"><i class="ph ph-check-circle text-3xl mb-2 block"></i><p class="font-pixel text-[10px]">no tasks in this filter</p></div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
list.innerHTML = filtered.map(t => `
|
||||
<div class="px-4 py-3 border-b border-t-border flex items-start gap-3 hover:bg-t-raised/40">
|
||||
<button data-task-toggle="${t.cardId}:${t.todoIndex}" class="group mt-0.5 shrink-0 border rounded-sm flex items-center justify-center transition-colors ${t.done ? 'border-t-accent bg-t-accent' : 'border-t-border bg-transparent hover:border-t-accent'}" style="width:30px;height:30px;">
|
||||
<i class="ph ph-check text-[18px] transition-opacity ${t.done ? 'opacity-100 text-t-bg' : 'opacity-0 text-t-accent group-hover:opacity-75'}"></i>
|
||||
</button>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="text-xs leading-relaxed ${t.done ? 'text-t-dim line-through' : 'text-t-text'}">${esc(t.text)}</div>
|
||||
<button data-task-open="${t.cardId}" class="mt-1 inline-flex items-center gap-1.5 text-[10px] font-pixel text-t-muted hover:text-t-accent2 transition-colors">
|
||||
<span class="w-1.5 h-1.5 rounded-full" style="background:${t.cardColor}"></span>
|
||||
<span>${esc(t.cardTitle)}</span><span class="text-t-dim">/ ${t.bucketName ? esc(t.bucketName) : 'unsorted'}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>`).join('');
|
||||
|
||||
list.querySelectorAll('[data-task-toggle]').forEach(b => b.addEventListener('click', async e => {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
const [cardId, idx] = b.dataset.taskToggle.split(':');
|
||||
await toggleTask(cardId, Number(idx));
|
||||
}));
|
||||
list.querySelectorAll('[data-task-open]').forEach(b => b.addEventListener('click', e => {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
closeTasks();
|
||||
window._openDetail(b.dataset.taskOpen);
|
||||
}));
|
||||
}
|
||||
|
||||
async function toggleTask(cardId, todoIdx) {
|
||||
const card = state.cards.find(c => c.id == cardId);
|
||||
if (!card) return;
|
||||
let idx = 0, changed = false;
|
||||
const next = (card.body || '').replace(/- \[([ xX])\]/g, (m, s) => {
|
||||
if (idx++ !== todoIdx) return m;
|
||||
changed = true;
|
||||
return `- [${s.toLowerCase() === 'x' ? ' ' : 'x'}]`;
|
||||
});
|
||||
if (!changed) return;
|
||||
const updated = await api(`api/cards/${cardId}`, { method: 'PATCH', body: { body: next } });
|
||||
if (updated?.id) { const i = state.cards.findIndex(c => c.id == updated.id); if (i !== -1) state.cards[i] = updated; }
|
||||
else card.body = next;
|
||||
if (state.viewingCard?.id == cardId) {
|
||||
const latest = state.cards.find(c => c.id == cardId);
|
||||
if (latest) { state.viewingCard = latest; /* detail re-render handled by board */ }
|
||||
}
|
||||
window._renderBoard();
|
||||
}
|
||||
|
||||
export function openTasks() {
|
||||
if (!overlay) return;
|
||||
window._closeSearch?.();
|
||||
overlay.classList.add('open');
|
||||
refreshTasksView();
|
||||
}
|
||||
|
||||
export function closeTasks() { overlay?.classList.remove('open'); }
|
||||
|
||||
btn?.addEventListener('click', openTasks);
|
||||
closeBtn?.addEventListener('click', closeTasks);
|
||||
overlay?.addEventListener('click', e => { if (e.target === overlay) closeTasks(); });
|
||||
statusFilter?.addEventListener('change', refreshTasksView);
|
||||
bucketFilter?.addEventListener('change', refreshTasksView);
|
||||
Reference in new issue
Block a user