Files
markdown_editor/static/js/confirmation.js
Mahmoud-Emad 3961628b3d feat: Implement collection deletion and loading spinners
- Add API endpoint and handler to delete collections
- Introduce LoadingSpinner component for async operations
- Show loading spinners during file loading and preview rendering
- Enhance modal accessibility by removing aria-hidden attribute
- Refactor delete functionality to distinguish between collections and files/folders
- Remove unused collection definitions from config
2025-10-27 11:32:20 +03:00

181 lines
6.6 KiB
JavaScript

/**
* Unified Modal Manager
* Handles showing and hiding a Bootstrap modal for confirmations and prompts.
* Uses a single reusable modal element to prevent double-opening issues.
*/
class ModalManager {
constructor(modalId) {
this.modalElement = document.getElementById(modalId);
if (!this.modalElement) {
console.error(`Modal element with id "${modalId}" not found`);
return;
}
this.modal = new bootstrap.Modal(this.modalElement, {
backdrop: 'static',
keyboard: true
});
this.messageElement = this.modalElement.querySelector('#confirmationMessage');
this.inputElement = this.modalElement.querySelector('#confirmationInput');
this.confirmButton = this.modalElement.querySelector('#confirmButton');
this.cancelButton = this.modalElement.querySelector('[data-bs-dismiss="modal"]');
this.titleElement = this.modalElement.querySelector('.modal-title');
this.currentResolver = null;
this.isShowing = false;
}
/**
* Show a confirmation dialog
* @param {string} message - The message to display
* @param {string} title - The dialog title
* @param {boolean} isDangerous - Whether this is a dangerous action (shows red button)
* @returns {Promise<boolean>} - Resolves to true if confirmed, false/null if cancelled
*/
confirm(message, title = 'Confirmation', isDangerous = false) {
return new Promise((resolve) => {
// Prevent double-opening
if (this.isShowing) {
console.warn('Modal is already showing, ignoring duplicate request');
resolve(null);
return;
}
this.isShowing = true;
this.currentResolver = resolve;
this.titleElement.textContent = title;
this.messageElement.textContent = message;
this.inputElement.style.display = 'none';
// Update button styling based on danger level
if (isDangerous) {
this.confirmButton.className = 'btn-flat btn-flat-danger';
this.confirmButton.innerHTML = '<i class="bi bi-trash"></i> Delete';
} else {
this.confirmButton.className = 'btn-flat btn-flat-primary';
this.confirmButton.innerHTML = '<i class="bi bi-check-circle"></i> OK';
}
// Set up event handlers
this.confirmButton.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
this._handleConfirm(false);
};
// Handle modal hidden event for cleanup
this.modalElement.addEventListener('hidden.bs.modal', () => {
if (this.currentResolver) {
this._handleCancel();
}
}, { once: true });
// Remove aria-hidden before showing to prevent accessibility warning
this.modalElement.removeAttribute('aria-hidden');
this.modal.show();
// Focus confirm button after modal is shown
this.modalElement.addEventListener('shown.bs.modal', () => {
this.confirmButton.focus();
}, { once: true });
});
}
/**
* Show a prompt dialog (input dialog)
* @param {string} message - The message/label to display
* @param {string} defaultValue - The default input value
* @param {string} title - The dialog title
* @returns {Promise<string|null>} - Resolves to input value if confirmed, null if cancelled
*/
prompt(message, defaultValue = '', title = 'Input') {
return new Promise((resolve) => {
// Prevent double-opening
if (this.isShowing) {
console.warn('Modal is already showing, ignoring duplicate request');
resolve(null);
return;
}
this.isShowing = true;
this.currentResolver = resolve;
this.titleElement.textContent = title;
this.messageElement.textContent = message;
this.inputElement.style.display = 'block';
this.inputElement.value = defaultValue;
// Reset button to primary style for prompts
this.confirmButton.className = 'btn-flat btn-flat-primary';
this.confirmButton.innerHTML = '<i class="bi bi-check-circle"></i> OK';
// Set up event handlers
this.confirmButton.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
this._handleConfirm(true);
};
// Handle Enter key in input
this.inputElement.onkeydown = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
this._handleConfirm(true);
}
};
// Handle modal hidden event for cleanup
this.modalElement.addEventListener('hidden.bs.modal', () => {
if (this.currentResolver) {
this._handleCancel();
}
}, { once: true });
// Remove aria-hidden before showing to prevent accessibility warning
this.modalElement.removeAttribute('aria-hidden');
this.modal.show();
// Focus and select input after modal is shown
this.modalElement.addEventListener('shown.bs.modal', () => {
this.inputElement.focus();
this.inputElement.select();
}, { once: true });
});
}
_handleConfirm(isPrompt) {
if (this.currentResolver) {
const value = isPrompt ? this.inputElement.value.trim() : true;
const resolver = this.currentResolver;
this._cleanup();
resolver(value);
}
}
_handleCancel() {
if (this.currentResolver) {
const resolver = this.currentResolver;
this._cleanup();
resolver(null);
}
}
_cleanup() {
this.confirmButton.onclick = null;
this.inputElement.onkeydown = null;
this.currentResolver = null;
this.isShowing = false;
this.modal.hide();
// Restore aria-hidden after modal is hidden
this.modalElement.addEventListener('hidden.bs.modal', () => {
this.modalElement.setAttribute('aria-hidden', 'true');
}, { once: true });
}
}
// Make it globally available
window.ConfirmationManager = new ModalManager('confirmationModal');
window.ModalManager = window.ConfirmationManager; // Alias for clarity