fix: use OWA native multi-select via Ctrl+click then single toolbar action
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user