/** * GoingsOn - Tasks Render Module * Task row rendering, badges, subtask modal rendering */ (function() { 'use strict'; const esc = GoingsOn.utils.escapeHtml; const escAttr = GoingsOn.utils.escapeAttrValue; const escArg = GoingsOn.utils.escapeHandlerArg; const escAttrVal = GoingsOn.utils.escapeAttrValue; // ============ Task Rendering Helpers ============ /** * Render subtask count and annotation badges for a task row. * @param {Object} task - Task object with subtaskCount, subtaskCompleted, annotations * @returns {string} HTML string of badge elements, or empty string if none */ function renderTaskBadges(task) { let badges = ''; const subtaskCount = task.subtaskCount; const completedSubtasks = task.subtaskCompleted; const annotationCount = task.annotations ? task.annotations.length : 0; if (subtaskCount > 0) { badges += `${completedSubtasks}/${subtaskCount}`; } if (annotationCount > 0) { badges += `Notes: ${annotationCount}`; } return badges ? `${badges}` : ''; } /** * Format a task's recurrence pattern for display. * Only shows recurrence when the task also has a due date. * @param {Object} task - Task object with recurrence and due fields * @returns {string} Recurrence label or '-' */ function formatRecurrence(task) { // Only show recurrence if task has both recurrence AND a due date if (task.recurrence && task.recurrence !== 'None' && task.due) { return task.recurrence; } return '-'; } /** * Render a single task row as a div (for virtual scrolling). * @param {Object} t - Task object * @param {number} index - Task index * @returns {string} HTML string */ /** * Format a minute count as a human-readable duration string. * @param {number} mins - Minutes to format * @returns {string} Formatted string (e.g., "1h 30m" or "45m") */ function formatMinutes(mins) { if (mins >= 60) return `${Math.floor(mins / 60)}h ${mins % 60}m`; return `${mins}m`; } /** * Render time tracking badge for a task (active timer or tracked/estimated). * @param {Object} t - Task object with timerActive, estimatedMinutes, actualMinutes * @returns {string} HTML string for the time badge, or empty string */ function renderTimeBadge(t) { if (t.timerActive) { return ''; } if (t.estimatedMinutes || t.actualMinutes) { const actual = t.actualMinutes || 0; const est = t.estimatedMinutes; const isOver = est && actual > est; const label = est ? `${formatMinutes(actual)} / ${formatMinutes(est)}` : formatMinutes(actual); return `${esc(label)}`; } return ''; } /** * Render the at-a-glance status-token dot for a task row. The colour rolls the * task's tokens up: neutral (muted, no tokens), pending (amber, a token is not * yet done — e.g. a commit not pushed), or complete (green, all done). Clicking * opens the commits/token manager. * @param {Object} t - Task with tokenSummary + statusTokens * @returns {string} HTML for the dot button */ function renderTokenDot(t) { const count = (t.statusTokens || []).length; // Only tasks with tokens get a dot — a plain "real life" task stays clean. if (count === 0) return ''; const summary = t.tokenSummary || 'neutral'; let title; if (summary === 'complete') { title = count > 1 ? 'All commits pushed' : 'Commit pushed'; } else { title = count > 1 ? 'Commits associated, not all pushed' : 'Commit associated, not pushed'; } return ``; } function renderTaskRow(t, index) { const progress = t.subtaskProgress ?? 0; const displayDesc = t.displayDescription || t.description; const isSelected = GoingsOn.tasks.selection.isSelected(t.id); const isStarted = t.status === 'Started'; return `