- Rename extension to "Outcut" throughout all source files and console logs - Add KEY_PRESETS object (gmail/outlook) and matchesAction() helper to keyboard.js - Rewrite handleKeydown to use preset-based dispatch instead of hardcoded switch - Update updateSettings to re-init when keyboardPreset changes - Add keyboardPreset: 'gmail' default to settings-defaults.js - Replace popup Design Tweaks UI with preset dropdown + dynamic help text - Keep all design tweak content script logic intact (activatable via storage) - Update manifest: version 1.0.0, remove activeTab, add homepage_url
293 lines
9.7 KiB
JavaScript
293 lines
9.7 KiB
JavaScript
// 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('[Outcut] 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('[Outcut] 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('[Outcut] 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('[Outcut] 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 };
|
|
})();
|