// Collaboration System class CollaborationSystem { constructor(options = {}) { this.onCommentAdded = options.onCommentAdded || (() => {}); this.onCommentResolved = options.onCommentResolved || (() => {}); this.onThreadCreated = options.onThreadCreated || (() => {}); this.socket = null; this.currentUser = options.currentUser; } initialize(socketUrl) { this.socket = new WebSocket(socketUrl); this.setupSocketHandlers(); } setupSocketHandlers() { if (!this.socket) return; this.socket.addEventListener('open', () => { console.log('Collaboration system connected'); }); this.socket.addEventListener('message', (event) => { try { const data = JSON.parse(event.data); this.handleEvent(data); } catch (error) { console.error('Failed to parse collaboration event:', error); } }); this.socket.addEventListener('close', () => { console.log('Collaboration system disconnected'); // Attempt to reconnect after delay setTimeout(() => this.reconnect(), 5000); }); this.socket.addEventListener('error', (error) => { console.error('Collaboration system error:', error); }); } handleEvent(event) { switch (event.type) { case 'comment_added': this.onCommentAdded(event.data); break; case 'comment_resolved': this.onCommentResolved(event.data); break; case 'thread_created': this.onThreadCreated(event.data); break; default: console.warn('Unknown collaboration event:', event.type); } } async createCommentThread(changeId, anchor, initialComment) { try { const response = await fetch('/vcs/comments/threads/create/', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': this.getCsrfToken() }, body: JSON.stringify({ change_id: changeId, anchor: anchor, initial_comment: initialComment }) }); if (!response.ok) { throw new Error('Failed to create comment thread'); } const thread = await response.json(); // Notify other users through WebSocket this.broadcastEvent({ type: 'thread_created', data: { thread_id: thread.id, change_id: changeId, anchor: anchor, author: this.currentUser, timestamp: new Date().toISOString() } }); return thread; } catch (error) { console.error('Error creating comment thread:', error); throw error; } } async addComment(threadId, content, parentId = null) { try { const response = await fetch('/vcs/comments/create/', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': this.getCsrfToken() }, body: JSON.stringify({ thread_id: threadId, content: content, parent_id: parentId }) }); if (!response.ok) { throw new Error('Failed to add comment'); } const comment = await response.json(); // Notify other users through WebSocket this.broadcastEvent({ type: 'comment_added', data: { comment_id: comment.id, thread_id: threadId, parent_id: parentId, author: this.currentUser, content: content, timestamp: new Date().toISOString() } }); return comment; } catch (error) { console.error('Error adding comment:', error); throw error; } } async resolveThread(threadId) { try { const response = await fetch(`/vcs/comments/threads/${threadId}/resolve/`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': this.getCsrfToken() } }); if (!response.ok) { throw new Error('Failed to resolve thread'); } const result = await response.json(); // Notify other users through WebSocket this.broadcastEvent({ type: 'comment_resolved', data: { thread_id: threadId, resolver: this.currentUser, timestamp: new Date().toISOString() } }); return result; } catch (error) { console.error('Error resolving thread:', error); throw error; } } broadcastEvent(event) { if (this.socket && this.socket.readyState === WebSocket.OPEN) { this.socket.send(JSON.stringify(event)); } } reconnect() { if (this.socket) { try { this.socket.close(); } catch (error) { console.error('Error closing socket:', error); } } this.initialize(this.socketUrl); } getCsrfToken() { return document.querySelector('[name=csrfmiddlewaretoken]').value; } disconnect() { if (this.socket) { this.socket.close(); this.socket = null; } } } export default CollaborationSystem;