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
This commit is contained in:
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
|
## Features
|
||||||
|
|
||||||
- **Switchable themes:** Swiss (Helvetica minimalism) and Material (clean, subtle elevation)
|
- **Gmail or Outlook shortcut presets** — choose your preferred key bindings
|
||||||
- **Light & Dark modes** with system preference detection
|
- **Multi-select** — select multiple messages and act on them at once
|
||||||
- **~40 granular toggles** for density, element hiding, readability, and behavior
|
- **Batch actions** — delete, archive, flag, pin, move, read/unread on selected messages
|
||||||
- **MutationObserver** suppresses dynamically injected clutter (Copilot, banners, suggested replies)
|
- **Works everywhere** — outlook.office.com, outlook.cloud.microsoft, and Outlook PWA
|
||||||
- **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
|
|
||||||
|
|
||||||
## 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
|
1. Clone this repo
|
||||||
2. Open `chrome://extensions` in Chrome
|
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
|
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
|
## 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):**
|
Outcut stores only your preferences locally. No data is collected or transmitted. See [PRIVACY.md](PRIVACY.md).
|
||||||
- `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`
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ window.OutlookRelook.Behavior = (function () {
|
|||||||
for (var i = 0; i < elements.length; i++) {
|
for (var i = 0; i < elements.length; i++) {
|
||||||
if (elements[i].getAttribute('aria-expanded') === 'true') {
|
if (elements[i].getAttribute('aria-expanded') === 'true') {
|
||||||
elements[i].click();
|
elements[i].click();
|
||||||
console.log('[Outlook Relook] Auto-collapsed ribbon');
|
console.log('[Outcut] Auto-collapsed ribbon');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 2000);
|
}, 2000);
|
||||||
@@ -217,7 +217,7 @@ window.OutlookRelook.Behavior = (function () {
|
|||||||
for (var w = 0; w < composeWindows.length; w++) {
|
for (var w = 0; w < composeWindows.length; w++) {
|
||||||
composeWindows[w].style.minHeight = '60vh';
|
composeWindows[w].style.minHeight = '60vh';
|
||||||
composeWindows[w].style.height = '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) {
|
window.Notification = function (title, options) {
|
||||||
var now = Date.now();
|
var now = Date.now();
|
||||||
if (now - lastNotificationTime < MIN_INTERVAL) {
|
if (now - lastNotificationTime < MIN_INTERVAL) {
|
||||||
console.log('[Outlook Relook] Throttled notification: "' + title + '"');
|
console.log('[Outcut] Throttled notification: "' + title + '"');
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
lastNotificationTime = now;
|
lastNotificationTime = now;
|
||||||
@@ -271,7 +271,7 @@ window.OutlookRelook.Behavior = (function () {
|
|||||||
setupAutoResizeCompose();
|
setupAutoResizeCompose();
|
||||||
setupThrottleNotifications();
|
setupThrottleNotifications();
|
||||||
|
|
||||||
console.log('[Outlook Relook] Behavior patches applied');
|
console.log('[Outcut] Behavior patches applied');
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSettings(settings) {
|
function updateSettings(settings) {
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
if (settings.accentColor) {
|
if (settings.accentColor) {
|
||||||
document.documentElement.style.setProperty('--or-accent-override', settings.accentColor);
|
document.documentElement.style.setProperty('--or-accent-override', settings.accentColor);
|
||||||
}
|
}
|
||||||
console.log('[Outlook Relook] Settings applied to DOM');
|
console.log('[Outcut] Settings applied to DOM');
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearDesignFromDOM() {
|
function clearDesignFromDOM() {
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
var themeLink = document.getElementById('outlook-relook-theme');
|
var themeLink = document.getElementById('outlook-relook-theme');
|
||||||
if (themeLink) themeLink.remove();
|
if (themeLink) themeLink.remove();
|
||||||
|
|
||||||
console.log('[Outlook Relook] Design tweaks cleared from DOM');
|
console.log('[Outcut] Design tweaks cleared from DOM');
|
||||||
}
|
}
|
||||||
|
|
||||||
function injectThemeCSS(theme) {
|
function injectThemeCSS(theme) {
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
link.rel = 'stylesheet';
|
link.rel = 'stylesheet';
|
||||||
link.href = chrome.runtime.getURL('themes/' + theme + '.css');
|
link.href = chrome.runtime.getURL('themes/' + theme + '.css');
|
||||||
document.head.appendChild(link);
|
document.head.appendChild(link);
|
||||||
console.log('[Outlook Relook] Theme loaded: ' + theme);
|
console.log('[Outcut] Theme loaded: ' + theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
function startDesignTweaks(settings) {
|
function startDesignTweaks(settings) {
|
||||||
@@ -101,7 +101,7 @@
|
|||||||
OR.Behavior.start(settings);
|
OR.Behavior.start(settings);
|
||||||
OR.Injector.start(settings);
|
OR.Injector.start(settings);
|
||||||
designTweaksActive = true;
|
designTweaksActive = true;
|
||||||
console.log('[Outlook Relook] Design tweaks enabled');
|
console.log('[Outcut] Design tweaks enabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopDesignTweaks() {
|
function stopDesignTweaks() {
|
||||||
@@ -110,12 +110,12 @@
|
|||||||
OR.Injector.stop();
|
OR.Injector.stop();
|
||||||
clearDesignFromDOM();
|
clearDesignFromDOM();
|
||||||
designTweaksActive = false;
|
designTweaksActive = false;
|
||||||
console.log('[Outlook Relook] Design tweaks disabled');
|
console.log('[Outcut] Design tweaks disabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
const settings = await OR.loadSettings();
|
const settings = await OR.loadSettings();
|
||||||
console.log('[Outlook Relook] Loaded settings:', settings);
|
console.log('[Outcut] Loaded settings:', settings);
|
||||||
|
|
||||||
// Keyboard navigation — always starts (gated by its own toggle internally)
|
// Keyboard navigation — always starts (gated by its own toggle internally)
|
||||||
OR.Keyboard.start(settings);
|
OR.Keyboard.start(settings);
|
||||||
|
|||||||
@@ -60,13 +60,13 @@ window.OutlookRelook.Injector = (function () {
|
|||||||
for (var j = 0; j < menuItems.length; j++) {
|
for (var j = 0; j < menuItems.length; j++) {
|
||||||
if (/mark all as read/i.test(menuItems[j].textContent)) {
|
if (/mark all as read/i.test(menuItems[j].textContent)) {
|
||||||
menuItems[j].click();
|
menuItems[j].click();
|
||||||
console.log('[Outlook Relook] Marked all as read');
|
console.log('[Outcut] Marked all as read');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Close context menu if option not found
|
// Close context menu if option not found
|
||||||
document.body.click();
|
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);
|
}, 300);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -244,7 +244,7 @@ window.OutlookRelook.Injector = (function () {
|
|||||||
currentSettings = settings;
|
currentSettings = settings;
|
||||||
setupMarkAllRead();
|
setupMarkAllRead();
|
||||||
setupFolderJump();
|
setupFolderJump();
|
||||||
console.log('[Outlook Relook] Injector started');
|
console.log('[Outcut] Injector started');
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSettings(settings) {
|
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.
|
// Adds keyboard focus cursor and multi-select to OWA's message list.
|
||||||
// Gated by the keyboardMultiSelect setting.
|
// Gated by the keyboardMultiSelect setting.
|
||||||
//
|
//
|
||||||
@@ -7,6 +7,53 @@
|
|||||||
|
|
||||||
window.OutlookRelook = window.OutlookRelook || {};
|
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 () {
|
window.OutlookRelook.Keyboard = (function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
@@ -246,7 +293,7 @@ window.OutlookRelook.Keyboard = (function () {
|
|||||||
performAction(targets, function (target) {
|
performAction(targets, function (target) {
|
||||||
var btn = findButton(target, 'Delete');
|
var btn = findButton(target, 'Delete');
|
||||||
if (btn) { btn.click(); return true; }
|
if (btn) { btn.click(); return true; }
|
||||||
console.warn('[Outlook Relook] Delete button not found');
|
console.warn('[Outcut] Delete button not found');
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -255,7 +302,7 @@ window.OutlookRelook.Keyboard = (function () {
|
|||||||
performAction(targets, function (target) {
|
performAction(targets, function (target) {
|
||||||
var btn = findButton(target, 'Archive');
|
var btn = findButton(target, 'Archive');
|
||||||
if (btn) { btn.click(); return true; }
|
if (btn) { btn.click(); return true; }
|
||||||
console.warn('[Outlook Relook] Archive button not found');
|
console.warn('[Outcut] Archive button not found');
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -288,7 +335,7 @@ window.OutlookRelook.Keyboard = (function () {
|
|||||||
var btn = findButton(target, 'Flag this message')
|
var btn = findButton(target, 'Flag this message')
|
||||||
|| findButton(target, 'Flag / Unflag');
|
|| findButton(target, 'Flag / Unflag');
|
||||||
if (btn) { btn.click(); return true; }
|
if (btn) { btn.click(); return true; }
|
||||||
console.warn('[Outlook Relook] Flag button not found');
|
console.warn('[Outcut] Flag button not found');
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -297,7 +344,7 @@ window.OutlookRelook.Keyboard = (function () {
|
|||||||
performAction(targets, function (target) {
|
performAction(targets, function (target) {
|
||||||
var btn = findButton(target, 'Pin / Unpin');
|
var btn = findButton(target, 'Pin / Unpin');
|
||||||
if (btn) { btn.click(); return true; }
|
if (btn) { btn.click(); return true; }
|
||||||
console.warn('[Outlook Relook] Pin button not found');
|
console.warn('[Outcut] Pin button not found');
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -332,8 +379,8 @@ window.OutlookRelook.Keyboard = (function () {
|
|||||||
var items = getMessageItems();
|
var items = getMessageItems();
|
||||||
if (items.length === 0) return;
|
if (items.length === 0) return;
|
||||||
|
|
||||||
var key = e.key;
|
var presetName = currentSettings.keyboardPreset || 'gmail';
|
||||||
var shift = e.shiftKey;
|
var preset = KEY_PRESETS[presetName] || KEY_PRESETS.gmail;
|
||||||
|
|
||||||
// Initialize focus if not set
|
// Initialize focus if not set
|
||||||
var currentIdx = getFocusedIndex(items);
|
var currentIdx = getFocusedIndex(items);
|
||||||
@@ -356,101 +403,54 @@ window.OutlookRelook.Keyboard = (function () {
|
|||||||
var handled = true;
|
var handled = true;
|
||||||
var targets;
|
var targets;
|
||||||
|
|
||||||
switch (key) {
|
if (matchesAction(e, preset.selectExtendDown)) {
|
||||||
case 'j':
|
|
||||||
case 'ArrowDown':
|
|
||||||
if (shift) {
|
|
||||||
toggleSelect(items, currentIdx);
|
toggleSelect(items, currentIdx);
|
||||||
if (currentIdx < items.length - 1) {
|
if (currentIdx < items.length - 1) {
|
||||||
setFocus(items, currentIdx + 1);
|
setFocus(items, currentIdx + 1);
|
||||||
toggleSelect(items, currentIdx + 1);
|
toggleSelect(items, currentIdx + 1);
|
||||||
}
|
}
|
||||||
} else {
|
} else if (matchesAction(e, preset.selectExtendUp)) {
|
||||||
if (currentIdx < items.length - 1) {
|
|
||||||
setFocus(items, currentIdx + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'k':
|
|
||||||
case 'ArrowUp':
|
|
||||||
if (shift) {
|
|
||||||
toggleSelect(items, currentIdx);
|
toggleSelect(items, currentIdx);
|
||||||
if (currentIdx > 0) {
|
if (currentIdx > 0) {
|
||||||
setFocus(items, currentIdx - 1);
|
setFocus(items, currentIdx - 1);
|
||||||
toggleSelect(items, currentIdx - 1);
|
toggleSelect(items, currentIdx - 1);
|
||||||
}
|
}
|
||||||
} else {
|
} else if (matchesAction(e, preset.nextMessage)) {
|
||||||
if (currentIdx > 0) {
|
if (currentIdx < items.length - 1) setFocus(items, currentIdx + 1);
|
||||||
setFocus(items, currentIdx - 1);
|
} else if (matchesAction(e, preset.prevMessage)) {
|
||||||
}
|
if (currentIdx > 0) setFocus(items, currentIdx - 1);
|
||||||
}
|
} else if (matchesAction(e, preset.toggleSelect)) {
|
||||||
break;
|
|
||||||
|
|
||||||
case 'x':
|
|
||||||
case ' ':
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
toggleSelect(items, currentIdx);
|
toggleSelect(items, currentIdx);
|
||||||
break;
|
} else if (matchesAction(e, preset.delete)) {
|
||||||
|
|
||||||
case '#':
|
|
||||||
targets = getActionTargets(items);
|
targets = getActionTargets(items);
|
||||||
actionDelete(targets);
|
actionDelete(targets);
|
||||||
break;
|
} else if (matchesAction(e, preset.archive)) {
|
||||||
|
|
||||||
case 'e':
|
|
||||||
targets = getActionTargets(items);
|
targets = getActionTargets(items);
|
||||||
actionArchive(targets);
|
actionArchive(targets);
|
||||||
break;
|
} else if (matchesAction(e, preset.readUnread)) {
|
||||||
|
|
||||||
case 'I':
|
|
||||||
case 'U':
|
|
||||||
// Shift+i or Shift+u — OWA uses a single "Read / Unread" toggle
|
|
||||||
if (shift) {
|
|
||||||
targets = getActionTargets(items);
|
targets = getActionTargets(items);
|
||||||
actionMarkReadUnread(targets);
|
actionMarkReadUnread(targets);
|
||||||
} else {
|
} else if (matchesAction(e, preset.move)) {
|
||||||
handled = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'v':
|
|
||||||
targets = getActionTargets(items);
|
targets = getActionTargets(items);
|
||||||
actionMove(targets);
|
actionMove(targets);
|
||||||
break;
|
} else if (matchesAction(e, preset.flag)) {
|
||||||
|
|
||||||
case 'f':
|
|
||||||
// Flag/unflag
|
|
||||||
targets = getActionTargets(items);
|
targets = getActionTargets(items);
|
||||||
actionFlag(targets);
|
actionFlag(targets);
|
||||||
break;
|
} else if (matchesAction(e, preset.pin)) {
|
||||||
|
|
||||||
case 'p':
|
|
||||||
// Pin/unpin
|
|
||||||
targets = getActionTargets(items);
|
targets = getActionTargets(items);
|
||||||
actionPin(targets);
|
actionPin(targets);
|
||||||
break;
|
} else if (matchesAction(e, preset.deselect)) {
|
||||||
|
|
||||||
case 'Escape':
|
|
||||||
clearSelection();
|
clearSelection();
|
||||||
break;
|
} else if (matchesAction(e, preset.open)) {
|
||||||
|
if (currentIdx >= 0 && currentIdx < items.length) items[currentIdx].click();
|
||||||
case 'Enter':
|
} else {
|
||||||
case 'o':
|
|
||||||
if (currentIdx >= 0 && currentIdx < items.length) {
|
|
||||||
items[currentIdx].click();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
handled = false;
|
handled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handled) {
|
if (handled) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (key !== 'Enter' && key !== 'o') {
|
if (e.key !== 'Enter') e.preventDefault();
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -497,7 +497,7 @@ window.OutlookRelook.Keyboard = (function () {
|
|||||||
document.addEventListener('keydown', handleKeydown, true);
|
document.addEventListener('keydown', handleKeydown, true);
|
||||||
setupListObserver();
|
setupListObserver();
|
||||||
|
|
||||||
console.log('[Outlook Relook] Keyboard navigation started');
|
console.log('[Outcut] Keyboard navigation started');
|
||||||
cleanupFns.push(function () {
|
cleanupFns.push(function () {
|
||||||
document.removeEventListener('keydown', handleKeydown, true);
|
document.removeEventListener('keydown', handleKeydown, true);
|
||||||
});
|
});
|
||||||
@@ -506,9 +506,11 @@ window.OutlookRelook.Keyboard = (function () {
|
|||||||
function updateSettings(settings) {
|
function updateSettings(settings) {
|
||||||
var wasEnabled = currentSettings.keyboardMultiSelect;
|
var wasEnabled = currentSettings.keyboardMultiSelect;
|
||||||
var isEnabled = settings.keyboardMultiSelect;
|
var isEnabled = settings.keyboardMultiSelect;
|
||||||
|
var oldPreset = currentSettings.keyboardPreset;
|
||||||
|
var newPreset = settings.keyboardPreset;
|
||||||
currentSettings = settings;
|
currentSettings = settings;
|
||||||
|
|
||||||
if (wasEnabled !== isEnabled) {
|
if (wasEnabled !== isEnabled || oldPreset !== newPreset) {
|
||||||
stop();
|
stop();
|
||||||
start(settings);
|
start(settings);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ window.OutlookRelook.Observer = (function () {
|
|||||||
for (const el of elements) {
|
for (const el of elements) {
|
||||||
if (el.style.display !== 'none') {
|
if (el.style.display !== 'none') {
|
||||||
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 || '';
|
var text = el.textContent || '';
|
||||||
if (pattern.test(text) && text.length < 200) {
|
if (pattern.test(text) && text.length < 200) {
|
||||||
el.style.display = 'none';
|
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,
|
subtree: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('[Outlook Relook] Observer started');
|
console.log('[Outcut] Observer started');
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSettings(settings) {
|
function updateSettings(settings) {
|
||||||
@@ -118,7 +118,7 @@ window.OutlookRelook.Observer = (function () {
|
|||||||
if (observer) {
|
if (observer) {
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
observer = null;
|
observer = null;
|
||||||
console.log('[Outlook Relook] Observer stopped');
|
console.log('[Outcut] Observer stopped');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ window.OutlookRelook = window.OutlookRelook || {};
|
|||||||
window.OutlookRelook.DEFAULTS = {
|
window.OutlookRelook.DEFAULTS = {
|
||||||
// Keyboard Navigation (primary feature, always visible)
|
// Keyboard Navigation (primary feature, always visible)
|
||||||
keyboardMultiSelect: true,
|
keyboardMultiSelect: true,
|
||||||
|
keyboardPreset: 'gmail',
|
||||||
|
|
||||||
// Design Tweaks (experimental, hidden by default)
|
// Design Tweaks (experimental, hidden by default)
|
||||||
enableDesignTweaks: false,
|
enableDesignTweaks: false,
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "Outlook Relook",
|
"name": "Outcut",
|
||||||
"version": "0.1.0",
|
"version": "1.0.0",
|
||||||
"description": "Minimalist reskin for Outlook Web App — clean themes, less clutter, more email.",
|
"description": "Keyboard shortcuts for Outlook — Gmail-style multi-select, delete, archive, and more.",
|
||||||
"permissions": ["storage", "activeTab"],
|
"homepage_url": "https://github.com/joelbrockcoluminate/outcut",
|
||||||
|
"permissions": ["storage"],
|
||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
"matches": ["https://outlook.office.com/*", "https://outlook.office365.com/*", "https://outlook.cloud.microsoft/*"],
|
"matches": ["https://outlook.office.com/*", "https://outlook.office365.com/*", "https://outlook.cloud.microsoft/*"],
|
||||||
|
|||||||
259
popup/popup.html
259
popup/popup.html
@@ -7,7 +7,7 @@
|
|||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div class="or-header">
|
<div class="or-header">
|
||||||
<h1>Outlook Relook</h1>
|
<h1>Outcut</h1>
|
||||||
<span class="or-version" id="version"></span>
|
<span class="or-version" id="version"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -15,263 +15,24 @@
|
|||||||
<div class="or-section open" data-section="keyboard">
|
<div class="or-section open" data-section="keyboard">
|
||||||
<div class="or-section-header">Keyboard Navigation</div>
|
<div class="or-section-header">Keyboard Navigation</div>
|
||||||
<div class="or-section-body">
|
<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">
|
<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 class="or-switch"><input type="checkbox" id="keyboardMultiSelect" data-setting="keyboardMultiSelect"><span class="slider"></span></div>
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size:11px;color:#888;padding:4px 0 2px;line-height:1.4;">
|
<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,
|
j/k navigate, x/Space select, # delete, e archive,
|
||||||
Shift+i/u read/unread, v move, f flag, p pin, Esc deselect
|
Shift+i/u read/unread, v move, f flag, p pin, Esc deselect
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Design Tweaks Master Toggle -->
|
|
||||||
<div class="or-section" data-section="design-master">
|
|
||||||
<div class="or-section-header" style="cursor:default;">Design Tweaks</div>
|
|
||||||
<div class="or-section-body" style="display:block;">
|
|
||||||
<div class="or-toggle-row">
|
|
||||||
<label for="enableDesignTweaks">Enable experimental UI customization</label>
|
|
||||||
<div class="or-switch"><input type="checkbox" id="enableDesignTweaks" data-setting="enableDesignTweaks"><span class="slider"></span></div>
|
|
||||||
</div>
|
|
||||||
<div style="font-size:11px;color:#888;padding:2px 0;line-height:1.4;">
|
|
||||||
Themes, density, element hiding, and behavior patches. May conflict with OWA updates.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Design Tweaks Sections (hidden when enableDesignTweaks is off) -->
|
|
||||||
<div id="designSections">
|
|
||||||
|
|
||||||
<!-- Theme & Appearance -->
|
|
||||||
<div class="or-section" 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>
|
|
||||||
|
|
||||||
<!-- 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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div><!-- /designSections -->
|
|
||||||
|
|
||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<div class="or-footer">
|
<div class="or-footer">
|
||||||
<button id="exportBtn" title="Export settings as JSON">Export</button>
|
<button id="exportBtn" title="Export settings as JSON">Export</button>
|
||||||
|
|||||||
106
popup/popup.js
106
popup/popup.js
@@ -1,4 +1,4 @@
|
|||||||
// Outlook Relook — Popup Settings Logic
|
// Outcut — Popup Settings Logic
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
var DEFAULTS = {
|
var DEFAULTS = {
|
||||||
// Primary feature
|
// Primary feature
|
||||||
keyboardMultiSelect: true,
|
keyboardMultiSelect: true,
|
||||||
|
keyboardPreset: 'gmail',
|
||||||
|
|
||||||
// Design tweaks master toggle
|
// Design tweaks master toggle
|
||||||
enableDesignTweaks: false,
|
enableDesignTweaks: false,
|
||||||
@@ -67,44 +68,15 @@
|
|||||||
quickFolderJump: true,
|
quickFolderJump: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
var DENSITY_PRESETS = {
|
// --- Keyboard help text ---
|
||||||
comfortable: {
|
function updateKeyboardHelp(presetName) {
|
||||||
compactTopBar: false,
|
var helpEl = document.getElementById('keyboardHelpText');
|
||||||
compactCommandBar: false,
|
if (!helpEl) return;
|
||||||
compactMessageList: false,
|
var presets = {
|
||||||
compactReadingPane: false,
|
gmail: 'j/k navigate, x/Space select, # delete, e archive, Shift+i/u read/unread, v move, f flag, p pin, Esc deselect',
|
||||||
compactFolderPane: false,
|
outlook: 'Arrows navigate, Space select, Del/Backspace delete, e archive, q read/unread, v move, f flag, p pin, Esc deselect'
|
||||||
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,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
helpEl.textContent = presets[presetName] || presets.gmail;
|
||||||
// --- Design sections visibility ---
|
|
||||||
var designSections = document.getElementById('designSections');
|
|
||||||
|
|
||||||
function updateDesignSectionsVisibility(enabled) {
|
|
||||||
designSections.style.display = enabled ? 'block' : 'none';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Load settings and populate UI ---
|
// --- Load settings and populate UI ---
|
||||||
@@ -122,26 +94,12 @@
|
|||||||
selects[j].value = settings[selects[j].dataset.setting] || '';
|
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
|
// Version
|
||||||
var manifest = chrome.runtime.getManifest();
|
var manifest = chrome.runtime.getManifest();
|
||||||
document.getElementById('version').textContent = 'v' + manifest.version;
|
document.getElementById('version').textContent = 'v' + manifest.version;
|
||||||
|
|
||||||
// Show/hide design sections
|
// Update help text based on current preset
|
||||||
updateDesignSectionsVisibility(settings.enableDesignTweaks);
|
updateKeyboardHelp(settings.keyboardPreset || 'gmail');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,11 +123,6 @@
|
|||||||
for (var c = 0; c < checkboxes.length; c++) {
|
for (var c = 0; c < checkboxes.length; c++) {
|
||||||
checkboxes[c].addEventListener('change', function () {
|
checkboxes[c].addEventListener('change', function () {
|
||||||
saveSetting(this.dataset.setting, this.checked);
|
saveSetting(this.dataset.setting, this.checked);
|
||||||
|
|
||||||
// Toggle design sections visibility when master toggle changes
|
|
||||||
if (this.dataset.setting === 'enableDesignTweaks') {
|
|
||||||
updateDesignSectionsVisibility(this.checked);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,38 +134,9 @@
|
|||||||
var value = this.value;
|
var value = this.value;
|
||||||
saveSetting(settingKey, value);
|
saveSetting(settingKey, value);
|
||||||
|
|
||||||
// Density preset: apply to individual toggles
|
if (settingKey === 'keyboardPreset') {
|
||||||
if (settingKey === 'densityPreset' && DENSITY_PRESETS[value]) {
|
updateKeyboardHelp(value);
|
||||||
var preset = DENSITY_PRESETS[value];
|
|
||||||
chrome.storage.sync.set(preset);
|
|
||||||
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]];
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- 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);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +147,7 @@
|
|||||||
var url = URL.createObjectURL(blob);
|
var url = URL.createObjectURL(blob);
|
||||||
var a = document.createElement('a');
|
var a = document.createElement('a');
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = 'outlook-relook-settings.json';
|
a.download = 'outcut-settings.json';
|
||||||
a.click();
|
a.click();
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user