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);
|
target.dispatchEvent(overEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// performAction takes an array of IDs (strings), re-resolves each to a
|
// Find a button in the global OWA toolbar (not inline on a row).
|
||||||
// fresh DOM element before acting, and processes them sequentially.
|
// The toolbar button acts on OWA's currently selected messages.
|
||||||
// This handles OWA's re-renders that invalidate stale element references.
|
function findToolbarButton(label) {
|
||||||
function performAction(ids, actionFn) {
|
var sels = [
|
||||||
if (!ids || ids.length === 0) return;
|
'[role="toolbar"] button[aria-label="' + label + '"]',
|
||||||
var i = 0;
|
'.fui-Toolbar button[aria-label="' + label + '"]',
|
||||||
function processNext() {
|
'[aria-label*="Quick actions" i] button[aria-label="' + label + '"]'
|
||||||
if (i >= ids.length) {
|
];
|
||||||
clearSelection();
|
for (var i = 0; i < sels.length; i++) {
|
||||||
return;
|
var btn = document.querySelector(sels[i]);
|
||||||
}
|
if (btn) return btn;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
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) {
|
// Ctrl+click a message row to add it to OWA's native multi-selection.
|
||||||
performAction(targets, function (target) {
|
// Uses MouseEvent with ctrlKey + metaKey to handle both Mac and Win.
|
||||||
var btn = findButton(target, 'Delete');
|
function ctrlClickRow(target) {
|
||||||
if (btn) { btn.click(); return true; }
|
var rect = target.getBoundingClientRect();
|
||||||
console.warn('[Outcut] Delete button not found');
|
var opts = {
|
||||||
return false;
|
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) {
|
// Build OWA's native selection by Ctrl+clicking each ID, then run a single
|
||||||
performAction(targets, function (target) {
|
// toolbar action that operates on the entire selection.
|
||||||
var btn = findButton(target, 'Archive');
|
function performToolbarAction(ids, toolbarLabel, fallbackContextPattern) {
|
||||||
if (btn) { btn.click(); return true; }
|
if (!ids || ids.length === 0) return;
|
||||||
console.warn('[Outcut] Archive button not found');
|
|
||||||
return false;
|
// 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) {
|
function actionDelete(ids) {
|
||||||
performAction(targets, function (target) {
|
performToolbarAction(ids, 'Delete', /^delete$/i);
|
||||||
var btn = findButton(target, 'Read / Unread');
|
}
|
||||||
if (btn) { btn.click(); return true; }
|
|
||||||
triggerContextMenuAction(target, /mark as read|mark as unread|read \/ unread/i);
|
function actionArchive(ids) {
|
||||||
return true; // context menu handles it
|
performToolbarAction(ids, 'Archive', /^archive$/i);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
function actionMarkReadUnread(ids) {
|
||||||
|
performToolbarAction(ids, 'Read / Unread', /mark as read|mark as unread|read \/ unread/i);
|
||||||
}
|
}
|
||||||
|
|
||||||
function actionMove(ids) {
|
function actionMove(ids) {
|
||||||
|
performToolbarAction(ids, 'Move to', /move to/i);
|
||||||
|
}
|
||||||
|
|
||||||
|
function actionFlag(ids) {
|
||||||
|
// Try multiple labels
|
||||||
if (!ids || ids.length === 0) return;
|
if (!ids || ids.length === 0) return;
|
||||||
var first = resolveTargetById(ids[0]);
|
var first = resolveTargetById(ids[0]);
|
||||||
if (!first) return;
|
if (!first) return;
|
||||||
triggerHover(first);
|
first.click();
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
var btn = findButton(first, 'Move to');
|
var i = 1;
|
||||||
if (btn) {
|
function addNext() {
|
||||||
btn.click();
|
if (i >= ids.length) {
|
||||||
} else {
|
setTimeout(function () {
|
||||||
triggerContextMenuAction(first, /move to/i);
|
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) {
|
function actionPin(ids) {
|
||||||
performAction(targets, function (target) {
|
performToolbarAction(ids, 'Pin / Unpin', /pin/i);
|
||||||
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 triggerContextMenuAction(target, pattern) {
|
function triggerContextMenuAction(target, pattern) {
|
||||||
|
|||||||
Reference in New Issue
Block a user