fix: use OWA's actual button selectors for keyboard actions, add flag/pin shortcuts

This commit is contained in:
Joel Brock
2026-04-23 10:35:13 -07:00
parent 8fe0808bbd
commit c66265e48f

View File

@@ -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;