Spaces:
Running
Running
| // Main application JavaScript | |
| class SingularityPortal { | |
| constructor() { | |
| this.apiBase = '/api'; | |
| this.currentUser = null; | |
| this.init(); | |
| } | |
| init() { | |
| this.setupEventListeners(); | |
| this.checkAuthStatus(); | |
| this.loadInitialData(); | |
| } | |
| setupEventListeners() { | |
| // Global event listeners | |
| document.addEventListener('keydown', this.handleKeyboardShortcuts.bind(this)); | |
| } | |
| handleKeyboardShortcuts(event) { | |
| if (event.ctrlKey && event.key === 'k') { | |
| event.preventDefault(); | |
| this.toggleAIAssistant(); | |
| } | |
| } | |
| async checkAuthStatus() { | |
| try { | |
| const response = await fetch(`${this.apiBase}/auth/status`); | |
| if (response.ok) { | |
| this.currentUser = await response.json(); | |
| this.updateUIForAuth(); | |
| } | |
| } catch (error) { | |
| console.log('Auth check failed:', error); | |
| } | |
| } | |
| updateUIForAuth() { | |
| const authElements = document.querySelectorAll('[data-auth]'); | |
| authElements.forEach(element => { | |
| const authType = element.getAttribute('data-auth'); | |
| if (authType === 'required' && !this.currentUser) { | |
| element.style.display = 'none'; | |
| } else if (authType === 'admin' && (!this.currentUser || !this.currentUser.isAdmin)) { | |
| element.style.display = 'none'; | |
| } | |
| }); | |
| } | |
| async loadInitialData() { | |
| await this.loadLatestUpdates(); | |
| } | |
| async loadLatestUpdates() { | |
| try { | |
| const response = await fetch(`${this.apiBase}/content/latest`); | |
| if (response.ok) { | |
| const updates = await response.json(); | |
| this.renderLatestUpdates(updates); | |
| } | |
| } catch (error) { | |
| console.log('Failed to load latest updates:', error); | |
| } | |
| } | |
| renderLatestUpdates(updates) { | |
| const container = document.getElementById('latest-updates'); | |
| if (!container) return; | |
| container.innerHTML = updates.map(update => ` | |
| <div class="cyber-timeline-item"> | |
| <div class="flex justify-between items-start mb-2"> | |
| <h4 class="font-cyber text-cyber-primary">${update.title}</h4> | |
| <span class="text-sm text-gray-400">${this.formatDate(update.date)}</span> | |
| </div> | |
| <p class="text-gray-300">${update.description}</p> | |
| <a href="${update.link}" class="cyber-link text-sm mt-2">Подробнее →</a> | |
| </div> | |
| `).join(''); | |
| } | |
| formatDate(dateString) { | |
| const date = new Date(dateString); | |
| return date.toLocaleDateString('ru-RU'); | |
| } | |
| toggleAIAssistant() { | |
| const assistant = document.querySelector('ai-assistant'); | |
| if (assistant) { | |
| assistant.toggleChat(); | |
| } | |
| } | |
| } | |
| // Initialize the portal | |
| let portal; | |
| document.addEventListener('DOMContentLoaded', function() { | |
| portal = new SingularityPortal(); | |
| feather.replace(); | |
| }); | |
| // Utility functions | |
| const Utils = { | |
| debounce(func, wait) { | |
| let timeout; | |
| return function executedFunction(...args) { | |
| const later = () => { | |
| clearTimeout(timeout); | |
| func(...args); | |
| }; | |
| clearTimeout(timeout); | |
| timeout = setTimeout(later, wait); | |
| }; | |
| }, | |
| generateId() { | |
| return Date.now().toString(36) + Math.random().toString(36).substr(2); | |
| }, | |
| formatFileSize(bytes) { | |
| const sizes = ['Bytes', 'KB', 'MB', 'GB']; | |
| if (bytes === 0) return '0 Bytes'; | |
| const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); | |
| return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i]; | |
| } | |
| }; | |
| // API service for backend communication | |
| class APIService { | |
| constructor() { | |
| this.baseURL = '/api'; | |
| } | |
| async request(endpoint, options = {}) { | |
| const url = `${this.baseURL}${endpoint}`; | |
| const config = { | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| ...options | |
| }; | |
| try { | |
| const response = await fetch(url, config); | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! status: ${response.status}`); | |
| } | |
| return await response.json(); | |
| } catch (error) { | |
| console.error('API request failed:', error); | |
| throw error; | |
| } | |
| } | |
| // Content management | |
| async getContent(section, filters = {}) { | |
| const query = new URLSearchParams(filters).toString(); | |
| return this.request(`/content/${section}?${query}`); | |
| } | |
| async createContent(contentData) { | |
| return this.request('/content', { | |
| method: 'POST', | |
| body: JSON.stringify(contentData) | |
| }); | |
| } | |
| async updateContent(id, contentData) { | |
| return this.request(`/content/${id}`, { | |
| method: 'PUT', | |
| body: JSON.stringify(contentData) | |
| }); | |
| } | |
| // AI Assistant communication | |
| async sendMessageToAI(message, context = {}) { | |
| return this.request('/ai/chat', { | |
| method: 'POST', | |
| body: JSON.stringify({ message, context }) | |
| }); | |
| } | |
| // User management | |
| async login(credentials) { | |
| return this.request('/auth/login', { | |
| method: 'POST', | |
| body: JSON.stringify(credentials) | |
| }); | |
| } | |
| async register(userData) { | |
| return this.request('/auth/register', { | |
| method: 'POST', | |
| body: JSON.stringify(userData) | |
| }); | |
| } | |
| } | |
| // Initialize API service | |
| const apiService = new APIService(); | |
| // Export for use in components | |
| window.SingularityPortal = SingularityPortal; | |
| window.APIService = APIService; | |
| window.Utils = Utils; |