// Outlook Relook — DOM Injector // Injects custom UI elements: mark-all-read button, folder jump dialog. window.OutlookRelook = window.OutlookRelook || {}; window.OutlookRelook.Injector = (function () { 'use strict'; var OR = window.OutlookRelook; var currentSettings = {}; var cleanupFns = []; // --- "Mark all as read" button --- function setupMarkAllRead() { if (!currentSettings.markAllReadButton) return; function injectButton() { // Find folder headers that don't already have our button var headers = OR.resolveSelector('folder-header'); for (var i = 0; i < headers.length; i++) { var header = headers[i]; if (header.querySelector('.or-mark-all-read')) continue; var btn = document.createElement('button'); btn.className = 'or-mark-all-read'; btn.textContent = '\u2713\u2713'; // double checkmark btn.title = 'Mark all as read'; btn.style.cssText = [ 'background: none;', 'border: 1px solid var(--or-border, #ccc);', 'color: var(--or-text-secondary, #666);', 'cursor: pointer;', 'font-size: 11px;', 'padding: 2px 6px;', 'margin-left: 8px;', 'border-radius: 3px;', 'line-height: 1;', 'vertical-align: middle;' ].join(' '); btn.addEventListener('click', (function (hdr) { return function (e) { e.stopPropagation(); // Find the context menu "Mark all as read" option var folder = hdr.closest('[role="treeitem"]'); if (folder) { // Dispatch a contextmenu event to open OWA's context menu var rect = folder.getBoundingClientRect(); var contextEvent = new MouseEvent('contextmenu', { bubbles: true, cancelable: true, clientX: rect.x + 10, clientY: rect.y + 10, }); folder.dispatchEvent(contextEvent); // Wait for context menu to render, then find and click "Mark all as read" setTimeout(function () { var menuItems = document.querySelectorAll('[role="menuitem"]'); for (var j = 0; j < menuItems.length; j++) { if (/mark all as read/i.test(menuItems[j].textContent)) { menuItems[j].click(); console.log('[Outlook Relook] Marked all as read'); return; } } // Close context menu if option not found document.body.click(); console.warn('[Outlook Relook] "Mark all as read" menu item not found'); }, 300); } }; })(header)); header.appendChild(btn); } } // Inject on load and watch for new headers var timer = setTimeout(injectButton, 3000); var injectObserver = new MutationObserver(function () { injectButton(); }); injectObserver.observe(document.body, { childList: true, subtree: true }); cleanupFns.push(function () { clearTimeout(timer); injectObserver.disconnect(); var btns = document.querySelectorAll('.or-mark-all-read'); for (var k = 0; k < btns.length; k++) btns[k].remove(); }); } // --- Quick folder jump (Ctrl+Shift+K) --- function setupFolderJump() { if (!currentSettings.quickFolderJump) return; var dialog = null; function createDialog() { var overlay = document.createElement('div'); overlay.id = 'or-folder-jump'; overlay.style.cssText = [ 'position: fixed;', 'top: 0; left: 0; right: 0; bottom: 0;', 'background: rgba(0,0,0,0.4);', 'z-index: 999999;', 'display: flex;', 'align-items: flex-start;', 'justify-content: center;', 'padding-top: 20vh;' ].join(' '); var box = document.createElement('div'); box.style.cssText = [ 'background: var(--or-bg-primary, #fff);', 'border: 1px solid var(--or-border, #ccc);', 'border-radius: 8px;', 'padding: 12px;', 'width: 400px;', 'max-height: 400px;', 'box-shadow: 0 8px 32px rgba(0,0,0,0.2);', 'font-family: inherit;' ].join(' '); var input = document.createElement('input'); input.type = 'text'; input.placeholder = 'Jump to folder...'; input.style.cssText = [ 'width: 100%;', 'padding: 8px 12px;', 'border: 1px solid var(--or-border, #ccc);', 'border-radius: 4px;', 'font-size: 14px;', 'outline: none;', 'box-sizing: border-box;', 'background: var(--or-bg-secondary, #f5f5f5);', 'color: var(--or-text-primary, #000);' ].join(' '); var results = document.createElement('div'); results.style.cssText = 'margin-top: 8px; max-height: 300px; overflow-y: auto;'; input.addEventListener('input', function () { var query = input.value.toLowerCase().trim(); // Clear results using safe DOM methods while (results.firstChild) { results.removeChild(results.firstChild); } if (!query) return; // Find all folder tree items var folders = document.querySelectorAll('[role="treeitem"]'); var matches = []; for (var i = 0; i < folders.length; i++) { var name = (folders[i].textContent || '').trim(); if (name.toLowerCase().indexOf(query) !== -1) { matches.push({ name: name, element: folders[i] }); } } var limit = Math.min(matches.length, 10); for (var j = 0; j < limit; j++) { (function (match) { var item = document.createElement('div'); item.textContent = match.name; item.style.cssText = [ 'padding: 6px 12px;', 'cursor: pointer;', 'border-radius: 4px;', 'color: var(--or-text-primary, #000);' ].join(' '); item.addEventListener('mouseenter', function () { item.style.backgroundColor = 'var(--or-bg-hover, #e0e0e0)'; }); item.addEventListener('mouseleave', function () { item.style.backgroundColor = ''; }); item.addEventListener('click', function () { match.element.click(); closeDialog(); }); results.appendChild(item); })(matches[j]); } }); box.appendChild(input); box.appendChild(results); overlay.appendChild(box); overlay.addEventListener('click', function (e) { if (e.target === overlay) closeDialog(); }); input.addEventListener('keydown', function (e) { if (e.key === 'Escape') closeDialog(); if (e.key === 'Enter') { var firstResult = results.querySelector('div'); if (firstResult) firstResult.click(); } }); return { overlay: overlay, input: input }; } function openDialog() { if (dialog) return; var created = createDialog(); document.body.appendChild(created.overlay); dialog = created.overlay; setTimeout(function () { created.input.focus(); }, 50); } function closeDialog() { if (dialog) { dialog.remove(); dialog = null; } } var handler = function (e) { // Ctrl+Shift+K (or Cmd+Shift+K on Mac) if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'K') { e.preventDefault(); e.stopPropagation(); if (dialog) { closeDialog(); } else { openDialog(); } } }; document.addEventListener('keydown', handler, true); cleanupFns.push(function () { document.removeEventListener('keydown', handler, true); closeDialog(); }); } // --- Public API --- function start(settings) { currentSettings = settings; setupMarkAllRead(); setupFolderJump(); console.log('[Outlook Relook] Injector started'); } function updateSettings(settings) { 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 }; })();