diff --git a/content/behavior.js b/content/behavior.js index 4eb3a4c..c6ee825 100644 --- a/content/behavior.js +++ b/content/behavior.js @@ -1 +1,291 @@ -// Outlook Relook — behavior.js (stub, implemented in later task) +// 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; + + var savedState = localStorage.getItem('or-sidebar-collapsed'); + if (savedState === 'true') { + 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; + localStorage.setItem('or-sidebar-collapsed', String(!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 }; +})();