initial commit

Hunter
Thursday, July 25th, 2024 at 11:07 PM

todo.htmlnew

todo.html
  1 +<!DOCTYPE html>
  2 +<html lang="en">
  3 +<head>
  4 +	<meta charset="UTF-8">
  5 +	<meta name="viewport" content="width=device-width, initial-scale=1.0">
  6 +	<title>Advanced Task Tracker</title>
  7 +	<style>
  8 +		body {
  9 +			font-family: Arial, sans-serif;
 10 +			max-width: 800px;
 11 +			margin: 0 auto;
 12 +			padding: 20px;
 13 +		}
 14 +		ul {
 15 +			list-style-type: none;
 16 +			padding-left: 20px;
 17 +		}
 18 +		input[type="text"] {
 19 +			border: none;
 20 +			background: transparent;
 21 +			font-size: 1.17em;
 22 +			font-weight: bold;
 23 +			width: calc(100% - 30px);
 24 +			margin-left: 5px;
 25 +			padding: 5px;
 26 +		}
 27 +		input[type="text"]:focus {
 28 +			outline: none;
 29 +		}
 30 +		.task-container {
 31 +			display: flex;
 32 +			align-items: center;
 33 +			margin: 5px 0;
 34 +		}
 35 +		.active {
 36 +			background-color: #e6f3ff;
 37 +		}
 38 +		.parent-task {
 39 +			font-size: 1.5em;
 40 +			font-weight: bold;
 41 +		}
 42 +	</style>
 43 +	<script>
 44 +		document.addEventListener('DOMContentLoaded', function() {
 45 +			const appContainer = document.getElementById('app-container');
 46 +			let currentTask = { id: 'root', text: 'todo', state: 0, subtasks: [{ id: Date.now(), text: '', state: 0, subtasks: [] }], selectedSubtaskId: null };
 47 +			let taskPath = [currentTask];
 48 +
 49 +			function createTaskElement(task, isParent = false) {
 50 +				const taskContainer = document.createElement('div');
 51 +				taskContainer.className = 'task-container';
 52 +				taskContainer.dataset.id = task.id;
 53 +				if (isParent) taskContainer.classList.add('parent-task');
 54 +
 55 +				const checkbox = document.createElement('input');
 56 +				checkbox.type = 'checkbox';
 57 +				updateCheckboxState(checkbox, task.state);
 58 +				checkbox.addEventListener('click', (e) => {
 59 +					e.preventDefault(); // Prevent default checkbox behavior
 60 +					toggleTaskState(task);
 61 +				});
 62 +
 63 +				const taskInput = document.createElement('input');
 64 +				taskInput.type = 'text';
 65 +				taskInput.value = task.text;
 66 +				taskInput.addEventListener('keydown', (e) => handleKeyDown(e, task));
 67 +				taskInput.addEventListener('input', () => task.text = taskInput.value);
 68 +				taskInput.addEventListener('focus', () => setActiveTask(taskInput, task));
 69 +
 70 +				taskContainer.appendChild(checkbox);
 71 +				taskContainer.appendChild(taskInput);
 72 +
 73 +				return taskContainer;
 74 +			}
 75 +
 76 +			function updateCheckboxState(checkbox, state) {
 77 +				checkbox.checked = state === 1;
 78 +				checkbox.indeterminate = state === 2;
 79 +			}
 80 +
 81 +			function toggleTaskState(task) {
 82 +				if (task.state === 1) {
 83 +					// If the task is checked, only uncheck it if it's not a parent with all checked direct children
 84 +					if (task.subtasks.length === 0 || !task.subtasks.every(t => t.state === 1)) {
 85 +						task.state = 0;
 86 +						if (task.subtasks.length > 0) {
 87 +							// If it's a parent task, set indeterminate subtasks to unchecked
 88 +							task.subtasks.forEach(subtask => {
 89 +								if (subtask.state === 2) {
 90 +									subtask.state = 0;
 91 +								}
 92 +							});
 93 +						}
 94 +					}
 95 +					// If it's a parent with all direct children checked, do nothing (stay checked)
 96 +				} else {
 97 +					// If the task is unchecked or indeterminate, check it
 98 +					task.state = 1;
 99 +					if (task.subtasks.length > 0) {
100 +						// If it's a parent task, set unchecked subtasks to indeterminate
101 +						task.subtasks.forEach(subtask => {
102 +							if (subtask.state === 0) {
103 +								subtask.state = 2; // Set to indeterminate
104 +							}
105 +						});
106 +					}
107 +				}
108 +				updateParentState(task);
109 +				renderCurrentView();
110 +				selectAndFocusTask(task);
111 +			}
112 +
113 +			function updateParentState(task) {
114 +				const parent = findParentTask(task);
115 +				if (parent) {
116 +					const allChecked = parent.subtasks.every(t => t.state === 1);
117 +					const anyUnchecked = parent.subtasks.some(t => t.state === 0);
118 +
119 +					if (allChecked) {
120 +						parent.state = 1; // Checked
121 +					} else if (anyUnchecked) {
122 +						parent.state = 0; // Unchecked
123 +					}
124 +					// If all children are either checked or indeterminate, don't change the parent's state
125 +
126 +					updateParentState(parent);
127 +				}
128 +			}
129 +
130 +			function updateSubtasksState(task, state) {
131 +				task.subtasks.forEach(subtask => {
132 +					subtask.state = state;
133 +					if (subtask.subtasks.length > 0) {
134 +						updateSubtasksState(subtask, state);
135 +					}
136 +				});
137 +			}
138 +
139 +			function findParentTask(task) {
140 +				for (let i = taskPath.length - 1; i >= 0; i--) {
141 +					const potentialParent = taskPath[i];
142 +					if (potentialParent.subtasks.some(t => t.id === task.id)) {
143 +						return potentialParent;
144 +					}
145 +				}
146 +				return null;
147 +			}
148 +
149 +			function handleKeyDown(e, task) {
150 +				if (e.key === 'Enter') {
151 +					e.preventDefault();
152 +					if (e.shiftKey) {
153 +						toggleTaskState(task);
154 +					} else {
155 +						addNewTask(currentTask, task);
156 +					}
157 +				} else if (e.key === 'Backspace' && e.target.value === '' && task !== currentTask) {
158 +					e.preventDefault();
159 +					deleteTask(task);
160 +				} else if (e.key === 'ArrowUp') {
161 +					e.preventDefault();
162 +					navigateTasks('up');
163 +				} else if (e.key === 'ArrowDown') {
164 +					e.preventDefault();
165 +					navigateTasks('down');
166 +				} else if (e.key === 'ArrowRight' && e.shiftKey) {
167 +					e.preventDefault();
168 +					if (task !== currentTask) {
169 +						navigateInto(task);
170 +					}
171 +				} else if (e.key === 'ArrowLeft' && e.shiftKey) {
172 +					e.preventDefault();
173 +					navigateOut();
174 +				}
175 +			}
176 +
177 +			function addNewTask(parentTask, currentSubtask = null) {
178 +				const newTask = { id: Date.now(), text: '', state: 0, subtasks: [], selectedSubtaskId: null };
179 +				if (currentSubtask) {
180 +					const index = parentTask.subtasks.findIndex(t => t.id === currentSubtask.id);
181 +					parentTask.subtasks.splice(index + 1, 0, newTask);
182 +				} else {
183 +					parentTask.subtasks.push(newTask);
184 +				}
185 +
186 +				parentTask.state = 0;
187 +				parentTask.subtasks.forEach(subtask => {
188 +					if (subtask.state === 2) {
189 +						subtask.state = 0;
190 +					}
191 +				});
192 +
193 +				renderCurrentView();
194 +				selectAndFocusTask(newTask);
195 +			}
196 +
197 +			function selectAndFocusTask(task) {
198 +				const taskInput = document.querySelector(`.task-container[data-id="${task.id}"] input[type="text"]`);
199 +				if (taskInput) {
200 +					taskInput.focus();
201 +					setActiveTask(taskInput, task);
202 +				}
203 +			}
204 +
205 +			function deleteTask(task) {
206 +				const parent = taskPath[taskPath.length - 1];
207 +				const index = parent.subtasks.findIndex(t => t.id === task.id);
208 +
209 +				if (parent.id === 'root' && parent.subtasks.length === 1) {
210 +					return;
211 +				}
212 +
213 +				parent.subtasks = parent.subtasks.filter(t => t.id !== task.id);
214 +				updateParentState(parent);
215 +
216 +				if (parent.subtasks.length === 0 && taskPath.length > 1) {
217 +					navigateOut();
218 +				} else {
219 +					renderCurrentView();
220 +					if (parent.subtasks.length > 0) {
221 +						const targetIndex = Math.max(0, index - 1);
222 +						selectAndFocusTask(parent.subtasks[targetIndex]);
223 +					} else {
224 +						selectAndFocusTask(parent);
225 +					}
226 +				}
227 +			}
228 +
229 +			function navigateTasks(direction) {
230 +				const tasks = currentTask.subtasks;
231 +				const currentElement = document.activeElement;
232 +				const currentContainer = currentElement.closest('.task-container');
233 +				const currentIndex = Array.from(appContainer.querySelectorAll('.task-container')).indexOf(currentContainer);
234 +
235 +				if (direction === 'up') {
236 +					if (currentIndex > 0) {
237 +						const prevTask = currentIndex === 1 ? currentTask : tasks[currentIndex - 2];
238 +						selectAndFocusTask(prevTask);
239 +					}
240 +				} else {
241 +					if (currentIndex === tasks.length) {
242 +						addNewTask(currentTask);
243 +					} else {
244 +						selectAndFocusTask(tasks[currentIndex]);
245 +					}
246 +				}
247 +			}
248 +
249 +			function navigateInto(task) {
250 +				if (task.subtasks.length > 0) {
251 +					currentTask.selectedSubtaskId = task.id;
252 +					taskPath.push(task);
253 +					currentTask = task; // Add this line
254 +					renderCurrentView();
255 +					const selectedTask = task.selectedSubtaskId ? 
256 +						task.subtasks.find(t => t.id === task.selectedSubtaskId) : 
257 +						task.subtasks[0];
258 +					selectAndFocusTask(selectedTask);
259 +				} else {
260 +					addNewTask(task);
261 +					currentTask.selectedSubtaskId = task.id;
262 +					taskPath.push(task);
263 +					currentTask = task; // Add this line
264 +					renderCurrentView();
265 +					selectAndFocusTask(task.subtasks[0]);
266 +				}
267 +			}
268 +
269 +			function navigateOut() {
270 +				if (taskPath.length > 1) {
271 +					const currentTaskId = currentTask.id;
272 +					taskPath.pop();
273 +					currentTask = taskPath[taskPath.length - 1];
274 +					updateParentState(currentTask);
275 +					renderCurrentView();
276 +					const selectedTask = currentTask.subtasks.find(t => t.id === currentTaskId);
277 +					selectAndFocusTask(selectedTask || currentTask.subtasks[0]);
278 +					currentTask.selectedSubtaskId = currentTaskId;
279 +				}
280 +			}
281 +
282 +			function reevaluateTaskState(task) {
283 +				if (task.subtasks.length > 0) {
284 +					const allChecked = task.subtasks.every(t => t.state === 1);
285 +					const anyChecked = task.subtasks.some(t => t.state === 1);
286 +
287 +					if (allChecked) {
288 +						task.state = 1; // Checked
289 +					} else if (!anyChecked) {
290 +						task.state = 0; // Unchecked
291 +					}
292 +					// If some subtasks are checked and some are not, we don't change the parent's state
293 +				}
294 +			}
295 +
296 +			function setActiveTask(input, task) {
297 +				document.querySelectorAll('.active').forEach(el => el.classList.remove('active'));
298 +				input.closest('.task-container').classList.add('active');
299 +				if (task !== currentTask) {
300 +					currentTask.selectedSubtaskId = task.id;
301 +				}
302 +			}
303 +
304 +			function renderCurrentView() {
305 +				appContainer.innerHTML = '';
306 +				currentTask = taskPath[taskPath.length - 1];
307 +
308 +				const parentElement = createTaskElement(currentTask, true);
309 +				appContainer.appendChild(parentElement);
310 +
311 +				const subtasksList = document.createElement('ul');
312 +				currentTask.subtasks.forEach(subtask => {
313 +					const li = document.createElement('li');
314 +					li.appendChild(createTaskElement(subtask));
315 +					subtasksList.appendChild(li);
316 +				});
317 +				appContainer.appendChild(subtasksList);
318 +
319 +				const parentCheckbox = parentElement.querySelector('input[type="checkbox"]');
320 +				updateCheckboxState(parentCheckbox, currentTask.state);
321 +			}
322 +
323 +			renderCurrentView();
324 +			if (currentTask.subtasks.length > 0) {
325 +				setTimeout(() => selectAndFocusTask(currentTask.subtasks[0]), 0);
326 +			}
327 +		});
328 +	</script>
329 +</head>
330 +<body>
331 +	<div id="app-container"></div>
332 +</body>
333 +</html>