allow deletion of currently viewed parent task

Hunter
Tuesday, July 30th, 2024 at 5:32 PM

todo.html+112-90

todo.html
 51  			let taskPath = [currentTask];
 52  			let globalBackspaceBlock = false;
 53  
    -			function createTaskElement(task, isParent = false) {
 54 +			function createTaskElement(task, isParentTask = false) {
 55  				const taskContainer = document.createElement('div');
 56  				taskContainer.className = 'task-container';
 57  				taskContainer.dataset.id = task.id;
    -				if (isParent) taskContainer.classList.add('parent-task');
 58 +				if (isParentTask) taskContainer.classList.add('parent-task');
 59  
 60  				const checkbox = document.createElement('input');
 61  				checkbox.type = 'checkbox';

 79  									e.preventDefault();
 80  									return;
 81  								}
    -								if (taskInput.value === '' && canDelete && task !== taskPath[0]) {
 82 +								if (taskInput.value === '' && canDelete) {
 83  									e.preventDefault();
    -									globalBackspaceBlock = true;
    -									deleteTask(task);
 84 +									if (task !== taskPath[0]) {
 85 +										globalBackspaceBlock = true;
 86 +										if (task === currentTask) {
 87 +											deleteCurrentParentTask();
 88 +										} else {
 89 +											deleteSubtask(task);
 90 +										}
 91 +									}
 92  								} else if (taskInput.value !== '') {
 93  									canDelete = false;
 94  								}

124  				let currentTask = rootTask;
125  				let currentDepth = 0;
126  
    -				// Add empty circles for levels before the current one
127  				for (let i = 0; i < currentPath.length - 1; i++) {
128  					breadcrumbs += '○ ';
129  					currentTask = currentTask.subtasks.find(t => t.id === currentPath[i + 1].id);
130  				}
131  
    -				// Add filled circle for the current level
132  				breadcrumbs += '● ';
133  				currentDepth = currentPath.length - 1;
134  
    -				// If the selected task is not the current view's parent task, add empty circles for potential deeper levels
135  				if (selectedTaskId !== currentTask.id) {
136  					let selectedTask = currentTask.subtasks.find(t => t.id === selectedTaskId);
137  					

143  
144  						let maxDepth = calculateMaxDepth(selectedTask, currentDepth + 1);
145  
    -						// Add empty circles for potential deeper levels
146  						for (let i = currentDepth + 1; i < maxDepth; i++) {
147  							breadcrumbs += '○ ';
148  						}

159  
160  			function toggleTaskState(task) {
161  				if (task.state === 1) {
    -					// If the task is checked, only uncheck it if it's not a parent with all checked direct children
162  					if (task.subtasks.length === 0 || !task.subtasks.every(t => t.state === 1)) {
163  						task.state = 0;
164  						if (task.subtasks.length > 0) {
    -							// If it's a parent task, set indeterminate subtasks to unchecked
165  							task.subtasks.forEach(subtask => {
166  								if (subtask.state === 2) {
167  									subtask.state = 0;

169  							});
170  						}
171  					}
    -					// If it's a parent with all direct children checked, do nothing (stay checked)
172  				} else {
    -					// If the task is unchecked or indeterminate, check it
173  					task.state = 1;
174  					if (task.subtasks.length > 0) {
    -						// If it's a parent task, set unchecked subtasks to indeterminate
175  						task.subtasks.forEach(subtask => {
176  							if (subtask.state === 0) {
    -								subtask.state = 2; // Set to indeterminate
177 +								subtask.state = 2;
178  							}
179  						});
180  					}
181  				}
    -				updateParentState(task);
182 +				updateParentTaskState(task);
183  				renderCurrentView();
184  				selectAndFocusTask(task);
185  			}
186  
    -			function updateParentState(task) {
187 +			function updateParentTaskState(task) {
188  				const parent = findParentTask(task);
189  				if (parent) {
190  					const allChecked = parent.subtasks.every(t => t.state === 1);
191  					const anyUnchecked = parent.subtasks.some(t => t.state === 0);
192  
193  					if (allChecked) {
    -						parent.state = 1; // Checked
194 +						parent.state = 1;
195  					} else if (anyUnchecked) {
    -						parent.state = 0; // Unchecked
196 +						parent.state = 0;
197  					}
    -					// If all children are either checked or indeterminate, don't change the parent's state
198  
    -					updateParentState(parent);
199 +					updateParentTaskState(parent);
200  				}
201  			}
202  

222  			function handleKeyDown(e, task) {
223  				if (e.key === 'Enter' && !e.shiftKey) {
224  					e.preventDefault();
    -					addNewTask(currentTask, task);
225 +					addNewSubtask(currentTask, task);
226  				} else if (e.key === 'Enter' && e.shiftKey) {
227  					e.preventDefault();
228  					toggleTaskState(task);
229  				} else if (e.key === 'Backspace' && e.target.value === '' && task !== currentTask) {
230  					e.preventDefault();
    -					deleteTask(task);
231 +					if (task === currentTask) {
232 +						deleteCurrentParentTask();
233 +					} else {
234 +						deleteSubtask(task);
235 +					}
236  				} else if (e.key === 'ArrowUp' && !e.shiftKey) {
237  					e.preventDefault();
238  					navigateTasks('up');

241  					navigateTasks('down');
242  				} else if (e.key === 'ArrowUp' && e.shiftKey) {
243  					e.preventDefault();
    -					moveTask(task, 'up');
244 +					moveSubtask(task, 'up');
245  				} else if (e.key === 'ArrowDown' && e.shiftKey) {
246  					e.preventDefault();
    -					moveTask(task, 'down');
247 +					moveSubtask(task, 'down');
248  				} else if (e.key === 'ArrowRight' && e.shiftKey) {
249  					e.preventDefault();
250  					if (task !== currentTask) {
    -						navigateInto(task);
251 +						navigateIntoSubtask(task);
252  					}
253  				} else if (e.key === 'ArrowLeft' && e.shiftKey) {
254  					e.preventDefault();
    -					navigateOut();
255 +					navigateToParentTask();
256  				}
257  			}
258  
    -			function moveTask(task, direction) {
    -				const parent = findParentTask(task);
    -				if (!parent) return; // Can't move the root task
259 +			function moveSubtask(subtask, direction) {
260 +				const parentTask = findParentTask(subtask);
261 +				if (!parentTask) return;
262  
    -				const index = parent.subtasks.findIndex(t => t.id === task.id);
    -				if (index === -1) return; // Task not found in parent's subtasks
263 +				const index = parentTask.subtasks.findIndex(t => t.id === subtask.id);
264 +				if (index === -1) return;
265  
266  				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]];
267 +					[parentTask.subtasks[index - 1], parentTask.subtasks[index]] = [parentTask.subtasks[index], parentTask.subtasks[index - 1]];
268 +				} else if (direction === 'down' && index < parentTask.subtasks.length - 1) {
269 +					[parentTask.subtasks[index], parentTask.subtasks[index + 1]] = [parentTask.subtasks[index + 1], parentTask.subtasks[index]];
270  				}
271  
272  				renderCurrentView();
    -				selectAndFocusTask(task);
273 +				selectAndFocusTask(subtask);
274  			}
275  
    -			function addNewTask(parentTask, currentSubtask = null) {
    -				const newTask = { id: Date.now(), text: '', state: 0, subtasks: [], selectedSubtaskId: null };
276 +			function addNewSubtask(parentTask, currentSubtask = null) {
277 +				const newSubtask = { id: Date.now(), text: '', state: 0, subtasks: [], selectedSubtaskId: null };
278  				if (currentSubtask) {
279  					const index = parentTask.subtasks.findIndex(t => t.id === currentSubtask.id);
    -					parentTask.subtasks.splice(index + 1, 0, newTask);
280 +					parentTask.subtasks.splice(index + 1, 0, newSubtask);
281  				} else {
    -					parentTask.subtasks.push(newTask);
282 +					parentTask.subtasks.push(newSubtask);
283  				}
284  
285  				parentTask.state = 0;

290  				});
291  
292  				renderCurrentView();
    -				selectAndFocusTask(newTask);
293 +				selectAndFocusTask(newSubtask);
294  			}
295  
296  			function selectAndFocusTask(task) {

301  				}
302  			}
303  
    -			function deleteTask(task) {
    -				const parent = taskPath[taskPath.length - 1];
    -				const index = parent.subtasks.findIndex(t => t.id === task.id);
304 +			function deleteSubtask(subtask) {
305 +				const parentTask = taskPath[taskPath.length - 1];
306 +				const index = parentTask.subtasks.findIndex(t => t.id === subtask.id);
307  
    -				if (parent.id === 'root' && parent.subtasks.length === 1) {
308 +				if (parentTask.id === 'root' && parentTask.subtasks.length === 1) {
309  					return;
310  				}
311  
    -				parent.subtasks = parent.subtasks.filter(t => t.id !== task.id);
    -				updateParentState(parent);
312 +				parentTask.subtasks = parentTask.subtasks.filter(t => t.id !== subtask.id);
313 +				updateParentTaskState(parentTask);
314  
    -				if (parent.subtasks.length === 0 && taskPath.length > 1) {
    -					navigateOut();
315 +				if (parentTask.subtasks.length === 0 && taskPath.length > 1) {
316 +					navigateToParentTask();
317  				} else {
318  					renderCurrentView();
    -					if (parent.subtasks.length > 0) {
319 +					if (parentTask.subtasks.length > 0) {
320  						const targetIndex = Math.max(0, index - 1);
    -						selectAndFocusTask(parent.subtasks[targetIndex]);
321 +						selectAndFocusTask(parentTask.subtasks[targetIndex]);
322  					} else {
    -						selectAndFocusTask(parent);
323 +						selectAndFocusTask(parentTask);
324 +					}
325 +				}
326 +			}
327 +
328 +			function deleteCurrentParentTask() {
329 +				if (taskPath.length <= 1) return; // Don't delete root task
330 +
331 +				const currentParentTask = taskPath[taskPath.length - 1];
332 +				const grandparentTask = taskPath[taskPath.length - 2];
333 +
334 +				// Check if we're trying to delete the sole child of the root
335 +				if (grandparentTask.id === 'root' && grandparentTask.subtasks.length === 1) {
336 +					return; // Don't allow deletion of the sole child of root
337 +				}
338 +
339 +				const index = grandparentTask.subtasks.findIndex(t => t.id === currentParentTask.id);
340 +
341 +				grandparentTask.subtasks = grandparentTask.subtasks.filter(t => t.id !== currentParentTask.id);
342 +				updateParentTaskState(grandparentTask);
343 +
344 +				taskPath.pop(); // Remove the deleted task from the path
345 +				currentTask = grandparentTask;
346 +
347 +				if (grandparentTask.subtasks.length === 0 && taskPath.length > 1) {
348 +					// If we've just deleted the last subtask, navigate up another level
349 +					navigateToParentTask();
350 +				} else {
351 +					renderCurrentView();
352 +					if (grandparentTask.subtasks.length > 0) {
353 +						// Select the subtask before the deleted one, or the one after if at the start
354 +						const targetIndex = Math.max(0, index - 1);
355 +						selectAndFocusTask(grandparentTask.subtasks[targetIndex]);
356 +					} else {
357 +						selectAndFocusTask(grandparentTask);
358  					}
359  				}
360  			}

372  					}
373  				} else {
374  					if (currentIndex === tasks.length) {
    -						addNewTask(currentTask);
375 +						addNewSubtask(currentTask);
376  					} else {
377  						selectAndFocusTask(tasks[currentIndex]);
378  					}
379  				}
380  			}
381  
    -			function navigateInto(task) {
    -				if (task.subtasks.length > 0) {
    -					currentTask.selectedSubtaskId = task.id;
    -					taskPath.push(task);
    -					currentTask = task;
382 +			function navigateIntoSubtask(subtask) {
383 +				if (subtask.subtasks.length > 0) {
384 +					currentTask.selectedSubtaskId = subtask.id;
385 +					taskPath.push(subtask);
386 +					currentTask = subtask;
387  					updateBreadcrumbs(currentTask);
388  					renderCurrentView();
    -					const selectedTask = task.selectedSubtaskId ? 
    -						task.subtasks.find(t => t.id === task.selectedSubtaskId) : 
    -						task.subtasks[0];
    -					selectAndFocusTask(selectedTask);
389 +					const selectedSubtask = subtask.selectedSubtaskId ? 
390 +						subtask.subtasks.find(t => t.id === subtask.selectedSubtaskId) : 
391 +						subtask.subtasks[0];
392 +					selectAndFocusTask(selectedSubtask);
393  				} else {
    -					addNewTask(task);
    -					currentTask.selectedSubtaskId = task.id;
    -					taskPath.push(task);
    -					currentTask = task;
394 +					addNewSubtask(subtask);
395 +					currentTask.selectedSubtaskId = subtask.id;
396 +					taskPath.push(subtask);
397 +					currentTask = subtask;
398  					updateBreadcrumbs(currentTask);
399  					renderCurrentView();
    -					selectAndFocusTask(task.subtasks[0]);
400 +					selectAndFocusTask(subtask.subtasks[0]);
401  				}
402  			}
403  
    -			function navigateOut() {
404 +			function navigateToParentTask() {
405  				if (taskPath.length > 1) {
406  					const currentTaskId = currentTask.id;
407  					taskPath.pop();
408  					currentTask = taskPath[taskPath.length - 1];
    -					updateParentState(currentTask);
409 +					updateParentTaskState(currentTask);
410  					updateBreadcrumbs(currentTask);
411  					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
412 +					const selectedSubtask = currentTask.subtasks.find(t => t.id === currentTaskId);
413 +					if (selectedSubtask) {
414 +						selectAndFocusTask(selectedSubtask);
415 +					} else if (currentTask.subtasks.length > 0) {
416 +						// If for some reason the previous subtask isn't found, select the first subtask
417 +						selectAndFocusTask(currentTask.subtasks[0]);
418 +					} else {
419 +						selectAndFocusTask(currentTask);
420  					}
    -					// If some subtasks are checked and some are not, we don't change the parent's state
421 +					currentTask.selectedSubtaskId = currentTaskId;
422  				}
423  			}
424  

428  				if (task !== currentTask) {
429  					currentTask.selectedSubtaskId = task.id;
430  				}
    -				// Update breadcrumbs when active task changes
431  				updateBreadcrumbs(task);
432  			}
433  

441  				appContainer.innerHTML = '';
442  				currentTask = taskPath[taskPath.length - 1];
443  
    -				// Generate and display breadcrumbs
444  				updateBreadcrumbs(currentTask);
445  
446  				const parentElement = createTaskElement(currentTask, true);

457  				const parentCheckbox = parentElement.querySelector('input[type="checkbox"]');
458  				updateCheckboxState(parentCheckbox, currentTask.state);
459  
    -				// Maintain focus on the selected task
460  				if (currentTask.selectedSubtaskId) {
461  					const selectedTask = currentTask.subtasks.find(t => t.id === currentTask.selectedSubtaskId);
462  					if (selectedTask) {