Compare commits
10 Commits
8fe0808bbd
...
e75bcfa478
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e75bcfa478 | ||
|
|
8bd41f2721 | ||
|
|
3de2db7d89 | ||
|
|
93a5888d83 | ||
|
|
823de95891 | ||
|
|
e950a0e1c4 | ||
|
|
13901e4453 | ||
|
|
919bdf2bf3 | ||
|
|
7f0a88b131 | ||
|
|
c66265e48f |
29
PRIVACY.md
Normal file
29
PRIVACY.md
Normal 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
109
README.md
@@ -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).
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 |
@@ -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/*"],
|
||||
|
||||
247
popup/popup.html
247
popup/popup.html
@@ -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 & 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 & 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>
|
||||
|
||||
124
popup/popup.js
124
popup/popup.js
@@ -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++) {
|
||||
|
||||
131
themes/base.css
131
themes/base.css
@@ -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) */
|
||||
|
||||
Reference in New Issue
Block a user