commit aee6fcadf42a45c8e4e2447d9e73ff8391ac2867
parent 78efa1f6bc17acd6b4fa81b13ea0c9a8145c521c
Author: Hunter
Date: Wed, 31 Jul 2024 14:59:47 -0400
implement return/down arrow stopping
Diffstat:
| M | index.html | | | 1023 | +++++++++++++++++++++++++++++++++++++++++-------------------------------------- |
1 file changed, 528 insertions(+), 495 deletions(-)
diff --git a/index.html b/index.html
@@ -1,502 +1,534 @@
<!DOCTYPE html>
<html lang="en">
<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🪆</text></svg>">
- <title>todo</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;
- }
- #breadcrumbs {
- font-size: 24px;
- margin-bottom: 10px;
- }
- </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];
- let globalBackspaceBlock = false;
-
- function createTaskElement(task, isParentTask = false) {
- const taskContainer = document.createElement('div');
- taskContainer.className = 'task-container';
- taskContainer.dataset.id = task.id;
- if (isParentTask) taskContainer.classList.add('parent-task');
-
- const checkbox = document.createElement('input');
- checkbox.type = 'checkbox';
- updateCheckboxState(checkbox, task.state);
- checkbox.addEventListener('click', (e) => {
- e.preventDefault();
- toggleTaskState(task);
- });
-
- const taskInput = document.createElement('input');
- taskInput.type = 'text';
- taskInput.value = task.text;
-
- const backspaceHandler = (function() {
- let canDelete = true;
- return {
- keydown: function(e) {
- if (e.key === 'Backspace') {
- if (globalBackspaceBlock) {
- e.preventDefault();
- return;
- }
- if (taskInput.value === '' && canDelete) {
- e.preventDefault();
- if (task !== taskPath[0]) {
- globalBackspaceBlock = true;
- if (task === currentTask) {
- deleteCurrentParentTask();
- } else {
- deleteSubtask(task);
- }
- }
- } else if (taskInput.value !== '') {
- canDelete = false;
- }
- } else {
- handleKeyDown(e, task);
- }
- },
- keyup: function(e) {
- if (e.key === 'Backspace') {
- canDelete = (taskInput.value === '');
- globalBackspaceBlock = false;
- }
- }
- };
- })();
-
- taskInput.addEventListener('keydown', backspaceHandler.keydown);
- taskInput.addEventListener('keyup', backspaceHandler.keyup);
- taskInput.addEventListener('keydown', copyActiveTaskText);
-
- taskInput.addEventListener('input', () => {
- task.text = taskInput.value;
- if (task === currentTask) {
- updatePageTitle(task);
- }
- });
-
- taskInput.addEventListener('focus', () => setActiveTask(taskInput, task));
-
- taskContainer.appendChild(checkbox);
- taskContainer.appendChild(taskInput);
-
- return taskContainer;
- }
-
- function generateBreadcrumbs(rootTask, currentPath, selectedTaskId) {
- let breadcrumbs = '';
- let currentTask = rootTask;
- let currentDepth = 0;
-
- for (let i = 0; i < currentPath.length - 1; i++) {
- breadcrumbs += '○ ';
- currentTask = currentTask.subtasks.find(t => t.id === currentPath[i + 1].id);
- }
-
- breadcrumbs += '● ';
- currentDepth = currentPath.length - 1;
-
- if (selectedTaskId !== currentTask.id) {
- let selectedTask = currentTask.subtasks.find(t => t.id === selectedTaskId);
-
- if (selectedTask) {
- function calculateMaxDepth(task, depth) {
- if (task.subtasks.length === 0) return depth;
- return Math.max(...task.subtasks.map(st => calculateMaxDepth(st, depth + 1)));
- }
-
- let maxDepth = calculateMaxDepth(selectedTask, currentDepth + 1);
-
- for (let i = currentDepth + 1; i < maxDepth; i++) {
- breadcrumbs += '○ ';
- }
- }
- }
-
- return breadcrumbs.trim();
- }
-
- function updateCheckboxState(checkbox, state) {
- checkbox.checked = state === 1;
- checkbox.indeterminate = state === 2;
- }
-
- function toggleTaskState(task) {
- if (task.state === 1) {
- if (task.subtasks.length === 0 || !task.subtasks.every(t => t.state === 1)) {
- task.state = 0;
- if (task.subtasks.length > 0) {
- task.subtasks.forEach(subtask => {
- if (subtask.state === 2) {
- subtask.state = 0;
- }
- });
- }
- }
- } else {
- task.state = 1;
- if (task.subtasks.length > 0) {
- task.subtasks.forEach(subtask => {
- if (subtask.state === 0) {
- subtask.state = 2;
- }
- });
- }
- }
- updateParentTaskState(task);
- renderCurrentView();
- selectAndFocusTask(task);
- }
-
- function updateParentTaskState(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;
- } else if (anyUnchecked) {
- parent.state = 0;
- }
-
- updateParentTaskState(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.shiftKey) {
- e.preventDefault();
- addNewSubtask(currentTask, task);
- } else if (e.key === 'Enter' && e.shiftKey) {
- e.preventDefault();
- toggleTaskState(task);
- } else if (e.key === 'Backspace' && e.target.value === '' && task !== currentTask) {
- e.preventDefault();
- if (task === currentTask) {
- deleteCurrentParentTask();
- } else {
- deleteSubtask(task);
- }
- } else if (e.key === 'ArrowUp' && !e.shiftKey) {
- e.preventDefault();
- navigateTasks('up');
- } else if (e.key === 'ArrowDown' && !e.shiftKey) {
- e.preventDefault();
- navigateTasks('down');
- } else if (e.key === 'ArrowUp' && e.shiftKey) {
- e.preventDefault();
- moveSubtask(task, 'up');
- } else if (e.key === 'ArrowDown' && e.shiftKey) {
- e.preventDefault();
- moveSubtask(task, 'down');
- } else if (e.key === 'ArrowRight' && e.shiftKey) {
- e.preventDefault();
- if (task !== currentTask) {
- navigateIntoSubtask(task);
- }
- } else if (e.key === 'ArrowLeft' && e.shiftKey) {
- e.preventDefault();
- navigateToParentTask();
- }
- }
-
- function moveSubtask(subtask, direction) {
- const parentTask = findParentTask(subtask);
- if (!parentTask) return;
-
- const index = parentTask.subtasks.findIndex(t => t.id === subtask.id);
- if (index === -1) return;
-
- if (direction === 'up' && index > 0) {
- [parentTask.subtasks[index - 1], parentTask.subtasks[index]] = [parentTask.subtasks[index], parentTask.subtasks[index - 1]];
- } else if (direction === 'down' && index < parentTask.subtasks.length - 1) {
- [parentTask.subtasks[index], parentTask.subtasks[index + 1]] = [parentTask.subtasks[index + 1], parentTask.subtasks[index]];
- }
-
- renderCurrentView();
- selectAndFocusTask(subtask);
- }
-
- function addNewSubtask(parentTask, currentSubtask = null) {
- const newSubtask = { 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, newSubtask);
- } else {
- parentTask.subtasks.push(newSubtask);
- }
-
- parentTask.state = 0;
- parentTask.subtasks.forEach(subtask => {
- if (subtask.state === 2) {
- subtask.state = 0;
- }
- });
-
- renderCurrentView();
- selectAndFocusTask(newSubtask);
- }
-
- function selectAndFocusTask(task) {
- const taskInput = document.querySelector(`.task-container[data-id="${task.id}"] input[type="text"]`);
- if (taskInput) {
- taskInput.focus();
- setActiveTask(taskInput, task);
- }
- }
-
- function deleteSubtask(subtask) {
- const parentTask = taskPath[taskPath.length - 1];
- const index = parentTask.subtasks.findIndex(t => t.id === subtask.id);
-
- if (parentTask.id === 'root' && parentTask.subtasks.length === 1) {
- return;
- }
-
- parentTask.subtasks = parentTask.subtasks.filter(t => t.id !== subtask.id);
- updateParentTaskState(parentTask);
-
- if (parentTask.subtasks.length === 0 && taskPath.length > 1) {
- navigateToParentTask();
- } else {
- renderCurrentView();
- if (parentTask.subtasks.length > 0) {
- const targetIndex = Math.max(0, index - 1);
- selectAndFocusTask(parentTask.subtasks[targetIndex]);
- } else {
- selectAndFocusTask(parentTask);
- }
- }
- }
-
- function deleteCurrentParentTask() {
- if (taskPath.length <= 1) return; // Don't delete root task
-
- const currentParentTask = taskPath[taskPath.length - 1];
- const grandparentTask = taskPath[taskPath.length - 2];
-
- // Check if we're trying to delete the sole child of the root
- if (grandparentTask.id === 'root' && grandparentTask.subtasks.length === 1) {
- return; // Don't allow deletion of the sole child of root
- }
-
- const index = grandparentTask.subtasks.findIndex(t => t.id === currentParentTask.id);
-
- grandparentTask.subtasks = grandparentTask.subtasks.filter(t => t.id !== currentParentTask.id);
- updateParentTaskState(grandparentTask);
-
- taskPath.pop(); // Remove the deleted task from the path
- currentTask = grandparentTask;
-
- if (grandparentTask.subtasks.length === 0 && taskPath.length > 1) {
- // If we've just deleted the last subtask, navigate up another level
- navigateToParentTask();
- } else {
- renderCurrentView();
- if (grandparentTask.subtasks.length > 0) {
- // Select the subtask before the deleted one, or the one after if at the start
- const targetIndex = Math.max(0, index - 1);
- selectAndFocusTask(grandparentTask.subtasks[targetIndex]);
- } else {
- selectAndFocusTask(grandparentTask);
- }
- }
- }
-
- 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) {
- addNewSubtask(currentTask);
- } else {
- selectAndFocusTask(tasks[currentIndex]);
- }
- }
- }
-
- function navigateIntoSubtask(subtask) {
- if (subtask.subtasks.length > 0) {
- currentTask.selectedSubtaskId = subtask.id;
- taskPath.push(subtask);
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🪆</text></svg>">
+ <title>todo</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;
+ }
+ #breadcrumbs {
+ font-size: 24px;
+ margin-bottom: 10px;
+ }
+ </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];
+
+ const keyHandler = {
+ backspace: {
+ canDelete: true,
+ blocked: false
+ },
+ enter: {
+ canAdd: true,
+ blocked: false
+ },
+ arrowDown: {
+ canAdd: true,
+ blocked: false
+ }
+ };
+
+ function createTaskElement(task, isParentTask = false) {
+ const taskContainer = document.createElement('div');
+ taskContainer.className = 'task-container';
+ taskContainer.dataset.id = task.id;
+ if (isParentTask) taskContainer.classList.add('parent-task');
+
+ const checkbox = document.createElement('input');
+ checkbox.type = 'checkbox';
+ updateCheckboxState(checkbox, task.state);
+ checkbox.addEventListener('click', (e) => {
+ e.preventDefault();
+ toggleTaskState(task);
+ });
+
+ const taskInput = document.createElement('input');
+ taskInput.type = 'text';
+ taskInput.value = task.text;
+
+ const keydownHandler = function(e) {
+ if (e.key === 'Backspace') {
+ if (keyHandler.backspace.blocked) {
+ e.preventDefault();
+ return;
+ }
+ if (taskInput.value === '' && keyHandler.backspace.canDelete) {
+ e.preventDefault();
+ if (task !== taskPath[0]) {
+ keyHandler.backspace.blocked = true;
+ if (task === currentTask) {
+ deleteCurrentParentTask();
+ } else {
+ deleteSubtask(task);
+ }
+ }
+ } else if (taskInput.value !== '') {
+ keyHandler.backspace.canDelete = false;
+ }
+ } else if (e.key === 'Enter' && !e.shiftKey) {
+ if (keyHandler.enter.blocked) {
+ e.preventDefault();
+ return;
+ }
+ if (keyHandler.enter.canAdd) {
+ e.preventDefault();
+ keyHandler.enter.blocked = true;
+ addNewSubtask(currentTask, task);
+ }
+ } else if (e.key === 'ArrowDown' && !e.shiftKey) {
+ if (keyHandler.arrowDown.blocked) {
+ e.preventDefault();
+ return;
+ }
+ if (keyHandler.arrowDown.canAdd && isLastSubtask(task)) {
+ e.preventDefault();
+ keyHandler.arrowDown.blocked = true;
+ addNewSubtask(currentTask, task);
+ } else {
+ handleKeyDown(e, task);
+ }
+ } else {
+ handleKeyDown(e, task);
+ }
+ };
+
+ const keyupHandler = function(e) {
+ if (e.key === 'Backspace') {
+ keyHandler.backspace.canDelete = true;
+ keyHandler.backspace.blocked = false;
+ } else if (e.key === 'Enter') {
+ keyHandler.enter.canAdd = true;
+ keyHandler.enter.blocked = false;
+ } else if (e.key === 'ArrowDown') {
+ keyHandler.arrowDown.canAdd = true;
+ keyHandler.arrowDown.blocked = false;
+ }
+ };
+
+ taskInput.addEventListener('keydown', keydownHandler);
+ taskInput.addEventListener('keyup', keyupHandler);
+ taskInput.addEventListener('keydown', copyActiveTaskText);
+
+ taskInput.addEventListener('input', () => {
+ task.text = taskInput.value;
+ if (task === currentTask) {
+ updatePageTitle(task);
+ }
+ });
+
+ taskInput.addEventListener('focus', () => setActiveTask(taskInput, task));
+
+ taskContainer.appendChild(checkbox);
+ taskContainer.appendChild(taskInput);
+
+ return taskContainer;
+ }
+
+ function generateBreadcrumbs(rootTask, currentPath, selectedTaskId) {
+ let breadcrumbs = '';
+ let currentTask = rootTask;
+ let currentDepth = 0;
+
+ for (let i = 0; i < currentPath.length - 1; i++) {
+ breadcrumbs += '○ ';
+ currentTask = currentTask.subtasks.find(t => t.id === currentPath[i + 1].id);
+ }
+
+ breadcrumbs += '● ';
+ currentDepth = currentPath.length - 1;
+
+ if (selectedTaskId !== currentTask.id) {
+ let selectedTask = currentTask.subtasks.find(t => t.id === selectedTaskId);
+
+ if (selectedTask) {
+ function calculateMaxDepth(task, depth) {
+ if (task.subtasks.length === 0) return depth;
+ return Math.max(...task.subtasks.map(st => calculateMaxDepth(st, depth + 1)));
+ }
+
+ let maxDepth = calculateMaxDepth(selectedTask, currentDepth + 1);
+
+ for (let i = currentDepth + 1; i < maxDepth; i++) {
+ breadcrumbs += '○ ';
+ }
+ }
+ }
+
+ return breadcrumbs.trim();
+ }
+
+ function updateCheckboxState(checkbox, state) {
+ checkbox.checked = state === 1;
+ checkbox.indeterminate = state === 2;
+ }
+
+ function toggleTaskState(task) {
+ if (task.state === 1) {
+ if (task.subtasks.length === 0 || !task.subtasks.every(t => t.state === 1)) {
+ task.state = 0;
+ if (task.subtasks.length > 0) {
+ task.subtasks.forEach(subtask => {
+ if (subtask.state === 2) {
+ subtask.state = 0;
+ }
+ });
+ }
+ }
+ } else {
+ task.state = 1;
+ if (task.subtasks.length > 0) {
+ task.subtasks.forEach(subtask => {
+ if (subtask.state === 0) {
+ subtask.state = 2;
+ }
+ });
+ }
+ }
+ updateParentTaskState(task);
+ renderCurrentView();
+ selectAndFocusTask(task);
+ }
+
+ function updateParentTaskState(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;
+ } else if (anyUnchecked) {
+ parent.state = 0;
+ }
+
+ updateParentTaskState(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.shiftKey) {
+ e.preventDefault();
+ toggleTaskState(task);
+ } else if (e.key === 'ArrowUp' && !e.shiftKey) {
+ e.preventDefault();
+ navigateTasks('up');
+ } else if (e.key === 'ArrowDown' && !e.shiftKey) {
+ e.preventDefault();
+ navigateTasks('down');
+ } else if (e.key === 'ArrowUp' && e.shiftKey) {
+ e.preventDefault();
+ moveSubtask(task, 'up');
+ } else if (e.key === 'ArrowDown' && e.shiftKey) {
+ e.preventDefault();
+ moveSubtask(task, 'down');
+ } else if (e.key === 'ArrowRight' && e.shiftKey) {
+ e.preventDefault();
+ if (task !== currentTask) {
+ navigateIntoSubtask(task);
+ }
+ } else if (e.key === 'ArrowLeft' && e.shiftKey) {
+ e.preventDefault();
+ navigateToParentTask();
+ }
+ }
+
+ function moveSubtask(subtask, direction) {
+ const parentTask = findParentTask(subtask);
+ if (!parentTask) return;
+
+ const index = parentTask.subtasks.findIndex(t => t.id === subtask.id);
+ if (index === -1) return;
+
+ if (direction === 'up' && index > 0) {
+ [parentTask.subtasks[index - 1], parentTask.subtasks[index]] = [parentTask.subtasks[index], parentTask.subtasks[index - 1]];
+ } else if (direction === 'down' && index < parentTask.subtasks.length - 1) {
+ [parentTask.subtasks[index], parentTask.subtasks[index + 1]] = [parentTask.subtasks[index + 1], parentTask.subtasks[index]];
+ }
+
+ renderCurrentView();
+ selectAndFocusTask(subtask);
+ }
+
+ function addNewSubtask(parentTask, currentSubtask = null) {
+ const newSubtask = { 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, newSubtask);
+ } else {
+ parentTask.subtasks.push(newSubtask);
+ }
+
+ parentTask.state = 0;
+ parentTask.subtasks.forEach(subtask => {
+ if (subtask.state === 2) {
+ subtask.state = 0;
+ }
+ });
+
+ renderCurrentView();
+ selectAndFocusTask(newSubtask);
+ }
+
+ function selectAndFocusTask(task) {
+ const taskInput = document.querySelector(`.task-container[data-id="${task.id}"] input[type="text"]`);
+ if (taskInput) {
+ taskInput.focus();
+ setActiveTask(taskInput, task);
+ }
+ }
+
+ function deleteSubtask(subtask) {
+ const parentTask = taskPath[taskPath.length - 1];
+ const index = parentTask.subtasks.findIndex(t => t.id === subtask.id);
+
+ if (parentTask.id === 'root' && parentTask.subtasks.length === 1) {
+ return;
+ }
+
+ parentTask.subtasks = parentTask.subtasks.filter(t => t.id !== subtask.id);
+ updateParentTaskState(parentTask);
+
+ if (parentTask.subtasks.length === 0 && taskPath.length > 1) {
+ navigateToParentTask();
+ } else {
+ renderCurrentView();
+ if (parentTask.subtasks.length > 0) {
+ const targetIndex = Math.max(0, index - 1);
+ selectAndFocusTask(parentTask.subtasks[targetIndex]);
+ } else {
+ selectAndFocusTask(parentTask);
+ }
+ }
+ }
+
+ function deleteCurrentParentTask() {
+ if (taskPath.length <= 1) return; // Don't delete root task
+
+ const currentParentTask = taskPath[taskPath.length - 1];
+ const grandparentTask = taskPath[taskPath.length - 2];
+
+ // Check if we're trying to delete the sole child of the root
+ if (grandparentTask.id === 'root' && grandparentTask.subtasks.length === 1) {
+ return; // Don't allow deletion of the sole child of root
+ }
+
+ const index = grandparentTask.subtasks.findIndex(t => t.id === currentParentTask.id);
+
+ grandparentTask.subtasks = grandparentTask.subtasks.filter(t => t.id !== currentParentTask.id);
+ updateParentTaskState(grandparentTask);
+
+ taskPath.pop(); // Remove the deleted task from the path
+ currentTask = grandparentTask;
+
+ if (grandparentTask.subtasks.length === 0 && taskPath.length > 1) {
+ // If we've just deleted the last subtask, navigate up another level
+ navigateToParentTask();
+ } else {
+ renderCurrentView();
+ if (grandparentTask.subtasks.length > 0) {
+ // Select the subtask before the deleted one, or the one after if at the start
+ const targetIndex = Math.max(0, index - 1);
+ selectAndFocusTask(grandparentTask.subtasks[targetIndex]);
+ } else {
+ selectAndFocusTask(grandparentTask);
+ }
+ }
+ }
+
+ 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) {
+ selectAndFocusTask(tasks[currentIndex]);
+ }
+ }
+ }
+
+ function navigateIntoSubtask(subtask) {
+ if (subtask.subtasks.length > 0) {
+ currentTask.selectedSubtaskId = subtask.id;
+ taskPath.push(subtask);
currentTask = subtask;
- updateBreadcrumbs(currentTask);
- renderCurrentView();
- const selectedSubtask = subtask.selectedSubtaskId ?
- subtask.subtasks.find(t => t.id === subtask.selectedSubtaskId) :
- subtask.subtasks[0];
- selectAndFocusTask(selectedSubtask);
- } else {
- addNewSubtask(subtask);
- currentTask.selectedSubtaskId = subtask.id;
- taskPath.push(subtask);
- currentTask = subtask;
- updateBreadcrumbs(currentTask);
- renderCurrentView();
- selectAndFocusTask(subtask.subtasks[0]);
- }
- }
-
- function navigateToParentTask() {
- if (taskPath.length > 1) {
- const currentTaskId = currentTask.id;
- taskPath.pop();
- currentTask = taskPath[taskPath.length - 1];
- updateParentTaskState(currentTask);
- updateBreadcrumbs(currentTask);
- renderCurrentView();
- const selectedSubtask = currentTask.subtasks.find(t => t.id === currentTaskId);
- if (selectedSubtask) {
- selectAndFocusTask(selectedSubtask);
- } else if (currentTask.subtasks.length > 0) {
- // If for some reason the previous subtask isn't found, select the first subtask
- selectAndFocusTask(currentTask.subtasks[0]);
- } else {
- selectAndFocusTask(currentTask);
- }
- currentTask.selectedSubtaskId = currentTaskId;
- }
- }
-
- 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;
- }
- updateBreadcrumbs(task);
- }
-
- function updateBreadcrumbs(selectedTask) {
- const breadcrumbsContainer = document.getElementById('breadcrumbs');
- const trail = generateBreadcrumbs(taskPath[0], taskPath, selectedTask.id);
- breadcrumbsContainer.textContent = trail;
- }
-
- function renderCurrentView() {
- appContainer.innerHTML = '';
- currentTask = taskPath[taskPath.length - 1];
-
- updateBreadcrumbs(currentTask);
- updatePageTitle(currentTask);
-
- 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);
-
- if (currentTask.selectedSubtaskId) {
- const selectedTask = currentTask.subtasks.find(t => t.id === currentTask.selectedSubtaskId);
- if (selectedTask) {
- selectAndFocusTask(selectedTask);
- }
- }
- }
-
- function copyActiveTaskText(e) {
- // Check if the key combination is Ctrl+C (Windows) or Cmd+C (Mac)
- if ((e.ctrlKey || e.metaKey) && e.key === 'c') {
- const activeTaskInput = document.querySelector('.task-container.active input[type="text"]');
- if (activeTaskInput) {
- // Check if there's a selection within the active task input
- const selectedText = activeTaskInput.value.substring(activeTaskInput.selectionStart, activeTaskInput.selectionEnd);
-
- const textToCopy = selectedText || activeTaskInput.value;
-
- e.preventDefault(); // Prevent default copy behavior
- navigator.clipboard.writeText(textToCopy);
- }
- }
- }
-
- function updatePageTitle(task) {
- document.title = task.text || '?';
- }
-
- renderCurrentView();
- });
- </script>
+ updateBreadcrumbs(currentTask);
+ renderCurrentView();
+ const selectedSubtask = subtask.selectedSubtaskId ?
+ subtask.subtasks.find(t => t.id === subtask.selectedSubtaskId) :
+ subtask.subtasks[0];
+ selectAndFocusTask(selectedSubtask);
+ } else {
+ addNewSubtask(subtask);
+ currentTask.selectedSubtaskId = subtask.id;
+ taskPath.push(subtask);
+ currentTask = subtask;
+ updateBreadcrumbs(currentTask);
+ renderCurrentView();
+ selectAndFocusTask(subtask.subtasks[0]);
+ }
+ }
+
+ function navigateToParentTask() {
+ if (taskPath.length > 1) {
+ const currentTaskId = currentTask.id;
+ taskPath.pop();
+ currentTask = taskPath[taskPath.length - 1];
+ updateParentTaskState(currentTask);
+ updateBreadcrumbs(currentTask);
+ renderCurrentView();
+ const selectedSubtask = currentTask.subtasks.find(t => t.id === currentTaskId);
+ if (selectedSubtask) {
+ selectAndFocusTask(selectedSubtask);
+ } else if (currentTask.subtasks.length > 0) {
+ // If for some reason the previous subtask isn't found, select the first subtask
+ selectAndFocusTask(currentTask.subtasks[0]);
+ } else {
+ selectAndFocusTask(currentTask);
+ }
+ currentTask.selectedSubtaskId = currentTaskId;
+ }
+ }
+
+ 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;
+ }
+ updateBreadcrumbs(task);
+ }
+
+ function updateBreadcrumbs(selectedTask) {
+ const breadcrumbsContainer = document.getElementById('breadcrumbs');
+ const trail = generateBreadcrumbs(taskPath[0], taskPath, selectedTask.id);
+ breadcrumbsContainer.textContent = trail;
+ }
+
+ function renderCurrentView() {
+ appContainer.innerHTML = '';
+ currentTask = taskPath[taskPath.length - 1];
+
+ updateBreadcrumbs(currentTask);
+ updatePageTitle(currentTask);
+
+ 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);
+
+ if (currentTask.selectedSubtaskId) {
+ const selectedTask = currentTask.subtasks.find(t => t.id === currentTask.selectedSubtaskId);
+ if (selectedTask) {
+ selectAndFocusTask(selectedTask);
+ }
+ }
+ }
+
+ function copyActiveTaskText(e) {
+ // Check if the key combination is Ctrl+C (Windows) or Cmd+C (Mac)
+ if ((e.ctrlKey || e.metaKey) && e.key === 'c') {
+ const activeTaskInput = document.querySelector('.task-container.active input[type="text"]');
+ if (activeTaskInput) {
+ // Check if there's a selection within the active task input
+ const selectedText = activeTaskInput.value.substring(activeTaskInput.selectionStart, activeTaskInput.selectionEnd);
+
+ const textToCopy = selectedText || activeTaskInput.value;
+
+ e.preventDefault(); // Prevent default copy behavior
+ navigator.clipboard.writeText(textToCopy);
+ }
+ }
+ }
+
+ function updatePageTitle(task) {
+ document.title = task.text || '?';
+ }
+
+ function isLastSubtask(task) {
+ const parentTask = findParentTask(task);
+ if (!parentTask) return false;
+ return parentTask.subtasks[parentTask.subtasks.length - 1].id === task.id;
+ }
+
+ renderCurrentView();
+ });
+ </script>
</head>
<body>
- <div id="breadcrumbs"></div>
- <div id="app-container"></div>
+ <div id="breadcrumbs"></div>
+ <div id="app-container"></div>
</body>
-</html>
+</html>
+\ No newline at end of file