refactor: keyboard nav as primary feature, design tweaks behind experimental toggle

This commit is contained in:
Joel Brock
2026-04-23 17:20:14 -07:00
parent 823de95891
commit 93a5888d83
4 changed files with 352 additions and 278 deletions

View File

@@ -1,11 +1,14 @@
// Outlook Relook — Content Script Entry Point
//
// Primary feature: Keyboard navigation (always active)
// Experimental: Design tweaks (themes, density, hiding, behavior) — gated by enableDesignTweaks
(function () {
'use strict';
const OR = window.OutlookRelook;
// Map setting keys to data attributes on <html>
// Map setting keys to data attributes on <html> (design tweaks only)
const SETTING_TO_ATTR = {
compactTopBar: 'data-or-compact-topbar',
compactCommandBar: 'data-or-compact-commandbar',
@@ -34,11 +37,12 @@
darkModeEmailFix: 'data-or-darkmode-fix',
};
// Non-boolean attributes
const SETTING_TO_ATTR_VALUE = {
messageListFontSize: 'data-or-fontsize',
};
let designTweaksActive = false;
function applyColorScheme(scheme) {
let resolved = scheme;
if (scheme === 'system') {
@@ -48,34 +52,41 @@
}
function applySettingsToDOM(settings) {
// Boolean toggles -> data attributes
for (const [key, attr] of Object.entries(SETTING_TO_ATTR)) {
document.documentElement.setAttribute(attr, String(!!settings[key]));
}
// Value-based attributes
for (const [key, attr] of Object.entries(SETTING_TO_ATTR_VALUE)) {
document.documentElement.setAttribute(attr, settings[key] || '');
}
// Color scheme
applyColorScheme(settings.colorScheme);
// Accent color
if (settings.accentColor) {
document.documentElement.style.setProperty('--or-accent-override', settings.accentColor);
}
console.log('[Outlook Relook] Settings applied to DOM');
}
function injectThemeCSS(theme) {
// Remove existing theme stylesheet
const existing = document.getElementById('outlook-relook-theme');
if (existing) existing.remove();
function clearDesignFromDOM() {
// Remove all data-or-* attributes
for (const attr of Object.values(SETTING_TO_ATTR)) {
document.documentElement.removeAttribute(attr);
}
for (const attr of Object.values(SETTING_TO_ATTR_VALUE)) {
document.documentElement.removeAttribute(attr);
}
document.documentElement.removeAttribute('data-outlook-relook-scheme');
document.documentElement.style.removeProperty('--or-accent-override');
// Inject the theme CSS
const link = document.createElement('link');
// Remove injected theme CSS
var themeLink = document.getElementById('outlook-relook-theme');
if (themeLink) themeLink.remove();
console.log('[Outlook Relook] Design tweaks cleared from DOM');
}
function injectThemeCSS(theme) {
var existing = document.getElementById('outlook-relook-theme');
if (existing) existing.remove();
var link = document.createElement('link');
link.id = 'outlook-relook-theme';
link.rel = 'stylesheet';
link.href = chrome.runtime.getURL('themes/' + theme + '.css');
@@ -83,49 +94,61 @@
console.log('[Outlook Relook] Theme loaded: ' + theme);
}
function startDesignTweaks(settings) {
applySettingsToDOM(settings);
injectThemeCSS(settings.theme);
OR.Observer.start(settings);
OR.Behavior.start(settings);
OR.Injector.start(settings);
designTweaksActive = true;
console.log('[Outlook Relook] Design tweaks enabled');
}
function stopDesignTweaks() {
OR.Observer.stop();
OR.Behavior.stop();
OR.Injector.stop();
clearDesignFromDOM();
designTweaksActive = false;
console.log('[Outlook Relook] Design tweaks disabled');
}
async function init() {
const settings = await OR.loadSettings();
console.log('[Outlook Relook] Loaded settings:', settings);
applySettingsToDOM(settings);
injectThemeCSS(settings.theme);
// Start the MutationObserver
OR.Observer.start(settings);
// Apply behavior patches
OR.Behavior.start(settings);
// Start keyboard navigation
// Keyboard navigation — always starts (gated by its own toggle internally)
OR.Keyboard.start(settings);
// Start DOM injector (quick actions)
OR.Injector.start(settings);
// Design tweaks — only if enabled
if (settings.enableDesignTweaks) {
startDesignTweaks(settings);
}
// Listen for setting changes from popup
chrome.storage.onChanged.addListener((changes, area) => {
if (area !== 'sync') return;
// Build updated settings object
OR.loadSettings().then((updated) => {
applySettingsToDOM(updated);
// Update observer with new settings
OR.Observer.updateSettings(updated);
// Update behavior patches
OR.Behavior.updateSettings(updated);
// Update keyboard navigation
// Keyboard — always update
OR.Keyboard.updateSettings(updated);
// Update injector
// Design tweaks — toggle on/off or update
if (updated.enableDesignTweaks) {
if (!designTweaksActive) {
startDesignTweaks(updated);
} else {
applySettingsToDOM(updated);
OR.Observer.updateSettings(updated);
OR.Behavior.updateSettings(updated);
OR.Injector.updateSettings(updated);
// Swap theme CSS if theme changed
if (changes.theme) {
injectThemeCSS(changes.theme.newValue);
}
}
} else if (designTweaksActive) {
stopDesignTweaks();
}
});
});
}
@@ -133,7 +156,7 @@
// Listen for system theme changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', async () => {
const settings = await OR.loadSettings();
if (settings.colorScheme === 'system') {
if (settings.enableDesignTweaks && settings.colorScheme === 'system') {
applyColorScheme('system');
}
});

View File

@@ -4,6 +4,12 @@
window.OutlookRelook = window.OutlookRelook || {};
window.OutlookRelook.DEFAULTS = {
// Keyboard Navigation (primary feature, always visible)
keyboardMultiSelect: true,
// Design Tweaks (experimental, hidden by default)
enableDesignTweaks: false,
// Theme & Appearance
theme: 'swiss',
colorScheme: 'system', // 'light' | 'dark' | 'system'
@@ -53,9 +59,6 @@ window.OutlookRelook.DEFAULTS = {
autoResizeCompose: true,
throttleNotifications: false,
// Keyboard Navigation
keyboardMultiSelect: true,
// Quick Actions
markAllReadButton: true,
quickFolderJump: true,

View File

@@ -11,8 +11,40 @@
<span class="or-version" id="version"></span>
</div>
<!-- Keyboard Navigation (primary feature, always visible) -->
<div class="or-section open" data-section="keyboard">
<div class="or-section-header">Keyboard Navigation</div>
<div class="or-section-body">
<div class="or-toggle-row">
<label for="keyboardMultiSelect">Gmail-style keyboard multi-select</label>
<div class="or-switch"><input type="checkbox" id="keyboardMultiSelect" data-setting="keyboardMultiSelect"><span class="slider"></span></div>
</div>
<div style="font-size:11px;color:#888;padding:4px 0 2px;line-height:1.4;">
j/k navigate, x/Space select, # delete, e archive,
Shift+i/u read/unread, v move, f flag, p pin, Esc deselect
</div>
</div>
</div>
<!-- Design Tweaks Master Toggle -->
<div class="or-section" data-section="design-master">
<div class="or-section-header" style="cursor:default;">Design Tweaks</div>
<div class="or-section-body" style="display:block;">
<div class="or-toggle-row">
<label for="enableDesignTweaks">Enable experimental UI customization</label>
<div class="or-switch"><input type="checkbox" id="enableDesignTweaks" data-setting="enableDesignTweaks"><span class="slider"></span></div>
</div>
<div style="font-size:11px;color:#888;padding:2px 0;line-height:1.4;">
Themes, density, element hiding, and behavior patches. May conflict with OWA updates.
</div>
</div>
</div>
<!-- Design Tweaks Sections (hidden when enableDesignTweaks is off) -->
<div id="designSections">
<!-- Theme & Appearance -->
<div class="or-section open" data-section="theme">
<div class="or-section" data-section="theme">
<div class="or-section-header">Theme &amp; Appearance</div>
<div class="or-section-body">
<div class="or-select-row">
@@ -223,20 +255,6 @@
</div>
</div>
<!-- Keyboard Navigation -->
<div class="or-section" data-section="keyboard">
<div class="or-section-header">Keyboard Navigation</div>
<div class="or-section-body">
<div class="or-toggle-row">
<label for="keyboardMultiSelect">Gmail-style keyboard multi-select</label>
<div class="or-switch"><input type="checkbox" id="keyboardMultiSelect" data-setting="keyboardMultiSelect"><span class="slider"></span></div>
</div>
<div style="font-size:11px;color:#888;padding:4px 0 2px;line-height:1.4;">
j/k or arrows to navigate, x/Space to select, # delete, e archive, Shift+i read, Shift+u unread, v move, Esc deselect
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="or-section" data-section="actions">
<div class="or-section-header">Quick Actions</div>
@@ -252,6 +270,8 @@
</div>
</div>
</div><!-- /designSections -->
<!-- Footer -->
<div class="or-footer">
<button id="exportBtn" title="Export settings as JSON">Export</button>

View File

@@ -3,14 +3,22 @@
(function () {
'use strict';
// We need access to DEFAULTS and DENSITY_PRESETS from settings-defaults.js,
// but popup runs in its own context. Duplicate the defaults here.
// (Content scripts and popup don't share a JS context.)
// Defaults — must stay in sync with content/settings-defaults.js
// (popup runs in its own JS context, no access to content script globals)
var DEFAULTS = {
// Primary feature
keyboardMultiSelect: true,
// Design tweaks master toggle
enableDesignTweaks: false,
// Theme & Appearance
theme: 'swiss',
colorScheme: 'system',
accentColor: '',
// Density & Spacing
densityPreset: 'compact',
compactTopBar: true,
compactCommandBar: true,
@@ -21,6 +29,8 @@
compressComposeToolbar: true,
readingPaneMaxWidth: true,
unifiedHeader: false,
// Hide Elements
hideCopilot: true,
hideSuggestedReplies: true,
hidePromoBanners: true,
@@ -33,11 +43,15 @@
hideVivaInsights: true,
hideUnreadOtherBanner: true,
hideActivityFeed: true,
// Readability
unreadDistinction: true,
previewOwnLine: false,
normalizeFontWeight: true,
darkModeEmailFix: true,
messageListFontSize: 'medium',
// Behavior
autoCollapseRibbon: true,
rememberSidebarState: true,
suppressContactHover: true,
@@ -47,7 +61,8 @@
stickyReplyBar: true,
autoResizeCompose: true,
throttleNotifications: false,
keyboardMultiSelect: true,
// Quick Actions
markAllReadButton: true,
quickFolderJump: true,
};
@@ -85,6 +100,13 @@
},
};
// --- Design sections visibility ---
var designSections = document.getElementById('designSections');
function updateDesignSectionsVisibility(enabled) {
designSections.style.display = enabled ? 'block' : 'none';
}
// --- Load settings and populate UI ---
function loadUI() {
chrome.storage.sync.get(DEFAULTS, function (settings) {
@@ -117,6 +139,9 @@
// Version
var manifest = chrome.runtime.getManifest();
document.getElementById('version').textContent = 'v' + manifest.version;
// Show/hide design sections
updateDesignSectionsVisibility(settings.enableDesignTweaks);
});
}
@@ -140,6 +165,11 @@
for (var c = 0; c < checkboxes.length; c++) {
checkboxes[c].addEventListener('change', function () {
saveSetting(this.dataset.setting, this.checked);
// Toggle design sections visibility when master toggle changes
if (this.dataset.setting === 'enableDesignTweaks') {
updateDesignSectionsVisibility(this.checked);
}
});
}
@@ -155,7 +185,6 @@
if (settingKey === 'densityPreset' && DENSITY_PRESETS[value]) {
var preset = DENSITY_PRESETS[value];
chrome.storage.sync.set(preset);
// Update checkboxes in the UI
var toggleKeys = Object.keys(preset);
for (var p = 0; p < toggleKeys.length; p++) {
var checkbox = document.getElementById(toggleKeys[p]);
@@ -213,7 +242,6 @@
reader.onload = function (event) {
try {
var imported = JSON.parse(event.target.result);
// Only import known keys
var cleaned = {};
var defaultKeys = Object.keys(DEFAULTS);
for (var i = 0; i < defaultKeys.length; i++) {