commit 367246067ae003855bfcf39dc01b51dcab77f11e
parent 3bd3c169abaeb7b74955a03ee3181cb2191953cf
Author: Hunter
Date:   Tue, 30 Jul 2024 17:32:03 -0400

allow deletion of currently viewed parent task

Diffstat:
Mtodo.html | 202++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
1 file changed, 112 insertions(+), 90 deletions(-)

diff --git a/todo.html b/todo.html @@ -51,11 +51,11 @@ let taskPath = [currentTask]; let globalBackspaceBlock = false; - function createTaskElement(task, isParent = false) { + function createTaskElement(task, isParentTask = false) { const taskContainer = document.createElement('div'); taskContainer.className = 'task-container'; taskContainer.dataset.id = task.id; - if (isParent) taskContainer.classList.add('parent-task'); + if (isParentTask) taskContainer.classList.add('parent-task'); const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; @@ -79,10 +79,16 @@ e.preventDefault(); return; } - if (taskInput.value === '' && canDelete && task !== taskPath[0]) { + if (taskInput.value === '' && canDelete) { e.preventDefault(); - globalBackspaceBlock = true; - deleteTask(task); + if (task !== taskPath[0]) { + globalBackspaceBlock = true; + if (task === currentTask) { + deleteCurrentParentTask(); + } else { + deleteSubtask(task); + } + } } else if (taskInput.value !== '') { canDelete = false; } @@ -118,17 +124,14 @@ let currentTask = rootTask; let currentDepth = 0; - // Add empty circles for levels before the current one for (let i = 0; i < currentPath.length - 1; i++) { breadcrumbs += '○ '; currentTask = currentTask.subtasks.find(t => t.id === currentPath[i + 1].id); } - // Add filled circle for the current level breadcrumbs += '● '; currentDepth = currentPath.length - 1; - // If the selected task is not the current view's parent task, add empty circles for potential deeper levels if (selectedTaskId !== currentTask.id) { let selectedTask = currentTask.subtasks.find(t => t.id === selectedTaskId); @@ -140,7 +143,6 @@ let maxDepth = calculateMaxDepth(selectedTask, currentDepth + 1); - // Add empty circles for potential deeper levels for (let i = currentDepth + 1; i < maxDepth; i++) { breadcrumbs += '○ '; } @@ -157,11 +159,9 @@ function toggleTaskState(task) { if (task.state === 1) { - // If the task is checked, only uncheck it if it's not a parent with all checked direct children if (task.subtasks.length === 0 || !task.subtasks.every(t => t.state === 1)) { task.state = 0; if (task.subtasks.length > 0) { - // If it's a parent task, set indeterminate subtasks to unchecked task.subtasks.forEach(subtask => { if (subtask.state === 2) { subtask.state = 0; @@ -169,38 +169,34 @@ }); } } - // If it's a parent with all direct children checked, do nothing (stay checked) } else { - // If the task is unchecked or indeterminate, check it task.state = 1; if (task.subtasks.length > 0) { - // If it's a parent task, set unchecked subtasks to indeterminate task.subtasks.forEach(subtask => { if (subtask.state === 0) { - subtask.state = 2; // Set to indeterminate + subtask.state = 2; } }); } } - updateParentState(task); + updateParentTaskState(task); renderCurrentView(); selectAndFocusTask(task); } - function updateParentState(task) { + function updateParentTaskState(task) { const parent = findParentTask(task); if (parent) { const allChecked = parent.subtasks.every(t => t.state === 1); const anyUnchecked = parent.subtasks.some(t => t.state === 0); if (allChecked) { - parent.state = 1; // Checked + parent.state = 1; } else if (anyUnchecked) { - parent.state = 0; // Unchecked + parent.state = 0; } - // If all children are either checked or indeterminate, don't change the parent's state - updateParentState(parent); + updateParentTaskState(parent); } } @@ -226,13 +222,17 @@ function handleKeyDown(e, task) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); - addNewTask(currentTask, task); + addNewSubtask(currentTask, task); } else if (e.key === 'Enter' && e.shiftKey) { e.preventDefault(); toggleTaskState(task); } else if (e.key === 'Backspace' && e.target.value === '' && task !== currentTask) { e.preventDefault(); - deleteTask(task); + if (task === currentTask) { + deleteCurrentParentTask(); + } else { + deleteSubtask(task); + } } else if (e.key === 'ArrowUp' && !e.shiftKey) { e.preventDefault(); navigateTasks('up'); @@ -241,47 +241,45 @@ navigateTasks('down'); } else if (e.key === 'ArrowUp' && e.shiftKey) { e.preventDefault(); - moveTask(task, 'up'); + moveSubtask(task, 'up'); } else if (e.key === 'ArrowDown' && e.shiftKey) { e.preventDefault(); - moveTask(task, 'down'); + moveSubtask(task, 'down'); } else if (e.key === 'ArrowRight' && e.shiftKey) { e.preventDefault(); if (task !== currentTask) { - navigateInto(task); + navigateIntoSubtask(task); } } else if (e.key === 'ArrowLeft' && e.shiftKey) { e.preventDefault(); - navigateOut(); + navigateToParentTask(); } } - function moveTask(task, direction) { - const parent = findParentTask(task); - if (!parent) return; // Can't move the root task + function moveSubtask(subtask, direction) { + const parentTask = findParentTask(subtask); + if (!parentTask) return; - const index = parent.subtasks.findIndex(t => t.id === task.id); - if (index === -1) return; // Task not found in parent's subtasks + const index = parentTask.subtasks.findIndex(t => t.id === subtask.id); + if (index === -1) return; if (direction === 'up' && index > 0) { - // Move task up - [parent.subtasks[index - 1], parent.subtasks[index]] = [parent.subtasks[index], parent.subtasks[index - 1]]; - } else if (direction === 'down' && index < parent.subtasks.length - 1) { - // Move task down - [parent.subtasks[index], parent.subtasks[index + 1]] = [parent.subtasks[index + 1], parent.subtasks[index]]; + [parentTask.subtasks[index - 1], parentTask.subtasks[index]] = [parentTask.subtasks[index], parentTask.subtasks[index - 1]]; + } else if (direction === 'down' && index < parentTask.subtasks.length - 1) { + [parentTask.subtasks[index], parentTask.subtasks[index + 1]] = [parentTask.subtasks[index + 1], parentTask.subtasks[index]]; } renderCurrentView(); - selectAndFocusTask(task); + selectAndFocusTask(subtask); } - function addNewTask(parentTask, currentSubtask = null) { - const newTask = { id: Date.now(), text: '', state: 0, subtasks: [], selectedSubtaskId: null }; + function addNewSubtask(parentTask, currentSubtask = null) { + const newSubtask = { id: Date.now(), text: '', state: 0, subtasks: [], selectedSubtaskId: null }; if (currentSubtask) { const index = parentTask.subtasks.findIndex(t => t.id === currentSubtask.id); - parentTask.subtasks.splice(index + 1, 0, newTask); + parentTask.subtasks.splice(index + 1, 0, newSubtask); } else { - parentTask.subtasks.push(newTask); + parentTask.subtasks.push(newSubtask); } parentTask.state = 0; @@ -292,7 +290,7 @@ }); renderCurrentView(); - selectAndFocusTask(newTask); + selectAndFocusTask(newSubtask); } function selectAndFocusTask(task) { @@ -303,26 +301,60 @@ } } - function deleteTask(task) { - const parent = taskPath[taskPath.length - 1]; - const index = parent.subtasks.findIndex(t => t.id === task.id); + function deleteSubtask(subtask) { + const parentTask = taskPath[taskPath.length - 1]; + const index = parentTask.subtasks.findIndex(t => t.id === subtask.id); - if (parent.id === 'root' && parent.subtasks.length === 1) { + if (parentTask.id === 'root' && parentTask.subtasks.length === 1) { return; } - parent.subtasks = parent.subtasks.filter(t => t.id !== task.id); - updateParentState(parent); + parentTask.subtasks = parentTask.subtasks.filter(t => t.id !== subtask.id); + updateParentTaskState(parentTask); - if (parent.subtasks.length === 0 && taskPath.length > 1) { - navigateOut(); + if (parentTask.subtasks.length === 0 && taskPath.length > 1) { + navigateToParentTask(); } else { renderCurrentView(); - if (parent.subtasks.length > 0) { + if (parentTask.subtasks.length > 0) { const targetIndex = Math.max(0, index - 1); - selectAndFocusTask(parent.subtasks[targetIndex]); + selectAndFocusTask(parentTask.subtasks[targetIndex]); } else { - selectAndFocusTask(parent); + selectAndFocusTask(parentTask); + } + } + } + + function deleteCurrentParentTask() { + if (taskPath.length <= 1) return; // Don't delete root task + + const currentParentTask = taskPath[taskPath.length - 1]; + const grandparentTask = taskPath[taskPath.length - 2]; + + // Check if we're trying to delete the sole child of the root + if (grandparentTask.id === 'root' && grandparentTask.subtasks.length === 1) { + return; // Don't allow deletion of the sole child of root + } + + const index = grandparentTask.subtasks.findIndex(t => t.id === currentParentTask.id); + + grandparentTask.subtasks = grandparentTask.subtasks.filter(t => t.id !== currentParentTask.id); + updateParentTaskState(grandparentTask); + + taskPath.pop(); // Remove the deleted task from the path + currentTask = grandparentTask; + + if (grandparentTask.subtasks.length === 0 && taskPath.length > 1) { + // If we've just deleted the last subtask, navigate up another level + navigateToParentTask(); + } else { + renderCurrentView(); + if (grandparentTask.subtasks.length > 0) { + // Select the subtask before the deleted one, or the one after if at the start + const targetIndex = Math.max(0, index - 1); + selectAndFocusTask(grandparentTask.subtasks[targetIndex]); + } else { + selectAndFocusTask(grandparentTask); } } } @@ -340,60 +372,53 @@ } } else { if (currentIndex === tasks.length) { - addNewTask(currentTask); + addNewSubtask(currentTask); } else { selectAndFocusTask(tasks[currentIndex]); } } } - function navigateInto(task) { - if (task.subtasks.length > 0) { - currentTask.selectedSubtaskId = task.id; - taskPath.push(task); - currentTask = task; + function navigateIntoSubtask(subtask) { + if (subtask.subtasks.length > 0) { + currentTask.selectedSubtaskId = subtask.id; + taskPath.push(subtask); + currentTask = subtask; updateBreadcrumbs(currentTask); renderCurrentView(); - const selectedTask = task.selectedSubtaskId ? - task.subtasks.find(t => t.id === task.selectedSubtaskId) : - task.subtasks[0]; - selectAndFocusTask(selectedTask); + const selectedSubtask = subtask.selectedSubtaskId ? + subtask.subtasks.find(t => t.id === subtask.selectedSubtaskId) : + subtask.subtasks[0]; + selectAndFocusTask(selectedSubtask); } else { - addNewTask(task); - currentTask.selectedSubtaskId = task.id; - taskPath.push(task); - currentTask = task; + addNewSubtask(subtask); + currentTask.selectedSubtaskId = subtask.id; + taskPath.push(subtask); + currentTask = subtask; updateBreadcrumbs(currentTask); renderCurrentView(); - selectAndFocusTask(task.subtasks[0]); + selectAndFocusTask(subtask.subtasks[0]); } } - function navigateOut() { + function navigateToParentTask() { if (taskPath.length > 1) { const currentTaskId = currentTask.id; taskPath.pop(); currentTask = taskPath[taskPath.length - 1]; - updateParentState(currentTask); + updateParentTaskState(currentTask); updateBreadcrumbs(currentTask); renderCurrentView(); - const selectedTask = currentTask.subtasks.find(t => t.id === currentTaskId); - selectAndFocusTask(selectedTask || currentTask.subtasks[0]); - currentTask.selectedSubtaskId = currentTaskId; - } - } - - function reevaluateTaskState(task) { - if (task.subtasks.length > 0) { - const allChecked = task.subtasks.every(t => t.state === 1); - const anyChecked = task.subtasks.some(t => t.state === 1); - - if (allChecked) { - task.state = 1; // Checked - } else if (!anyChecked) { - task.state = 0; // Unchecked + const selectedSubtask = currentTask.subtasks.find(t => t.id === currentTaskId); + if (selectedSubtask) { + selectAndFocusTask(selectedSubtask); + } else if (currentTask.subtasks.length > 0) { + // If for some reason the previous subtask isn't found, select the first subtask + selectAndFocusTask(currentTask.subtasks[0]); + } else { + selectAndFocusTask(currentTask); } - // If some subtasks are checked and some are not, we don't change the parent's state + currentTask.selectedSubtaskId = currentTaskId; } } @@ -403,7 +428,6 @@ if (task !== currentTask) { currentTask.selectedSubtaskId = task.id; } - // Update breadcrumbs when active task changes updateBreadcrumbs(task); } @@ -417,7 +441,6 @@ appContainer.innerHTML = ''; currentTask = taskPath[taskPath.length - 1]; - // Generate and display breadcrumbs updateBreadcrumbs(currentTask); const parentElement = createTaskElement(currentTask, true); @@ -434,7 +457,6 @@ const parentCheckbox = parentElement.querySelector('input[type="checkbox"]'); updateCheckboxState(parentCheckbox, currentTask.state); - // Maintain focus on the selected task if (currentTask.selectedSubtaskId) { const selectedTask = currentTask.subtasks.find(t => t.id === currentTask.selectedSubtaskId); if (selectedTask) {