diff --git a/content/keyboard.js b/content/keyboard.js index 330581a..b3f3fa3 100644 --- a/content/keyboard.js +++ b/content/keyboard.js @@ -168,6 +168,29 @@ window.OutlookRelook.Keyboard = (function () { // --- Actions --- + // --- Find a button by aria-label, searching within a target element first, + // then the global toolbar, then the whole document --- + function findButton(target, label) { + // 1. Look for inline button within/near the message row + // OWA renders Delete/Archive/Flag buttons on each row on hover + var btn = target.querySelector('button[aria-label="' + label + '"]'); + if (btn) return btn; + + // 2. Check the parent (some buttons are siblings of the row) + if (target.parentElement) { + btn = target.parentElement.querySelector('button[aria-label="' + label + '"]'); + if (btn) return btn; + } + + // 3. Look in the global toolbar area + btn = document.querySelector('[role="toolbar"] button[aria-label="' + label + '"], .fui-Toolbar button[aria-label="' + label + '"]'); + if (btn) return btn; + + // 4. Anywhere in the document + btn = document.querySelector('button[aria-label="' + label + '"]'); + return btn; + } + function performAction(targets, actionFn) { if (targets.length === 0) return; var i = 0; @@ -178,99 +201,102 @@ window.OutlookRelook.Keyboard = (function () { } var target = targets[i]; i++; + // Click to select in OWA, then perform action target.click(); setTimeout(function () { actionFn(target); - setTimeout(processNext, 150); - }, 100); + setTimeout(processNext, 200); + }, 150); } processNext(); } function actionDelete(targets) { - performAction(targets, function () { - var deleteBtn = document.querySelector( - '[aria-label*="Delete" i][role="button"], [aria-label*="delete" i][role="menuitem"]' - ); - if (deleteBtn) { - deleteBtn.click(); + performAction(targets, function (target) { + var btn = findButton(target, 'Delete'); + if (btn) { + btn.click(); } else { - var deleteEvent = new KeyboardEvent('keydown', { - key: 'Delete', code: 'Delete', bubbles: true, cancelable: true - }); - document.activeElement.dispatchEvent(deleteEvent); + console.warn('[Outlook Relook] Delete button not found'); } }); } function actionArchive(targets) { - performAction(targets, function () { - var archiveBtn = document.querySelector( - '[aria-label*="Archive" i][role="button"], [aria-label*="archive" i][role="menuitem"]' - ); - if (archiveBtn) { - archiveBtn.click(); + performAction(targets, function (target) { + var btn = findButton(target, 'Archive'); + if (btn) { + btn.click(); } else { console.warn('[Outlook Relook] Archive button not found'); } }); } - function actionMarkRead(targets) { - performAction(targets, function () { - var readBtn = document.querySelector( - '[aria-label*="Mark as read" i][role="button"], [aria-label*="Mark as read" i][role="menuitem"]' - ); - if (readBtn) { - readBtn.click(); + function actionMarkReadUnread(targets) { + // OWA uses a single toggle button "Read / Unread" + performAction(targets, function (target) { + var btn = findButton(target, 'Read / Unread'); + if (btn) { + btn.click(); } else { - triggerContextMenuAction(/mark as read/i); - } - }); - } - - function actionMarkUnread(targets) { - performAction(targets, function () { - var unreadBtn = document.querySelector( - '[aria-label*="Mark as unread" i][role="button"], [aria-label*="Mark as unread" i][role="menuitem"]' - ); - if (unreadBtn) { - unreadBtn.click(); - } else { - triggerContextMenuAction(/mark as unread/i); + // Try context menu fallback + triggerContextMenuAction(target, /mark as read|mark as unread|read \/ unread/i); } }); } function actionMove(targets) { if (targets.length > 0) { + // Select first target, then open move dialog targets[0].click(); setTimeout(function () { - var moveBtn = document.querySelector( - '[aria-label*="Move to" i][role="button"], [aria-label*="Move" i][role="menuitem"]' - ); - if (moveBtn) { - moveBtn.click(); + var btn = findButton(targets[0], 'Move to'); + if (btn) { + btn.click(); } else { - triggerContextMenuAction(/move to/i); + triggerContextMenuAction(targets[0], /move to/i); } - }, 100); + }, 150); } } - function triggerContextMenuAction(pattern) { - var focused = document.querySelector('.or-kb-focused'); - if (!focused) return; - var rect = focused.getBoundingClientRect(); + function actionFlag(targets) { + performAction(targets, function (target) { + var btn = findButton(target, 'Flag this message') + || findButton(target, 'Flag / Unflag'); + if (btn) { + btn.click(); + } else { + console.warn('[Outlook Relook] Flag button not found'); + } + }); + } + + function actionPin(targets) { + performAction(targets, function (target) { + var btn = findButton(target, 'Pin / Unpin'); + if (btn) { + btn.click(); + } else { + console.warn('[Outlook Relook] Pin button not found'); + } + }); + } + + function triggerContextMenuAction(target, pattern) { + var el = target || document.querySelector('.or-kb-focused'); + if (!el) return; + var rect = el.getBoundingClientRect(); var contextEvent = new MouseEvent('contextmenu', { bubbles: true, cancelable: true, clientX: rect.x + 10, clientY: rect.y + 10, }); - focused.dispatchEvent(contextEvent); + el.dispatchEvent(contextEvent); setTimeout(function () { var menuItems = document.querySelectorAll('[role="menuitem"]'); for (var j = 0; j < menuItems.length; j++) { - if (pattern.test(menuItems[j].textContent)) { + if (pattern.test(menuItems[j].textContent || menuItems[j].getAttribute('aria-label') || '')) { menuItems[j].click(); return; } @@ -360,18 +386,11 @@ window.OutlookRelook.Keyboard = (function () { break; case 'I': - if (shift) { - targets = getActionTargets(items); - actionMarkRead(targets); - } else { - handled = false; - } - break; - case 'U': + // Shift+i or Shift+u — OWA uses a single "Read / Unread" toggle if (shift) { targets = getActionTargets(items); - actionMarkUnread(targets); + actionMarkReadUnread(targets); } else { handled = false; } @@ -382,6 +401,18 @@ window.OutlookRelook.Keyboard = (function () { actionMove(targets); break; + case 'f': + // Flag/unflag + targets = getActionTargets(items); + actionFlag(targets); + break; + + case 'p': + // Pin/unpin + targets = getActionTargets(items); + actionPin(targets); + break; + case 'Escape': clearSelection(); break;