From e50b11450fe7f01291b5e5da8594938ed3c1d885 Mon Sep 17 00:00:00 2001 From: Joel Brock Date: Thu, 23 Apr 2026 08:55:06 -0700 Subject: [PATCH] feat: selector registry with resilient aria/data/structural selectors --- content/selectors.js | 166 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 165 insertions(+), 1 deletion(-) diff --git a/content/selectors.js b/content/selectors.js index 440b1fe..f6e8e54 100644 --- a/content/selectors.js +++ b/content/selectors.js @@ -1 +1,165 @@ -// Outlook Relook — selectors.js (stub, implemented in later task) +// Outlook Relook — Selector Registry +// Maps logical element names to resilient CSS selectors. +// Selectors use aria-label, data-*, role, and structural patterns +// to avoid depending on OWA's obfuscated class names. +// +// Selectors marked /* INSPECT OWA */ must be verified against the live DOM. +// Use Chrome DevTools on outlook.office.com to find correct selectors. + +window.OutlookRelook = window.OutlookRelook || {}; + +window.OutlookRelook.SELECTORS = { + // --- Hide Elements --- + 'copilot-button': { + primary: '[aria-label*="Copilot" i]', + fallbacks: ['[data-app-section="Copilot"]', '[title*="Copilot" i]'], + }, + 'copilot-pane': { + primary: '[aria-label*="Copilot" i][role="complementary"]', + fallbacks: [], + }, + 'copilot-compose-suggestions': { + primary: '[aria-label*="writing suggestion" i]', + fallbacks: ['[aria-label*="Copilot" i][role="listbox"]'], + }, + 'suggested-replies': { + primary: '[aria-label*="Suggested repl" i]', + fallbacks: ['[role="group"][aria-label*="Reply suggestion" i]'], + }, + 'promo-banners': { + primary: '[aria-label*="Try the new" i], [aria-label*="Upgrade" i], [aria-label*="Get the app" i], [aria-label*="premium" i]', + fallbacks: [], + }, + 'focused-other-tabs': { + primary: '[role="tablist"][aria-label*="Focused" i]', + fallbacks: ['[aria-label*="Focused Inbox" i]'], + }, + 'sidebar-app-icons': { + primary: 'nav[aria-label*="App" i], [role="navigation"][aria-label*="Module" i]', + fallbacks: [], + }, + 'groups-section': { + primary: '[aria-label*="Groups" i][role="tree"], [aria-label*="Groups" i][role="treeitem"]', + fallbacks: [], + }, + 'my-day-buttons': { + primary: '[aria-label*="My Day" i], [aria-label*="To Do" i][role="button"]', + fallbacks: [], + }, + 'sender-avatars': { + primary: '[role="listbox"] [aria-hidden="true"] img[src*="profile"], [role="listbox"] [aria-label*="avatar" i]', + fallbacks: [], + }, + 'feature-discovery': { + primary: '[role="dialog"][aria-label*="new feature" i], [role="dialog"][aria-label*="what\'s new" i], [aria-label*="teaching" i]', + fallbacks: [], + }, + 'viva-insights': { + primary: '[aria-label*="Viva" i], [aria-label*="Daily Briefing" i], [aria-label*="Briefing" i]', + fallbacks: [], + }, + 'unread-other-banner': { + primary: '[aria-label*="unread in Other" i]', + fallbacks: [], + }, + 'activity-feed': { + primary: '[aria-label*="Activity" i][role="complementary"], [aria-label*="mentioned you" i]', + fallbacks: [], + }, + + // --- Layout Regions (for density CSS) --- + 'top-bar': { + primary: '[role="banner"]', + fallbacks: ['header'], + }, + 'command-bar': { + primary: '[role="toolbar"][aria-label*="command" i], [role="toolbar"][aria-label*="action" i]', + fallbacks: [], + }, + 'message-list': { + primary: '[role="listbox"][aria-label*="Message list" i], [role="list"][aria-label*="Message" i]', + fallbacks: [], + }, + 'reading-pane': { + primary: '[role="main"][aria-label*="Reading" i], [aria-label*="Message body" i]', + fallbacks: [], + }, + 'folder-pane': { + primary: '[role="navigation"][aria-label*="Folder" i], [role="tree"][aria-label*="Folder" i]', + fallbacks: [], + }, + 'compose-toolbar': { + primary: '[role="toolbar"][aria-label*="Format" i]', + fallbacks: [], + }, + 'search-bar': { + primary: '[role="search"], [aria-label*="Search" i] input', + fallbacks: [], + }, + + // --- Behavior targets --- + 'ribbon-collapse-button': { + primary: '[aria-label*="Ribbon" i][aria-expanded], [aria-label*="collapse" i][role="button"]', + fallbacks: [], + }, + 'contact-hover-card': { + primary: '[role="dialog"][aria-label*="contact" i], [role="tooltip"][aria-label*="contact" i]', + fallbacks: [], + }, + 'notification-toast': { + primary: '[role="alert"], [role="status"][aria-live]', + fallbacks: [], + }, + 'reply-forward-bar': { + primary: '[aria-label*="Reply" i][role="button"], [aria-label*="Forward" i][role="button"]', + fallbacks: [], + }, + 'compose-window': { + primary: '[aria-label*="compose" i][role="dialog"], [aria-label*="New message" i]', + fallbacks: [], + }, + 'folder-header': { + primary: '[role="heading"][aria-level]', + fallbacks: [], + }, +}; + +// Resolve a logical name to matching DOM elements. +// Tries primary selector first, then fallbacks in order. +// Returns an array of elements (possibly empty). +window.OutlookRelook.resolveSelector = function (name) { + const entry = window.OutlookRelook.SELECTORS[name]; + if (!entry) { + console.warn('[Outlook Relook] Unknown selector: ' + name); + return []; + } + + const selectors = [entry.primary, ...entry.fallbacks]; + for (const selector of selectors) { + try { + const elements = document.querySelectorAll(selector); + if (elements.length > 0) return Array.from(elements); + } catch (e) { + console.warn('[Outlook Relook] Invalid selector for "' + name + '": ' + selector, e); + } + } + + return []; +}; + +// Resolve a logical name to a CSS selector string that currently matches. +// Returns the first matching selector string, or null. +window.OutlookRelook.resolveSelectorString = function (name) { + const entry = window.OutlookRelook.SELECTORS[name]; + if (!entry) return null; + + const selectors = [entry.primary, ...entry.fallbacks]; + for (const selector of selectors) { + try { + if (document.querySelector(selector)) return selector; + } catch (e) { + // skip invalid + } + } + return null; +};