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