allow deletion of currently viewed parent task
Hunter
Tuesday, July 30th, 2024 at 5:32 PM
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) {