diff --git a/index.html b/index.html index 8d9ea76..d4773ee 100644 --- a/index.html +++ b/index.html @@ -1674,63 +1674,77 @@ function renderModalComments(container, comments) { container.innerHTML = commentsHtml; } -// 4. Add function to handle comment votes +// Add function to handle comment votes async function handleCommentVote(commentId, voteType) { if (!commentId || !voteType) return; - // Find all instances of this comment (may be in modal and in main feed) - const commentItems = document.querySelectorAll(`.comment-item[data-comment-id="${commentId}"]`); - if (commentItems.length === 0) return; + // Find all instances of this comment vote buttons (may be in modal and in main feed) + const upvoteButtons = document.querySelectorAll(`.comment-vote-button[data-comment-id="${commentId}"][data-vote-type="upvote"]`); + const downvoteButtons = document.querySelectorAll(`.comment-vote-button[data-comment-id="${commentId}"][data-vote-type="downvote"]`); - // Get the buttons and counts for all instances - commentItems.forEach(commentItem => { - const upvoteButton = commentItem.querySelector(`.comment-vote-button[data-vote-type="upvote"]`); - const downvoteButton = commentItem.querySelector(`.comment-vote-button[data-vote-type="downvote"]`); - const upvoteCount = upvoteButton.querySelector('.comment-vote-count'); - const downvoteCount = downvoteButton.querySelector('.comment-vote-count'); - - // Get current state - const wasUpvoted = upvoteButton.classList.contains('upvoted'); - const wasDownvoted = downvoteButton.classList.contains('downvoted'); - - // Get current counts - let upvotes = parseInt(upvoteCount.textContent); - let downvotes = parseInt(downvoteCount.textContent); - - // Update UI optimistically - if (voteType === 'upvote') { - if (wasUpvoted) { - // Toggle off upvote - upvoteButton.classList.remove('upvoted'); - upvoteCount.textContent = upvotes - 1; - } else { - // Add upvote - upvoteButton.classList.add('upvoted'); - upvoteCount.textContent = upvotes + 1; - - // If was downvoted, remove downvote - if (wasDownvoted) { - downvoteButton.classList.remove('downvoted'); - downvoteCount.textContent = downvotes - 1; - } - } - } else if (voteType === 'downvote') { + if (upvoteButtons.length === 0 || downvoteButtons.length === 0) return; + + // Track the state for all instances + let wasUpvoted = false; + let wasDownvoted = false; + let upvotes = 0; + let downvotes = 0; + + // Get current state from the first button set + wasUpvoted = upvoteButtons[0].classList.contains('upvoted'); + wasDownvoted = downvoteButtons[0].classList.contains('downvoted'); + upvotes = parseInt(upvoteButtons[0].querySelector('.comment-vote-count').textContent); + downvotes = parseInt(downvoteButtons[0].querySelector('.comment-vote-count').textContent); + + // Calculate new states + let newUpvotes = upvotes; + let newDownvotes = downvotes; + let newUpvotedClass = wasUpvoted; + let newDownvotedClass = wasDownvoted; + + if (voteType === 'upvote') { + if (wasUpvoted) { + // Toggle off upvote + newUpvotedClass = false; + newUpvotes = upvotes - 1; + } else { + // Add upvote + newUpvotedClass = true; + newUpvotes = upvotes + 1; + + // If was downvoted, remove downvote if (wasDownvoted) { - // Toggle off downvote - downvoteButton.classList.remove('downvoted'); - downvoteCount.textContent = downvotes - 1; - } else { - // Add downvote - downvoteButton.classList.add('downvoted'); - downvoteCount.textContent = downvotes + 1; - - // If was upvoted, remove upvote - if (wasUpvoted) { - upvoteButton.classList.remove('upvoted'); - upvoteCount.textContent = upvotes - 1; - } + newDownvotedClass = false; + newDownvotes = downvotes - 1; } } + } else if (voteType === 'downvote') { + if (wasDownvoted) { + // Toggle off downvote + newDownvotedClass = false; + newDownvotes = downvotes - 1; + } else { + // Add downvote + newDownvotedClass = true; + newDownvotes = downvotes + 1; + + // If was upvoted, remove upvote + if (wasUpvoted) { + newUpvotedClass = false; + newUpvotes = upvotes - 1; + } + } + } + + // Update all instances + upvoteButtons.forEach(button => { + button.classList.toggle('upvoted', newUpvotedClass); + button.querySelector('.comment-vote-count').textContent = newUpvotes; + }); + + downvoteButtons.forEach(button => { + button.classList.toggle('downvoted', newDownvotedClass); + button.querySelector('.comment-vote-count').textContent = newDownvotes; }); // Submit vote to server @@ -1751,21 +1765,25 @@ async function handleCommentVote(commentId, voteType) { throw new Error('Failed to submit comment vote'); } - const result = await response.json(); - - // If the server response indicates we should update the UI differently, we could handle that here - // But generally the optimistic UI update above should match the server result - + // Server response successful } catch (error) { console.error('Error voting on comment:', error); - showToastMessage('Hmm, your comment vote didn\'t count. Please try again.', '#ff0099'); + showToastMessage('Your vote didn\'t count. Please try again.', '#ff0099'); - // Revert UI changes by reloading the comments - // This is a simple approach; you could be more sophisticated by reverting just the affected elements - await loadComments(currentProposalId); + // Revert UI changes + upvoteButtons.forEach(button => { + button.classList.toggle('upvoted', wasUpvoted); + button.querySelector('.comment-vote-count').textContent = upvotes; + }); + + downvoteButtons.forEach(button => { + button.classList.toggle('downvoted', wasDownvoted); + button.querySelector('.comment-vote-count').textContent = downvotes; + }); } } + function getShareableImageUrl(proposal) { // Base Cloudinary URL const cloudinaryUrl = "https://res.cloudinary.com/dh8apmjya/image/fetch"; @@ -1826,31 +1844,66 @@ function toggleComments(proposalId, button) { } // Function to load comments for a proposal -async function loadComments(proposalId) { - const commentsContainer = document.querySelector(`.comments-container[data-proposal-id="${proposalId}"]`); - const commentsContent = commentsContainer.querySelector('.comments-content'); +function loadComments(proposalId) { + const commentsList = document.getElementById(`comments-list-${proposalId}`); + if (!commentsList) return; - // Show loading state - commentsContent.innerHTML = `
Loading comments...
`; + commentsList.innerHTML = '
Loading comments...
'; - try { - // Include the current user ID to get their votes - const response = await fetch(`${API_BASE_URL}/comments?proposalId=${proposalId}&userId=${currentUser.id}`); - - if (!response.ok) { - throw new Error('Failed to fetch comments'); - } - - const comments = await response.json(); - renderComments(commentsContent, comments); - } catch (error) { - console.error('Error fetching comments:', error); - commentsContent.innerHTML = ` -
- Failed to load comments. Please try again. -
- `; - } + fetch(`${API_BASE_URL}/comments?proposalId=${proposalId}&userId=${currentUser.id}`) + .then(response => response.json()) + .then(comments => { + if (comments.length === 0) { + commentsList.innerHTML = '
No comments yet. Be the first to comment!
'; + return; + } + + commentsList.innerHTML = ''; + comments.forEach(comment => { + const upvotedClass = comment.userVote === 'upvote' ? 'upvoted' : ''; + const downvotedClass = comment.userVote === 'downvote' ? 'downvoted' : ''; + + const commentElement = document.createElement('div'); + commentElement.className = 'comment'; + commentElement.innerHTML = ` +
+ ${comment.user_name} + ${formatDate(comment.timestamp)} +
+
${comment.comment_text}
+
+ + +
+ `; + + // Add event listeners for the vote buttons + const upvoteButton = commentElement.querySelector(`.comment-vote-button[data-vote-type="upvote"]`); + const downvoteButton = commentElement.querySelector(`.comment-vote-button[data-vote-type="downvote"]`); + + upvoteButton.addEventListener('click', function(e) { + e.stopPropagation(); // Prevent triggering parent elements + handleCommentVote(comment.id, 'upvote'); + }); + + downvoteButton.addEventListener('click', function(e) { + e.stopPropagation(); // Prevent triggering parent elements + handleCommentVote(comment.id, 'downvote'); + }); + + commentsList.appendChild(commentElement); + }); + }) + .catch(error => { + commentsList.innerHTML = '
Failed to load comments.
'; + console.error('Error loading comments:', error); + }); } // Function to render comments @@ -1912,70 +1965,34 @@ function formatCommentText(text) { } // Function to submit a new comment -async function submitComment(proposalId, form) { - const commentInput = form.querySelector('.comment-input'); - const submitButton = form.querySelector('.comment-submit'); - - const commentText = commentInput.value.trim(); - - if (!commentText) return; - - // Disable form while submitting - commentInput.disabled = true; - submitButton.disabled = true; - - try { - const response = await fetch(`${API_BASE_URL}/comments`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - proposalId: proposalId, - userId: currentUser.id, - commentText: commentText - }) +function submitComment(proposalId, commentText) { + fetch(`${API_BASE_URL}/comments`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + proposalId: proposalId, + userId: currentUser.id, + commentText: commentText + }) + }) + .then(response => { + if (!response.ok) { + throw new Error('Failed to post comment'); + } + return response.json(); + }) + .then(data => { + // Reload comments to show the new one with votes + loadComments(proposalId); + updateCommentCount(proposalId); + showToastMessage('Comment posted!', '#11cc77'); + }) + .catch(error => { + console.error('Error posting comment:', error); + showToastMessage('Failed to post comment', '#ff0099'); }); - - if (!response.ok) { - throw new Error('Failed to submit comment'); - } - - // Clear input - commentInput.value = ''; - - // Reload comments to show the new one - const commentsContainer = document.querySelector(`.comments-container[data-proposal-id="${proposalId}"]`); - const commentsContent = commentsContainer.querySelector('.comments-content'); - - // Add new comment to the top (optimistic update) - const newComment = await response.json(); - const tempCommentHtml = ` -
-
-
${newComment.userName}
-
just now
-
-
${formatCommentText(newComment.commentText)}
-
- `; - - // If there are no comments yet, replace the "no comments" message - if (commentsContent.querySelector('.no-comments')) { - commentsContent.innerHTML = tempCommentHtml; - } else { - // Otherwise, prepend to existing comments - commentsContent.insertAdjacentHTML('afterbegin', tempCommentHtml); - } - - } catch (error) { - console.error('Error posting comment:', error); - showToastMessage('Failed to post comment. Please try again.', '#ff0099'); - } finally { - // Re-enable form - commentInput.disabled = false; - submitButton.disabled = false; - } } // Function to check if comment input is not empty @@ -1984,6 +2001,7 @@ function checkCommentInput(input, submitButton) { submitButton.disabled = !hasContent; } + // Addition to renderProposals function - Add comments section to each proposal card function addCommentsToProposal(proposalCard, proposalId) { const commentsSection = document.createElement('div'); @@ -1991,45 +2009,38 @@ function addCommentsToProposal(proposalCard, proposalId) { const commentsToggle = document.createElement('button'); commentsToggle.className = 'comments-toggle'; + commentsToggle.id = `comments-toggle-${proposalId}`; commentsToggle.setAttribute('aria-expanded', 'false'); + commentsToggle.innerHTML = ' 0 comments'; const commentsContainer = document.createElement('div'); commentsContainer.className = 'comments-container'; + commentsContainer.id = `comments-container-${proposalId}`; + commentsContainer.style.display = 'none'; commentsContainer.setAttribute('data-proposal-id', proposalId); - const commentsForm = document.createElement('form'); + const commentsList = document.createElement('div'); + commentsList.className = 'comments-list'; + commentsList.id = `comments-list-${proposalId}`; + + const commentsForm = document.createElement('div'); commentsForm.className = 'comment-form'; - commentsForm.onsubmit = (e) => { - e.preventDefault(); - submitComment(proposalId, commentsForm); - }; const commentInput = document.createElement('textarea'); commentInput.className = 'comment-input'; - commentInput.placeholder = 'Add a comment...'; - commentInput.rows = 1; - commentInput.oninput = () => checkCommentInput(commentInput, commentSubmit); - - // Auto-resize textarea as user types - commentInput.addEventListener('input', function() { - this.style.height = 'auto'; - this.style.height = (this.scrollHeight) + 'px'; - }); + commentInput.id = `comment-input-${proposalId}`; + commentInput.placeholder = 'Add your comment...'; const commentSubmit = document.createElement('button'); commentSubmit.className = 'comment-submit'; - commentSubmit.type = 'submit'; - commentSubmit.textContent = 'Defy'; - commentSubmit.disabled = true; + commentSubmit.setAttribute('data-proposal-id', proposalId); + commentSubmit.textContent = 'Submit'; commentsForm.appendChild(commentInput); commentsForm.appendChild(commentSubmit); - const commentsContent = document.createElement('div'); - commentsContent.className = 'comments-content'; - + commentsContainer.appendChild(commentsList); commentsContainer.appendChild(commentsForm); - commentsContainer.appendChild(commentsContent); commentsSection.appendChild(commentsToggle); commentsSection.appendChild(commentsContainer); @@ -2037,10 +2048,30 @@ function addCommentsToProposal(proposalCard, proposalId) { proposalCard.appendChild(commentsSection); // Add event listener to toggle button - commentsToggle.addEventListener('click', () => { - toggleComments(proposalId, commentsToggle); + commentsToggle.addEventListener('click', function() { + const isVisible = commentsContainer.style.display !== 'none'; + + if (isVisible) { + commentsContainer.style.display = 'none'; + this.querySelector('.comments-toggle-icon').textContent = '▶'; + } else { + commentsContainer.style.display = 'block'; + this.querySelector('.comments-toggle-icon').textContent = '▼'; + loadComments(proposalId); // This now includes proper vote UI + } + }); + + // Add event listener for comment submission + commentSubmit.addEventListener('click', function() { + const commentText = commentInput.value.trim(); + + if (commentText) { + submitComment(proposalId, commentText); + commentInput.value = ''; + } }); } + // Music player functionality document.addEventListener('DOMContentLoaded', () => { // Set up variables diff --git a/styles.css b/styles.css index a9fe940..860daa1 100644 --- a/styles.css +++ b/styles.css @@ -1143,78 +1143,111 @@ a { color: unset; } -/* Comment voting styles */ -.comment-vote-buttons { - display: flex; - align-items: center; - margin-top: 8px; +/* Styles for comments in the main feed */ +.comments-list { + margin-top: 10px; } - .comment-vote-button { + .comments-list .comment { + padding: 10px; + border-bottom: 1px solid var(--border); + font-size: 14px; + } + + .comments-list .comment:last-child { + border-bottom: none; + } + + .comments-list .comment-header { + display: flex; + justify-content: space-between; + font-size: 12px; + margin-bottom: 4px; + } + + .comments-list .comment-author { + font-weight: bold; + color: var(--text); + } + + .comments-list .comment-time { + color: var(--text-light); + } + + .comments-list .comment-body { + margin-bottom: 6px; + line-height: 1.4; + word-break: break-word; + color: var(--text); + } + + .comments-list .comment-vote-buttons { + display: flex; + align-items: center; + margin-top: 4px; + } + + .comments-list .comment-vote-button { background: none; border: none; cursor: pointer; - font-size: 14px; + font-size: 12px; color: var(--text-light); - padding: 3px 6px; + padding: 2px 4px; border-radius: 3px; - margin-right: 10px; + margin-right: 8px; transition: all 0.2s ease; display: flex; align-items: center; } - .comment-vote-button:hover { + .comments-list .comment-vote-button:hover { background-color: rgba(255, 255, 255, 0.05); } - .comment-vote-button.upvoted { + .comments-list .comment-vote-button.upvoted { color: var(--primary); } - .comment-vote-button.downvoted { + .comments-list .comment-vote-button.downvoted { color: var(--primary); } - .comment-vote-icon { - font-size: 10px; - margin-right: 4px; + .comments-list .comment-vote-icon { + font-size: 9px; + margin-right: 3px; } - .comment-vote-count { - font-size: 12px; - min-width: 15px; - text-align: center; + .comments-list .comment-vote-count { + font-size: 11px; + min-width: 12px; } - /* Adjust existing comment styles to accommodate voting */ - .comment-item { - padding: 12px; - border-bottom: 1px solid var(--border); - animation: fadeIn 0.3s ease; + /* Make sure comments container has max-height and scrolling */ + .comments-container { + max-height: 400px; + overflow-y: auto; + background-color: var(--background); + border-top: 1px solid var(--border); } - .comment-text { - margin: 6px 0; - font-size: 14px; - line-height: 1.4; - word-break: break-word; + /* Ensure proposal card buttons don't conflict with comment vote buttons */ + .proposal-card .vote-button { + z-index: 5; } - .comment-footer { - display: flex; - justify-content: space-between; - align-items: center; - margin-top: 8px; + /* Stop propagation for clicks inside comments */ + .comments-container * { + pointer-events: auto; } - - /* Mobile optimizations */ - @media (max-width: 768px) { - .comment-vote-buttons { - margin-top: 6px; - } - - .comment-vote-button { - padding: 2px 4px; - } - } \ No newline at end of file + + .comment-vote-button.upvoted .comment-vote-icon, +.comment-vote-button.downvoted .comment-vote-icon { + animation: pulse 0.3s ease-in-out; +} + +@keyframes pulse { + 0% { transform: scale(1); } + 50% { transform: scale(1.5); } + 100% { transform: scale(1); } +} \ No newline at end of file