You're viewing the US website. Looking for the Site edition?
Latest
See All ${reply?.comment_author}${reply?.eln_tier_label ? `${reply.eln_tier_label}` : ''} • ${reply?.comment_relative_time} ${reply?.comment_content?.trim()} `; } function createComment(comment, replies, isLastComment) { const commentListDiv = document.getElementById(PublicCommentSelectors.LIST); if (!commentListDiv) { return; } const borderClasses = isLastComment ? '' : 'tw:border-b tw:border-slate-200'; const currentUser = window?.tp?.pianoId?.getUser() ?? null; const currentUserInitials = currentUser ? getInitials(`${currentUser?.firstName ?? ''} ${currentUser?.lastName ?? ''}`) : ''; const repliesHTML = replies.length ? ` ${replies.map(buildReplyHTML).join('')} ` : ''; const replyButtonHTML = allowsComments ? ` Reply ` : ''; const replyFormHTML = allowsComments ? ` Cancel Post Reply ` : ''; const commentDiv = ` ${comment?.comment_author}${comment?.eln_tier_label ? “ : ''} • ${comment?.comment_relative_time} ${comment?.comment_content?.trim()} ${replyButtonHTML} ${repliesHTML} ${replyFormHTML} `; commentListDiv.innerHTML += commentDiv; } function displayComments() { // Empty out the comment list container before refreshing comments. const commentListDiv = document.getElementById(PublicCommentSelectors.LIST); commentListDiv.innerHTML = ''; const comments = state?.comments ?? []; if (!comments.length) { return; } // Build a threaded structure: separate top-level from replies. const repliesByParent = {}; const topLevelComments = []; comments.forEach(comment => { if (comment.comment_parent === 0) { topLevelComments.push(comment); } else { const parentId = comment.comment_parent; if (!repliesByParent[parentId]) { repliesByParent[parentId] = []; } repliesByParent[parentId].push(comment); } }); // Apply top-level sort order. Server returns DESC; reverse for oldest-first. if (state.sortOrder === 'oldest') { topLevelComments.reverse(); } // Replies always show oldest first within a thread (server returns DESC, so reverse). Object.keys(repliesByParent).forEach(parentId => { repliesByParent[parentId].reverse(); }); topLevelComments.forEach((comment, index) => { const isLastComment = index === topLevelComments.length – 1; const replies = repliesByParent[comment.comment_ID] ?? []; createComment(comment, replies, isLastComment); }); createCommentAvatars(); } function updateCommentCount() { const countDiv = document.getElementById(PublicCommentSelectors.COMMENT_COUNT); const sortControls = document.getElementById(PublicCommentSelectors.SORT_CONTROLS); if (!countDiv) { return; } const count = state?.comments?.length ?? 0; // Do not display if there are no comments. if (count === 0) { return; } countDiv.innerHTML = `${count} Comment${count > 1 ? 's' : ''}`; if (sortControls) { sortControls.classList.remove('tw:hidden'); sortControls.classList.add('tw:flex'); } } function displayLoader(show) { const loader = document.getElementById(PublicCommentSelectors.BUTTON_LOADER); const text = document.getElementById(PublicCommentSelectors.BUTTON_TEXT); if (!loader || !text) { return; } // Show or hide the loader. if (show) { loader.classList.remove('tw:hidden'); text.classList.add('tw:hidden'); } else { loader.classList.add('tw:hidden'); text.classList.remove('tw:hidden'); } } function displayError(error) { const errorDiv = document.getElementById(PublicCommentSelectors.ERROR); if (!errorDiv) { return; } if (error) { errorDiv.classList.add('tw:block'); errorDiv.classList.remove('tw:hidden'); errorDiv.textContent = `Comment submission error: ${error}`; } else { errorDiv.classList.remove('tw:block'); errorDiv.classList.add('tw:hidden'); errorDiv.textContent = ''; } } function clearCommentInput() { const commentField = document.getElementById(PublicCommentSelectors.INPUT); if (!commentField) { return; } commentField.value = ''; } function updateComments(comments = []) { state.comments = comments; displayComments(); updateCommentCount(); } function runCommentProcess(type) { if (type === 'postComment') { const comment = document.getElementById(PublicCommentSelectors.INPUT); if (!comment || !comment?.value) { return; } // Hide errors, show loader. displayError(); displayLoader(true); } const data = { type, postId: 451500, }; // Get a fresh nonce and either fetch comments or post comment. fetch(`${adminAjaxUrl}?action=ep_get_public_comment_nonce`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data), }) .then((r) => r.json()) .then((response) => { // Handle errors. if (!response.success) { displayLoader(false); console.error('Error getting nonce: ', response?.data?.message); return; } const nonce = response?.data?.nonce; // Get comments. if (type === 'getComments') { getComments(nonce); return; } // Post comment. if (type === 'postComment') { postComment(nonce); } }) .catch((e) => { displayLoader(false); console.error(`Error running comment process ${type}: `, e); }); } function getComments(nonce) { const data = { nonce, postId: 451500, }; // Fetch comments. fetch(`${adminAjaxUrl}?action=ep_get_public_comments`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data), }) .then((r) => r.json()) .then((response) => { // Handle errors. if (!response.success) { displayLoader(false); console.error('Error getting comments: ', response?.data?.message); return; } updateComments(response?.data?.comments); displayLoader(false); }) .catch((e) => { displayLoader(false); console.error('Error getting comments: ', e); }); } function postComment(nonce) { const user = window?.tp?.pianoId?.getUser() ?? null; if (!user || !user?.uid) { console.error('Error posting comment: Unauthorized. Submission aborted.'); return; } const comment = document.getElementById(PublicCommentSelectors.INPUT); if (!comment || !comment?.value) { return; } const data = { nonce, comment: comment.value, commentParent: 0, postId: 451500, uid: user?.uid, }; // Post the comment. fetch(`${adminAjaxUrl}?action=ep_create_public_comment`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify(data), }) .then((r) => r.json()) .then((response) => { // Handle errors. if (!response.success) { displayLoader(false); displayError(response?.data?.message, true); console.error('Error posting comment: ', response?.data?.message); return; } // Handle success. clearCommentInput(); runCommentProcess('getComments'); }) .catch((e) => { displayLoader(false); displayError(e); console.error('Error posting comment: ', e); }); } function toggleReplyForm(commentId) { // Close any other open reply forms. document.querySelectorAll('.ep-reply-form').forEach(function(form) { if (form.dataset.parentId !== String(commentId)) { form.classList.add('tw:hidden'); } }); // Toggle the targeted reply form. const targetForm = document.querySelector('.ep-reply-form[data-parent-id="' + commentId + '"]'); if (targetForm) { targetForm.classList.toggle('tw:hidden'); if (!targetForm.classList.contains('tw:hidden')) { targetForm.querySelector('.ep-reply-input')?.focus(); } } } function setReplyLoading(formEl, isLoading) { const btn = formEl.querySelector('.ep-reply-submit'); const btnText = formEl.querySelector('.ep-reply-button-text'); if (!btn || !btnText) { return; } if (isLoading) { btnText.textContent = 'Posting…'; btn.disabled = true; } else { btnText.textContent = 'Post Reply'; btn.disabled = false; } } function displayReplyError(formEl, error) { const errorDiv = formEl.querySelector('.ep-reply-error'); if (!errorDiv || !error) { return; } errorDiv.textContent = 'Reply error: ' + error; errorDiv.classList.remove('tw:hidden'); errorDiv.classList.add('tw:block'); } function clearReplyError(formEl) { const errorDiv = formEl.querySelector('.ep-reply-error'); if (!errorDiv) { return; } errorDiv.textContent = ''; errorDiv.classList.add('tw:hidden'); errorDiv.classList.remove('tw:block'); } function postReply(nonce, parentId, replyText, formEl) { const user = window?.tp?.pianoId?.getUser() ?? null; if (!user || !user?.uid) { setReplyLoading(formEl, false); return; } const data = { nonce, comment: replyText, commentParent: parentId, postId: 451500, uid: user?.uid, }; fetch(`${adminAjaxUrl}?action=ep_create_public_comment`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify(data), }) .then((r) => r.json()) .then((response) => { setReplyLoading(formEl, false); if (!response.success) { displayReplyError(formEl, response?.data?.message); console.error('Error posting reply: ', response?.data?.message); return; } formEl.querySelector('.ep-reply-input').value = ''; formEl.classList.add('tw:hidden'); runCommentProcess('getComments'); }) .catch((e) => { setReplyLoading(formEl, false); displayReplyError(formEl, 'Network error. Please try again.'); console.error('Error posting reply: ', e); }); } function submitReply(parentId, formEl) { const input = formEl.querySelector('.ep-reply-input'); if (!input || !input.value.trim()) { return; } const user = window?.tp?.pianoId?.getUser() ?? null; if (!user || !user?.uid) { openLoginModal(); return; } setReplyLoading(formEl, true); clearReplyError(formEl); const data = { type: 'postComment', postId: 451500, }; fetch(`${adminAjaxUrl}?action=ep_get_public_comment_nonce`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data), }) .then((r) => r.json()) .then((response) => { if (!response.success) { setReplyLoading(formEl, false); displayReplyError(formEl, response?.data?.message); return; } postReply(response?.data?.nonce, parseInt(parentId, 10), input.value.trim(), formEl); }) .catch((e) => { setReplyLoading(formEl, false); displayReplyError(formEl, 'Network error. Please try again.'); console.error('Error getting nonce for reply: ', e); }); } function setupCommentListDelegation() { const commentListDiv = document.getElementById(PublicCommentSelectors.LIST); if (!commentListDiv) { return; } commentListDiv.addEventListener('click', function(e) { // Reply button: toggle the inline reply form. const replyButton = e.target.closest('.' + PublicCommentSelectors.REPLY_BUTTON); if (replyButton) { const commentId = replyButton.dataset.commentId; const user = window?.tp?.pianoId?.getUser() ?? null; if (!user) { openLoginModal(); return; } toggleReplyForm(commentId); return; } // Reply cancel: hide the reply form. const replyCancel = e.target.closest('.ep-reply-cancel'); if (replyCancel) { const parentId = replyCancel.dataset.parentId; const form = document.querySelector('.ep-reply-form[data-parent-id="' + parentId + '"]'); if (form) { form.classList.add('tw:hidden'); } return; } // Reply submit: post the reply. const replySubmit = e.target.closest('.ep-reply-submit'); if (replySubmit) { const parentId = replySubmit.dataset.parentId; const form = document.querySelector('.ep-reply-form[data-parent-id="' + parentId + '"]'); if (form) { submitReply(parentId, form); } } }); } function setupUserAvatar() { const usernameDiv = document.getElementById(PublicCommentSelectors.USERNAME); const userAvatar = document.getElementById(PublicCommentSelectors.USER_AVATAR); if (!usernameDiv || !userAvatar) { return; } const user = window?.tp?.pianoId?.getUser() ?? null; if (!user) { return; } const fullName = `${user?.firstName ?? ''} ${user?.lastName ?? ''}`; usernameDiv.innerHTML = fullName; userAvatar.innerHTML = getInitials(fullName); } function openLoginModal() { window?.tp?.pianoId?.show({ displayMode: 'modal', screen: 'login' }); } function setupCommentSubmitButton() { const submitButton = document.getElementById(PublicCommentSelectors.SUBMIT); if (!submitButton || state.submitCommentButtonRegistered) { return; } // Register a single click handler that checks auth state at click time. submitButton.addEventListener('click', function() { const user = window?.tp?.pianoId?.getUser() ?? null; if (!user) { openLoginModal(); return; } runCommentProcess('postComment'); }); state.submitCommentButtonRegistered = true; } function setSortOrder(order) { state.sortOrder = order; const newestBtn = document.getElementById(PublicCommentSelectors.SORT_NEWEST); const oldestBtn = document.getElementById(PublicCommentSelectors.SORT_OLDEST); if (newestBtn && oldestBtn) { const activeClasses = ['tw:font-bold', 'tw:text-blue-800']; const inactiveClasses = ['tw:font-normal', 'tw:text-slate-500']; if (order === 'newest') { newestBtn.classList.add(…activeClasses); newestBtn.classList.remove(…inactiveClasses); oldestBtn.classList.add(…inactiveClasses); oldestBtn.classList.remove(…activeClasses); } else { oldestBtn.classList.add(…activeClasses); oldestBtn.classList.remove(…inactiveClasses); newestBtn.classList.add(…inactiveClasses); newestBtn.classList.remove(…activeClasses); } } displayComments(); } function setupSortControls() { const newestBtn = document.getElementById(PublicCommentSelectors.SORT_NEWEST); const oldestBtn = document.getElementById(PublicCommentSelectors.SORT_OLDEST); if (newestBtn) { newestBtn.addEventListener('click', function() { setSortOrder('newest'); }); } if (oldestBtn) { oldestBtn.addEventListener('click', function() { setSortOrder('oldest'); }); } } runCommentProcess('getComments'); listenForPianoLogin(); setupCommentSubmitButton(); setupCommentListDelegation(); setupSortControls(); });
Related Content
Entrepreneurs
I Left a Secure Career to Become an Entrepreneur — and I Wasn’t Nearly as Ready as I Thought
Building a Business
Why Innovation Now Depends on Resilience, Not Just Speed
Building a Business
7 Quiet Signs Your Company Culture Is in Trouble
Tech
Why Coding Agents Work and Go-to-Market Agents Don’t (Yet)
By Henry Schuck
Marketing
The Marketing Funnel Is Being Rewritten. Here’s How Transaction-Based Advertising Is Changing the Game.
By Al Sefati
Building a Business
Most Franchise Growth Strategies Fail. Here’s What Leaders Can Do About It.
By Art Coley



