⌘ + shift + arrow to (move task & navigate)
Hunter
Wednesday, July 2nd, 2025 at 12:23 AM
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');
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