Compare commits

...

10 Commits

Author SHA1 Message Date
Joel Brock
e75bcfa478 Merge dev/initial-build: Outcut v1.0.0 2026-04-27 14:56:00 -07:00
Joel Brock
8bd41f2721 chore: publish prep — icons, privacy policy, version 1.0.0
- Generate real PNG icons (16/48/128px) with white O on dark background
- Add PRIVACY.md documenting no data collection, storage-only permissions
2026-04-27 14:11:49 -07:00
Joel Brock
3de2db7d89 rename: Outlook Relook → Outcut, add keyboard presets, remove design UI from popup
- 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
2026-04-27 14:11:45 -07:00
Joel Brock
93a5888d83 refactor: keyboard nav as primary feature, design tweaks behind experimental toggle 2026-04-23 17:20:14 -07:00
Joel Brock
823de95891 chore: remove debug logging from keyboard selection 2026-04-23 16:23:40 -07:00
Joel Brock
e950a0e1c4 fix: hover to reveal inline buttons instead of clicking rows, prevents fill-screen open 2026-04-23 16:21:43 -07:00
Joel Brock
13901e4453 fix: My Day toggle only hides buttons not panel, default to false 2026-04-23 11:25:19 -07:00
Joel Brock
919bdf2bf3 fix: target actual OWA DOM for Focused/Other tabs and unified header containers 2026-04-23 10:49:37 -07:00
Joel Brock
7f0a88b131 fix: boost keyboard selection CSS specificity, add debug logging for visual state 2026-04-23 10:43:47 -07:00
Joel Brock
c66265e48f fix: use OWA's actual button selectors for keyboard actions, add flag/pin shortcuts 2026-04-23 10:35:13 -07:00
15 changed files with 508 additions and 645 deletions

29
PRIVACY.md Normal file
View File

@@ -0,0 +1,29 @@
# Outcut Privacy Policy
Outcut is a browser extension that adds keyboard shortcuts to Outlook Web App.
## Data Collection
Outcut does **not** collect, transmit, or store any personal data.
## Storage
Outcut stores your preferences (keyboard shortcut preset selection and toggle states) using Chrome's built-in `chrome.storage.sync` API. This data:
- Stays on your device and within your Chrome profile
- Syncs across your Chrome instances if you use Chrome Sync (a Google feature, not ours)
- Is never sent to any external server
## Permissions
- **storage**: Save your extension preferences locally
- No other permissions are required
## Email Content
Outcut never reads, processes, stores, or transmits the content of your emails. It only interacts with the Outlook Web App's user interface elements (buttons, message list items) to provide keyboard shortcut functionality.
## Contact
For questions about this privacy policy, open an issue on the project's GitHub repository.
*Last updated: April 2026*

109
README.md
View File

