commit 490dbc200234f992a22aa825166ca2f0b83653ca
parent 73db3520231afc78cab639bfeb0fc0e9b6022422
Author: Hunter
Date:   Sat,  7 Mar 2026 18:52:54 -0500

add vertical shake; apply shake animation when nav/push/pull fails

Diffstat:
Mindex.html | 112++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
1 file changed, 74 insertions(+), 38 deletions(-)

diff --git a/index.html b/index.html @@ -216,6 +216,17 @@ .shake { animation: shake 0.25s ease-out; } + @keyframes shake-vertical { + 0% { transform: translateY(0); } + 20% { transform: translateY(3px); } + 40% { transform: translateY(-3px); } + 60% { transform: translateY(3px); } + 80% { transform: translateY(-3px); } + 100% { transform: translateY(0); } + } + .shake-vertical { + animation: shake-vertical 0.25s ease-out; + } html { scroll-behavior: smooth; overflow-y: scroll; @@ -587,7 +598,7 @@ keyHandler.enter.blocked = true; addNewSubtask(currentTask, task); } - } else if (e.key === 'ArrowDown' && !e.shiftKey) { + } else if (e.key === 'ArrowDown' && !e.shiftKey && !e.metaKey && !e.ctrlKey) { if (keyHandler.arrowDown.blocked) { e.preventDefault(); return; @@ -723,13 +734,14 @@ } } - function applyShakeAnimation(taskId) { + function applyShakeAnimation(taskId, direction = 'horizontal') { const checkbox = document.querySelector(`.task-container[data-id="${taskId}"] .checkbox-label`); if (checkbox) { - checkbox.classList.add('shake'); + const className = direction === 'vertical' ? 'shake-vertical' : 'shake' + checkbox.classList.add(className); setTimeout(() => { - checkbox.classList.remove('shake'); - }, 250); // Remove class after animation completes + checkbox.classList.remove(className); + }, 250); } } @@ -870,39 +882,66 @@ } function handleKeyDown(e, task) { + const cmd = e.metaKey || e.ctrlKey; + if (e.key === 'Enter' && e.shiftKey) { e.preventDefault(); if (!keyHandler.shiftEnter.pressed) { keyHandler.shiftEnter.pressed = true; toggleTaskState(task); } - } else if (e.key === 'ArrowUp' && (e.metaKey || e.ctrlKey) && !e.shiftKey) { + } else if (e.key === 'ArrowUp' && cmd && !e.shiftKey) { + e.preventDefault(); + if (!e.repeat && !pushSubtaskIntoTarget(task, 'up')) { + applyShakeAnimation(task.id, 'vertical'); + } + } else if (e.key === 'ArrowDown' && cmd && !e.shiftKey) { e.preventDefault(); - if (!e.repeat) pushSubtaskIntoTarget(task, 'up'); - } else if (e.key === 'ArrowDown' && (e.metaKey || e.ctrlKey) && !e.shiftKey) { + if (!e.repeat && !pushSubtaskIntoTarget(task, 'down')) { + applyShakeAnimation(task.id, 'vertical'); + } + } else if (e.key === 'ArrowRight' && cmd && !e.shiftKey) { + e.preventDefault(); + applyShakeAnimation(task.id); + } else if (e.key === 'ArrowLeft' && cmd && !e.shiftKey) { + e.preventDefault(); + if (!e.repeat && !pullSubtaskOutLayer(task)) { + applyShakeAnimation(task.id); + } + } else if (e.key === 'ArrowUp' && cmd && e.shiftKey) { + e.preventDefault(); + if (!e.repeat && !pushSubtaskIntoTarget(task, 'up', true)) { + applyShakeAnimation(task.id, 'vertical'); + } + } else if (e.key === 'ArrowDown' && cmd && e.shiftKey) { + e.preventDefault(); + if (!e.repeat && !pushSubtaskIntoTarget(task, 'down', true)) { + applyShakeAnimation(task.id, 'vertical'); + } + } else if (e.key === 'ArrowLeft' && cmd && e.shiftKey) { + e.preventDefault(); + if (!e.repeat && !pullSubtaskOutLayer(task, true)) { + applyShakeAnimation(task.id); + } + } else if (e.key === 'ArrowRight' && cmd && e.shiftKey) { e.preventDefault(); - if (!e.repeat) pushSubtaskIntoTarget(task, 'down'); + applyShakeAnimation(task.id); } 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 === 'ArrowLeft' && (e.metaKey || e.ctrlKey) && e.shiftKey) { - e.preventDefault(); - if (!e.repeat) pullSubtaskOutLayer(task, true); - } else if (e.key === 'ArrowUp' && (e.metaKey || e.ctrlKey) && e.shiftKey) { - e.preventDefault(); - if (!e.repeat) pushSubtaskIntoTarget(task, 'up', true); - } else if (e.key === 'ArrowDown' && (e.metaKey || e.ctrlKey) && e.shiftKey) { - e.preventDefault(); - if (!e.repeat) pushSubtaskIntoTarget(task, 'down', true); } else if (e.key === 'ArrowUp' && e.shiftKey) { e.preventDefault(); - moveSubtask(task, 'up'); + if (!moveSubtask(task, 'up')) { + applyShakeAnimation(task.id, 'vertical'); + } } else if (e.key === 'ArrowDown' && e.shiftKey) { e.preventDefault(); - moveSubtask(task, 'down'); + if (!moveSubtask(task, 'down')) { + applyShakeAnimation(task.id, 'vertical'); + } } else if (e.key === 'ArrowRight' && e.shiftKey) { e.preventDefault(); if (!keyHandler.shiftRight.pressed) { @@ -910,13 +949,9 @@ if (task !== currentTask) { navigateIntoSubtask(task); } else { - // Apply shake animation when trying to navigate into the parent task applyShakeAnimation(task.id); } } - } else if (e.key === 'ArrowLeft' && (e.metaKey || e.ctrlKey) && !e.shiftKey) { - e.preventDefault(); - if (!e.repeat) pullSubtaskOutLayer(task); } else if (e.key === 'ArrowLeft' && e.shiftKey) { e.preventDefault(); if (!keyHandler.shiftLeft.pressed) { @@ -927,38 +962,41 @@ } function moveSubtask(subtask, direction) { + if (subtask === currentTask) return false; + const parentTask = findParentTask(subtask); - if (!parentTask) return; + if (!parentTask) return false; const index = parentTask.subtasks.findIndex(t => t.id === subtask.id); - if (index === -1) return; + if (index === -1) return false; 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]]; + } else { + return false; } renderCurrentView(); selectAndFocusTask(subtask); scheduleSave(); + return true; } function pushSubtaskIntoTarget(subtask, direction, navigate = false) { // Don't allow pushing the current parent task - if (subtask === currentTask) { - return; - } + if (subtask === currentTask) return false; if (navigate) { document.documentElement.style.scrollBehavior = 'auto'; } const parentTask = findParentTask(subtask); - if (!parentTask) return; + if (!parentTask) return false; const index = parentTask.subtasks.findIndex(t => t.id === subtask.id); - if (index === -1) return; + if (index === -1) return false; let targetTask = null; if (direction === 'up' && index > 0) { @@ -967,7 +1005,7 @@ targetTask = parentTask.subtasks[index + 1]; } - if (!targetTask) return; + if (!targetTask) return false; // Remove the subtask from its current parent parentTask.subtasks.splice(index, 1); @@ -995,18 +1033,15 @@ } scheduleSave(); + return true; } function pullSubtaskOutLayer(subtask, navigate = false) { // Don't allow pulling the current parent task - if (subtask === currentTask) { - return; - } + if (subtask === currentTask) return false; // Don't allow pulling when at root level - if (taskPath.length <= 1) { - return; - } + if (taskPath.length <= 1) return false; if (navigate) { document.documentElement.style.scrollBehavior = 'auto'; @@ -1056,6 +1091,7 @@ } scheduleSave(); + return true; }