Polish email tasks and window controls

This commit is contained in:
pewdiepie-archdaemon
2026-06-01 20:56:11 +09:00
parent 5c390d6b3e
commit 5ed9b74cd0
14 changed files with 919 additions and 203 deletions

View File

@@ -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) {