fix event listener leak; ensure unique IDs (fix jumping bug)
Hunter
Saturday, March 7th, 2026 at 4:44 PM
454 return serialized;
455 }
456
457 + function generateId() {
458 + return Date.now().toString(36) + Math.random().toString(36).slice(2)
459 + }
460 +
461 function loadTasksFromLocalStorage() {
462 const savedTasks = localStorage.getItem('taskTree');
463 if (savedTasks) {
465 console.log(savedTasks);
466 return deserializeTaskTree(savedTasks);
467 } else {
- return { id: 'root', text: 'todo', state: 0, subtasks: [{ id: Date.now(), text: '', state: 0, subtasks: [] }], selectedSubtaskId: null };
468 + return { id: 'root', text: 'todo', state: 0, subtasks: [{ id: generateId(), text: '', state: 0, subtasks: [] }], selectedSubtaskId: null };
469 }
470 }
471
480 const text = line.slice(depth + 2);
481
482 const newTask = {
- id: depth === 0 ? 'root' : Date.now() + Math.random(),
483 + id: depth === 0 ? 'root' : generateId(),
484 text: text,
485 state: status === '_' ? 0 : (status === 'x' ? 1 : 2),
486 subtasks: [],
631 taskInput.addEventListener('keydown', keydownHandler);
632 taskInput.addEventListener('keyup', keyupHandler);
633 taskInput.addEventListener('keydown', handleCopyAndCut);
- appContainer.addEventListener('focusin', function(e) {
- if (e.target.tagName === 'INPUT' && e.target.type === 'text') {
- document.querySelectorAll('input[type="text"]').forEach(input => {
- if (input !== e.target) {
- placeCursorAtBeginning(input);
- }
- });
- }
- });
-
634 taskInput.addEventListener('input', () => {
635 task.text = taskInput.value;
636 if (task === currentTask) {
1053
1054
1055 function addNewSubtask(parentTask, currentSubtask = null) {
- const newSubtask = { id: Date.now(), text: '', state: 0, subtasks: [], selectedSubtaskId: null };
1056 + const newSubtask = { id: generateId(), text: '', state: 0, subtasks: [], selectedSubtaskId: null };
1057 if (currentSubtask) {
1058 const index = parentTask.subtasks.findIndex(t => t.id === currentSubtask.id);
1059 parentTask.subtasks.splice(index + 1, 0, newSubtask);
1083
1084 function navigateTasks(direction) {
1085 const tasks = currentTask.subtasks;
1086 + const subtaskIndex = tasks.findIndex(t => t.id === currentTask.selectedSubtaskId);
1087 +
1088 + // Check if the parent task itself is focused
1089 const currentElement = document.activeElement;
- const currentContainer = currentElement.closest('.task-container');
- const currentIndex = Array.from(appContainer.querySelectorAll('.task-container')).indexOf(currentContainer);
1090 + const currentContainer = currentElement ? currentElement.closest('.task-container') : null;
1091 + const isParentFocused = currentContainer && currentContainer.dataset.id == currentTask.id;
1092
1093 if (direction === 'up') {
- if (currentIndex > 0) {
- const prevTask = currentIndex === 1 ? currentTask : tasks[currentIndex - 2];
- selectAndFocusTask(prevTask);
1094 + if (isParentFocused) {
1095 + // Already at parent, can't go higher
1096 + } else if (subtaskIndex <= 0) {
1097 + // At first subtask or not found, go to parent
1098 + selectAndFocusTask(currentTask);
1099 + } else {
1100 + selectAndFocusTask(tasks[subtaskIndex - 1]);
1101 }
1102 } else {
- if (currentIndex < tasks.length) {
- selectAndFocusTask(tasks[currentIndex]);
1103 + if (isParentFocused) {
1104 + // Move from parent to first subtask
1105 + if (tasks.length > 0) {
1106 + selectAndFocusTask(tasks[0]);
1107 + }
1108 + } else if (subtaskIndex >= 0 && subtaskIndex < tasks.length - 1) {
1109 + selectAndFocusTask(tasks[subtaskIndex + 1]);
1110 }
1111 }
1112 lastSubtaskDownArrowReleased = false;
1443 e.preventDefault();
1444 }
1445
1446 + // Register focusin handler once (not per-element) to reset cursor on non-focused inputs
1447 + appContainer.addEventListener('focusin', function(e) {
1448 + if (e.target.tagName === 'INPUT' && e.target.type === 'text') {
1449 + document.querySelectorAll('input[type="text"]').forEach(input => {
1450 + if (input !== e.target) {
1451 + placeCursorAtBeginning(input);
1452 + }
1453 + });
1454 + }
1455 + });
1456 +
1457 getThemesFromCSS();
1458 setInitialTheme();
1459 renderCurrentView();