commit 5d729515bbd565ab0b8465db01bca949e0ac200c
parent dc9ff492cf38cc7f13c4c81285d78a271917163f
Author: Hunter
Date: Wed, 2 Jul 2025 00:23:07 -0400
⌘ + shift + arrow to (move task & navigate)
Diffstat:
| M | index.html | | | 139 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- |
| M | readme.md | | | 24 | ++++++++++++++---------- |
2 files changed, 151 insertions(+), 12 deletions(-)
diff --git a/index.html b/index.html
@@ -877,6 +877,15 @@
} else if (e.key === 'ArrowDown' && !e.shiftKey) {
e.preventDefault();
navigateTasks('down');
+ } else if (e.key === 'ArrowLeft' && (e.metaKey || e.ctrlKey) && e.shiftKey) {
+ e.preventDefault();
+ pullSubtaskOutLayerAndNavigate(task);
+ } else if (e.key === 'ArrowUp' && (e.metaKey || e.ctrlKey) && e.shiftKey) {
+ e.preventDefault();
+ pushSubtaskIntoTargetAndNavigate(task, 'up');
+ } else if (e.key === 'ArrowDown' && (e.metaKey || e.ctrlKey) && e.shiftKey) {
+ e.preventDefault();
+ pushSubtaskIntoTargetAndNavigate(task, 'down');
} else if (e.key === 'ArrowUp' && e.shiftKey) {
e.preventDefault();
moveSubtask(task, 'up');
@@ -966,6 +975,48 @@
scheduleSave();
}
+ function pushSubtaskIntoTargetAndNavigate(subtask, direction) {
+ // Don't allow pushing the current parent task
+ if (subtask === currentTask) {
+ return;
+ }
+
+ // Temporarily disable smooth scrolling
+ document.documentElement.style.scrollBehavior = 'auto';
+
+ const parentTask = findParentTask(subtask);
+ if (!parentTask) return;
+
+ const index = parentTask.subtasks.findIndex(t => t.id === subtask.id);
+ if (index === -1) return;
+
+ let targetTask = null;
+ if (direction === 'up' && index > 0) {
+ targetTask = parentTask.subtasks[index - 1];
+ } else if (direction === 'down' && index < parentTask.subtasks.length - 1) {
+ targetTask = parentTask.subtasks[index + 1];
+ }
+
+ if (!targetTask) return;
+
+ // Remove the subtask from its current parent
+ parentTask.subtasks.splice(index, 1);
+
+ // Add the subtask to the target task's subtasks
+ targetTask.subtasks.push(subtask);
+
+ // Update parent task state after removal
+ updateTaskAndAncestors(parentTask);
+
+ // Update target task state after addition
+ updateTaskAndAncestors(targetTask);
+
+ // Navigate into the target task and select the moved subtask
+ navigateIntoTaskAndSelectSubtask(targetTask, subtask);
+
+ scheduleSave();
+ }
+
function pullSubtaskOutLayer(subtask) {
// Don't allow pulling the current parent task
if (subtask === currentTask) {
@@ -1005,8 +1056,8 @@
// Handle view and selection based on remaining tasks at current level
if (currentParent.subtasks.length === 0 && taskPath.length > 1) {
- // No remaining subtasks, navigate to parent level
- navigateToParentTask();
+ // No remaining subtasks, navigate to parent level and select the moved task
+ navigateToParentTaskAndSelectTask(subtask);
} else {
// Stay at current level and select adjacent task
renderCurrentView();
@@ -1023,6 +1074,52 @@
scheduleSave();
}
+ function pullSubtaskOutLayerAndNavigate(subtask) {
+ // Don't allow pulling the current parent task
+ if (subtask === currentTask) {
+ return;
+ }
+
+ // Don't allow pulling when at root level
+ if (taskPath.length <= 1) {
+ return;
+ }
+
+ // Temporarily disable smooth scrolling
+ document.documentElement.style.scrollBehavior = 'auto';
+
+ const currentParent = findParentTask(subtask);
+ if (!currentParent) return;
+
+ // Find the grandparent (the outer layer we're pulling into)
+ const grandParent = findGrandParentTask(currentParent);
+ if (!grandParent) return;
+
+ // Remove the subtask from its current parent
+ const currentIndex = currentParent.subtasks.findIndex(t => t.id === subtask.id);
+ if (currentIndex === -1) return;
+ currentParent.subtasks.splice(currentIndex, 1);
+
+ // Find where to insert in the grandparent's subtasks
+ const currentParentIndex = grandParent.subtasks.findIndex(t => t.id === currentParent.id);
+ if (currentParentIndex === -1) {
+ // Fallback: add to end of grandparent's subtasks
+ grandParent.subtasks.push(subtask);
+ } else {
+ // Insert after the current parent's position
+ grandParent.subtasks.splice(currentParentIndex + 1, 0, subtask);
+ }
+
+ // Update task states
+ updateTaskAndAncestors(currentParent);
+ updateTaskAndAncestors(grandParent);
+
+ // Always navigate to parent level and select the moved task
+ navigateToParentTaskAndSelectTask(subtask);
+
+ scheduleSave();
+ }
+
function findGrandParentTask(parentTask) {
// Find the grandparent of the given parent task
for (let i = taskPath.length - 1; i >= 0; i--) {
@@ -1107,6 +1204,24 @@
}
}
+ function navigateIntoTaskAndSelectSubtask(targetTask, subtaskToSelect) {
+ // Temporarily disable smooth scrolling
+ document.documentElement.style.scrollBehavior = 'auto';
+
+ // Set the selectedSubtaskId on the current task before navigating
+ currentTask.selectedSubtaskId = targetTask.id;
+
+ // Navigate into the target task
+ taskPath.push(targetTask);
+ currentTask = targetTask;
+ updateBreadcrumbs(currentTask);
+ renderCurrentView();
+
+ // Select the moved subtask
+ selectAndFocusTask(subtaskToSelect);
+ currentTask.selectedSubtaskId = subtaskToSelect.id;
+ }
+
function navigateToParentTask() {
// Check if the current active parent task is the root
if (currentTask.id === 'root') {
@@ -1138,6 +1253,26 @@
}
}
+ function navigateToParentTaskAndSelectTask(targetTask) {
+ // Check if the current active parent task is the root
+ if (currentTask.id === 'root') {
+ // We're at root level, just re-render and select the target task
+ renderCurrentView();
+ selectAndFocusTask(targetTask);
+ } else if (taskPath.length > 1) {
+ // Normal navigation out - we're deeper than root level
+ // Temporarily disable smooth scrolling
+ document.documentElement.style.scrollBehavior = 'auto';
+
+ taskPath.pop();
+ currentTask = taskPath[taskPath.length - 1];
+ updateBreadcrumbs(currentTask);
+ renderCurrentView();
+ selectAndFocusTask(targetTask);
+ currentTask.selectedSubtaskId = targetTask.id;
+ }
+ }
+
function setActiveTask(input, task) {
document.querySelectorAll('.active').forEach(el => el.classList.remove('active'));
input.closest('.task-container').classList.add('active');
diff --git a/readme.md b/readme.md
@@ -16,28 +16,32 @@ the nested todo list that breaks complex tasks into manageable subtasks.
### quickstart
1. press the `Return` / `Enter` key to add subtasks to the root "todo" task
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
+3. use `Shift + ➡️` to navigate into a subtask
+4. use `Shift + ⬅️` to return to the parent task
5. use `Shift + Enter` to quickly mark tasks as complete or incomplete
## controls
### 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
+- `⬆️/⬇️` move between tasks at the same level
+- `Shift + ➡️` navigate into a subtask
+- `Shift + ⬅️` return to the enclosing parent task
### task management
- `Enter` add a new task
-- `Arrow Down` (on last subtask) add a new task at the bottom of the list
+- `⬇️` (on last subtask) add a new task at the bottom of the list
- `Backspace` (when selected task's text is empty) remove the task and its subtasks
- `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)
+- `Shift + ⬆️/⬇️` reposition the selected task within its current level
+- `⌘ + ⬆️/⬇️` push the selected task into the task above or below it
+- `⌘ + ⬅️` pull the selected task out one level (to the level of its parent)
+
+### combined movement + navigation
+- `⌘ + Shift + ⬆️/⬇️` push the selected task into the task above or below it AND navigate to its new position
+- `⌘ + Shift + ⬅️` pull the selected task out one level AND navigate to its new position
### text editing
-- `Left/Right` move text cursor within selected task
+- `⬅️/➡️` move text cursor within selected task
- `⌘ + C` copy the selected task's text (or highlighted substring)
- `⌘ + X` cut the selected task's text (or highlighted substring)
- `⌘ + V` paste text content from the clipboard