commit b9562e0956c511508a027ff1b19a3964db1562b9
Author: Hunter
Date: Thu, 25 Jul 2024 23:07:29 -0400
initial commit
Diffstat:
| A | todo.html | | | 333 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 333 insertions(+), 0 deletions(-)
diff --git a/todo.html b/todo.html
@@ -0,0 +1,333 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Advanced Task Tracker</title>
+ <style>
+ body {
+ font-family: Arial, sans-serif;
+ max-width: 800px;
+ margin: 0 auto;
+ padding: 20px;
+ }
+ ul {
+ list-style-type: none;
+ padding-left: 20px;
+ }
+ input[type="text"] {
+ border: none;
+ background: transparent;
+ font-size: 1.17em;
+ font-weight: bold;
+ width: calc(100% - 30px);
+ margin-left: 5px;
+ padding: 5px;
+ }
+ input[type="text"]:focus {
+ outline: none;
+ }
+ .task-container {
+ display: flex;
+ align-items: center;
+ margin: 5px 0;
+ }
+ .active {
+ background-color: #e6f3ff;
+ }
+ .parent-task {
+ font-size: 1.5em;
+ font-weight: bold;
+ }
+ </style>
+ <script>
+ document.addEventListener('DOMContentLoaded', function() {
+ const appContainer = document.getElementById('app-container');
+ let currentTask = { id: 'root', text: 'todo', state: 0, subtasks: [{ id: Date.now(), text: '', state: 0, subtasks: [] }], selectedSubtaskId: null };
+ let taskPath = [currentTask];
+
+ function createTaskElement(task, isParent = false) {
+ const taskContainer = document.createElement('div');
+ taskContainer.className = 'task-container';
+ taskContainer.dataset.id = task.id;
+ if (isParent) taskContainer.classList.add('parent-task');
+
+ const checkbox = document.createElement('input');
+ checkbox.type = 'checkbox';
+ updateCheckboxState(checkbox, task.state);
+ checkbox.addEventListener('click', (e) => {
+ e.preventDefault(); // Prevent default checkbox behavior
+ toggleTaskState(task);
+ });
+
+ const taskInput = document.createElement('input');
+ taskInput.type = 'text';
+ taskInput.value = task.text;
+ taskInput.addEventListener('keydown', (e) => handleKeyDown(e, task));
+ taskInput.addEventListener('input', () => task.text = taskInput.value);
+ taskInput.addEventListener('focus', () => setActiveTask(taskInput, task));
+
+ taskContainer.appendChild(checkbox);
+ taskContainer.appendChild(taskInput);
+
+ return taskContainer;
+ }
+
+ function updateCheckboxState(checkbox, state) {
+ checkbox.checked = state === 1;
+ checkbox.indeterminate = state === 2;
+ }
+
+ function toggleTaskState(task) {
+ if (task.state === 1) {
+ // If the task is checked, only uncheck it if it's not a parent with all checked direct children
+ if (task.subtasks.length === 0 || !task.subtasks.every(t => t.state === 1)) {
+ task.state = 0;
+ if (task.subtasks.length > 0) {
+ // If it's a parent task, set indeterminate subtasks to unchecked
+ task.subtasks.forEach(subtask => {
+ if (subtask.state === 2) {
+ subtask.state = 0;
+ }
+ });
+ }
+ }
+ // If it's a parent with all direct children checked, do nothing (stay checked)
+ } else {
+ // If the task is unchecked or indeterminate, check it
+ task.state = 1;
+ if (task.subtasks.length > 0) {
+ // If it's a parent task, set unchecked subtasks to indeterminate
+ task.subtasks.forEach(subtask => {
+ if (subtask.state === 0) {
+ subtask.state = 2; // Set to indeterminate
+ }
+ });
+ }
+ }
+ updateParentState(task);
+ renderCurrentView();
+ selectAndFocusTask(task);
+ }
+
+ function updateParentState(task) {
+ const parent = findParentTask(task);
+ if (parent) {
+ const allChecked = parent.subtasks.every(t => t.state === 1);
+ const anyUnchecked = parent.subtasks.some(t => t.state === 0);
+
+ if (allChecked) {
+ parent.state = 1; // Checked
+ } else if (anyUnchecked) {
+ parent.state = 0; // Unchecked
+ }
+ // If all children are either checked or indeterminate, don't change the parent's state
+
+ updateParentState(parent);
+ }
+ }
+
+ function updateSubtasksState(task, state) {
+ task.subtasks.forEach(subtask => {
+ subtask.state = state;
+ if (subtask.subtasks.length > 0) {
+ updateSubtasksState(subtask, state);
+ }
+ });
+ }
+
+ function findParentTask(task) {
+ for (let i = taskPath.length - 1; i >= 0; i--) {
+ const potentialParent = taskPath[i];
+ if (potentialParent.subtasks.some(t => t.id === task.id)) {
+ return potentialParent;
+ }
+ }
+ return null;
+ }
+
+ function handleKeyDown(e, task) {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ if (e.shiftKey) {
+ toggleTaskState(task);
+ } else {
+ addNewTask(currentTask, task);
+ }
+ } else if (e.key === 'Backspace' && e.target.value === '' && task !== currentTask) {
+ e.preventDefault();
+ deleteTask(task);
+ } else if (e.key === 'ArrowUp') {
+ e.preventDefault();
+ navigateTasks('up');
+ } else if (e.key === 'ArrowDown') {
+ e.preventDefault();
+ navigateTasks('down');
+ } else if (e.key === 'ArrowRight' && e.shiftKey) {
+ e.preventDefault();
+ if (task !== currentTask) {
+ navigateInto(task);
+ }
+ } else if (e.key === 'ArrowLeft' && e.shiftKey) {
+ e.preventDefault();
+ navigateOut();
+ }
+ }
+
+ function addNewTask(parentTask, currentSubtask = null) {
+ const newTask = { id: Date.now(), text: '', state: 0, subtasks: [], selectedSubtaskId: null };
+ if (currentSubtask) {
+ const index = parentTask.subtasks.findIndex(t => t.id === currentSubtask.id);
+ parentTask.subtasks.splice(index + 1, 0, newTask);
+ } else {
+ parentTask.subtasks.push(newTask);
+ }
+
+ parentTask.state = 0;
+ parentTask.subtasks.forEach(subtask => {
+ if (subtask.state === 2) {
+ subtask.state = 0;
+ }
+ });
+
+ renderCurrentView();
+ selectAndFocusTask(newTask);
+ }
+
+ function selectAndFocusTask(task) {
+ const taskInput = document.querySelector(`.task-container[data-id="${task.id}"] input[type="text"]`);
+ if (taskInput) {
+ taskInput.focus();
+ setActiveTask(taskInput, task);
+ }
+ }
+
+ function deleteTask(task) {
+ const parent = taskPath[taskPath.length - 1];
+ const index = parent.subtasks.findIndex(t => t.id === task.id);
+
+ if (parent.id === 'root' && parent.subtasks.length === 1) {
+ return;
+ }
+
+ parent.subtasks = parent.subtasks.filter(t => t.id !== task.id);
+ updateParentState(parent);
+
+ if (parent.subtasks.length === 0 && taskPath.length > 1) {
+ navigateOut();
+ } else {
+ renderCurrentView();
+ if (parent.subtasks.length > 0) {
+ const targetIndex = Math.max(0, index - 1);
+ selectAndFocusTask(parent.subtasks[targetIndex]);
+ } else {
+ selectAndFocusTask(parent);
+ }
+ }
+ }
+
+ function navigateTasks(direction) {
+ const tasks = currentTask.subtasks;
+ const currentElement = document.activeElement;
+ const currentContainer = currentElement.closest('.task-container');
+ const currentIndex = Array.from(appContainer.querySelectorAll('.task-container')).indexOf(currentContainer);
+
+ if (direction === 'up') {
+ if (currentIndex > 0) {
+ const prevTask = currentIndex === 1 ? currentTask : tasks[currentIndex - 2];
+ selectAndFocusTask(prevTask);
+ }
+ } else {
+ if (currentIndex === tasks.length) {
+ addNewTask(currentTask);
+ } else {
+ selectAndFocusTask(tasks[currentIndex]);
+ }
+ }
+ }
+
+ function navigateInto(task) {
+ if (task.subtasks.length > 0) {
+ currentTask.selectedSubtaskId = task.id;
+ taskPath.push(task);
+ currentTask = task; // Add this line
+ renderCurrentView();
+ const selectedTask = task.selectedSubtaskId ?
+ task.subtasks.find(t => t.id === task.selectedSubtaskId) :
+ task.subtasks[0];
+ selectAndFocusTask(selectedTask);
+ } else {
+ addNewTask(task);
+ currentTask.selectedSubtaskId = task.id;
+ taskPath.push(task);
+ currentTask = task; // Add this line
+ renderCurrentView();
+ selectAndFocusTask(task.subtasks[0]);
+ }
+ }
+
+ function navigateOut() {
+ if (taskPath.length > 1) {
+ const currentTaskId = currentTask.id;
+ taskPath.pop();
+ currentTask = taskPath[taskPath.length - 1];
+ updateParentState(currentTask);
+ 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
+ }
+ // If some subtasks are checked and some are not, we don't change the parent's state
+ }
+ }
+
+ function setActiveTask(input, task) {
+ document.querySelectorAll('.active').forEach(el => el.classList.remove('active'));
+ input.closest('.task-container').classList.add('active');
+ if (task !== currentTask) {
+ currentTask.selectedSubtaskId = task.id;
+ }
+ }
+
+ function renderCurrentView() {
+ appContainer.innerHTML = '';
+ currentTask = taskPath[taskPath.length - 1];
+
+ const parentElement = createTaskElement(currentTask, true);
+ appContainer.appendChild(parentElement);
+
+ const subtasksList = document.createElement('ul');
+ currentTask.subtasks.forEach(subtask => {
+ const li = document.createElement('li');
+ li.appendChild(createTaskElement(subtask));
+ subtasksList.appendChild(li);
+ });
+ appContainer.appendChild(subtasksList);
+
+ const parentCheckbox = parentElement.querySelector('input[type="checkbox"]');
+ updateCheckboxState(parentCheckbox, currentTask.state);
+ }
+
+ renderCurrentView();
+ if (currentTask.subtasks.length > 0) {
+ setTimeout(() => selectAndFocusTask(currentTask.subtasks[0]), 0);
+ }
+ });
+ </script>
+</head>
+<body>
+ <div id="app-container"></div>
+</body>
+</html>