'use strict'; const { escapeHtmlAttribute: drawerEscapeHtmlAttribute, escapeHtml: drawerEscapeHtml, } = globalThis.TabOutIconUtils || {}; const { t: drawerT, } = globalThis.TabHarborI18n || {}; const { clampTriggerTop: drawerClampTriggerTop, normalizeTriggerPosition: drawerNormalizeTriggerPosition, } = globalThis.TabOutDeferredTriggerPosition || {}; const { clearArchivedTodos: drawerClearArchivedTodos, completeTodo: drawerCompleteTodo, createTodo: drawerCreateTodo, deleteTodo: drawerDeleteTodo, normalizeTodos: drawerNormalizeTodos, searchTodos: drawerSearchTodos, splitTodos: drawerSplitTodos, updateTodo: drawerUpdateTodo, } = globalThis.TabOutTodosStore || {}; let deferredPanelOpen = false; let deferredTriggerPosition = drawerNormalizeTriggerPosition ? drawerNormalizeTriggerPosition() : { top: null }; const DEFERRED_TRIGGER_POSITION_KEY = 'deferredTriggerPosition'; let deferredTriggerDragState = null; let deferredTriggerSuppressClickUntil = 0; let drawerView = 'todos'; let todoDetailId = ''; let todoSearchOpen = false; let todoSearchQuery = ''; let drawerFocusReturnEl = null; const TODOS_KEY = 'todos'; function createTodoEditorState(input = {}) { return { open: Boolean(input.open), mode: input.mode === 'edit' ? 'edit' : 'create', todoId: input.todoId || '', title: input.title || '', description: input.description || '', error: input.error || '', }; } let todoEditorState = createTodoEditorState(); function drawerTodoText(key, fallback) { return drawerT ? drawerT(key) : fallback; } function drawerEscapeAttr(value = '') { if (drawerEscapeHtmlAttribute) return drawerEscapeHtmlAttribute(value); return String(value).replace(/&/g, '&').replace(/"/g, '"').replace(/
${drawerEscapeHtml ? drawerEscapeHtml(todo.title) : todo.title}
${ago}
`; } function renderTodoListItem(todo, { dragEnabled = true } = {}) { const ago = timeAgo(todo.createdAt); const dragHandle = dragEnabled ? `` : ''; return `
${dragHandle}
`; } function renderTodoDetail(todo) { return `

${drawerEscapeHtml ? drawerEscapeHtml(todo.title) : todo.title}

${drawerEscapeHtml ? drawerEscapeHtml(todo.description || 'Add a note when this task needs more context.') : (todo.description || 'Add a note when this task needs more context.')}

Created ${timeAgo(todo.createdAt)}
`; } function renderTodoEditor() { const isEdit = todoEditorState.mode === 'edit'; const editorTitle = isEdit ? drawerTodoText('todoEditorEditTitle', 'Edit todo') : drawerTodoText('todoEditorCreateTitle', 'New todo'); const titleLabel = drawerTodoText('todoTitleLabel', 'Title'); const descriptionLabel = drawerTodoText('todoDescriptionLabel', 'Details'); const titlePlaceholder = drawerTodoText('todoEditorTitlePlaceholder', 'What needs attention?'); const descriptionPlaceholder = drawerTodoText('todoEditorDescriptionPlaceholder', 'Add notes, context, or a next step.'); const titleValue = drawerEscapeAttr(todoEditorState.title); const descriptionValue = drawerEscapeHtml ? drawerEscapeHtml(todoEditorState.description) : todoEditorState.description; const errorHtml = todoEditorState.error ? `
${drawerEscapeHtml ? drawerEscapeHtml(todoEditorState.error) : todoEditorState.error}
` : '
'; return `

${drawerEscapeHtml ? drawerEscapeHtml(editorTitle) : editorTitle}

${errorHtml}
`; } async function renderTodoPanel() { const panel = document.getElementById('todoPanel'); const countEl = document.getElementById('todoCount'); const list = document.getElementById('todoList'); const empty = document.getElementById('todoEmpty'); const archive = document.getElementById('todoArchive'); const archiveCount = document.getElementById('todoArchiveCount'); const archiveList = document.getElementById('todoArchiveList'); const clearArchiveBtn = document.getElementById('clearTodoArchiveBtn'); const detail = document.getElementById('todoDetailView'); const searchWrap = document.getElementById('todoSearchWrap'); const searchInput = document.getElementById('todoSearchInput'); const todoSearchToggle = document.getElementById('todoSearchToggle'); const todoArchiveToggle = document.getElementById('todoArchiveToggle'); const todoArchiveBody = document.getElementById('todoArchiveBody'); if (!panel) return; const todos = await getTodos(); const { active, archived } = drawerSplitTodos(todos); const filtered = drawerSearchTodos(active, todoSearchQuery); const todoDragEnabled = !todoSearchQuery.trim() && !todoDetailId && !todoEditorState.open; let editor = document.getElementById('todoEditorView'); countEl.textContent = `${active.length}`; if (todoSearchToggle) { todoSearchToggle.setAttribute('aria-expanded', String(todoSearchOpen)); } searchWrap.style.display = todoSearchOpen ? 'block' : 'none'; searchWrap.hidden = !todoSearchOpen; if (searchInput && searchInput.value !== todoSearchQuery) searchInput.value = todoSearchQuery; if (todoArchiveToggle && todoArchiveBody) { const archiveExpanded = todoArchiveBody.style.display !== 'none' && !todoArchiveBody.hidden; todoArchiveToggle.setAttribute('aria-expanded', String(archiveExpanded)); } if (todoEditorState.open) { if (editor) { const wrapper = document.createElement('div'); wrapper.innerHTML = renderTodoEditor().trim(); editor.replaceWith(wrapper.firstElementChild); } else { detail.insertAdjacentHTML('beforebegin', renderTodoEditor()); } editor = document.getElementById('todoEditorView'); if (editor) editor.style.display = 'block'; detail.style.display = 'none'; list.style.display = 'none'; empty.style.display = 'none'; } else if (editor) { editor.style.display = 'none'; editor.innerHTML = ''; } if (!todoEditorState.open && todoDetailId) { const todo = active.find(item => item.id === todoDetailId); if (todo) { detail.innerHTML = renderTodoDetail(todo); detail.style.display = 'block'; list.style.display = 'none'; empty.style.display = 'none'; } else { todoDetailId = ''; detail.style.display = 'none'; } } else { detail.style.display = 'none'; } if (!todoEditorState.open && !todoDetailId) { if (filtered.length > 0) { list.innerHTML = filtered.map(todo => renderTodoListItem(todo, { dragEnabled: todoDragEnabled })).join(''); list.style.display = 'block'; empty.style.display = 'none'; } else { list.style.display = 'none'; empty.style.display = 'block'; } } if (todoEditorState.open) { archive.style.display = 'none'; if (clearArchiveBtn) clearArchiveBtn.style.display = 'none'; } else if (archived.length > 0) { archiveCount.textContent = `(${archived.length})`; archiveList.innerHTML = archived.map(todo => renderTodoArchiveItem(todo)).join(''); archive.style.display = 'block'; if (clearArchiveBtn) clearArchiveBtn.style.display = 'inline-flex'; } else { archive.style.display = 'none'; if (clearArchiveBtn) clearArchiveBtn.style.display = 'none'; } } async function renderDeferredColumn() { const column = document.getElementById('drawerColumn'); const triggerStack = document.getElementById('drawerTriggerStack'); const todoTrigger = document.getElementById('todoTrigger'); const overlay = document.getElementById('deferredOverlay'); const todoPanel = document.getElementById('todoPanel'); const titleButtons = document.querySelectorAll('.drawer-title-btn'); if (!column) return; try { await getTodos(); await loadDeferredTriggerPosition(); column.style.display = 'block'; if (triggerStack) triggerStack.style.display = 'flex'; if (todoTrigger) todoTrigger.style.display = 'inline-flex'; column.classList.toggle('open', deferredPanelOpen); column.setAttribute('aria-hidden', String(!deferredPanelOpen)); if (todoTrigger) { todoTrigger.setAttribute('aria-expanded', String(deferredPanelOpen)); todoTrigger.setAttribute('aria-label', deferredPanelOpen ? 'Close todos' : 'Open todos'); } overlay.hidden = !deferredPanelOpen; overlay.classList.toggle('visible', deferredPanelOpen); applyDeferredTriggerPosition(); todoPanel?.classList.add('is-active'); titleButtons.forEach(button => { button.classList.add('is-active'); button.setAttribute('aria-selected', 'true'); button.tabIndex = 0; }); await renderTodoPanel(); } catch (err) { console.warn('[tab-harbor] Could not load todos:', err); column.style.display = 'none'; if (triggerStack) triggerStack.style.display = 'none'; overlay.hidden = true; overlay.classList.remove('visible'); } } function setDeferredPanelOpen(nextOpen) { const shouldOpen = Boolean(nextOpen); if (shouldOpen) { drawerFocusReturnEl = document.activeElement instanceof HTMLElement ? document.activeElement : null; } deferredPanelOpen = shouldOpen; renderDeferredColumn().then(() => { if (deferredPanelOpen) { const preferredFocusId = todoSearchOpen ? 'todoSearchInput' : 'todoPanel'; const preferredTarget = document.getElementById(preferredFocusId); if (preferredTarget?.focus) { preferredTarget.focus({ preventScroll: true }); } else { focusFirstElement(document.getElementById('drawerColumn')); } return; } drawerFocusReturnEl?.focus?.({ preventScroll: true }); drawerFocusReturnEl = null; }); }