// Outlook Relook — Behavior Patches // JS-based UX improvements, each gated by a setting key. window.OutlookRelook = window.OutlookRelook || {}; window.OutlookRelook.Behavior = (function () { 'use strict'; var OR = window.OutlookRelook; var currentSettings = {}; var cleanupFns = []; // --- Auto-collapse ribbon on page load --- function setupAutoCollapseRibbon() { if (!currentSettings.autoCollapseRibbon) return; // Wait for OWA to finish rendering, then collapse the ribbon var timer = setTimeout(function () { var elements = OR.resolveSelector('ribbon-collapse-button'); for (var i = 0; i < elements.length; i++) { if (elements[i].getAttribute('aria-expanded') === 'true') { elements[i].click(); console.log('[Outlook Relook] Auto-collapsed ribbon'); } } }, 2000); cleanupFns.push(function () { clearTimeout(timer); }); } // --- Remember sidebar collapsed/expanded state --- function setupRememberSidebar() { if (!currentSettings.rememberSidebarState) return; chrome.storage.local.get({ sidebarCollapsed: false }, function (result) { if (result.sidebarCollapsed) { var timer = setTimeout(function () { var pane = OR.resolveSelector('folder-pane'); for (var i = 0; i < pane.length; i++) { var toggle = pane[i].closest('[aria-expanded]') || pane[i].querySelector('[aria-expanded]'); if (toggle && toggle.getAttribute('aria-expanded') === 'true') { toggle.click(); } } }, 2000); cleanupFns.push(function () { clearTimeout(timer); }); } }); // Watch for sidebar toggle changes var sidebarObserver = new MutationObserver(function () { var pane = OR.resolveSelector('folder-pane'); if (pane.length > 0) { var isVisible = pane[0].offsetWidth > 50; chrome.storage.local.set({ sidebarCollapsed: !isVisible }); } }); var timer2 = setTimeout(function () { var pane = OR.resolveSelector('folder-pane'); if (pane.length > 0 && pane[0].parentElement) { sidebarObserver.observe(pane[0].parentElement, { attributes: true, subtree: true }); } }, 3000); cleanupFns.push(function () { clearTimeout(timer2); sidebarObserver.disconnect(); }); } // --- Suppress contact card hover popups --- function setupSuppressContactHover() { if (!currentSettings.suppressContactHover) return; var handler = function (e) { // Check if the hovered element or its ancestors trigger a contact card var target = e.target.closest('[data-lpc-hover-target], [aria-haspopup="dialog"]'); if (target) { e.stopPropagation(); e.preventDefault(); } }; document.addEventListener('mouseenter', handler, true); cleanupFns.push(function () { document.removeEventListener('mouseenter', handler, true); }); } // --- Auto-advance to next email after delete --- function setupAutoAdvance() { if (!currentSettings.autoAdvanceAfterDelete) return; var handler = function (e) { // Detect delete key press if (e.key === 'Delete' || e.key === 'Backspace') { var selected = document.querySelector( '[role="option"][aria-selected="true"], [role="listitem"][aria-selected="true"]' ); if (selected) { var next = selected.nextElementSibling; if (next) { // Wait for OWA to process the delete, then focus next setTimeout(function () { next.click(); next.focus(); }, 200); } } } }; document.addEventListener('keydown', handler, true); cleanupFns.push(function () { document.removeEventListener('keydown', handler, true); }); } // --- Auto-dismiss notification toasts --- function setupAutoDismissToasts() { if (currentSettings.autoDismissToasts === 'off') return; var delay = parseInt(currentSettings.autoDismissToasts, 10) * 1000; if (isNaN(delay) || delay <= 0) return; var toastObserver = new MutationObserver(function (mutations) { for (var m = 0; m < mutations.length; m++) { var addedNodes = mutations[m].addedNodes; for (var n = 0; n < addedNodes.length; n++) { var node = addedNodes[n]; if (node.nodeType !== 1) continue; var toasts = []; if (node.matches && node.matches('[role="alert"], [role="status"][aria-live]')) { toasts.push(node); } if (node.querySelectorAll) { var found = node.querySelectorAll('[role="alert"], [role="status"][aria-live]'); for (var t = 0; t < found.length; t++) toasts.push(found[t]); } for (var i = 0; i < toasts.length; i++) { (function (toast) { setTimeout(function () { // Try to find a dismiss button var dismiss = toast.querySelector('[aria-label*="Close" i], [aria-label*="Dismiss" i]'); if (dismiss) { dismiss.click(); } else { toast.style.display = 'none'; } }, delay); })(toasts[i]); } } } }); toastObserver.observe(document.body, { childList: true, subtree: true }); cleanupFns.push(function () { toastObserver.disconnect(); }); } // --- Reposition toast notifications --- function setupToastPosition() { if (currentSettings.toastPosition === 'bottom-left') return; // OWA default var style = document.createElement('style'); style.id = 'or-toast-position'; style.textContent = [ '[role="alert"], [role="status"][aria-live] {', ' position: fixed !important;', ' top: 8px !important;', ' right: 8px !important;', ' bottom: auto !important;', ' left: auto !important;', ' z-index: 999999 !important;', '}' ].join('\n'); document.head.appendChild(style); cleanupFns.push(function () { style.remove(); }); } // --- Sticky Reply/Forward bar --- function setupStickyReplyBar() { if (!currentSettings.stickyReplyBar) return; var style = document.createElement('style'); style.id = 'or-sticky-reply'; style.textContent = [ '[aria-label*="Reply all" i][role="button"],', '[aria-label*="Reply" i][role="button"],', '[aria-label*="Forward" i][role="button"] {', ' position: sticky !important;', ' bottom: 0 !important;', ' z-index: 10 !important;', ' background-color: var(--or-bg-primary, #fff) !important;', '}' ].join('\n'); document.head.appendChild(style); cleanupFns.push(function () { style.remove(); }); } // --- Auto-resize compose window --- function setupAutoResizeCompose() { if (!currentSettings.autoResizeCompose) return; var composeObserver = new MutationObserver(function (mutations) { for (var m = 0; m < mutations.length; m++) { var addedNodes = mutations[m].addedNodes; for (var n = 0; n < addedNodes.length; n++) { var node = addedNodes[n]; if (node.nodeType !== 1) continue; var composeWindows = []; if (node.matches && node.matches('[aria-label*="compose" i][role="dialog"], [aria-label*="New message" i]')) { composeWindows.push(node); } if (node.querySelectorAll) { var found = node.querySelectorAll('[aria-label*="compose" i][role="dialog"], [aria-label*="New message" i]'); for (var i = 0; i < found.length; i++) composeWindows.push(found[i]); } for (var w = 0; w < composeWindows.length; w++) { composeWindows[w].style.minHeight = '60vh'; composeWindows[w].style.height = '60vh'; console.log('[Outlook Relook] Auto-resized compose window'); } } } }); composeObserver.observe(document.body, { childList: true, subtree: true }); cleanupFns.push(function () { composeObserver.disconnect(); }); } // --- Throttle desktop notifications --- function setupThrottleNotifications() { if (!currentSettings.throttleNotifications) return; var OrigNotification = window.Notification; // Only patch if Notification API exists if (!OrigNotification) return; var lastNotificationTime = 0; var MIN_INTERVAL = 30000; // 30 seconds between notifications window.Notification = function (title, options) { var now = Date.now(); if (now - lastNotificationTime < MIN_INTERVAL) { console.log('[Outlook Relook] Throttled notification: "' + title + '"'); return {}; } lastNotificationTime = now; return new OrigNotification(title, options); }; window.Notification.permission = OrigNotification.permission; window.Notification.requestPermission = OrigNotification.requestPermission.bind(OrigNotification); cleanupFns.push(function () { window.Notification = OrigNotification; }); } // --- Public API --- function start(settings) { currentSettings = settings; setupAutoCollapseRibbon(); setupRememberSidebar(); setupSuppressContactHover(); setupAutoAdvance(); setupAutoDismissToasts(); setupToastPosition(); setupStickyReplyBar(); setupAutoResizeCompose(); setupThrottleNotifications(); console.log('[Outlook Relook] Behavior patches applied'); } function updateSettings(settings) { // Tear down existing behaviors and re-apply stop(); currentSettings = settings; start(settings); } function stop() { for (var i = 0; i < cleanupFns.length; i++) { try { cleanupFns[i](); } catch (e) { /* ignore */ } } cleanupFns = []; } return { start: start, updateSettings: updateSettings, stop: stop }; })();