fix: track action targets by ID, re-resolve before each action to survive re-renders

This commit is contained in:
Joel Brock
2026-04-28 07:51:45 -07:00
parent e75bcfa478
commit 97ca274070

View File

@@ -176,18 +176,24 @@ window.OutlookRelook.Keyboard = (function () {
updateCountBadge(); updateCountBadge();
} }
function getActionTargets(items) { // Returns an array of message IDs to act on.
var targets = []; // We return IDs (not DOM elements) because OWA re-renders rows after each
// action, invalidating direct element references.
function getActionTargetIds(items) {
var ids = [];
if (selectedIds.size > 0) { if (selectedIds.size > 0) {
selectedIds.forEach(function (id) { selectedIds.forEach(function (id) { ids.push(id); });
var idx = findItemById(items, id);
if (idx >= 0) targets.push(items[idx]);
});
} else { } else {
var fi = getFocusedIndex(items); if (focusedId) ids.push(focusedId);
if (fi >= 0) targets.push(items[fi]);
} }
return targets; return ids;
}
// Re-resolve an ID to the current DOM element (or null if it's gone).
function resolveTargetById(id) {
var items = getMessageItems();
var idx = findItemById(items, id);
return idx >= 0 ? items[idx] : null;
} }
// --- Selection count badge --- // --- Selection count badge ---
@@ -255,36 +261,48 @@ window.OutlookRelook.Keyboard = (function () {
target.dispatchEvent(overEvent); target.dispatchEvent(overEvent);
} }
function performAction(targets, actionFn) { // performAction takes an array of IDs (strings), re-resolves each to a
if (targets.length === 0) return; // 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; var i = 0;
function processNext() { function processNext() {
if (i >= targets.length) { if (i >= ids.length) {
clearSelection(); clearSelection();
return; return;
} }
var target = targets[i]; var id = ids[i];
i++; i++;
// First, try to find the inline button without clicking the row. // Re-resolve the ID to the current DOM element each iteration
// Hover to make OWA's inline action buttons appear. 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); triggerHover(target);
setTimeout(function () { setTimeout(function () {
// Try inline button first (avoids opening the email in fill-screen mode)
var inlineHandled = actionFn(target); var inlineHandled = actionFn(target);
// If inline button wasn't found, fall back to clicking the row + toolbar
if (inlineHandled === false) { if (inlineHandled === false) {
// Fallback: click the row + use toolbar
target.click(); target.click();
setTimeout(function () { setTimeout(function () {
actionFn(target); // Re-resolve once more in case clicking caused a re-render
setTimeout(processNext, 200); var freshTarget = resolveTargetById(id) || target;
actionFn(freshTarget);
setTimeout(processNext, 250);
}, 150); }, 150);
} else { } else {
setTimeout(processNext, 200); // Wait long enough for OWA to finish re-rendering before next action
setTimeout(processNext, 300);
} }
}, 100); }, 120);
} }
processNext(); processNext();
} }
@@ -316,18 +334,19 @@ window.OutlookRelook.Keyboard = (function () {
}); });
} }
function actionMove(targets) { function actionMove(ids) {
if (targets.length > 0) { if (!ids || ids.length === 0) return;
triggerHover(targets[0]); var first = resolveTargetById(ids[0]);
setTimeout(function () { if (!first) return;
var btn = findButton(targets[0], 'Move to'); triggerHover(first);
if (btn) { setTimeout(function () {
btn.click(); var btn = findButton(first, 'Move to');
} else { if (btn) {
triggerContextMenuAction(targets[0], /move to/i); btn.click();
} } else {
}, 150); triggerContextMenuAction(first, /move to/i);
} }
}, 150);
} }
function actionFlag(targets) { function actionFlag(targets) {
@@ -423,22 +442,22 @@ window.OutlookRelook.Keyboard = (function () {
e.preventDefault(); e.preventDefault();
toggleSelect(items, currentIdx); toggleSelect(items, currentIdx);
} else if (matchesAction(e, preset.delete)) { } else if (matchesAction(e, preset.delete)) {
targets = getActionTargets(items); targets = getActionTargetIds(items);
actionDelete(targets); actionDelete(targets);
} else if (matchesAction(e, preset.archive)) { } else if (matchesAction(e, preset.archive)) {
targets = getActionTargets(items); targets = getActionTargetIds(items);
actionArchive(targets); actionArchive(targets);
} else if (matchesAction(e, preset.readUnread)) { } else if (matchesAction(e, preset.readUnread)) {
targets = getActionTargets(items); targets = getActionTargetIds(items);
actionMarkReadUnread(targets); actionMarkReadUnread(targets);
} else if (matchesAction(e, preset.move)) { } else if (matchesAction(e, preset.move)) {
targets = getActionTargets(items); targets = getActionTargetIds(items);
actionMove(targets); actionMove(targets);
} else if (matchesAction(e, preset.flag)) { } else if (matchesAction(e, preset.flag)) {
targets = getActionTargets(items); targets = getActionTargetIds(items);
actionFlag(targets); actionFlag(targets);
} else if (matchesAction(e, preset.pin)) { } else if (matchesAction(e, preset.pin)) {
targets = getActionTargets(items); targets = getActionTargetIds(items);
actionPin(targets); actionPin(targets);
} else if (matchesAction(e, preset.deselect)) { } else if (matchesAction(e, preset.deselect)) {
clearSelection(); clearSelection();