diff --git a/VERSION b/VERSION index 0d91a54..9e11b32 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.0 +0.3.1 diff --git a/docker/ploughshares/static/css/hotkeys.css b/docker/ploughshares/static/css/hotkeys.css new file mode 100644 index 0000000..0214cf9 --- /dev/null +++ b/docker/ploughshares/static/css/hotkeys.css @@ -0,0 +1,75 @@ +/** + * Keyboard shortcuts styling for Project Ploughshares Transaction Management System + */ + +/* Keyboard key styling */ +kbd { + display: inline-block; + padding: 0.2em 0.4em; + font-size: 0.85em; + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + line-height: 1; + color: #212529; + background-color: #f8f9fa; + border: 1px solid #dee2e6; + border-radius: 0.2rem; + box-shadow: inset 0 -0.1rem 0 rgba(0, 0, 0, 0.25); +} + +/* Hotkey reference button */ +.hotkey-help-btn { + position: fixed; + bottom: 20px; + right: 20px; + z-index: 1030; + opacity: 0.7; + transition: opacity 0.3s; +} + +.hotkey-help-btn:hover { + opacity: 1; +} + +/* Tooltip for hotkeys */ +[data-hotkey]::after { + content: attr(data-hotkey); + display: inline-block; + margin-left: 5px; + padding: 0.1em 0.3em; + font-size: 0.75em; + color: #6c757d; + background-color: #f8f9fa; + border: 1px solid #dee2e6; + border-radius: 0.2rem; + vertical-align: middle; +} + +/* Hotkey badge in buttons */ +.btn .hotkey-badge { + margin-left: 5px; + font-size: 0.75em; + vertical-align: middle; + opacity: 0.7; +} + +/* Hotkey modal styling */ +.hotkey-table td:first-child { + width: 100px; + text-align: right; +} + +.hotkey-table td { + padding: 0.3rem 0.5rem; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .hotkey-help-btn { + bottom: 10px; + right: 10px; + } + + [data-hotkey]::after { + display: none; + } +} \ No newline at end of file diff --git a/docker/ploughshares/static/js/hotkeys.js b/docker/ploughshares/static/js/hotkeys.js new file mode 100644 index 0000000..ecbcbba --- /dev/null +++ b/docker/ploughshares/static/js/hotkeys.js @@ -0,0 +1,411 @@ +/** + * Keyboard shortcuts for Project Ploughshares Transaction Management System + */ +document.addEventListener('DOMContentLoaded', function() { + // Initialize hotkeys + initHotkeys(); + + // Add hotkey tooltips to elements + addHotkeyTooltips(); +}); + +/** + * Initialize keyboard shortcuts + */ +function initHotkeys() { + document.addEventListener('keydown', function(event) { + // Skip if user is typing in an input field, textarea or has a modal open + if (isUserTyping() || isModalOpen()) { + return; + } + + // Get the current page based on URL + const currentPath = window.location.pathname; + + // Global shortcuts (available on all pages) + switch (event.key) { + case 'h': // Help - Show hotkey reference + event.preventDefault(); + showHotkeyReference(); + break; + case 'g': // Go to + if (event.altKey) { + event.preventDefault(); + showGoToModal(); + } + break; + } + + // Page-specific shortcuts + if (currentPath === '/' || currentPath === '/index.html') { + // Transaction list page + handleTransactionListHotkeys(event); + } else if (currentPath.startsWith('/transaction/') && !currentPath.includes('/edit')) { + // Transaction view page + handleTransactionViewHotkeys(event); + } else if (currentPath === '/pending-approval') { + // Pending approval page + handlePendingApprovalHotkeys(event); + } + }); +} + +/** + * Handle hotkeys for the transaction list page + */ +function handleTransactionListHotkeys(event) { + switch (event.key) { + case 'n': // New transaction + event.preventDefault(); + window.location.href = '/transaction/add'; + break; + case 'p': // Pending approval + event.preventDefault(); + window.location.href = '/pending-approval'; + break; + case 'f': // Focus search + event.preventDefault(); + const searchInput = document.getElementById('searchInput'); + if (searchInput) { + searchInput.focus(); + } + break; + case 'r': // Refresh + event.preventDefault(); + window.location.reload(); + break; + } +} + +/** + * Handle hotkeys for the transaction view page + */ +function handleTransactionViewHotkeys(event) { + switch (event.key) { + case 'e': // Edit + event.preventDefault(); + const editBtn = document.querySelector('a[href^="/transaction/"][href$="/edit"]'); + if (editBtn) { + editBtn.click(); + } + break; + case 'b': // Back to list + event.preventDefault(); + window.location.href = '/'; + break; + case 'ArrowLeft': // Previous transaction + event.preventDefault(); + const prevBtn = document.querySelector('a[aria-label="Previous transaction"]'); + if (prevBtn && !prevBtn.classList.contains('disabled') && !prevBtn.hasAttribute('disabled')) { + prevBtn.click(); + } + break; + case 'ArrowRight': // Next transaction + event.preventDefault(); + const nextBtn = document.querySelector('a[aria-label="Next transaction"]'); + if (nextBtn && !nextBtn.classList.contains('disabled') && !nextBtn.hasAttribute('disabled')) { + nextBtn.click(); + } + break; + case 'a': // Approve + event.preventDefault(); + const approveBtn = document.querySelector('button[data-bs-target="#approveModal"]'); + if (approveBtn) { + approveBtn.click(); + } + break; + } +} + +/** + * Handle hotkeys for the pending approval page + */ +function handlePendingApprovalHotkeys(event) { + switch (event.key) { + case 'b': // Back to list + event.preventDefault(); + window.location.href = '/'; + break; + case 'r': // Refresh + event.preventDefault(); + window.location.reload(); + break; + // Number keys 1-9 to quickly view transactions + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + event.preventDefault(); + const index = parseInt(event.key) - 1; + const viewButtons = document.querySelectorAll('a[aria-label^="View transaction"]'); + if (viewButtons.length > index) { + viewButtons[index].click(); + } + break; + } +} + +/** + * Check if user is currently typing in an input field + */ +function isUserTyping() { + const activeElement = document.activeElement; + return activeElement.tagName === 'INPUT' || + activeElement.tagName === 'TEXTAREA' || + activeElement.isContentEditable; +} + +/** + * Check if a modal is currently open + */ +function isModalOpen() { + return document.querySelector('.modal.show') !== null; +} + +/** + * Show a modal with hotkey reference + */ +function showHotkeyReference() { + // Create modal if it doesn't exist + let modal = document.getElementById('hotkeyReferenceModal'); + + if (!modal) { + modal = document.createElement('div'); + modal.id = 'hotkeyReferenceModal'; + modal.className = 'modal fade'; + modal.tabIndex = '-1'; + modal.setAttribute('aria-labelledby', 'hotkeyReferenceModalLabel'); + modal.setAttribute('aria-hidden', 'true'); + + modal.innerHTML = ` +