Fix session close button with non-blocking confirmation modal

Replace blocking confirm() dialog with custom non-blocking modal to prevent
browser warning issues when users have "don't show warnings" enabled.

Changes:
- Add showConfirmModal() method with Promise-based async modal
- Update closeSession() to use non-blocking modal
- Update deleteSession() to use non-blocking modal
- Add complete CSS styling for confirmation modal
- Support keyboard (Escape key) and click-outside to close
- Responsive design for mobile devices
- Dark theme matching existing UI

Fixes issue where close button completely stopped working after browser
blocked confirm() dialog and user selected "don't show warnings".

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
uroma
2026-01-22 12:35:52 +00:00
Unverified
parent 455ab550c1
commit fc76581337

View File

@@ -170,7 +170,7 @@ class SessionTabs {
}
/**
* Close a session (with confirmation)
* Close a session (with non-blocking confirmation modal)
*/
async closeSession(sessionId) {
const session = this.sessions.find(s => s.id === sessionId);
@@ -178,8 +178,14 @@ class SessionTabs {
const sessionName = this.getSessionName(session);
// Confirm before closing
if (!confirm(`Close session "${sessionName}"?`)) {
// Show non-blocking confirmation modal
const confirmed = await this.showConfirmModal(
'Close Session',
`Are you sure you want to close "${escapeHtml(sessionName)}"?`,
'Close Session'
);
if (!confirmed) {
return;
}
@@ -287,6 +293,93 @@ class SessionTabs {
document.getElementById('session-context-menu')?.remove();
}
/**
* Show a non-blocking confirmation modal
* @param {string} title - Modal title
* @param {string} message - Confirmation message
* @param {string} confirmText - Text for confirm button (default: "Confirm")
* @returns {Promise<boolean>} - True if confirmed, false otherwise
*/
showConfirmModal(title, message, confirmText = 'Confirm') {
return new Promise((resolve) => {
// Remove existing modal if present
const existingModal = document.getElementById('confirm-modal-overlay');
if (existingModal) existingModal.remove();
// Create overlay
const overlay = document.createElement('div');
overlay.id = 'confirm-modal-overlay';
overlay.className = 'confirm-modal-overlay';
// Create modal
const modal = document.createElement('div');
modal.className = 'confirm-modal';
modal.innerHTML = `
<div class="confirm-modal-header">
<h3 class="confirm-modal-title">${escapeHtml(title)}</h3>
</div>
<div class="confirm-modal-body">
<p class="confirm-modal-message">${message}</p>
</div>
<div class="confirm-modal-footer">
<button class="btn-confirm-cancel" id="confirm-modal-cancel">Cancel</button>
<button class="btn-confirm-ok" id="confirm-modal-ok">${escapeHtml(confirmText)}</button>
</div>
`;
overlay.appendChild(modal);
document.body.appendChild(overlay);
// Prevent body scroll
document.body.style.overflow = 'hidden';
// Trigger animation
setTimeout(() => {
overlay.classList.add('visible');
modal.classList.add('visible');
}, 10);
// Handle confirm button
document.getElementById('confirm-modal-ok').addEventListener('click', () => {
closeModal(true);
});
// Handle cancel button
document.getElementById('confirm-modal-cancel').addEventListener('click', () => {
closeModal(false);
});
// Close on overlay click
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
closeModal(false);
}
});
// Close on Escape key
const escapeHandler = (e) => {
if (e.key === 'Escape') {
closeModal(false);
document.removeEventListener('keydown', escapeHandler);
}
};
document.addEventListener('keydown', escapeHandler);
// Function to close modal
function closeModal(result) {
overlay.classList.remove('visible');
modal.classList.remove('visible');
setTimeout(() => {
document.removeEventListener('keydown', escapeHandler);
overlay.remove();
document.body.style.overflow = '';
resolve(result);
}, 200);
}
});
}
/**
* Rename a session
*/
@@ -362,7 +455,13 @@ class SessionTabs {
async deleteSession(session) {
const sessionName = this.getSessionName(session);
if (!confirm(`Permanently delete "${sessionName}"? This cannot be undone.`)) {
const confirmed = await this.showConfirmModal(
'Delete Session',
`Are you sure you want to permanently delete "${escapeHtml(sessionName)}"? This action cannot be undone.`,
'Delete'
);
if (!confirmed) {
return;
}
@@ -424,4 +523,145 @@ if (document.readyState === 'loading') {
window.sessionTabs.initialize();
}
// ============================================================
// Add CSS Styles
// ============================================================
(function() {
const style = document.createElement('style');
style.textContent = `
/* Confirm Modal Overlay */
.confirm-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(4px);
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
opacity: 0;
transition: opacity 0.2s ease;
}
.confirm-modal-overlay.visible {
opacity: 1;
}
/* Confirm Modal */
.confirm-modal {
background: #1a1a1a;
border: 1px solid #333;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
width: 100%;
max-width: 400px;
overflow: hidden;
transform: scale(0.95);
transition: transform 0.2s ease;
}
.confirm-modal.visible {
transform: scale(1);
}
/* Header */
.confirm-modal-header {
padding: 20px 20px 12px 20px;
border-bottom: 1px solid #333;
}
.confirm-modal-title {
font-size: 18px;
font-weight: 600;
color: #e0e0e0;
margin: 0;
}
/* Body */
.confirm-modal-body {
padding: 20px;
}
.confirm-modal-message {
font-size: 14px;
color: #b0b0b0;
margin: 0;
line-height: 1.5;
}
/* Footer */
.confirm-modal-footer {
padding: 12px 20px 20px 20px;
display: flex;
justify-content: flex-end;
gap: 12px;
}
/* Buttons */
.btn-confirm-cancel,
.btn-confirm-ok {
padding: 10px 20px;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
border: none;
}
.btn-confirm-cancel {
background: transparent;
border: 1px solid #333;
color: #e0e0e0;
}
.btn-confirm-cancel:hover {
background: #252525;
border-color: #444;
}
.btn-confirm-ok {
background: linear-gradient(135deg, #4a9eff 0%, #a78bfa 100%);
color: white;
}
.btn-confirm-ok:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(74, 158, 255, 0.4);
}
.btn-confirm-ok:active {
transform: translateY(0);
}
/* Responsive */
@media (max-width: 640px) {
.confirm-modal {
max-width: 90%;
}
.confirm-modal-header,
.confirm-modal-body,
.confirm-modal-footer {
padding: 16px;
}
.confirm-modal-footer {
flex-direction: column-reverse;
}
.btn-confirm-cancel,
.btn-confirm-ok {
width: 100%;
}
}
`;
document.head.appendChild(style);
})();
console.log('[SessionTabs] Module loaded');