mirror of
https://github.com/Significant-Gravitas/AutoModerate.git
synced 2026-11-09 17:54:01 -10:00
317 lines
12 KiB
JavaScript
317 lines
12 KiB
JavaScript
// WebSocket connection for authenticated users
|
|
let reconnectionAttempts = 0;
|
|
const MAX_RECONNECTION_DELAY = 30000; // 30 seconds max delay
|
|
|
|
function initializeWebSocket() {
|
|
if (typeof io === 'undefined') {
|
|
console.warn('Socket.IO not loaded');
|
|
return;
|
|
}
|
|
|
|
// Don't create a new connection if one already exists
|
|
if (window.socket && window.socket.connected) {
|
|
console.log('WebSocket already connected');
|
|
return window.socket;
|
|
}
|
|
|
|
// Configure Socket.IO with automatic reconnection
|
|
const socket = io({
|
|
reconnection: true,
|
|
reconnectionAttempts: Infinity,
|
|
reconnectionDelay: 1000,
|
|
reconnectionDelayMax: MAX_RECONNECTION_DELAY,
|
|
timeout: 20000,
|
|
transports: ['websocket', 'polling'] // Try websocket first, fallback to polling
|
|
});
|
|
|
|
socket.on('connect', function() {
|
|
console.log('Global WebSocket connected');
|
|
reconnectionAttempts = 0; // Reset reconnection counter on successful connect
|
|
|
|
const statusDiv = document.getElementById('connection-status');
|
|
if (statusDiv) {
|
|
statusDiv.innerHTML = '<span class="badge bg-success" style="font-size: 0.75rem; padding: 0.25rem 0.5rem;"><i class="fas fa-circle"></i> <span class="connection-text">Live</span></span>';
|
|
}
|
|
|
|
// Trigger a custom event that page-specific scripts can listen to
|
|
window.dispatchEvent(new CustomEvent('websocket-reconnected'));
|
|
});
|
|
|
|
socket.on('disconnect', function(reason) {
|
|
console.log('Global WebSocket disconnected. Reason:', reason);
|
|
const statusDiv = document.getElementById('connection-status');
|
|
if (statusDiv) {
|
|
statusDiv.innerHTML = '<span class="badge bg-danger" style="font-size: 0.75rem; padding: 0.25rem 0.5rem;"><i class="fas fa-circle"></i> <span class="connection-text">Disconnected</span></span>';
|
|
}
|
|
|
|
// If disconnect was due to server-side (io server disconnect or io client disconnect)
|
|
// the client will automatically attempt to reconnect
|
|
if (reason === 'io server disconnect') {
|
|
// Server forcefully disconnected, manually reconnect
|
|
console.log('Server disconnected socket, attempting manual reconnect...');
|
|
socket.connect();
|
|
}
|
|
});
|
|
|
|
socket.on('reconnect_attempt', function(attemptNumber) {
|
|
reconnectionAttempts = attemptNumber;
|
|
console.log(`WebSocket reconnection attempt ${attemptNumber}`);
|
|
|
|
const statusDiv = document.getElementById('connection-status');
|
|
if (statusDiv) {
|
|
statusDiv.innerHTML = '<span class="badge bg-warning" style="font-size: 0.75rem; padding: 0.25rem 0.5rem;"><i class="fas fa-circle"></i> <span class="connection-text">Reconnecting...</span></span>';
|
|
}
|
|
});
|
|
|
|
socket.on('reconnect', function(attemptNumber) {
|
|
console.log(`WebSocket reconnected after ${attemptNumber} attempts`);
|
|
reconnectionAttempts = 0;
|
|
|
|
const statusDiv = document.getElementById('connection-status');
|
|
if (statusDiv) {
|
|
statusDiv.innerHTML = '<span class="badge bg-success" style="font-size: 0.75rem; padding: 0.25rem 0.5rem;"><i class="fas fa-circle"></i> <span class="connection-text">Live</span></span>';
|
|
}
|
|
});
|
|
|
|
socket.on('reconnect_error', function(error) {
|
|
console.error('WebSocket reconnection error:', error);
|
|
const statusDiv = document.getElementById('connection-status');
|
|
if (statusDiv) {
|
|
const delay = Math.min(1000 * Math.pow(2, reconnectionAttempts), MAX_RECONNECTION_DELAY);
|
|
const delaySec = Math.round(delay / 1000);
|
|
statusDiv.innerHTML = `<span class="badge bg-warning" style="font-size: 0.75rem; padding: 0.25rem 0.5rem;"><i class="fas fa-circle"></i> <span class="connection-text">Retry in ${delaySec}s</span></span>`;
|
|
}
|
|
});
|
|
|
|
socket.on('reconnect_failed', function() {
|
|
console.error('WebSocket reconnection failed after all attempts');
|
|
const statusDiv = document.getElementById('connection-status');
|
|
if (statusDiv) {
|
|
statusDiv.innerHTML = '<span class="badge bg-danger" style="font-size: 0.75rem; padding: 0.25rem 0.5rem;"><i class="fas fa-circle"></i> <span class="connection-text">Failed</span></span>';
|
|
}
|
|
});
|
|
|
|
socket.on('connect_error', function(error) {
|
|
console.error('WebSocket connection error:', error);
|
|
const statusDiv = document.getElementById('connection-status');
|
|
if (statusDiv) {
|
|
statusDiv.innerHTML = '<span class="badge bg-warning" style="font-size: 0.75rem; padding: 0.25rem 0.5rem;"><i class="fas fa-circle"></i> <span class="connection-text">Error</span></span>';
|
|
}
|
|
});
|
|
|
|
socket.on('error', function(error) {
|
|
console.error('WebSocket error:', error);
|
|
// Don't disconnect on errors, let Socket.IO handle reconnection
|
|
});
|
|
|
|
socket.on('moderation_update', function(data) {
|
|
// Handle real-time moderation updates
|
|
console.log('Global moderation update:', data);
|
|
// Page-specific handlers will handle the actual updates
|
|
});
|
|
|
|
// Make socket available globally for other scripts
|
|
window.socket = socket;
|
|
return socket;
|
|
}
|
|
|
|
// Dark mode functionality
|
|
function initializeDarkMode() {
|
|
const darkModeToggle = document.getElementById('dark-mode-toggle');
|
|
const darkModeToggleMobile = document.getElementById('dark-mode-toggle-mobile');
|
|
|
|
// Get saved theme preference or default to 'light'
|
|
const savedTheme = localStorage.getItem('theme') || 'light';
|
|
|
|
// Update icons to match current theme
|
|
if (darkModeToggle) updateToggleIcon(savedTheme);
|
|
if (darkModeToggleMobile) updateMobileToggleIcon(savedTheme);
|
|
|
|
// Add click event listener for desktop toggle
|
|
if (darkModeToggle) {
|
|
darkModeToggle.addEventListener('click', function() {
|
|
toggleTheme();
|
|
});
|
|
}
|
|
|
|
// Add click event listener for mobile toggle
|
|
if (darkModeToggleMobile) {
|
|
darkModeToggleMobile.addEventListener('click', function() {
|
|
toggleTheme();
|
|
});
|
|
}
|
|
|
|
function toggleTheme() {
|
|
const currentTheme = document.documentElement.getAttribute('data-bs-theme');
|
|
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
|
|
|
// Apply new theme
|
|
document.documentElement.setAttribute('data-bs-theme', newTheme);
|
|
|
|
// Save preference
|
|
localStorage.setItem('theme', newTheme);
|
|
|
|
// Update both icons
|
|
if (darkModeToggle) updateToggleIcon(newTheme);
|
|
if (darkModeToggleMobile) updateMobileToggleIcon(newTheme);
|
|
}
|
|
}
|
|
|
|
function updateToggleIcon(theme) {
|
|
const icon = document.querySelector('#dark-mode-toggle i');
|
|
if (!icon) return;
|
|
|
|
if (theme === 'dark') {
|
|
icon.className = 'fas fa-sun';
|
|
document.getElementById('dark-mode-toggle').title = 'Switch to light mode';
|
|
} else {
|
|
icon.className = 'fas fa-moon';
|
|
document.getElementById('dark-mode-toggle').title = 'Switch to dark mode';
|
|
}
|
|
}
|
|
|
|
function updateMobileToggleIcon(theme) {
|
|
const icon = document.querySelector('#dark-mode-toggle-mobile i');
|
|
if (!icon) return;
|
|
|
|
if (theme === 'dark') {
|
|
icon.className = 'fas fa-sun';
|
|
document.getElementById('dark-mode-toggle-mobile').title = 'Switch to light mode';
|
|
} else {
|
|
icon.className = 'fas fa-moon';
|
|
document.getElementById('dark-mode-toggle-mobile').title = 'Switch to dark mode';
|
|
}
|
|
}
|
|
|
|
// Sidebar collapse functionality
|
|
function initializeSidebarToggle() {
|
|
const sidebarToggle = document.getElementById('sidebar-toggle');
|
|
const sidebar = document.getElementById('sidebar');
|
|
const mobileSidebar = document.getElementById('mobile-sidebar');
|
|
const mainContent = document.getElementById('main-content');
|
|
const sidebarOverlay = document.getElementById('sidebar-overlay');
|
|
|
|
if (!sidebarToggle || !mainContent) return;
|
|
|
|
// Get saved sidebar state or default to 'expanded'
|
|
const savedSidebarState = localStorage.getItem('sidebarState') || 'expanded';
|
|
|
|
// Apply saved state on desktop only
|
|
if (window.innerWidth >= 768) {
|
|
if (savedSidebarState === 'collapsed') {
|
|
sidebar.classList.add('sidebar-collapsed');
|
|
mainContent.classList.add('content-expanded');
|
|
updateSidebarToggleIcon(true);
|
|
}
|
|
}
|
|
|
|
// Add click event listener
|
|
sidebarToggle.addEventListener('click', function() {
|
|
const isDesktop = window.innerWidth >= 768;
|
|
|
|
if (isDesktop) {
|
|
// Desktop behavior: hide/show sidebar
|
|
const isCollapsed = sidebar.classList.contains('sidebar-collapsed');
|
|
|
|
if (isCollapsed) {
|
|
sidebar.classList.remove('sidebar-collapsed');
|
|
mainContent.classList.remove('content-expanded');
|
|
localStorage.setItem('sidebarState', 'expanded');
|
|
updateSidebarToggleIcon(false);
|
|
} else {
|
|
sidebar.classList.add('sidebar-collapsed');
|
|
mainContent.classList.add('content-expanded');
|
|
localStorage.setItem('sidebarState', 'collapsed');
|
|
updateSidebarToggleIcon(true);
|
|
}
|
|
} else {
|
|
// Mobile behavior: toggle overlay sidebar
|
|
if (mobileSidebar) {
|
|
const isVisible = mobileSidebar.classList.contains('show');
|
|
console.log('Mobile sidebar toggle - Current state:', isVisible ? 'visible' : 'hidden');
|
|
|
|
if (isVisible) {
|
|
mobileSidebar.classList.remove('show');
|
|
if (sidebarOverlay) sidebarOverlay.classList.remove('show');
|
|
console.log('Hiding mobile sidebar');
|
|
} else {
|
|
mobileSidebar.classList.add('show');
|
|
if (sidebarOverlay) sidebarOverlay.classList.add('show');
|
|
console.log('Showing mobile sidebar');
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Close sidebar when clicking overlay (mobile)
|
|
if (sidebarOverlay) {
|
|
sidebarOverlay.addEventListener('click', function() {
|
|
if (mobileSidebar) {
|
|
mobileSidebar.classList.remove('show');
|
|
sidebarOverlay.classList.remove('show');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Handle window resize
|
|
window.addEventListener('resize', function() {
|
|
const isDesktop = window.innerWidth >= 768;
|
|
|
|
if (isDesktop) {
|
|
// Remove mobile classes
|
|
if (mobileSidebar) mobileSidebar.classList.remove('show');
|
|
if (sidebarOverlay) sidebarOverlay.classList.remove('show');
|
|
|
|
// Apply saved desktop state
|
|
if (sidebar && localStorage.getItem('sidebarState') === 'collapsed') {
|
|
sidebar.classList.add('sidebar-collapsed');
|
|
mainContent.classList.add('content-expanded');
|
|
updateSidebarToggleIcon(true);
|
|
} else if (sidebar) {
|
|
sidebar.classList.remove('sidebar-collapsed');
|
|
mainContent.classList.remove('content-expanded');
|
|
updateSidebarToggleIcon(false);
|
|
}
|
|
} else {
|
|
// Mobile: remove desktop classes
|
|
if (sidebar) {
|
|
sidebar.classList.remove('sidebar-collapsed');
|
|
mainContent.classList.remove('content-expanded');
|
|
}
|
|
updateSidebarToggleIcon(false);
|
|
}
|
|
});
|
|
}
|
|
|
|
function updateSidebarToggleIcon(isCollapsed) {
|
|
const icon = document.querySelector('#sidebar-toggle i');
|
|
if (!icon) return;
|
|
|
|
if (isCollapsed) {
|
|
icon.className = 'fas fa-chevron-right';
|
|
document.getElementById('sidebar-toggle').title = 'Show sidebar';
|
|
} else {
|
|
icon.className = 'fas fa-chevron-left';
|
|
document.getElementById('sidebar-toggle').title = 'Hide sidebar';
|
|
}
|
|
}
|
|
|
|
// Initialize WebSocket when DOM is loaded
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Initialize dark mode functionality
|
|
initializeDarkMode();
|
|
|
|
// Initialize sidebar toggle functionality
|
|
initializeSidebarToggle();
|
|
|
|
// Check if user is authenticated (presence of connection status element)
|
|
if (document.getElementById('connection-status')) {
|
|
// Small delay to allow page-specific scripts to load first
|
|
setTimeout(function() {
|
|
if (!window.socket || !window.socket.connected) {
|
|
initializeWebSocket();
|
|
}
|
|
}, 100);
|
|
}
|
|
});
|