@@ -1,75 +1,62 @@
# Outlook Relook
# Outcut
A Chrome extension that reskins Outlook Web App (outlook.office.com) with minimalist themes and granular control over visual clutter.
Keyboard shortcuts for Outlook Web App — Gmail-style multi-select, delete, archive, and more.
## Features
- **Switchable themes:** Swiss (Helvetica minimalism) and Material (clean, subtle elevation)
- **Light & Dark modes** with system preference detection
- **~40 granular toggles** for density, element hiding, readability, and behavior
- **MutationObserver** suppresses dynamically injected clutter (Copilot, banners, suggested replies)
- **Behavior patches:** auto-collapse ribbon, suppress contact hover cards, auto-advance after delete, toast management
- **Gmail-style keyboard navigation:** j/k to move, x/Space to multi-select, # delete, e archive, Shift+i/u read/unread, v move
- **Quick actions:** "Mark all as read" button, Ctrl+Shift+K folder jump
- **Settings sync** across Chrome instances via chrome.storage.sync
- **Export/Import/Reset** settings
- **Gmail or Outlook shortcut presets** — choose your preferred key bindings
- **Multi-select** — select multiple messages and act on them at once
- **Batch actions** — delete, archive, flag, pin, move, read/unread on selected messages
- **Works everywhere** — outlook.office.com, outlook.cloud.microsoft, and Outlook PWA
## Install (Development)
### Gmail Preset (default)
| Key | Action |
|-----|--------|
| j / Down | Next message |
| k / Up | Previous message |
| x / Space | Toggle select |
| Shift+Down/Up | Extend selection |
| # | Delete |
| e | Archive |
| Shift+i / Shift+u | Toggle read/unread |
| v | Move to folder |
| f | Flag/unflag |
| p | Pin/unpin |
| Escape | Deselect all |
| Enter / o | Open message |
### Outlook Preset
| Key | Action |
|-----|--------|
| Down / j | Next message |
| Up / k | Previous message |
| Space | Toggle select |
| Shift+Down/Up | Extend selection |
| Delete / Backspace | Delete |
| e | Archive |
| q / Shift+i | Toggle read/unread |
| v | Move to folder |
| f / Insert | Flag/unflag |
| p | Pin/unpin |
| Escape | Deselect all |
| Enter | Open message |
## Install
### From Chrome Web Store
Coming soon.
### Development
1. Clone this repo
2. Open `chrome://extensions` in Chrome
3. Enable "Developer mode" (top-right toggle)
3. Enable "Developer mode"
4. Click "Load unpacked" and select this directory
5. Open [outlook.office.com](https://outlook.office.com) — the extension activates automatically
5. Open Outlook Web App — the extension activates automatically
## Usage
Click the extension icon to open the settings panel. Changes apply immediately — no page reload needed.
Click the extension icon to choose your shortcut preset (Gmail or Outlook) and toggle keyboard navigation on/off.
### Keyboard Shortcuts
## Privacy
**Message list (Gmail-style):**
- `j` / `Down Arrow` — Next message
- `k` / `Up Arrow` — Previous message
- `x` / `Space` — Toggle select
- `Shift+Down` / `Shift+Up` — Extend selection
- `#` — Delete selected
- `e` — Archive selected
- `Shift+i` — Mark read
- `Shift+u` — Mark unread
- `v` — Move to folder
- `Escape` — Deselect all
- `Enter` / `o` — Open message
**Global:**
- `Ctrl+Shift+K` (or `Cmd+Shift+K` on Mac) — Quick folder jump
## Development
No build step. Edit files, reload the extension at `chrome://extensions`, refresh the OWA tab.
- `themes/base.css` — density/spacing/hiding rules (always loaded)
- `themes/swiss.css` / `themes/material.css` — theme-specific styles
- `content/` — content scripts (observer, behavior, injector)
- `popup/` — settings panel
- `selectors-test.html` — offline selector verification
### Selector Strategy
OWA uses obfuscated class names. Selectors prioritize `aria-label`, `data-*`, and `role` attributes over class names. See `content/selectors.js` for the registry.
When selectors break after an OWA update, check the console for `[Outlook Relook]` warnings, inspect the live DOM, and update `selectors.js`.
## Adding a New Theme
1. Create `themes/yourtheme.css` — define the same CSS custom properties as `swiss.css`
2. Add an option to the theme dropdown in `popup/popup.html`
3. That's it — the content script handles injection automatically
## Adding a New Toggle
1. Add the key and default to `DEFAULTS` in both `content/settings-defaults.js` and `popup/popup.js`
2. Add the toggle HTML to the appropriate section in `popup/popup.html`
3. If CSS-based: add a `data-or-*` attribute mapping in `content/content.js` and the CSS rule in `themes/base.css`
4. If observer-based: add a selector entry to `content/selectors.js` and a mapping in `content/observer.js`
5. If behavior-based: add a setup function in `content/behavior.js`
Outcut stores only your preferences locally. No data is collected or transmitted. See [PRIVACY.md](PRIVACY.md).

View File

@@ -20,7 +20,7 @@ window.OutlookRelook.Behavior = (function () {
for (var i = 0; i < elements.length; i++) {
if (elements[i].getAttribute('aria-expanded') === 'true') {
elements[i].click();
console.log('[Outlook Relook] Auto-collapsed ribbon');
console.log('[Outcut] Auto-collapsed ribbon');
}
}
}, 2000);
@@ -217,7 +217,7 @@ window.OutlookRelook.Behavior = (function () {
for (var w = 0; w < composeWindows.length; w++) {
composeWindows[w].style.minHeight = '60vh';
composeWindows[w].style.height = '60vh';
console.log('[Outlook Relook] Auto-resized compose window');
console.log('[Outcut] Auto-resized compose window');
}
}
}
@@ -242,7 +242,7 @@ window.OutlookRelook.Behavior = (function () {
window.Notification = function (title, options) {
var now = Date.now();
if (now - lastNotificationTime < MIN_INTERVAL) {
console.log('[Outlook Relook] Throttled notification: "' + title + '"');
console.log('[Outcut] Throttled notification: "' + title + '"');
return {};
}
lastNotificationTime = now;
@@ -271,7 +271,7 @@ window.OutlookRelook.Behavior = (function () {
setupAutoResizeCompose();
setupThrottleNotifications();
console.log('[Outlook Relook] Behavior patches applied');
console.log('[Outcut] Behavior patches applied');
}
function updateSettings(settings) {

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,83 +52,102 @@
}
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('[Outcut] Settings applied to DOM');
}
console.log('[Outlook Relook] Settings applied to DOM');
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');
// Remove injected theme CSS
var themeLink = document.getElementById('outlook-relook-theme');
if (themeLink) themeLink.remove();
console.log('[Outcut] Design tweaks cleared from DOM');
}
function injectThemeCSS(theme) {
// Remove existing theme stylesheet
const existing = document.getElementById('outlook-relook-theme');
var existing = document.getElementById('outlook-relook-theme');
if (existing) existing.remove();
// Inject the theme CSS
const link = document.createElement('link');
var link = document.createElement('link');
link.id = 'outlook-relook-theme';
link.rel = 'stylesheet';
link.href = chrome.runtime.getURL('themes/' + theme + '.css');
document.head.appendChild(link);
console.log('[Outlook Relook] Theme loaded: ' + theme);
console.log('[Outcut] 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('[Outcut] Design tweaks enabled');
}
function stopDesignTweaks() {
OR.Observer.stop();
OR.Behavior.stop();
OR.Injector.stop();
clearDesignFromDOM();
designTweaksActive = false;
console.log('[Outcut] Design tweaks disabled');
}
async function init() {
const settings = await OR.loadSettings();
console.log('[Outlook Relook] Loaded settings:', settings);
console.log('[Outcut] 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
OR.Injector.updateSettings(updated);
// Swap theme CSS if theme changed
if (changes.theme) {
injectThemeCSS(changes.theme.newValue);
// 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);
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

@@ -60,13 +60,13 @@ window.OutlookRelook.Injector = (function () {
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');
console.log('[Outcut] 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');
console.warn('[Outcut] "Mark all as read" menu item not found');
}, 300);
}
};
@@ -244,7 +244,7 @@ window.OutlookRelook.Injector = (function () {
currentSettings = settings;
setupMarkAllRead();
setupFolderJump();
console.log('[Outlook Relook] Injector started');
console.log('[Outcut] Injector started');
}
function updateSettings(settings) {

View File

@@ -1,4 +1,4 @@
// Outlook Relook — Gmail-Style Keyboard Navigation & Multi-Select
// Outcut — Keyboard Navigation & Multi-Select
// Adds keyboard focus cursor and multi-select to OWA's message list.
// Gated by the keyboardMultiSelect setting.
//
@@ -7,6 +7,53 @@
window.OutlookRelook = window.OutlookRelook || {};
// Keyboard shortcut presets
var KEY_PRESETS = {
gmail: {
nextMessage: [{key: 'j'}, {key: 'ArrowDown'}],
prevMessage: [{key: 'k'}, {key: 'ArrowUp'}],
selectExtendDown: [{key: 'j', shift: true}, {key: 'ArrowDown', shift: true}],
selectExtendUp: [{key: 'k', shift: true}, {key: 'ArrowUp', shift: true}],
toggleSelect: [{key: 'x'}, {key: ' '}],
delete: [{key: '#'}],
archive: [{key: 'e'}],
readUnread: [{key: 'I', shift: true}, {key: 'U', shift: true}],
move: [{key: 'v'}],
flag: [{key: 'f'}],
pin: [{key: 'p'}],
deselect: [{key: 'Escape'}],
open: [{key: 'Enter'}, {key: 'o'}],
label: 'Gmail-style: j/k nav, x select, # del, e archive'
},
outlook: {
nextMessage: [{key: 'ArrowDown'}, {key: 'j'}],
prevMessage: [{key: 'ArrowUp'}, {key: 'k'}],
selectExtendDown: [{key: 'ArrowDown', shift: true}],
selectExtendUp: [{key: 'ArrowUp', shift: true}],
toggleSelect: [{key: ' '}],
delete: [{key: 'Delete'}, {key: 'Backspace'}],
archive: [{key: 'e'}],
readUnread: [{key: 'q'}, {key: 'I', shift: true}],
move: [{key: 'v'}],
flag: [{key: 'Insert'}, {key: 'f'}],
pin: [{key: 'p'}],
deselect: [{key: 'Escape'}],
open: [{key: 'Enter'}],
label: 'Outlook-style: arrows nav, Space select, Del delete'
}
};
function matchesAction(e, actionBindings) {
if (!actionBindings) return false;
for (var i = 0; i < actionBindings.length; i++) {
var b = actionBindings[i];
if (e.key === b.key && !!e.shiftKey === !!b.shift && !!e.ctrlKey === !!b.ctrl) {
return true;
}
}
return false;
}
window.OutlookRelook.Keyboard = (function () {
'use strict';
@@ -168,6 +215,46 @@ window.OutlookRelook.Keyboard = (function () {
// --- Actions ---
// --- Find a button by aria-label, searching within a target element first,
// then the global toolbar, then the whole document ---
function findButton(target, label) {
// 1. Look for inline button within/near the message row
// OWA renders Delete/Archive/Flag buttons on each row on hover
var btn = target.querySelector('button[aria-label="' + label + '"]');
if (btn) return btn;
// 2. Check the parent (some buttons are siblings of the row)
if (target.parentElement) {
btn = target.parentElement.querySelector('button[aria-label="' + label + '"]');
if (btn) return btn;
}
// 3. Look in the global toolbar area
btn = document.querySelector('[role="toolbar"] button[aria-label="' + label + '"], .fui-Toolbar button[aria-label="' + label + '"]');
if (btn) return btn;
// 4. Anywhere in the document
btn = document.querySelector('button[aria-label="' + label + '"]');
return btn;
}
// Simulate hover on a message row to make OWA's inline action buttons appear
function triggerHover(target) {
var rect = target.getBoundingClientRect();
var enterEvent = new MouseEvent('mouseenter', {
bubbles: true, cancelable: true,
clientX: rect.x + rect.width - 30,
clientY: rect.y + rect.height / 2,
});
var overEvent = new MouseEvent('mouseover', {
bubbles: true, cancelable: true,
clientX: rect.x + rect.width - 30,
clientY: rect.y + rect.height / 2,
});
target.dispatchEvent(enterEvent);
target.dispatchEvent(overEvent);
}
function performAction(targets, actionFn) {
if (targets.length === 0) return;
var i = 0;
@@ -178,99 +265,103 @@ window.OutlookRelook.Keyboard = (function () {
}
var target = targets[i];
i++;
target.click();
// First, try to find the inline button without clicking the row.
// Hover to make OWA's inline action buttons appear.
triggerHover(target);
setTimeout(function () {
actionFn(target);
setTimeout(processNext, 150);
// Try inline button first (avoids opening the email in fill-screen mode)
var inlineHandled = actionFn(target);
// If inline button wasn't found, fall back to clicking the row + toolbar
if (inlineHandled === false) {
target.click();
setTimeout(function () {
actionFn(target);
setTimeout(processNext, 200);
}, 150);
} else {
setTimeout(processNext, 200);
}
}, 100);
}
processNext();
}
function actionDelete(targets) {
performAction(targets, function () {
var deleteBtn = document.querySelector(
'[aria-label*="Delete" i][role="button"], [aria-label*="delete" i][role="menuitem"]'
);
if (deleteBtn) {
deleteBtn.click();
} else {
var deleteEvent = new KeyboardEvent('keydown', {
key: 'Delete', code: 'Delete', bubbles: true, cancelable: true
});
document.activeElement.dispatchEvent(deleteEvent);
}
performAction(targets, function (target) {
var btn = findButton(target, 'Delete');
if (btn) { btn.click(); return true; }
console.warn('[Outcut] Delete button not found');
return false;
});
}
function actionArchive(targets) {
performAction(targets, function () {
var archiveBtn = document.querySelector(
'[aria-label*="Archive" i][role="button"], [aria-label*="archive" i][role="menuitem"]'
);
if (archiveBtn) {
archiveBtn.click();
} else {
console.warn('[Outlook Relook] Archive button not found');
}
performAction(targets, function (target) {
var btn = findButton(target, 'Archive');
if (btn) { btn.click(); return true; }
console.warn('[Outcut] Archive button not found');
return false;
});
}
function actionMarkRead(targets) {
performAction(targets, function () {
var readBtn = document.querySelector(
'[aria-label*="Mark as read" i][role="button"], [aria-label*="Mark as read" i][role="menuitem"]'
);
if (readBtn) {
readBtn.click();
} else {
triggerContextMenuAction(/mark as read/i);
}
});
}
function actionMarkUnread(targets) {
performAction(targets, function () {
var unreadBtn = document.querySelector(
'[aria-label*="Mark as unread" i][role="button"], [aria-label*="Mark as unread" i][role="menuitem"]'
);
if (unreadBtn) {
unreadBtn.click();
} else {
triggerContextMenuAction(/mark as unread/i);
}
function actionMarkReadUnread(targets) {
performAction(targets, function (target) {
var btn = findButton(target, 'Read / Unread');
if (btn) { btn.click(); return true; }
triggerContextMenuAction(target, /mark as read|mark as unread|read \/ unread/i);
return true; // context menu handles it
});
}
function actionMove(targets) {
if (targets.length > 0) {
targets[0].click();
triggerHover(targets[0]);
setTimeout(function () {
var moveBtn = document.querySelector(
'[aria-label*="Move to" i][role="button"], [aria-label*="Move" i][role="menuitem"]'
);
if (moveBtn) {
moveBtn.click();
var btn = findButton(targets[0], 'Move to');
if (btn) {
btn.click();
} else {
triggerContextMenuAction(/move to/i);
triggerContextMenuAction(targets[0], /move to/i);
}
}, 100);
}, 150);
}
}
function triggerContextMenuAction(pattern) {
var focused = document.querySelector('.or-kb-focused');
if (!focused) return;
var rect = focused.getBoundingClientRect();
function actionFlag(targets) {
performAction(targets, function (target) {
var btn = findButton(target, 'Flag this message')
|| findButton(target, 'Flag / Unflag');
if (btn) { btn.click(); return true; }
console.warn('[Outcut] Flag button not found');
return false;
});
}
function actionPin(targets) {
performAction(targets, function (target) {
var btn = findButton(target, 'Pin / Unpin');
if (btn) { btn.click(); return true; }
console.warn('[Outcut] Pin button not found');
return false;
});
}
function triggerContextMenuAction(target, pattern) {
var el = target || document.querySelector('.or-kb-focused');
if (!el) return;
var rect = el.getBoundingClientRect();
var contextEvent = new MouseEvent('contextmenu', {
bubbles: true, cancelable: true,
clientX: rect.x + 10, clientY: rect.y + 10,
});
focused.dispatchEvent(contextEvent);
el.dispatchEvent(contextEvent);
setTimeout(function () {
var menuItems = document.querySelectorAll('[role="menuitem"]');
for (var j = 0; j < menuItems.length; j++) {
if (pattern.test(menuItems[j].textContent)) {
if (pattern.test(menuItems[j].textContent || menuItems[j].getAttribute('aria-label') || '')) {
menuItems[j].click();
return;
}
@@ -288,8 +379,8 @@ window.OutlookRelook.Keyboard = (function () {
var items = getMessageItems();
if (items.length === 0) return;
var key = e.key;
var shift = e.shiftKey;
var presetName = currentSettings.keyboardPreset || 'gmail';
var preset = KEY_PRESETS[presetName] || KEY_PRESETS.gmail;
// Initialize focus if not set
var currentIdx = getFocusedIndex(items);
@@ -312,96 +403,54 @@ window.OutlookRelook.Keyboard = (function () {
var handled = true;
var targets;
switch (key) {
case 'j':
case 'ArrowDown':
if (shift) {
toggleSelect(items, currentIdx);
if (currentIdx < items.length - 1) {
setFocus(items, currentIdx + 1);
toggleSelect(items, currentIdx + 1);
}
} else {
if (currentIdx < items.length - 1) {
setFocus(items, currentIdx + 1);
}
}
break;
case 'k':
case 'ArrowUp':
if (shift) {
toggleSelect(items, currentIdx);
if (currentIdx > 0) {
setFocus(items, currentIdx - 1);
toggleSelect(items, currentIdx - 1);
}
} else {
if (currentIdx > 0) {
setFocus(items, currentIdx - 1);
}
}
break;
case 'x':
case ' ':
e.preventDefault();
toggleSelect(items, currentIdx);
break;
case '#':
targets = getActionTargets(items);
actionDelete(targets);
break;
case 'e':
targets = getActionTargets(items);
actionArchive(targets);
break;
case 'I':
if (shift) {
targets = getActionTargets(items);
actionMarkRead(targets);
} else {
handled = false;
}
break;
case 'U':
if (shift) {
targets = getActionTargets(items);
actionMarkUnread(targets);
} else {
handled = false;
}
break;
case 'v':
targets = getActionTargets(items);
actionMove(targets);
break;
case 'Escape':
clearSelection();
break;
case 'Enter':
case 'o':
if (currentIdx >= 0 && currentIdx < items.length) {
items[currentIdx].click();
}
break;
default:
handled = false;
if (matchesAction(e, preset.selectExtendDown)) {
toggleSelect(items, currentIdx);
if (currentIdx < items.length - 1) {
setFocus(items, currentIdx + 1);
toggleSelect(items, currentIdx + 1);
}
} else if (matchesAction(e, preset.selectExtendUp)) {
toggleSelect(items, currentIdx);
if (currentIdx > 0) {
setFocus(items, currentIdx - 1);
toggleSelect(items, currentIdx - 1);
}
} else if (matchesAction(e, preset.nextMessage)) {
if (currentIdx < items.length - 1) setFocus(items, currentIdx + 1);
} else if (matchesAction(e, preset.prevMessage)) {
if (currentIdx > 0) setFocus(items, currentIdx - 1);
} else if (matchesAction(e, preset.toggleSelect)) {
e.preventDefault();
toggleSelect(items, currentIdx);
} else if (matchesAction(e, preset.delete)) {
targets = getActionTargets(items);
actionDelete(targets);
} else if (matchesAction(e, preset.archive)) {
targets = getActionTargets(items);
actionArchive(targets);
} else if (matchesAction(e, preset.readUnread)) {
targets = getActionTargets(items);
actionMarkReadUnread(targets);
} else if (matchesAction(e, preset.move)) {
targets = getActionTargets(items);
actionMove(targets);
} else if (matchesAction(e, preset.flag)) {
targets = getActionTargets(items);
actionFlag(targets);
} else if (matchesAction(e, preset.pin)) {
targets = getActionTargets(items);
actionPin(targets);
} else if (matchesAction(e, preset.deselect)) {
clearSelection();
} else if (matchesAction(e, preset.open)) {
if (currentIdx >= 0 && currentIdx < items.length) items[currentIdx].click();
} else {
handled = false;
}
if (handled) {
e.stopPropagation();
if (key !== 'Enter' && key !== 'o') {
e.preventDefault();
}
if (e.key !== 'Enter') e.preventDefault();
}
}
@@ -448,7 +497,7 @@ window.OutlookRelook.Keyboard = (function () {
document.addEventListener('keydown', handleKeydown, true);
setupListObserver();
console.log('[Outlook Relook] Keyboard navigation started');
console.log('[Outcut] Keyboard navigation started');
cleanupFns.push(function () {
document.removeEventListener('keydown', handleKeydown, true);
});
@@ -457,9 +506,11 @@ window.OutlookRelook.Keyboard = (function () {
function updateSettings(settings) {
var wasEnabled = currentSettings.keyboardMultiSelect;
var isEnabled = settings.keyboardMultiSelect;
var oldPreset = currentSettings.keyboardPreset;
var newPreset = settings.keyboardPreset;
currentSettings = settings;
if (wasEnabled !== isEnabled) {
if (wasEnabled !== isEnabled || oldPreset !== newPreset) {
stop();
start(settings);
}

View File

@@ -52,7 +52,7 @@ window.OutlookRelook.Observer = (function () {
for (const el of elements) {
if (el.style.display !== 'none') {
el.style.display = 'none';
console.log('[Outlook Relook] Suppressed: ' + name, el);
console.log('[Outcut] Suppressed: ' + name, el);
}
}
}
@@ -72,7 +72,7 @@ window.OutlookRelook.Observer = (function () {
var text = el.textContent || '';
if (pattern.test(text) && text.length < 200) {
el.style.display = 'none';
console.log('[Outlook Relook] Suppressed by text: "' + text.trim().substring(0, 50) + '"', el);
console.log('[Outcut] Suppressed by text: "' + text.trim().substring(0, 50) + '"', el);
}
}
}
@@ -105,7 +105,7 @@ window.OutlookRelook.Observer = (function () {
subtree: true,
});
console.log('[Outlook Relook] Observer started');
console.log('[Outcut] Observer started');
}
function updateSettings(settings) {
@@ -118,7 +118,7 @@ window.OutlookRelook.Observer = (function () {
if (observer) {
observer.disconnect();
observer = null;
console.log('[Outlook Relook] Observer stopped');
console.log('[Outcut] Observer stopped');
}
}

View File

@@ -4,6 +4,13 @@
window.OutlookRelook = window.OutlookRelook || {};
window.OutlookRelook.DEFAULTS = {
// Keyboard Navigation (primary feature, always visible)
keyboardMultiSelect: true,
keyboardPreset: 'gmail',
// Design Tweaks (experimental, hidden by default)
enableDesignTweaks: false,
// Theme & Appearance
theme: 'swiss',
colorScheme: 'system', // 'light' | 'dark' | 'system'
@@ -28,7 +35,7 @@ window.OutlookRelook.DEFAULTS = {
hideFocusedOtherTabs: true,
hideSidebarAppIcons: false,
hideGroupsSection: false,
hideMyDayButtons: true,
hideMyDayButtons: false,
hideSenderAvatars: false,
hideFeatureDiscovery: true,
hideVivaInsights: true,
@@ -53,9 +60,6 @@ window.OutlookRelook.DEFAULTS = {
autoResizeCompose: true,
throttleNotifications: false,
// Keyboard Navigation
keyboardMultiSelect: true,
// Quick Actions
markAllReadButton: true,
quickFolderJump: true,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 358 B

After

Width:  |  Height:  |  Size: 736 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 B

After

Width:  |  Height:  |  Size: 117 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 B

After

Width:  |  Height:  |  Size: 246 B

View File

@@ -1,9 +1,10 @@
{
"manifest_version": 3,
"name": "Outlook Relook",
"version": "0.1.0",
"description": "Minimalist reskin for Outlook Web App — clean themes, less clutter, more email.",
"permissions": ["storage", "activeTab"],
"name": "Outcut",
"version": "1.0.0",
"description": "Keyboard shortcuts for Outlook — Gmail-style multi-select, delete, archive, and more.",
"homepage_url": "https://github.com/joelbrockcoluminate/outcut",
"permissions": ["storage"],
"content_scripts": [
{
"matches": ["https://outlook.office.com/*", "https://outlook.office365.com/*", "https://outlook.cloud.microsoft/*"],

View File

@@ -7,247 +7,28 @@
<body>
<div class="or-header">
<h1>Outlook Relook</h1>
<h1>Outcut</h1>
<span class="or-version" id="version"></span>
</div>
<!-- Theme & Appearance -->
<div class="or-section open" data-section="theme">
<div class="or-section-header">Theme &amp; Appearance</div>
<div class="or-section-body">
<div class="or-select-row">
<label for="theme">Theme</label>
<select id="theme" data-setting="theme">
<option value="swiss">Swiss</option>
<option value="material">Material</option>
<option value="brutalist">Brutalist</option>
</select>
</div>
<div class="or-radio-group" data-setting="colorScheme">
<label><input type="radio" name="colorScheme" value="light"> Light</label>
<label><input type="radio" name="colorScheme" value="dark"> Dark</label>
<label><input type="radio" name="colorScheme" value="system"> System</label>
</div>
<div class="or-color-row">
<label for="accentColor">Accent color</label>
<input type="color" id="accentColor" data-setting="accentColor" value="#1976d2">
</div>
</div>
</div>
<!-- Density & Spacing -->
<div class="or-section" data-section="density">
<div class="or-section-header">Density &amp; Spacing</div>
<div class="or-section-body">
<div class="or-select-row">
<label for="densityPreset">Density preset</label>
<select id="densityPreset" data-setting="densityPreset">
<option value="comfortable">Comfortable</option>
<option value="compact">Compact</option>
<option value="ultra-compact">Ultra-compact</option>
</select>
</div>
<div class="or-toggle-row">
<label for="compactTopBar">Compact top bar</label>
<div class="or-switch"><input type="checkbox" id="compactTopBar" data-setting="compactTopBar"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="compactCommandBar">Compact command bar</label>
<div class="or-switch"><input type="checkbox" id="compactCommandBar" data-setting="compactCommandBar"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="compactMessageList">Compact message list</label>
<div class="or-switch"><input type="checkbox" id="compactMessageList" data-setting="compactMessageList"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="compactReadingPane">Compact reading pane</label>
<div class="or-switch"><input type="checkbox" id="compactReadingPane" data-setting="compactReadingPane"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="compactFolderPane">Compact folder pane</label>
<div class="or-switch"><input type="checkbox" id="compactFolderPane" data-setting="compactFolderPane"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="narrowDateColumn">Narrow date column</label>
<div class="or-switch"><input type="checkbox" id="narrowDateColumn" data-setting="narrowDateColumn"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="compressComposeToolbar">Compress compose toolbar</label>
<div class="or-switch"><input type="checkbox" id="compressComposeToolbar" data-setting="compressComposeToolbar"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="readingPaneMaxWidth">Reading pane max-width</label>
<div class="or-switch"><input type="checkbox" id="readingPaneMaxWidth" data-setting="readingPaneMaxWidth"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="unifiedHeader">Unified header (collapse all bars)</label>
<div class="or-switch"><input type="checkbox" id="unifiedHeader" data-setting="unifiedHeader"><span class="slider"></span></div>
</div>
</div>
</div>
<!-- Hide Elements -->
<div class="or-section" data-section="hide">
<div class="or-section-header">Hide Elements</div>
<div class="or-section-body">
<div class="or-toggle-row">
<label for="hideCopilot">Copilot (button, pane, suggestions)</label>
<div class="or-switch"><input type="checkbox" id="hideCopilot" data-setting="hideCopilot"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="hideSuggestedReplies">Suggested replies</label>
<div class="or-switch"><input type="checkbox" id="hideSuggestedReplies" data-setting="hideSuggestedReplies"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="hidePromoBanners">Promotional banners</label>
<div class="or-switch"><input type="checkbox" id="hidePromoBanners" data-setting="hidePromoBanners"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="hideFocusedOtherTabs">Focused / Other tabs</label>
<div class="or-switch"><input type="checkbox" id="hideFocusedOtherTabs" data-setting="hideFocusedOtherTabs"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="hideSidebarAppIcons">Sidebar app icons</label>
<div class="or-switch"><input type="checkbox" id="hideSidebarAppIcons" data-setting="hideSidebarAppIcons"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="hideGroupsSection">Groups section</label>
<div class="or-switch"><input type="checkbox" id="hideGroupsSection" data-setting="hideGroupsSection"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="hideMyDayButtons">My Day / panel buttons</label>
<div class="or-switch"><input type="checkbox" id="hideMyDayButtons" data-setting="hideMyDayButtons"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="hideSenderAvatars">Sender avatars</label>
<div class="or-switch"><input type="checkbox" id="hideSenderAvatars" data-setting="hideSenderAvatars"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="hideFeatureDiscovery">Feature discovery tooltips</label>
<div class="or-switch"><input type="checkbox" id="hideFeatureDiscovery" data-setting="hideFeatureDiscovery"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="hideVivaInsights">Viva Insights / Briefing</label>
<div class="or-switch"><input type="checkbox" id="hideVivaInsights" data-setting="hideVivaInsights"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="hideUnreadOtherBanner">Unread in Other banner</label>
<div class="or-switch"><input type="checkbox" id="hideUnreadOtherBanner" data-setting="hideUnreadOtherBanner"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="hideActivityFeed">Activity feed banners</label>
<div class="or-switch"><input type="checkbox" id="hideActivityFeed" data-setting="hideActivityFeed"><span class="slider"></span></div>
</div>
</div>
</div>
<!-- Readability -->
<div class="or-section" data-section="readability">
<div class="or-section-header">Readability</div>
<div class="or-section-body">
<div class="or-toggle-row">
<label for="unreadDistinction">Unread distinction (bold + border)</label>
<div class="or-switch"><input type="checkbox" id="unreadDistinction" data-setting="unreadDistinction"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="previewOwnLine">Preview text on own line</label>
<div class="or-switch"><input type="checkbox" id="previewOwnLine" data-setting="previewOwnLine"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="normalizeFontWeight">Normalize font weight</label>
<div class="or-switch"><input type="checkbox" id="normalizeFontWeight" data-setting="normalizeFontWeight"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="darkModeEmailFix">Dark mode email body fix</label>
<div class="or-switch"><input type="checkbox" id="darkModeEmailFix" data-setting="darkModeEmailFix"><span class="slider"></span></div>
</div>
<div class="or-select-row">
<label for="messageListFontSize">Message list font size</label>
<select id="messageListFontSize" data-setting="messageListFontSize">
<option value="small">Small</option>
<option value="medium">Medium</option>
<option value="large">Large</option>
</select>
</div>
</div>
</div>
<!-- Behavior -->
<div class="or-section" data-section="behavior">
<div class="or-section-header">Behavior</div>
<div class="or-section-body">
<div class="or-toggle-row">
<label for="autoCollapseRibbon">Auto-collapse ribbon</label>
<div class="or-switch"><input type="checkbox" id="autoCollapseRibbon" data-setting="autoCollapseRibbon"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="rememberSidebarState">Remember sidebar state</label>
<div class="or-switch"><input type="checkbox" id="rememberSidebarState" data-setting="rememberSidebarState"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="suppressContactHover">Suppress contact hover cards</label>
<div class="or-switch"><input type="checkbox" id="suppressContactHover" data-setting="suppressContactHover"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="autoAdvanceAfterDelete">Auto-advance after delete</label>
<div class="or-switch"><input type="checkbox" id="autoAdvanceAfterDelete" data-setting="autoAdvanceAfterDelete"><span class="slider"></span></div>
</div>
<div class="or-select-row">
<label for="autoDismissToasts">Auto-dismiss toasts</label>
<select id="autoDismissToasts" data-setting="autoDismissToasts">
<option value="off">Off</option>
<option value="3">3 seconds</option>
<option value="5">5 seconds</option>
<option value="10">10 seconds</option>
</select>
</div>
<div class="or-select-row">
<label for="toastPosition">Toast position</label>
<select id="toastPosition" data-setting="toastPosition">
<option value="bottom-left">Bottom-left</option>
<option value="top-right">Top-right</option>
</select>
</div>
<div class="or-toggle-row">
<label for="stickyReplyBar">Sticky Reply/Forward bar</label>
<div class="or-switch"><input type="checkbox" id="stickyReplyBar" data-setting="stickyReplyBar"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="autoResizeCompose">Auto-resize compose window</label>
<div class="or-switch"><input type="checkbox" id="autoResizeCompose" data-setting="autoResizeCompose"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="throttleNotifications">Throttle desktop notifications</label>
<div class="or-switch"><input type="checkbox" id="throttleNotifications" data-setting="throttleNotifications"><span class="slider"></span></div>
</div>
</div>
</div>
<!-- Keyboard Navigation -->
<div class="or-section" data-section="keyboard">
<!-- 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-select-row">
<label for="keyboardPreset">Shortcut style</label>
<select id="keyboardPreset" data-setting="keyboardPreset">
<option value="gmail">Gmail</option>
<option value="outlook">Outlook</option>
</select>
</div>
<div class="or-toggle-row">
<label for="keyboardMultiSelect">Gmail-style keyboard multi-select</label>
<label for="keyboardMultiSelect">Enable 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>
<div class="or-section-body">
<div class="or-toggle-row">
<label for="markAllReadButton">"Mark all as read" button</label>
<div class="or-switch"><input type="checkbox" id="markAllReadButton" data-setting="markAllReadButton"><span class="slider"></span></div>
</div>
<div class="or-toggle-row">
<label for="quickFolderJump">Quick folder jump (Ctrl+Shift+K)</label>
<div class="or-switch"><input type="checkbox" id="quickFolderJump" data-setting="quickFolderJump"><span class="slider"></span></div>
<div id="keyboardHelpText" 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>

View File

@@ -1,16 +1,25 @@
// Outlook Relook — Popup Settings Logic
// Outcut — Popup Settings Logic
(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,
keyboardPreset: 'gmail',
// Design tweaks master toggle
enableDesignTweaks: false,
// Theme & Appearance
theme: 'swiss',
colorScheme: 'system',
accentColor: '',
// Density & Spacing
densityPreset: 'compact',
compactTopBar: true,
compactCommandBar: true,
@@ -21,23 +30,29 @@
compressComposeToolbar: true,
readingPaneMaxWidth: true,
unifiedHeader: false,
// Hide Elements
hideCopilot: true,
hideSuggestedReplies: true,
hidePromoBanners: true,
hideFocusedOtherTabs: true,
hideSidebarAppIcons: false,
hideGroupsSection: false,
hideMyDayButtons: true,
hideMyDayButtons: false,
hideSenderAvatars: false,
hideFeatureDiscovery: true,
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,43 +62,22 @@
stickyReplyBar: true,
autoResizeCompose: true,
throttleNotifications: false,
keyboardMultiSelect: true,
// Quick Actions
markAllReadButton: true,
quickFolderJump: true,
};
var DENSITY_PRESETS = {
comfortable: {
compactTopBar: false,
compactCommandBar: false,
compactMessageList: false,
compactReadingPane: false,
compactFolderPane: false,
narrowDateColumn: false,
compressComposeToolbar: false,
readingPaneMaxWidth: true,
},
compact: {
compactTopBar: true,
compactCommandBar: true,
compactMessageList: true,
compactReadingPane: true,
compactFolderPane: true,
narrowDateColumn: true,
compressComposeToolbar: true,
readingPaneMaxWidth: true,
},
'ultra-compact': {
compactTopBar: true,
compactCommandBar: true,
compactMessageList: true,
compactReadingPane: true,
compactFolderPane: true,
narrowDateColumn: true,
compressComposeToolbar: true,
readingPaneMaxWidth: true,
},
};
// --- Keyboard help text ---
function updateKeyboardHelp(presetName) {
var helpEl = document.getElementById('keyboardHelpText');
if (!helpEl) return;
var presets = {
gmail: 'j/k navigate, x/Space select, # delete, e archive, Shift+i/u read/unread, v move, f flag, p pin, Esc deselect',
outlook: 'Arrows navigate, Space select, Del/Backspace delete, e archive, q read/unread, v move, f flag, p pin, Esc deselect'
};
helpEl.textContent = presets[presetName] || presets.gmail;
}
// --- Load settings and populate UI ---
function loadUI() {
@@ -100,23 +94,12 @@
selects[j].value = settings[selects[j].dataset.setting] || '';
}
// Radio groups
var radioGroups = document.querySelectorAll('.or-radio-group[data-setting]');
for (var k = 0; k < radioGroups.length; k++) {
var key = radioGroups[k].dataset.setting;
var radio = radioGroups[k].querySelector('input[value="' + settings[key] + '"]');
if (radio) radio.checked = true;
}
// Color picker
var colorPicker = document.querySelector('input[type="color"][data-setting]');
if (colorPicker) {
colorPicker.value = settings.accentColor || '#1976d2';
}
// Version
var manifest = chrome.runtime.getManifest();
document.getElementById('version').textContent = 'v' + manifest.version;
// Update help text based on current preset
updateKeyboardHelp(settings.keyboardPreset || 'gmail');
});
}
@@ -151,42 +134,12 @@
var value = this.value;
saveSetting(settingKey, value);
// Density preset: apply to individual toggles
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]);
if (checkbox) checkbox.checked = preset[toggleKeys[p]];
}
if (settingKey === 'keyboardPreset') {
updateKeyboardHelp(value);
}
});
}
// --- Radio group change handlers ---
var radioGroups = document.querySelectorAll('.or-radio-group[data-setting]');
for (var r = 0; r < radioGroups.length; r++) {
var groupKey = radioGroups[r].dataset.setting;
var radios = radioGroups[r].querySelectorAll('input[type="radio"]');
for (var ri = 0; ri < radios.length; ri++) {
(function (gk) {
radios[ri].addEventListener('change', function () {
if (this.checked) saveSetting(gk, this.value);
});
})(groupKey);
}
}
// --- Color picker change handler ---
var colorPickers = document.querySelectorAll('input[type="color"][data-setting]');
for (var cp = 0; cp < colorPickers.length; cp++) {
colorPickers[cp].addEventListener('input', function () {
saveSetting(this.dataset.setting, this.value);
});
}
// --- Export settings ---
document.getElementById('exportBtn').addEventListener('click', function () {
chrome.storage.sync.get(DEFAULTS, function (settings) {
@@ -194,7 +147,7 @@
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = 'outlook-relook-settings.json';
a.download = 'outcut-settings.json';
a.click();
URL.revokeObjectURL(url);
});
@@ -213,7 +166,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++) {

View File

@@ -164,9 +164,8 @@ html[data-or-hide-promos="true"] [aria-label*="Get the Outlook" i] {
display: none !important;
}
/* Focused / Other tabs */
html[data-or-hide-focusedtabs="true"] [role="tablist"][aria-label*="Focused" i],
html[data-or-hide-focusedtabs="true"] [aria-label*="Focused Inbox" i] {
/* Focused / Other tabs — OWA uses .fui-TabList with .fui-Tab buttons */
html[data-or-hide-focusedtabs="true"] .fui-TabList {
display: none !important;
}
@@ -182,9 +181,9 @@ html[data-or-hide-groups="true"] [aria-label*="Groups" i][role="treeitem"] {
display: none !important;
}
/* My Day / right-side panel buttons */
html[data-or-hide-myday="true"] [aria-label*="My Day" i],
html[data-or-hide-myday="true"] [aria-label*="To Do" i][role="button"] {
/* My Day / right-side panel TOGGLE BUTTONS only (not the panel itself) */
html[data-or-hide-myday="true"] button[aria-label*="My Day" i],
html[data-or-hide-myday="true"] button[aria-label*="To Do" i] {
display: none !important;
}
@@ -308,14 +307,17 @@ html[data-outlook-relook-scheme] [role="treeitem"][aria-current="true"]::before
UNIFIED HEADER — Collapse all top bars into one compact surface
data-or-unified-header="true"
OWA header structure (outlook.cloud.microsoft):
- Search bar: [role="search"]
- Ribbon: [role="toolbar"], .fui-Toolbar, .ms-OverflowSet
- Tabs: [role="tablist"], .fui-TabList
- Buttons: .ms-Button
OWA header structure (outlook.cloud.microsoft, measured):
Row 1: div.Rn_96 — 48px — top bar (logo + search)
Row 2: div.root-109 — 36px — ribbon tabs (Home/View/Help)
Row 3: toolbar buttons — 40px — ribbon content
Row 4: Focused/Other + filters — starts at y=124
Total: ~124px of header before email content
Goal: compress to ~60px total
============================================================ */
/* Search bar — shrink to minimum */
/* --- Row 1: Top bar container (48px → 28px) --- */
html[data-or-unified-header="true"] [role="search"] {
height: 24px !important;
min-height: 24px !important;
@@ -330,28 +332,52 @@ html[data-or-unified-header="true"] [role="search"] input {
padding: 1px 8px !important;
}
/* Toolbars — compress to single row, minimal height */
/* Crush the top bar container's height and padding */
html[data-or-unified-header="true"] [role="search"],
html[data-or-unified-header="true"] [role="search"] ~ *,
html[data-or-unified-header="true"] [role="search"] > * {
padding-top: 0 !important;
padding-bottom: 0 !important;
margin-top: 0 !important;
margin-bottom: 0 !important;
}
/* --- Row 2: Ribbon tabs (Home/View/Help) (36px → hide or minimize) --- */
/* The ribbon tab bar uses .ms-OverflowSet as a tablist */
html[data-or-unified-header="true"] .ms-OverflowSet[role="tablist"] {
max-height: 24px !important;
min-height: 20px !important;
padding: 0 !important;
}
html[data-or-unified-header="true"] .ms-OverflowSet[role="tablist"] [role="tab"] {
padding: 0 6px !important;
min-height: 20px !important;
max-height: 20px !important;
font-size: 11px !important;
}
/* --- Row 3: Ribbon toolbar content (40px → 26px) --- */
html[data-or-unified-header="true"] [role="toolbar"],
html[data-or-unified-header="true"] .fui-Toolbar {
max-height: 28px !important;
max-height: 26px !important;
min-height: 24px !important;
padding: 0 4px !important;
overflow: hidden !important;
gap: 0 !important;
}
/* Toolbar buttons — icons only, minimal size */
html[data-or-unified-header="true"] [role="toolbar"] button,
html[data-or-unified-header="true"] [role="toolbar"] .ms-Button,
html[data-or-unified-header="true"] .fui-Toolbar button {
padding: 1px 4px !important;
padding: 1px 3px !important;
min-height: 22px !important;
max-height: 22px !important;
min-width: unset !important;
font-size: 11px !important;
}
/* Hide button labels — keep icons only */
/* Hide button text labels — icons only */
html[data-or-unified-header="true"] [role="toolbar"] .ms-Button-label,
html[data-or-unified-header="true"] [role="toolbar"] .ms-Button-textContainer,
html[data-or-unified-header="true"] .fui-Toolbar .ms-Button-label,
@@ -360,7 +386,7 @@ html[data-or-unified-header="true"] .fui-Toolbar .ms-Button-textContainer {
}
/* Ribbon groups and overflow — compress */
html[data-or-unified-header="true"] .ms-OverflowSet {
html[data-or-unified-header="true"] .ms-OverflowSet:not([role="tablist"]) {
min-height: unset !important;
gap: 0 !important;
}
@@ -371,27 +397,36 @@ html[data-or-unified-header="true"] [role="group"] {
min-height: unset !important;
}
/* Tab lists (Focused/Other) — minimal */
html[data-or-unified-header="true"] [role="tablist"],
/* --- Row 4: Focused/Other tabs (fui-TabList) --- */
html[data-or-unified-header="true"] .fui-TabList {
max-height: 24px !important;
min-height: 22px !important;
max-height: 22px !important;
min-height: 20px !important;
padding: 0 !important;
gap: 0 !important;
}
html[data-or-unified-header="true"] [role="tab"] {
padding: 1px 8px !important;
html[data-or-unified-header="true"] .fui-TabList .fui-Tab {
padding: 0 8px !important;
min-height: 20px !important;
font-size: 11px !important;
}
/* Aggressively strip vertical padding from ALL divs in the header region.
We identify the header region as everything above the message list.
Target parent containers of search/toolbar/tablist. */
html[data-or-unified-header="true"] [role="search"] ~ div,
html[data-or-unified-header="true"] [role="toolbar"] ~ div,
html[data-or-unified-header="true"] .fui-Toolbar ~ div {
/* --- Strip ALL vertical space from container divs in the header --- */
/* Target every ancestor div between the top of the page and content.
We use the known parent container selectors + general approach. */
html[data-or-unified-header="true"] [role="search"],
html[data-or-unified-header="true"] [role="toolbar"],
html[data-or-unified-header="true"] .fui-Toolbar,
html[data-or-unified-header="true"] .ms-OverflowSet,
html[data-or-unified-header="true"] .fui-TabList {
margin-top: 0 !important;
margin-bottom: 0 !important;
}
/* Reduce height on the wrapping containers */
html[data-or-unified-header="true"] [role="search"] ~ *,
html[data-or-unified-header="true"] [role="toolbar"] ~ *,
html[data-or-unified-header="true"] .fui-Toolbar ~ * {
padding-top: 0 !important;
padding-bottom: 0 !important;
margin-top: 0 !important;
@@ -403,32 +438,32 @@ html[data-or-unified-header="true"] .fui-Toolbar ~ div {
KEYBOARD NAVIGATION
============================================================ */
/* Focus cursor — the message the keyboard is currently pointing at */
.or-kb-focused {
/* Focus cursor — the message the keyboard is currently pointing at.
High specificity to beat theme selectors. */
html[data-outlook-relook-scheme] [role="option"].or-kb-focused,
html[data-outlook-relook-scheme] [role="listitem"].or-kb-focused,
html .or-kb-focused {
outline: 2px solid var(--or-accent, #0078d4) !important;
outline-offset: -2px;
position: relative;
z-index: 1;
}
/* Selected/checked messages */
.or-kb-selected {
background-color: rgba(0, 120, 212, 0.08) !important;
/* Selected/checked messages — high specificity + strong visual */
html[data-outlook-relook-scheme] [role="option"].or-kb-selected,
html[data-outlook-relook-scheme] [role="listitem"].or-kb-selected,
html[data-outlook-relook-scheme="light"] [role="option"].or-kb-selected,
html[data-outlook-relook-scheme="light"] [role="listitem"].or-kb-selected,
html .or-kb-selected {
background-color: rgba(0, 120, 212, 0.12) !important;
border-left: 3px solid var(--or-accent, #0078d4) !important;
position: relative;
}
html[data-outlook-relook-scheme="dark"] .or-kb-selected {
background-color: rgba(100, 181, 246, 0.12) !important;
}
/* Selection indicator — small left bar */
.or-kb-selected::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background: var(--or-accent, #0078d4);
html[data-outlook-relook-scheme="dark"] [role="option"].or-kb-selected,
html[data-outlook-relook-scheme="dark"] [role="listitem"].or-kb-selected {
background-color: rgba(100, 181, 246, 0.18) !important;
border-left: 3px solid var(--or-accent, #64b5f6) !important;
}
/* Selection count badge (injected by keyboard.js) */