Polish email tasks and window controls
This commit is contained in:
@@ -6,12 +6,15 @@
|
||||
|
||||
import themeModule from './theme.js';
|
||||
import * as Modals from './modalManager.js';
|
||||
import spinnerModule from './spinner.js';
|
||||
|
||||
let toastEl = null;
|
||||
let autoScrollEnabled = true;
|
||||
let hoveredToggleCard = null;
|
||||
let hoveredToggleWindow = null;
|
||||
let hoveredDockChip = null;
|
||||
let _lastPointerClientX = null;
|
||||
let _lastPointerClientY = null;
|
||||
|
||||
// Smooth scroll state
|
||||
let _scrollRafId = null;
|
||||
@@ -74,6 +77,66 @@ function _spaceWindowId(win) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function _windowAtPointer() {
|
||||
if (_lastPointerClientX == null || _lastPointerClientY == null) return null;
|
||||
const x = _lastPointerClientX;
|
||||
const y = _lastPointerClientY;
|
||||
const candidates = [
|
||||
...document.querySelectorAll('.modal:not(.hidden):not(.modal-minimized) .modal-content'),
|
||||
...document.querySelectorAll('.doc-editor-pane'),
|
||||
].filter(el => {
|
||||
if (!document.contains(el)) return false;
|
||||
const r = el.getBoundingClientRect();
|
||||
return x >= r.left && x <= r.right && y >= r.top && y <= r.bottom;
|
||||
});
|
||||
if (!candidates.length) return null;
|
||||
return candidates.reduce((top, el) => {
|
||||
const mz = parseInt(getComputedStyle(el.closest('.modal') || el).zIndex, 10) || 0;
|
||||
const tz = parseInt(getComputedStyle(top.closest('.modal') || top).zIndex, 10) || 0;
|
||||
return mz >= tz ? el : top;
|
||||
});
|
||||
}
|
||||
|
||||
function _containsPointer(el) {
|
||||
if (!el || _lastPointerClientX == null || _lastPointerClientY == null) return false;
|
||||
const r = el.getBoundingClientRect();
|
||||
return _lastPointerClientX >= r.left && _lastPointerClientX <= r.right
|
||||
&& _lastPointerClientY >= r.top && _lastPointerClientY <= r.bottom;
|
||||
}
|
||||
|
||||
function _closeHoveredWindow() {
|
||||
let win = _windowAtPointer();
|
||||
if (!win) {
|
||||
try {
|
||||
const underPointer = document.elementFromPoint(_lastPointerClientX, _lastPointerClientY);
|
||||
win = underPointer?.closest?.('.modal:not(.hidden):not(.modal-minimized) .modal-content, .doc-editor-pane') || null;
|
||||
} catch {}
|
||||
}
|
||||
if (!win) win = hoveredToggleWindow;
|
||||
if (!win || !document.contains(win)) return false;
|
||||
const modalForWin = win.closest?.('.modal[id]');
|
||||
if (modalForWin?.id === 'email-lib-modal') {
|
||||
const closeBtn = document.getElementById('email-lib-close') || modalForWin.querySelector('.close-btn');
|
||||
if (closeBtn) {
|
||||
try { closeBtn.click(); return true; } catch {}
|
||||
}
|
||||
try { modalForWin.remove(); return true; } catch {}
|
||||
}
|
||||
const id = _spaceWindowId(win);
|
||||
if (id && Modals.isRegistered(id)) {
|
||||
Modals.close(id);
|
||||
return true;
|
||||
}
|
||||
const modal = _visibleModalForSpace(win);
|
||||
if (!modal) return false;
|
||||
const closeBtn = modal.querySelector('.close-btn, .modal-close, .modal-close-btn, [data-action="close"]');
|
||||
if (closeBtn) {
|
||||
try { closeBtn.click(); return true; } catch {}
|
||||
}
|
||||
try { modal.classList.add('hidden'); return true; } catch {}
|
||||
return false;
|
||||
}
|
||||
|
||||
function _spaceIsBlocked(e, surface) {
|
||||
const target = _targetEl(e.target);
|
||||
if (!target) return false;
|
||||
@@ -103,6 +166,8 @@ function _initHoverCardSpaceToggle() {
|
||||
if (document._odysseusHoverCardSpaceToggle) return;
|
||||
document._odysseusHoverCardSpaceToggle = true;
|
||||
document.addEventListener('pointerover', (e) => {
|
||||
_lastPointerClientX = e.clientX;
|
||||
_lastPointerClientY = e.clientY;
|
||||
const chip = e.target?.closest?.('.minimized-dock-chip[data-modal-id]');
|
||||
if (chip) hoveredDockChip = chip;
|
||||
const card = e.target?.closest?.(SPACE_CARD_SELECTOR);
|
||||
@@ -110,6 +175,10 @@ function _initHoverCardSpaceToggle() {
|
||||
const win = e.target?.closest?.('.modal:not(.hidden):not(.modal-minimized) .modal-content, .doc-editor-pane');
|
||||
if (win) hoveredToggleWindow = win;
|
||||
}, true);
|
||||
document.addEventListener('pointermove', (e) => {
|
||||
_lastPointerClientX = e.clientX;
|
||||
_lastPointerClientY = e.clientY;
|
||||
}, true);
|
||||
document.addEventListener('pointerout', (e) => {
|
||||
const next = e.relatedTarget;
|
||||
if (hoveredDockChip && (!next || !hoveredDockChip.contains(next))) hoveredDockChip = null;
|
||||
@@ -252,6 +321,12 @@ export function showToast(msg, durationOrOpts) {
|
||||
icon.className = 'toast-checkmark';
|
||||
icon.innerHTML = '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="20 6 9 17 4 12"/></svg>';
|
||||
toastEl.appendChild(icon);
|
||||
} else if (leadingIcon === 'spinner') {
|
||||
const wp = spinnerModule.createWhirlpool(14);
|
||||
const icon = wp.element;
|
||||
icon.classList.add('toast-whirlpool');
|
||||
icon.style.cssText = 'width:14px;height:14px;margin:0 8px 0 0;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;';
|
||||
toastEl.appendChild(icon);
|
||||
}
|
||||
textSpan.textContent = msg;
|
||||
toastEl.appendChild(textSpan);
|
||||
@@ -1114,8 +1189,6 @@ if (!window._odyEscExpandGuard) {
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key !== 'Escape' || e.defaultPrevented) return;
|
||||
const t = e.target;
|
||||
if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable)) return;
|
||||
|
||||
// Find the single thing to close, in priority order. The first hit wins.
|
||||
// Important: if a thinking block is open we MUST handle it ourselves and
|
||||
@@ -1123,6 +1196,12 @@ if (!window._odyEscExpandGuard) {
|
||||
// (the live-stream chat rebuilds thinking DOM mid-stream so the header
|
||||
// can briefly be absent). Toggling the `expanded` class directly is the
|
||||
// fallback so ESC never bypasses the thinking block to hit a modal.
|
||||
if (_closeHoveredWindow()) {
|
||||
e.stopImmediatePropagation(); e.preventDefault();
|
||||
return;
|
||||
}
|
||||
const t = e.target;
|
||||
if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable)) return;
|
||||
const expanded = document.querySelector('.doclib-card-expanded');
|
||||
const think = document.querySelector('.thinking-content.expanded');
|
||||
if (expanded) {
|
||||
|
||||
Reference in New Issue
Block a user