⌘ + shift + arrow to (move task & navigate)

Hunter
Wednesday, July 2nd, 2025 at 12:23 AM

index.html+137-2
readme.md+14-10

index.html
 877  				} else if (e.key === 'ArrowDown' && !e.shiftKey) {
 878  					e.preventDefault();
 879  					navigateTasks('down');
 880 +				} else if (e.key === 'ArrowLeft' && (e.metaKey || e.ctrlKey) && e.shiftKey) {
 881 +					e.preventDefault();
 882 +					pullSubtaskOutLayerAndNavigate(task);
 883 +				} else if (e.key === 'ArrowUp' && (e.metaKey || e.ctrlKey) && e.shiftKey) {
 884 +					e.preventDefault();
 885 +					pushSubtaskIntoTargetAndNavigate(task, 'up');
 886 +				} else if (e.key === 'ArrowDown' && (e.metaKey || e.ctrlKey) && e.shiftKey) {
 887 +					e.preventDefault();
 888 +					pushSubtaskIntoTargetAndNavigate(task, 'down');
 889  				} else if (e.key === 'ArrowUp' && e.shiftKey) {
 890  					e.preventDefault();
 891  					moveSubtask(task, 'up');

 975  				scheduleSave();
 976  			}
 977  
 978 +			function pushSubtaskIntoTargetAndNavigate(subtask, direction) {
 979 +				// Don't allow pushing the current parent task
 980 +				if (subtask === currentTask) {
 981 +					return;
 982 +				}
 983 +
 984 +				// Temporarily disable smooth scrolling
 985 +				document.documentElement.style.scrollBehavior = 'auto';
 986 +
 987 +				const parentTask = findParentTask(subtask);
 988 +				if (!parentTask) return;
 989 +
 990 +				const index = parentTask.subtasks.findIndex(t => t.id === subtask.id);
 991 +				if (index === -1) return;
 992 +
 993 +				let targetTask = null;
 994 +				if (direction === 'up' && index > 0) {
 995 +					targetTask = parentTask.subtasks[index - 1];
 996 +				} else if (direction === 'down' && index < parentTask.subtasks.length - 1) {
 997 +					targetTask = parentTask.subtasks[index + 1];
 998 +				}
 999 +
1000 +				if (!targetTask) return;
1001 +
1002 +				// Remove the subtask from its current parent
1003 +				parentTask.subtasks.splice(index, 1);
1004 +
1005 +				// Add the subtask to the target task's subtasks
1006 +				targetTask.subtasks.push(subtask);
1007 +
1008 +				// Update parent task state after removal
1009 +				updateTaskAndAncestors(parentTask);
1010 +
1011 +				// Update target task state after addition
1012 +				updateTaskAndAncestors(targetTask);
1013 +
1014 +				// Navigate into the target task and select the moved subtask
1015 +				navigateIntoTaskAndSelectSubtask(targetTask, subtask);
1016 +
1017 +				scheduleSave();
1018 +			}
1019 +
1020  			function pullSubtaskOutLayer(subtask) {
1021  				// Don't allow pulling the current parent task
1022  				if (subtask === currentTask) {

1056  
1057  				// Handle view and selection based on remaining tasks at current level
1058  				if (currentParent.subtasks.length === 0 && taskPath.length > 1) {
     -					// No remaining subtasks, navigate to parent level
     -					navigateToParentTask();
1059 +					// No remaining subtasks, navigate to parent level and select the moved task
1060 +					navigateToParentTaskAndSelectTask(subtask);
1061  				} else {
1062  					// Stay at current level and select adjacent task
1063  					renderCurrentView();

1074  				scheduleSave();
1075  			}
1076  
1077 +			function pullSubtaskOutLayerAndNavigate(subtask) {
1078 +				// Don't allow pulling the current parent task
1079 +				if (subtask === currentTask) {
1080 +					return;
1081 +				}
1082 +
1083 +				// Don't allow pulling when at root level
1084 +				if (taskPath.length <= 1) {
1085 +					return;
1086 +				}
1087 +
1088 +				// Temporarily disable smooth scrolling
1089 +				document.documentElement.style.scrollBehavior = 'auto';
1090 +
1091 +				const currentParent = findParentTask(subtask);
1092 +				if (!currentParent) return;
1093 +
1094 +				// Find the grandparent (the outer layer we're pulling into)
1095 +				const grandParent = findGrandParentTask(currentParent);
1096 +				if (!grandParent) return;
1097 +
1098 +				// Remove the subtask from its current parent
1099 +				const currentIndex = currentParent.subtasks.findIndex(t => t.id === subtask.id);
1100 +				if (currentIndex === -1) return;
1101 +				currentParent.subtasks.splice(currentIndex, 1);
1102 +
1103 +				// Find where to insert in the grandparent's subtasks
1104 +				const currentParentIndex = grandParent.subtasks.findIndex(t => t.id === currentParent.id);
1105 +				if (currentParentIndex === -1) {
1106 +					// Fallback: add to end of grandparent's subtasks
1107 +					grandParent.subtasks.push(subtask);
1108 +				} else {
1109 +					// Insert after the current parent's position
1110 +					grandParent.subtasks.splice(currentParentIndex + 1, 0, subtask);
1111 +				}
1112 +
1113 +				// Update task states
1114 +				updateTaskAndAncestors(currentParent);
1115 +				updateTaskAndAncestors(grandParent);
1116 +
1117 +				// Always navigate to parent level and select the moved task
1118 +				navigateToParentTaskAndSelectTask(subtask);
1119 +
1120 +				scheduleSave();
1121 +			}
1122 +
1123  			function findGrandParentTask(parentTask) {
1124  				// Find the grandparent of the given parent task
1125  				for (let i = taskPath.length - 1; i >= 0; i--) {

1204  				}
1205  			}
1206  
1207 +			function navigateIntoTaskAndSelectSubtask(targetTask, subtaskToSelect) {
1208 +				// Temporarily disable smooth scrolling
1209 +				document.documentElement.style.scrollBehavior = 'auto';
1210 +				
1211 +				// Set the selectedSubtaskId on the current task before navigating
1212 +				currentTask.selectedSubtaskId = targetTask.id;
1213 +				
1214 +				// Navigate into the target task
1215 +				taskPath.push(targetTask);
1216 +				currentTask = targetTask;
1217 +				updateBreadcrumbs(currentTask);
1218 +				renderCurrentView();
1219 +				
1220 +				// Select the moved subtask
1221 +				selectAndFocusTask(subtaskToSelect);
1222 +				currentTask.selectedSubtaskId = subtaskToSelect.id;
1223 +			}
1224 +
1225  			function navigateToParentTask() {
1226  				// Check if the current active parent task is the root
1227  				if (currentTask.id === 'root') {

1253  				}
1254  			}
1255  
1256 +			function navigateToParentTaskAndSelectTask(targetTask) {
1257 +				// Check if the current active parent task is the root
1258 +				if (currentTask.id === 'root') {
1259 +					// We're at root level, just re-render and select the target task
1260 +					renderCurrentView();
1261 +					selectAndFocusTask(targetTask);
1262 +				} else if (taskPath.length > 1) {
1263 +					// Normal navigation out - we're deeper than root level
1264 +					// Temporarily disable smooth scrolling
1265 +					document.documentElement.style.scrollBehavior = 'auto';
1266 +					
1267 +					taskPath.pop();
1268 +					currentTask = taskPath[taskPath.length - 1];
1269 +					updateBreadcrumbs(currentTask);
1270 +					renderCurrentView();
1271 +					selectAndFocusTask(targetTask);
1272 +					currentTask.selectedSubtaskId = targetTask.id;
1273 +				}
1274 +			}
1275 +
1276  			function setActiveTask(input, task) {
1277  				document.querySelectorAll('.active').forEach(el => el.classList.remove('active'));
1278  				input.closest('.task-container').classList.add('active');
readme.md
16  ### quickstart
17  1. press the `Return` / `Enter` key to add subtasks to the root "todo" task
18  2. give each new subtask a meaningful name
   -3. use `Shift + Arrow Right` to navigate into a subtask
   -4. use `Shift + Arrow Left` to return to the parent task
19 +3. use `Shift + ➡️` to navigate into a subtask
20 +4. use `Shift + ⬅️` to return to the parent task
21  5. use `Shift + Enter` to quickly mark tasks as complete or incomplete
22  
23  ## controls
24  
25  ### navigation
   -- `Arrow Up/Down` move between tasks at the same level
   -- `Shift + Arrow Right` navigate into a subtask
   -- `Shift + Arrow Left` return to the enclosing parent task
26 +- `⬆️/⬇️` move between tasks at the same level
27 +- `Shift + ➡️` navigate into a subtask
28 +- `Shift + ⬅️` return to the enclosing parent task
29  
30  ### task management
31  - `Enter` add a new task
   -- `Arrow Down` (on last subtask) add a new task at the bottom of the list
32 +- `⬇️` (on last subtask) add a new task at the bottom of the list
33  - `Backspace` (when selected task's text is empty) remove the task and its subtasks
34  - `Shift + Enter` toggle selected task's completion status
   -- `Shift + Arrow Up/Down` reposition the selected task within its current level
   -- `⌘ + Arrow Up/Down` move the selected task into the task above/below it
   -- `⌘ + Arrow Left` pull the selected task out one level (to its parent's level)
35 +- `Shift + ⬆️/⬇️` reposition the selected task within its current level
36 +- `⌘ + ⬆️/⬇️` push the selected task into the task above or below it
37 +- `⌘ + ⬅️` pull the selected task out one level (to the level of its parent)
38 +
39 +### combined movement + navigation
40 +- `⌘ + Shift + ⬆️/⬇️` push the selected task into the task above or below it AND navigate to its new position
41 +- `⌘ + Shift + ⬅️` pull the selected task out one level AND navigate to its new position
42  
43  ### text editing
   -- `Left/Right` move text cursor within selected task
44 +- `⬅️/➡️` move text cursor within selected task
45  - `⌘ + C` copy the selected task's text (or highlighted substring)
46  - `⌘ + X` cut the selected task's text (or highlighted substring)
47  - `⌘ + V` paste text content from the clipboard