document.addEventListener('DOMContentLoaded', () => { console.log('App script loaded.'); // Initialize Tiptap editor if element exists const editorElement = document.getElementById('editor'); if (editorElement) { initTiptapEditor(editorElement); } // Add Bootstrap tooltips var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { return new bootstrap.Tooltip(tooltipTriggerEl); }); // Close flash messages after 5 seconds setTimeout(function () { var alerts = document.querySelectorAll('.alert:not(.alert-persistent)'); alerts.forEach(function (alert) { var bsAlert = bootstrap.Alert.getInstance(alert); if (bsAlert) { bsAlert.close(); } else { alert.classList.add('fade'); setTimeout(function () { alert.remove(); }, 150); } }); }, 5000); // Add event listener for subnet scan buttons with HTMX document.body.addEventListener('htmx:afterOnLoad', function (event) { if (event.detail.xhr.status === 200) { showNotification('Subnet scan started successfully', 'success'); } }); // Add markdown preview for documentation fields const docTextareas = document.querySelectorAll('textarea[name="documentation"]'); docTextareas.forEach(function (textarea) { // Only if preview container exists const previewContainer = document.getElementById('markdown-preview'); if (previewContainer && textarea) { textarea.addEventListener('input', function () { // Use the server to render the markdown (safer) fetch('/api/markdown-preview', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ content: textarea.value }) }) .then(response => response.json()) .then(data => { previewContainer.innerHTML = data.html; }); }); } }); // Wait for DOM to be fully loaded document.addEventListener('DOMContentLoaded', function () { // Initialize theme toggle initThemeToggle(); // Initialize clipboard functionality initClipboard(); // Initialize port map tooltips initTooltips(); // Initialize mobile sidebar initMobileSidebar(); // Initialize notifications initNotifications(); }); }); function initTiptapEditor(element) { // Load required Tiptap scripts const editorContainer = document.getElementById('editor-container'); const preview = document.getElementById('markdown-preview'); // Initialize the Tiptap editor const { Editor } = window.tiptap; const { StarterKit } = window.tiptapExtensions; const editor = new Editor({ element: element, extensions: [ StarterKit ], content: element.getAttribute('data-content') || '', onUpdate: ({ editor }) => { // Update preview with current content if (preview) { const markdown = editor.getHTML(); fetch('/api/markdown-preview', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ markdown: markdown }) }) .then(response => response.json()) .then(data => { preview.innerHTML = data.html; }); } } }); // Store editor reference window.editor = editor; // Form submission handling const form = element.closest('form'); if (form) { form.addEventListener('submit', () => { const contentInput = form.querySelector('input[name="content"]'); if (contentInput) { contentInput.value = editor.getHTML(); } }); } } // Copy to clipboard function function copyToClipboard(text) { navigator.clipboard.writeText(text).then(function () { // Success notification showNotification('Copied to clipboard!', 'success'); }, function (err) { // Error notification showNotification('Could not copy text', 'danger'); }); } // Show notification function showNotification(message, type = 'info') { const notificationArea = document.getElementById('notification-area'); if (!notificationArea) return; const notification = document.createElement('div'); notification.className = `alert alert-${type} alert-dismissible fade show`; notification.innerHTML = ` ${message} `; notificationArea.appendChild(notification); // Auto-remove after 5 seconds setTimeout(() => { if (notification.parentNode) { notification.remove(); } }, 5000); } function initThemeToggle() { const themeToggle = document.getElementById('theme-toggle'); if (themeToggle) { themeToggle.addEventListener('click', function () { const currentTheme = document.documentElement.getAttribute('data-bs-theme') || 'light'; const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; document.documentElement.setAttribute('data-bs-theme', newTheme); localStorage.setItem('theme', newTheme); console.log(`Theme switched to ${newTheme} mode`); }); } // Load saved theme or use OS preference const storedTheme = localStorage.getItem('theme'); if (storedTheme) { document.documentElement.setAttribute('data-bs-theme', storedTheme); } else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { document.documentElement.setAttribute('data-bs-theme', 'dark'); localStorage.setItem('theme', 'dark'); } } function initClipboard() { // Add click handlers to any clipboard copy buttons document.querySelectorAll('.copy-btn').forEach(btn => { btn.addEventListener('click', function () { const textToCopy = this.getAttribute('data-clipboard-text'); if (textToCopy) { navigator.clipboard.writeText(textToCopy) .then(() => { showNotification('Copied to clipboard!', 'success'); }) .catch(err => { console.error('Failed to copy: ', err); }); } }); }); } function initTooltips() { const tooltips = document.querySelectorAll('[data-bs-toggle="tooltip"]'); if (tooltips.length > 0) { Array.from(tooltips).map(tooltipNode => new bootstrap.Tooltip(tooltipNode)); } } function initMobileSidebar() { // Sidebar toggle for mobile const sidebarToggler = document.querySelector('.sidebar-toggler'); if (sidebarToggler) { sidebarToggler.addEventListener('click', function () { document.querySelector('.sidebar').classList.toggle('show'); document.querySelector('.main-content').classList.toggle('sidebar-open'); }); } } function initNotifications() { // Add flash messages as notifications const flashMessages = document.querySelectorAll('.alert.flash-message'); flashMessages.forEach(message => { setTimeout(() => { const bsAlert = new bootstrap.Alert(message); bsAlert.close(); }, 5000); }); } // For random port suggestion async function suggestRandomPort(serverId) { try { const response = await fetch(`/api/servers/${serverId}/suggest_port`); if (!response.ok) throw new Error('Failed to get port suggestion'); const data = await response.json(); if (data.port) { // Copy to clipboard navigator.clipboard.writeText(data.port.toString()) .then(() => { showNotification(`Port ${data.port} copied to clipboard!`, 'success'); }) .catch(err => { console.error('Failed to copy: ', err); showNotification(`Suggested free port: ${data.port}`, 'info'); }); } return data.port; } catch (error) { console.error('Error:', error); showNotification('Failed to suggest port', 'danger'); return null; } }