// Inline Comment Panel Component class InlineCommentPanel { constructor(options = {}) { this.container = null; this.thread = null; this.canResolve = options.canResolve || false; this.onReply = options.onReply || (() => {}); this.onResolve = options.onResolve || (() => {}); this.currentUser = options.currentUser; } initialize(containerId) { this.container = document.getElementById(containerId); if (!this.container) { throw new Error(`Container element with id "${containerId}" not found`); } } setThread(thread) { this.thread = thread; this.render(); } render() { if (!this.container || !this.thread) return; this.container.innerHTML = `
${this.formatAnchor(this.thread.anchor)} ${this.thread.is_resolved ? ` Resolved ` : '' }
${this.canResolve && !this.thread.is_resolved ? `` : '' }
${this.renderComments(this.thread.comments)}
${!this.thread.is_resolved ? `
` : '' }
`; this.attachEventListeners(); } renderComments(comments) { return comments.map(comment => `
${comment.author.username} ${comment.author.username} ${this.formatDate(comment.created_at)}
${this.formatCommentContent(comment.content)}
${this.renderCommentActions(comment)} ${comment.replies ? `
${this.renderComments(comment.replies)}
` : '' }
`).join(''); } renderCommentActions(comment) { if (this.thread.is_resolved) return ''; return `
${comment.author.id === this.currentUser.id ? `` : '' }
`; } formatCommentContent(content) { // Replace @mentions with styled spans content = content.replace(/@(\w+)/g, '@$1'); // Convert URLs to links content = content.replace( /(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/g, '$1' ); return content; } formatAnchor(anchor) { const start = anchor.line_start; const end = anchor.line_end; const file = anchor.file_path.split('/').pop(); return end > start ? `${file}:${start}-${end}` : `${file}:${start}`; } formatDate(dateString) { const date = new Date(dateString); return date.toLocaleString(); } attachEventListeners() { const replyInput = this.container.querySelector('.reply-input'); if (replyInput) { replyInput.addEventListener('keydown', (e) => { if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { this.submitReply(); } }); } } async submitReply() { const input = this.container.querySelector('.reply-input'); const content = input.value.trim(); if (!content) return; try { await this.onReply(content); input.value = ''; } catch (error) { console.error('Failed to submit reply:', error); // Show error message to user } } async resolveThread() { try { await this.onResolve(); this.render(); } catch (error) { console.error('Failed to resolve thread:', error); // Show error message to user } } showReplyForm(commentId) { const comment = this.container.querySelector(`[data-comment-id="${commentId}"]`); if (!comment) return; const replyForm = document.createElement('div'); replyForm.className = 'reply-form nested'; replyForm.innerHTML = `
`; comment.appendChild(replyForm); replyForm.querySelector('.reply-input').focus(); } hideReplyForm(commentId) { const comment = this.container.querySelector(`[data-comment-id="${commentId}"]`); if (!comment) return; const replyForm = comment.querySelector('.reply-form'); if (replyForm) { replyForm.remove(); } } async submitNestedReply(parentId) { const comment = this.container.querySelector(`[data-comment-id="${parentId}"]`); if (!comment) return; const input = comment.querySelector('.reply-input'); const content = input.value.trim(); if (!content) return; try { await this.onReply(content, parentId); this.hideReplyForm(parentId); } catch (error) { console.error('Failed to submit reply:', error); // Show error message to user } } editComment(commentId) { const comment = this.container.querySelector(`[data-comment-id="${commentId}"]`); if (!comment) return; const contentDiv = comment.querySelector('.comment-content'); const content = contentDiv.textContent; contentDiv.innerHTML = `
`; } cancelEdit(commentId) { // Refresh the entire thread to restore original content this.render(); } async saveEdit(commentId) { const comment = this.container.querySelector(`[data-comment-id="${commentId}"]`); if (!comment) return; const input = comment.querySelector('.edit-input'); const content = input.value.trim(); if (!content) return; try { // Emit edit event const event = new CustomEvent('comment-edited', { detail: { commentId, content } }); this.container.dispatchEvent(event); // Refresh the thread this.render(); } catch (error) { console.error('Failed to edit comment:', error); // Show error message to user } } } export default InlineCommentPanel;