diff --git a/content/keyboard.js b/content/keyboard.js index 26f6029..0be58d1 100644 --- a/content/keyboard.js +++ b/content/keyboard.js @@ -261,111 +261,128 @@ window.OutlookRelook.Keyboard = (function () { target.dispatchEvent(overEvent); } - // performAction takes an array of IDs (strings), re-resolves each to a - // fresh DOM element before acting, and processes them sequentially. - // This handles OWA's re-renders that invalidate stale element references. - function performAction(ids, actionFn) { - if (!ids || ids.length === 0) return; - var i = 0; - function processNext() { - if (i >= ids.length) { - clearSelection(); - return; - } - var id = ids[i]; - i++; - - // Re-resolve the ID to the current DOM element each iteration - var target = resolveTargetById(id); - if (!target) { - // Element no longer exists (e.g., already deleted) — skip to next - processNext(); - return; - } - - // Hover to make OWA's inline action buttons appear - triggerHover(target); - - setTimeout(function () { - var inlineHandled = actionFn(target); - - if (inlineHandled === false) { - // Fallback: click the row + use toolbar - target.click(); - setTimeout(function () { - // Re-resolve once more in case clicking caused a re-render - var freshTarget = resolveTargetById(id) || target; - actionFn(freshTarget); - setTimeout(processNext, 250); - }, 150); - } else { - // Wait long enough for OWA to finish re-rendering before next action - setTimeout(processNext, 300); - } - }, 120); + // Find a button in the global OWA toolbar (not inline on a row). + // The toolbar button acts on OWA's currently selected messages. + function findToolbarButton(label) { + var sels = [ + '[role="toolbar"] button[aria-label="' + label + '"]', + '.fui-Toolbar button[aria-label="' + label + '"]', + '[aria-label*="Quick actions" i] button[aria-label="' + label + '"]' + ]; + for (var i = 0; i < sels.length; i++) { + var btn = document.querySelector(sels[i]); + if (btn) return btn; } - processNext(); + // Fallback: any button with that label that's NOT inside a message row + var all = document.querySelectorAll('button[aria-label="' + label + '"]'); + for (var j = 0; j < all.length; j++) { + if (!all[j].closest('[role="option"], [role="listitem"]')) { + return all[j]; + } + } + return null; } - function actionDelete(targets) { - performAction(targets, function (target) { - var btn = findButton(target, 'Delete'); - if (btn) { btn.click(); return true; } - console.warn('[Outcut] Delete button not found'); - return false; - }); + // Ctrl+click a message row to add it to OWA's native multi-selection. + // Uses MouseEvent with ctrlKey + metaKey to handle both Mac and Win. + function ctrlClickRow(target) { + var rect = target.getBoundingClientRect(); + var opts = { + bubbles: true, cancelable: true, view: window, + clientX: rect.x + 30, clientY: rect.y + rect.height / 2, + ctrlKey: true, metaKey: true, button: 0 + }; + target.dispatchEvent(new MouseEvent('mousedown', opts)); + target.dispatchEvent(new MouseEvent('mouseup', opts)); + target.dispatchEvent(new MouseEvent('click', opts)); } - function actionArchive(targets) { - performAction(targets, function (target) { - var btn = findButton(target, 'Archive'); - if (btn) { btn.click(); return true; } - console.warn('[Outcut] Archive button not found'); - return false; - }); + // Build OWA's native selection by Ctrl+clicking each ID, then run a single + // toolbar action that operates on the entire selection. + function performToolbarAction(ids, toolbarLabel, fallbackContextPattern) { + if (!ids || ids.length === 0) return; + + // First: clear any current OWA selection by clicking the first target plain + var first = resolveTargetById(ids[0]); + if (!first) { + clearSelection(); + return; + } + first.click(); + + setTimeout(function () { + // Now Ctrl+click the rest to add to selection + var i = 1; + function addNext() { + if (i >= ids.length) { + // All selected in OWA, now click the toolbar action button + setTimeout(function () { + var btn = findToolbarButton(toolbarLabel); + if (btn) { + btn.click(); + } else if (fallbackContextPattern) { + var fresh = resolveTargetById(ids[0]); + if (fresh) triggerContextMenuAction(fresh, fallbackContextPattern); + } else { + console.warn('[Outcut] Toolbar button not found: ' + toolbarLabel); + } + setTimeout(clearSelection, 200); + }, 100); + return; + } + var t = resolveTargetById(ids[i]); + i++; + if (t) ctrlClickRow(t); + setTimeout(addNext, 60); + } + addNext(); + }, 100); } - function actionMarkReadUnread(targets) { - performAction(targets, function (target) { - var btn = findButton(target, 'Read / Unread'); - if (btn) { btn.click(); return true; } - triggerContextMenuAction(target, /mark as read|mark as unread|read \/ unread/i); - return true; // context menu handles it - }); + function actionDelete(ids) { + performToolbarAction(ids, 'Delete', /^delete$/i); + } + + function actionArchive(ids) { + performToolbarAction(ids, 'Archive', /^archive$/i); + } + + function actionMarkReadUnread(ids) { + performToolbarAction(ids, 'Read / Unread', /mark as read|mark as unread|read \/ unread/i); } function actionMove(ids) { + performToolbarAction(ids, 'Move to', /move to/i); + } + + function actionFlag(ids) { + // Try multiple labels if (!ids || ids.length === 0) return; var first = resolveTargetById(ids[0]); if (!first) return; - triggerHover(first); + first.click(); setTimeout(function () { - var btn = findButton(first, 'Move to'); - if (btn) { - btn.click(); - } else { - triggerContextMenuAction(first, /move to/i); + var i = 1; + function addNext() { + if (i >= ids.length) { + setTimeout(function () { + var btn = findToolbarButton('Flag / Unflag') || findToolbarButton('Flag this message'); + if (btn) btn.click(); + setTimeout(clearSelection, 200); + }, 100); + return; + } + var t = resolveTargetById(ids[i]); + i++; + if (t) ctrlClickRow(t); + setTimeout(addNext, 60); } - }, 150); + addNext(); + }, 100); } - function actionFlag(targets) { - performAction(targets, function (target) { - var btn = findButton(target, 'Flag this message') - || findButton(target, 'Flag / Unflag'); - if (btn) { btn.click(); return true; } - console.warn('[Outcut] Flag button not found'); - return false; - }); - } - - function actionPin(targets) { - performAction(targets, function (target) { - var btn = findButton(target, 'Pin / Unpin'); - if (btn) { btn.click(); return true; } - console.warn('[Outcut] Pin button not found'); - return false; - }); + function actionPin(ids) { + performToolbarAction(ids, 'Pin / Unpin', /pin/i); } function triggerContextMenuAction(target, pattern) {