// Outlook Relook — MutationObserver // Watches OWA's DOM and removes dynamically injected elements // based on active settings. window.OutlookRelook = window.OutlookRelook || {}; window.OutlookRelook.Observer = (function () { 'use strict'; let observer = null; let currentSettings = {}; // Map setting keys to selector registry names // Each setting can suppress one or more logical elements const SETTING_TO_SELECTORS = { hideCopilot: ['copilot-button', 'copilot-pane', 'copilot-compose-suggestions'], hideSuggestedReplies: ['suggested-replies'], hidePromoBanners: ['promo-banners'], hideFocusedOtherTabs: ['focused-other-tabs'], hideSidebarAppIcons: ['sidebar-app-icons'], hideGroupsSection: ['groups-section'], hideMyDayButtons: ['my-day-buttons'], hideSenderAvatars: ['sender-avatars'], hideFeatureDiscovery: ['feature-discovery'], hideVivaInsights: ['viva-insights'], hideUnreadOtherBanner: ['unread-other-banner'], hideActivityFeed: ['activity-feed'], }; // Text content patterns to match elements by their inner text. // Used when aria/data selectors don't catch dynamically injected content. const TEXT_PATTERNS = { hidePromoBanners: [ /try the new outlook/i, /upgrade to premium/i, /get the outlook app/i, /switch to the new/i, ], hideFeatureDiscovery: [ /what's new/i, /new feature/i, /did you know/i, ], }; function suppressElements() { for (const [settingKey, selectorNames] of Object.entries(SETTING_TO_SELECTORS)) { if (!currentSettings[settingKey]) continue; for (const name of selectorNames) { const elements = window.OutlookRelook.resolveSelector(name); for (const el of elements) { if (el.style.display !== 'none') { el.style.display = 'none'; console.log('[Outcut] Suppressed: ' + name, el); } } } } // Text-based suppression for elements missed by selectors for (const [settingKey, patterns] of Object.entries(TEXT_PATTERNS)) { if (!currentSettings[settingKey]) continue; for (const pattern of patterns) { // Check buttons, banners, and generic containers const candidates = document.querySelectorAll( '[role="alert"], [role="banner"], [role="dialog"], [role="status"], button, a' ); for (const el of candidates) { if (el.style.display === 'none') continue; var text = el.textContent || ''; if (pattern.test(text) && text.length < 200) { el.style.display = 'none'; console.log('[Outcut] Suppressed by text: "' + text.trim().substring(0, 50) + '"', el); } } } } } function start(settings) { currentSettings = settings; // Initial pass suppressElements(); // Watch for DOM mutations observer = new MutationObserver(function (mutations) { // Debounce: only process if nodes were actually added var hasAddedNodes = false; for (var i = 0; i < mutations.length; i++) { if (mutations[i].addedNodes.length > 0) { hasAddedNodes = true; break; } } if (hasAddedNodes) { suppressElements(); } }); observer.observe(document.body, { childList: true, subtree: true, }); console.log('[Outcut] Observer started'); } function updateSettings(settings) { currentSettings = settings; // Re-run suppression with new settings suppressElements(); } function stop() { if (observer) { observer.disconnect(); observer = null; console.log('[Outcut] Observer stopped'); } } return { start: start, updateSettings: updateSettings, stop: stop }; })();