implement persistent storage with 1sec debounce

Hunter
Thursday, August 1st, 2024 at 7:28 PM

index.html+75-1

index.html
 48  	<script>
 49  		document.addEventListener('DOMContentLoaded', function() {
 50  			const appContainer = document.getElementById('app-container');
    -			let currentTask = { id: 'root', text: 'todo', state: 0, subtasks: [{ id: Date.now(), text: '', state: 0, subtasks: [] }], selectedSubtaskId: null };
 51 +			let rootTask = loadFromLocalStorage();
 52 +			let currentTask = rootTask;
 53  			let taskPath = [currentTask];
 54  			let lastSubtaskDownArrowReleased = false;
 55 +			let saveTimer = null;
 56 +
 57 +			function scheduleSave() {
 58 +				if (saveTimer) {
 59 +					clearTimeout(saveTimer);
 60 +				}
 61 +				saveTimer = setTimeout(saveToLocalStorage, 1000);
 62 +			}
 63 +
 64 +			function saveToLocalStorage() {
 65 +				const serializedTasks = serializeTaskTree(taskPath[0]);
 66 +				localStorage.setItem('taskTree', serializedTasks);
 67 +				saveTimer = null;
 68 +			}
 69 +
 70 +			function serializeTaskTree(task, depth = 0) {
 71 +				const indentation = '\t'.repeat(depth);
 72 +				let status = task.state === 0 ? '_' : (task.state === 1 ? 'x' : '?');
 73 +				let serialized = `${indentation}${status} ${task.text}\n`;
 74 +				
 75 +				for (let subtask of task.subtasks) {
 76 +					serialized += serializeTaskTree(subtask, depth + 1);
 77 +				}
 78 +				
 79 +				return serialized;
 80 +			}
 81 +
 82 +			function loadFromLocalStorage() {
 83 +				const savedTasks = localStorage.getItem('taskTree');
 84 +				if (savedTasks) {
 85 +					return deserializeTaskTree(savedTasks);
 86 +				} else {
 87 +					return { id: 'root', text: 'todo', state: 0, subtasks: [{ id: Date.now(), text: '', state: 0, subtasks: [] }], selectedSubtaskId: null };
 88 +				}
 89 +			}
 90 +
 91 +			function deserializeTaskTree(serialized) {
 92 +				const lines = serialized.split('\n').filter(line => line.trim() !== '');
 93 +				const root = { id: 'root', subtasks: [] };
 94 +				const stack = [{ task: root, depth: -1 }];
 95 +
 96 +				for (let line of lines) {
 97 +					const depth = (line.match(/^\t*/)[0] || '').length;
 98 +					const status = line[depth];
 99 +					const text = line.slice(depth + 2);
100 +					
101 +					const newTask = {
102 +						id: Date.now() + Math.random(),
103 +						text: text,
104 +						state: status === '_' ? 0 : (status === 'x' ? 1 : 2),
105 +						subtasks: [],
106 +						selectedSubtaskId: null
107 +					};
108 +					
109 +					while (stack.length > 1 && stack[stack.length - 1].depth >= depth) {
110 +						stack.pop();
111 +					}
112 +					
113 +					if (depth === 0) {
114 +						Object.assign(root, newTask);
115 +					} else {
116 +						stack[stack.length - 1].task.subtasks.push(newTask);
117 +					}
118 +					stack.push({ task: newTask, depth: depth });
119 +				}
120 +				
121 +				return root;
122 +			}
123  
124  			const keyHandler = {
125  				backspace: {

243  					if (task === currentTask) {
244  						updatePageTitle(task);
245  					}
246 +					scheduleSave();
247  				});
248  
249  				taskInput.addEventListener('focus', () => setActiveTask(taskInput, task));

317  				updateParentTaskState(task);
318  				renderCurrentView();
319  				selectAndFocusTask(task);
320 +				scheduleSave();
321  			}
322  
323  			function updateParentTaskState(task) {

406  
407  				renderCurrentView();
408  				selectAndFocusTask(subtask);
409 +				scheduleSave();
410  			}
411  
412  			function addNewSubtask(parentTask, currentSubtask = null) {

427  
428  				renderCurrentView();
429  				selectAndFocusTask(newSubtask);
430 +				scheduleSave();
431  			}
432  
433  			function selectAndFocusTask(task) {

460  						selectAndFocusTask(parentTask);
461  					}
462  				}
463 +				scheduleSave();
464  			}
465  
466  			function deleteCurrentParentTask() {