refined down arrow stopping
Hunter
Thursday, August 1st, 2024 at 1:03 AM
1 <!DOCTYPE html>
2 <html lang="en">
3 <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];
-
- 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);
4 + <meta charset="UTF-8">
5 + <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 + <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>">
7 + <title>todo</title>
8 + <style>
9 + body {
10 + font-family: Arial, sans-serif;
11 + max-width: 800px;
12 + margin: 0 auto;
13 + padding: 20px;
14 + }
15 + ul {
16 + list-style-type: none;
17 + padding-left: 20px;
18 + }
19 + input[type="text"] {
20 + border: none;
21 + background: transparent;
22 + font-size: 1.17em;
23 + font-weight: bold;
24 + width: calc(100% - 30px);
25 + margin-left: 5px;
26 + padding: 5px;
27 + }
28 + input[type="text"]:focus {
29 + outline: none;
30 + }
31 + .task-container {
32 + display: flex;
33 + align-items: center;
34 + margin: 5px 0;
35 + }
36 + .active {
37 + background-color: #e6f3ff;
38 + }
39 + .parent-task {
40 + font-size: 1.5em;
41 + font-weight: bold;
42 + }
43 + #breadcrumbs {
44 + font-size: 24px;
45 + margin-bottom: 10px;
46 + }
47 + </style>
48 + <script>
49 + document.addEventListener('DOMContentLoaded', function() {
50 + const appContainer = document.getElementById('app-container');
51 + let currentTask = { id: 'root', text: 'todo', state: 0, subtasks: [{ id: Date.now(), text: '', state: 0, subtasks: [] }], selectedSubtaskId: null };
52 + let taskPath = [currentTask];
53 + let lastSubtaskDownArrowReleased = false;
54 +
55 + const keyHandler = {
56 + backspace: {
57 + canDelete: true,
58 + blocked: false
59 + },
60 + enter: {
61 + canAdd: true,
62 + blocked: false
63 + },
64 + arrowDown: {
65 + canAdd: true,
66 + blocked: false
67 + }
68 + };
69 +
70 + function createTaskElement(task, isParentTask = false) {
71 + const taskContainer = document.createElement('div');
72 + taskContainer.className = 'task-container';
73 + taskContainer.dataset.id = task.id;
74 + if (isParentTask) taskContainer.classList.add('parent-task');
75 +
76 + const checkbox = document.createElement('input');
77 + checkbox.type = 'checkbox';
78 + updateCheckboxState(checkbox, task.state);
79 + checkbox.addEventListener('click', (e) => {
80 + e.preventDefault();
81 + toggleTaskState(task);
82 + });
83 +
84 + const taskInput = document.createElement('input');
85 + taskInput.type = 'text';
86 + taskInput.value = task.text;
87 +
88 + const keydownHandler = function(e) {
89 + if (e.key === 'Backspace') {
90 + if (keyHandler.backspace.blocked) {
91 + e.preventDefault();
92 + return;
93 + }
94 + if (taskInput.value === '' && keyHandler.backspace.canDelete) {
95 + e.preventDefault();
96 + if (task !== taskPath[0]) {
97 + keyHandler.backspace.blocked = true;
98 + if (task === currentTask) {
99 + deleteCurrentParentTask();
100 + } else {
101 + deleteSubtask(task);
102 + }
103 + }
104 + } else if (taskInput.value !== '') {
105 + keyHandler.backspace.canDelete = false;
106 + }
107 + } else if (e.key === 'Enter' && !e.shiftKey) {
108 + if (keyHandler.enter.blocked) {
109 + e.preventDefault();
110 + return;
111 + }
112 + if (keyHandler.enter.canAdd) {
113 + e.preventDefault();
114 + keyHandler.enter.blocked = true;
115 + addNewSubtask(currentTask, task);
116 + }
117 + } else if (e.key === 'ArrowDown' && !e.shiftKey) {
118 + if (keyHandler.arrowDown.blocked) {
119 + e.preventDefault();
120 + return;
121 + }
122 + if (isLastSubtask(task) && (lastSubtaskDownArrowReleased || currentTask.subtasks.length === 1)) {
123 + e.preventDefault();
124 + keyHandler.arrowDown.blocked = true;
125 + addNewSubtask(currentTask, task);
126 + lastSubtaskDownArrowReleased = false;
127 + } else {
128 + handleKeyDown(e, task);
129 + }
130 + } else {
131 + handleKeyDown(e, task);
132 + }
133 + };
134 +
135 + const keyupHandler = function(e) {
136 + if (e.key === 'Backspace') {
137 + keyHandler.backspace.canDelete = true;
138 + keyHandler.backspace.blocked = false;
139 + } else if (e.key === 'Enter') {
140 + keyHandler.enter.canAdd = true;
141 + keyHandler.enter.blocked = false;
142 + } else if (e.key === 'ArrowDown') {
143 + keyHandler.arrowDown.canAdd = true;
144 + keyHandler.arrowDown.blocked = false;
145 + if (isLastSubtask(task)) {
146 + lastSubtaskDownArrowReleased = true;
147 + } else {
148 + lastSubtaskDownArrowReleased = false;
149 + }
150 + }
151 + };
152 +
153 + taskInput.addEventListener('keydown', keydownHandler);
154 + taskInput.addEventListener('keyup', keyupHandler);
155 + taskInput.addEventListener('keydown', copyActiveTaskText);
156 +
157 + taskInput.addEventListener('input', () => {
158 + task.text = taskInput.value;
159 + if (task === currentTask) {
160 + updatePageTitle(task);
161 + }
162 + });
163 +
164 + taskInput.addEventListener('focus', () => setActiveTask(taskInput, task));
165 +
166 + taskContainer.appendChild(checkbox);
167 + taskContainer.appendChild(taskInput);
168 +
169 + return taskContainer;
170 + }
171 +
172 + function generateBreadcrumbs(rootTask, currentPath, selectedTaskId) {
173 + let breadcrumbs = '';
174 + let currentTask = rootTask;
175 + let currentDepth = 0;
176 +
177 + for (let i = 0; i < currentPath.length - 1; i++) {
178 + breadcrumbs += '○ ';
179 + currentTask = currentTask.subtasks.find(t => t.id === currentPath[i + 1].id);
180 + }
181 +
182 + breadcrumbs += '● ';
183 + currentDepth = currentPath.length - 1;
184 +
185 + if (selectedTaskId !== currentTask.id) {
186 + let selectedTask = currentTask.subtasks.find(t => t.id === selectedTaskId);
187 +
188 + if (selectedTask) {
189 + function calculateMaxDepth(task, depth) {
190 + if (task.subtasks.length === 0) return depth;
191 + return Math.max(...task.subtasks.map(st => calculateMaxDepth(st, depth + 1)));
192 + }
193 +
194 + let maxDepth = calculateMaxDepth(selectedTask, currentDepth + 1);
195 +
196 + for (let i = currentDepth + 1; i < maxDepth; i++) {
197 + breadcrumbs += '○ ';
198 + }
199 + }
200 + }
201 +
202 + return breadcrumbs.trim();
203 + }
204 +
205 + function updateCheckboxState(checkbox, state) {
206 + checkbox.checked = state === 1;
207 + checkbox.indeterminate = state === 2;
208 + }
209 +
210 + function toggleTaskState(task) {
211 + if (task.state === 1) {
212 + if (task.subtasks.length === 0 || !task.subtasks.every(t => t.state === 1)) {
213 + task.state = 0;
214 + if (task.subtasks.length > 0) {
215 + task.subtasks.forEach(subtask => {
216 + if (subtask.state === 2) {
217 + subtask.state = 0;
218 + }
219 + });
220 + }
221 + }
222 + } else {
223 + task.state = 1;
224 + if (task.subtasks.length > 0) {
225 + task.subtasks.forEach(subtask => {
226 + if (subtask.state === 0) {
227 + subtask.state = 2;
228 + }
229 + });
230 + }
231 + }
232 + updateParentTaskState(task);
233 + renderCurrentView();
234 + selectAndFocusTask(task);
235 + }
236 +
237 + function updateParentTaskState(task) {
238 + const parent = findParentTask(task);
239 + if (parent) {
240 + const allChecked = parent.subtasks.every(t => t.state === 1);
241 + const anyUnchecked = parent.subtasks.some(t => t.state === 0);
242 +
243 + if (allChecked) {
244 + parent.state = 1;
245 + } else if (anyUnchecked) {
246 + parent.state = 0;
247 + }
248 +
249 + updateParentTaskState(parent);
250 + }
251 + }
252 +
253 + function updateSubtasksState(task, state) {
254 + task.subtasks.forEach(subtask => {
255 + subtask.state = state;
256 + if (subtask.subtasks.length > 0) {
257 + updateSubtasksState(subtask, state);
258 + }
259 + });
260 + }
261 +
262 + function findParentTask(task) {
263 + for (let i = taskPath.length - 1; i >= 0; i--) {
264 + const potentialParent = taskPath[i];
265 + if (potentialParent.subtasks.some(t => t.id === task.id)) {
266 + return potentialParent;
267 + }
268 + }
269 + return null;
270 + }
271 +
272 + function handleKeyDown(e, task) {
273 + if (e.key === 'Enter' && e.shiftKey) {
274 + e.preventDefault();
275 + toggleTaskState(task);
276 + } else if (e.key === 'ArrowUp' && !e.shiftKey) {
277 + e.preventDefault();
278 + navigateTasks('up');
279 + } else if (e.key === 'ArrowDown' && !e.shiftKey) {
280 + e.preventDefault();
281 + navigateTasks('down');
282 + } else if (e.key === 'ArrowUp' && e.shiftKey) {
283 + e.preventDefault();
284 + moveSubtask(task, 'up');
285 + } else if (e.key === 'ArrowDown' && e.shiftKey) {
286 + e.preventDefault();
287 + moveSubtask(task, 'down');
288 + } else if (e.key === 'ArrowRight' && e.shiftKey) {
289 + e.preventDefault();
290 + if (task !== currentTask) {
291 + navigateIntoSubtask(task);
292 + }
293 + } else if (e.key === 'ArrowLeft' && e.shiftKey) {
294 + e.preventDefault();
295 + navigateToParentTask();
296 + }
297 + }
298 +
299 + function moveSubtask(subtask, direction) {
300 + const parentTask = findParentTask(subtask);
301 + if (!parentTask) return;
302 +
303 + const index = parentTask.subtasks.findIndex(t => t.id === subtask.id);
304 + if (index === -1) return;
305 +
306 + if (direction === 'up' && index > 0) {
307 + [parentTask.subtasks[index - 1], parentTask.subtasks[index]] = [parentTask.subtasks[index], parentTask.subtasks[index - 1]];
308 + } else if (direction === 'down' && index < parentTask.subtasks.length - 1) {
309 + [parentTask.subtasks[index], parentTask.subtasks[index + 1]] = [parentTask.subtasks[index + 1], parentTask.subtasks[index]];
310 + }
311 +
312 + renderCurrentView();
313 + selectAndFocusTask(subtask);
314 + }
315 +
316 + function addNewSubtask(parentTask, currentSubtask = null) {
317 + const newSubtask = { id: Date.now(), text: '', state: 0, subtasks: [], selectedSubtaskId: null };
318 + if (currentSubtask) {
319 + const index = parentTask.subtasks.findIndex(t => t.id === currentSubtask.id);
320 + parentTask.subtasks.splice(index + 1, 0, newSubtask);
321 + } else {
322 + parentTask.subtasks.push(newSubtask);
323 + }
324 +
325 + parentTask.state = 0;
326 + parentTask.subtasks.forEach(subtask => {
327 + if (subtask.state === 2) {
328 + subtask.state = 0;
329 + }
330 + });
331 +
332 + renderCurrentView();
333 + selectAndFocusTask(newSubtask);
334 + }
335 +
336 + function selectAndFocusTask(task) {
337 + const taskInput = document.querySelector(`.task-container[data-id="${task.id}"] input[type="text"]`);
338 + if (taskInput) {
339 + taskInput.focus();
340 + setActiveTask(taskInput, task);
341 + }
342 + }
343 +
344 + function deleteSubtask(subtask) {
345 + const parentTask = taskPath[taskPath.length - 1];
346 + const index = parentTask.subtasks.findIndex(t => t.id === subtask.id);
347 +
348 + if (parentTask.id === 'root' && parentTask.subtasks.length === 1) {
349 + return;
350 + }
351 +
352 + parentTask.subtasks = parentTask.subtasks.filter(t => t.id !== subtask.id);
353 + updateParentTaskState(parentTask);
354 +
355 + if (parentTask.subtasks.length === 0 && taskPath.length > 1) {
356 + navigateToParentTask();
357 + } else {
358 + renderCurrentView();
359 + if (parentTask.subtasks.length > 0) {
360 + const targetIndex = Math.max(0, index - 1);
361 + selectAndFocusTask(parentTask.subtasks[targetIndex]);
362 + } else {
363 + selectAndFocusTask(parentTask);
364 + }
365 + }
366 + }
367 +
368 + function deleteCurrentParentTask() {
369 + if (taskPath.length <= 1) return; // Don't delete root task
370 +
371 + const currentParentTask = taskPath[taskPath.length - 1];
372 + const grandparentTask = taskPath[taskPath.length - 2];
373 +
374 + // Check if we're trying to delete the sole child of the root
375 + if (grandparentTask.id === 'root' && grandparentTask.subtasks.length === 1) {
376 + return; // Don't allow deletion of the sole child of root
377 + }
378 +
379 + const index = grandparentTask.subtasks.findIndex(t => t.id === currentParentTask.id);
380 +
381 + grandparentTask.subtasks = grandparentTask.subtasks.filter(t => t.id !== currentParentTask.id);
382 + updateParentTaskState(grandparentTask);
383 +
384 + taskPath.pop(); // Remove the deleted task from the path
385 + currentTask = grandparentTask;
386 +
387 + if (grandparentTask.subtasks.length === 0 && taskPath.length > 1) {
388 + // If we've just deleted the last subtask, navigate up another level
389 + navigateToParentTask();
390 + } else {
391 + renderCurrentView();
392 + if (grandparentTask.subtasks.length > 0) {
393 + // Select the subtask before the deleted one, or the one after if at the start
394 + const targetIndex = Math.max(0, index - 1);
395 + selectAndFocusTask(grandparentTask.subtasks[targetIndex]);
396 + } else {
397 + selectAndFocusTask(grandparentTask);
398 + }
399 + }
400 + }
401 +
402 + function navigateTasks(direction) {
403 + const tasks = currentTask.subtasks;
404 + const currentElement = document.activeElement;
405 + const currentContainer = currentElement.closest('.task-container');
406 + const currentIndex = Array.from(appContainer.querySelectorAll('.task-container')).indexOf(currentContainer);
407 +
408 + if (direction === 'up') {
409 + if (currentIndex > 0) {
410 + const prevTask = currentIndex === 1 ? currentTask : tasks[currentIndex - 2];
411 + selectAndFocusTask(prevTask);
412 + }
413 + // } else if (currentTask !== taskPath[0]) {
414 + // navigateToParentTask();
415 + // }
416 + } else {
417 + if (currentIndex < tasks.length) {
418 + selectAndFocusTask(tasks[currentIndex]);
419 + }
420 + }
421 + lastSubtaskDownArrowReleased = false;
422 + }
423 +
424 + function navigateIntoSubtask(subtask) {
425 + if (subtask.subtasks.length > 0) {
426 + currentTask.selectedSubtaskId = subtask.id;
427 + taskPath.push(subtask);
428 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 || '?';
- }
-
- function isLastSubtask(task) {
- const parentTask = findParentTask(task);
- if (!parentTask) return false;
- return parentTask.subtasks[parentTask.subtasks.length - 1].id === task.id;
- }
-
- renderCurrentView();
- });
- </script>
429 + updateBreadcrumbs(currentTask);
430 + renderCurrentView();
431 + const selectedSubtask = subtask.selectedSubtaskId ?
432 + subtask.subtasks.find(t => t.id === subtask.selectedSubtaskId) :
433 + subtask.subtasks[0];
434 + selectAndFocusTask(selectedSubtask);
435 + } else {
436 + addNewSubtask(subtask);
437 + currentTask.selectedSubtaskId = subtask.id;
438 + taskPath.push(subtask);
439 + currentTask = subtask;
440 + updateBreadcrumbs(currentTask);
441 + renderCurrentView();
442 + selectAndFocusTask(subtask.subtasks[0]);
443 + }
444 + }
445 +
446 + function navigateToParentTask() {
447 + if (taskPath.length > 1) {
448 + const currentTaskId = currentTask.id;
449 + taskPath.pop();
450 + currentTask = taskPath[taskPath.length - 1];
451 + updateParentTaskState(currentTask);
452 + updateBreadcrumbs(currentTask);
453 + renderCurrentView();
454 + const selectedSubtask = currentTask.subtasks.find(t => t.id === currentTaskId);
455 + if (selectedSubtask) {
456 + selectAndFocusTask(selectedSubtask);
457 + } else if (currentTask.subtasks.length > 0) {
458 + selectAndFocusTask(currentTask.subtasks[0]);
459 + } else {
460 + selectAndFocusTask(currentTask);
461 + }
462 + currentTask.selectedSubtaskId = currentTaskId;
463 + }
464 + }
465 +
466 + function setActiveTask(input, task) {
467 + document.querySelectorAll('.active').forEach(el => el.classList.remove('active'));
468 + input.closest('.task-container').classList.add('active');
469 + if (task !== currentTask) {
470 + currentTask.selectedSubtaskId = task.id;
471 + }
472 + updateBreadcrumbs(task);
473 + }
474 +
475 + function updateBreadcrumbs(selectedTask) {
476 + const breadcrumbsContainer = document.getElementById('breadcrumbs');
477 + const trail = generateBreadcrumbs(taskPath[0], taskPath, selectedTask.id);
478 + breadcrumbsContainer.textContent = trail;
479 + }
480 +
481 + function renderCurrentView() {
482 + appContainer.innerHTML = '';
483 + currentTask = taskPath[taskPath.length - 1];
484 +
485 + updateBreadcrumbs(currentTask);
486 + updatePageTitle(currentTask);
487 +
488 + const parentElement = createTaskElement(currentTask, true);
489 + appContainer.appendChild(parentElement);
490 +
491 + const subtasksList = document.createElement('ul');
492 + currentTask.subtasks.forEach(subtask => {
493 + const li = document.createElement('li');
494 + li.appendChild(createTaskElement(subtask));
495 + subtasksList.appendChild(li);
496 + });
497 + appContainer.appendChild(subtasksList);
498 +
499 + const parentCheckbox = parentElement.querySelector('input[type="checkbox"]');
500 + updateCheckboxState(parentCheckbox, currentTask.state);
501 +
502 + if (currentTask.selectedSubtaskId) {
503 + const selectedTask = currentTask.subtasks.find(t => t.id === currentTask.selectedSubtaskId);
504 + if (selectedTask) {
505 + selectAndFocusTask(selectedTask);
506 + }
507 + }
508 + }
509 +
510 + function copyActiveTaskText(e) {
511 + // Check if the key combination is Ctrl+C (Windows) or Cmd+C (Mac)
512 + if ((e.ctrlKey || e.metaKey) && e.key === 'c') {
513 + const activeTaskInput = document.querySelector('.task-container.active input[type="text"]');
514 + if (activeTaskInput) {
515 + // Check if there's a selection within the active task input
516 + const selectedText = activeTaskInput.value.substring(activeTaskInput.selectionStart, activeTaskInput.selectionEnd);
517 +
518 + const textToCopy = selectedText || activeTaskInput.value;
519 +
520 + e.preventDefault(); // Prevent default copy behavior
521 + navigator.clipboard.writeText(textToCopy);
522 + }
523 + }
524 + }
525 +
526 + function updatePageTitle(task) {
527 + document.title = task.text || '?';
528 + }
529 +
530 + function isLastSubtask(task) {
531 + const parentTask = findParentTask(task);
532 + if (!parentTask) return false;
533 + return parentTask.subtasks[parentTask.subtasks.length - 1].id === task.id;
534 + }
535 +
536 + renderCurrentView();
537 + });
538 + </script>
539 </head>
540 <body>
- <div id="breadcrumbs"></div>
- <div id="app-container"></div>
541 + <div id="breadcrumbs"></div>
542 + <div id="app-container"></div>
543 </body>
544 </html>