Skip to content

Instantly share code, notes, and snippets.

@andrei0x309
Last active March 26, 2021 23:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andrei0x309/496c328a34e2a2c4bada00c439ef629b to your computer and use it in GitHub Desktop.
Save andrei0x309/496c328a34e2a2c4bada00c439ef629b to your computer and use it in GitHub Desktop.
Files for custom AMP WordPress Commenting System
// this file is loaded in worker-dom by amp-script and handles all comment functions
(async function ampComments() {
const addSiSpinner = function (element, prepend = false) {
const spinner = document.createElement('div');
spinner.classList.add('loadingspinner');
if (prepend) {
element.insertBefore(element, element.firstElementChild);
} else {
element.appendChild(spinner);
}
return spinner;
};
const delSiSpinner = (spinner) => {
if (spinner)
spinner.parentNode.removeChild(spinner);
};
const delAlertBox = ( ) => {
const oldAlertBox = document.getElementById('a309-alert-box');
if (oldAlertBox) {
oldAlertBox.parentNode.removeChild(oldAlertBox);
}
};
const alertBox = (alertClass = 'error', alertMsg = '', delAlertBox = '') => {
if (delAlertBox)
delAlertBox();
switch (alertClass) {
case 'error':
alertClass = 'alert-error';
break;
case 'info':
alertClass = 'alert-info';
break;
case 'success':
alertClass = 'alert-success';
break;
default:
alertClass = 'alert-error';
break;
}
const alertBox = document.createElement('div');
alertBox.id = 'a309-alert-box';
alertBox.classList.add('alert');
alertBox.classList.add(alertClass);
alertBox.classList.add('fade-in');
alertBox.innerHTML = alertMsg;
return alertBox;
};
const commentsEl = document.getElementById('comments');
const commentFromEl = document.getElementById('commentform');
commentFromEl.addEventListener('submit', (e) => {
e.target.preventDefault();
});
const actionUrl = document.getElementById('commentform').getAttribute('action-xhr');
const postId = document.getElementById('comments').getAttribute('data-post-id');
let storeRespEl = null;
let replyEl = null;
let hiddenRepLink = null;
let commentsList = null;
let commentsShowMoreBtn = null;
const showCommentsBtn = document.getElementById('comments-show-btn');
const postCommentBtn = document.getElementById('submit-amp');
let page = null;
const HTMLtoEL = (html) => {
const t = document.createElement('div');
t.innerHTML = html;
return t.children;
};
const DelShowMoreCBtn = () => {
if (commentsShowMoreBtn) {
commentsShowMoreBtn.parentNode.removeChild(commentsShowMoreBtn);
commentsShowMoreBtn = null;
}
};
const AddShowMoreCBtn = () => {
const showMoreBtn = document.createElement('button');
showMoreBtn.id = 'comments-show-more-btn';
showMoreBtn.innerHTML = 'Show More Comments';
showMoreBtn.addEventListener('click', showMoreCommentsFn);
commentsEl.appendChild(showMoreBtn);
return showMoreBtn;
};
const fetchCommentsNo = async () => {
const fetchUrl = `${window.location.origin}/wp-json/a309/v1/get-comments-no/post/${postId}`;
const response = await fetch(fetchUrl, {
headers: {
'Content-Type': 'application/json'
}
});
const data = await response.json();
page = Math.ceil(data.commentNumber / 5);
};
const fetchComments = async () => {
// fetch Comments
const fetchUrl = `${window.location.origin}/wp-json/a309/v1/get-comments/post/${postId}/page/${page}`;
const response = await fetch(fetchUrl, {
headers: {
'Content-Type': 'application/json',
}
});
const data = await response.json(); // parses JSON response into native JavaScript objects
page -= 1;
return data;
};
const repImgAMP = (...args) => {
const src = args[1].replace(/#038;/gms, '');
return `<amp-img
alt
src="${src}"
width="60"
height="60"
layout="fixed"
>
</amp-img>`;
};
const AMPifyComments = (comments) => {
return HTMLtoEL(comments
.replace(/<img.*?src=['"]{1}(.*?)['"]{1}.*?>/gms, repImgAMP)
.trim()
.replace(/<!--.*?-->/gms, '')
.replace(/\t/gm, '')
.replace(/\n/gm, ''));
};
const AddCommentsToList = (ListEl, comments) => {
for (const comment of comments) {
ListEl.appendChild(comment);
}
};
const showCommentsFn = async () => {
showCommentsBtn.innerHTML = `
Loading
<div class="loadingspinner"></div>
`;
showCommentsBtn.disabled = true;
await fetchCommentsNo();
const data = await fetchComments();
commentsList = document.createElement('ol');
commentsList.id = 'comment-list';
commentsList.classList.add('comment-list');
const comments = AMPifyComments(data.comments);
addReplyEvent(comments);
AddCommentsToList(commentsList, comments);
commentsEl.appendChild(commentsList);
commentsEl.removeChild(showCommentsBtn);
if (page) {
await showMoreCommentsFn();
}
};
const showMoreCommentsFn = async () => {
DelShowMoreCBtn();
const spinner = addSiSpinner(commentsEl);
const data = await fetchComments();
const commentsList = document.getElementById('comment-list');
const comments = AMPifyComments(data.comments);
addReplyEvent(comments);
AddCommentsToList(commentsList, comments);
if (page) {
commentsShowMoreBtn = AddShowMoreCBtn();
}
delSiSpinner(spinner);
};
const sumbitComment = async (e) => {
delAlertBox();
const respondEl = document.getElementById('amp-respond');
const spinner = addSiSpinner(respondEl);
const submitBtn = document.getElementById('submit-amp');
submitBtn.disabled = true;
//serialize and store form data in a variable
console.log(actionUrl);
const bodyData = {comment: document.getElementById('comment') ? document.getElementById('comment').value : '',
author: document.getElementById('author') ? document.getElementById('author').value : '',
email: document.getElementById('email') ? document.getElementById('email').value : '',
comment_post_ID: document.getElementById('comment_post_ID') ? document.getElementById('comment_post_ID').value : '',
comment_parent: document.getElementById('comment_parent') ? document.getElementById('comment_parent').value : '',
'wp-comment-cookies-consent': document.getElementById('wp-comment-cookies-consent') ? document.getElementById('wp-comment-cookies-consent').value : '',
akismet_comment_nonce: document.getElementById('akismet_comment_nonce') ? document.getElementById('akismet_comment_nonce').value : '',
'ak_js': document.getElementById('ak_js') ? document.getElementById('ak_js').value : '',
'ak_hp_textarea': document.getElementById('ak_hp_textarea') ? document.getElementById('ak_hp_textarea').value : '',
'redirect_to': document.getElementById('redirect_to') ? document.getElementById('redirect_to').value : ''
};
const searchParams = Object.keys(bodyData).map((key) => {
return `${encodeURIComponent(key)}${ bodyData[key] ? '=' + encodeURIComponent(bodyData[key]) : ''}`;
}).join('&');
const response = await fetch(actionUrl, {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, *same-origin, omit
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: searchParams // body data type must match "Content-Type" header
});
let alert = null;
if (response.ok) {
const data = await response.json();
if (data.error) {
alert = alertBox('error', `&#x26A0; ${data.msg}`, delAlertBox);
respondEl.appendChild(alert);
} else {
const approved = parseInt(data.comment.comment_approved) === 1;
if (approved) {
const noComments = document.getElementById('comments').getAttribute('data-no-comments');
if (noComments) {
const nCom = parseInt(noComments) + 1;
document.getElementById('comments').setAttribute('data-no-comments', nCom);
const comText = commentsEl.querySelector('.comments-title');
if (comText) {
comText.textContent = [nCom, comText.textContent.split(' ')[1]].join(' ');
}
}
}
console.log(approved);
const notApprovedText = 'Comment was posted but was not approved it will be live after approval.';
const commentList = document.getElementById('comment-list');
if (commentList) {
const com = addCommentToDOM(data.comment);
if (!approved) {
alert = alertBox('info', notApprovedText, delAlertBox);
com.appendChild(alert);
}
} else {
if (approved) {
alert = alertBox('success', 'Comment was posted', delAlertBox);
} else {
alert = alertBox('info', notApprovedText, delAlertBox);
}
respondEl.appendChild(alert);
}
}
} else {
alert = alertBox('error', '&#x26A0; HTTP fetch error, API down!', delAlertBox);
respondEl.appendChild(alert);
}
submitBtn.disabled = false;
delSiSpinner(spinner);
};
const addCommentToDOM = (comment) => {
const slug = commentsEl.getAttribute('data-post-slug');
const date = new Date(comment.comment_date);
const options = {year: 'numeric', month: 'long', day: 'numeric'};
const newCom = `<li id="comment-${comment.comment_ID}" class="comment byuser comment-author-${comment.comment_author} even thread-even comment-added fade-in">
<article id="div-comment-${comment.comment_ID}" class="comment-body">
<footer class="comment-meta">
<div class="comment-author vcard">
${comment.comment_avatar}
<b class="fn"><a href="${comment.comment_author_url}" rel="external nofollow ugc" class="url">${comment.comment_author}</a></b>
<span class="says">says:</span> </div><!-- .comment-author -->
<div class="comment-metadata">
<a href="${window.location.origin}/${slug}/#comment-${comment.comment_ID}">
<time datetime="${comment.comment_date}">${date.toLocaleDateString('en-US', options)} at ${date.toLocaleTimeString('en-US')}
</time></a></div><!-- .comment-metadata -->
</footer><!-- .comment-meta -->
<div class="comment-content">
<p>${comment.comment_content}</p>
</div><!-- .comment-content -->
</article><!-- .comment-body -->
</li>`;
const ampComment = AMPifyComments(newCom)[0];
const commentList = document.getElementById('comment-list');
if (Number(comment.comment_parent) === 0) {
commentList.insertBefore(ampComment, commentList.firstChild);
} else {
const cancelLink = document.getElementById('cancel-comment-reply-link');
cancelLink.click();
let commentParent = document.getElementById(`comment-${comment.comment_parent}`);
let children = commentParent.querySelector('.children');
if (!children) {
children = document.createElement('ol');
children.classList.add('children');
commentParent.appendChild(children);
}
children.insertBefore(ampComment, children.firstChild);
return ampComment;
}
};
const replyMoveForm = (e) => {
e.preventDefault();
if (hiddenRepLink)
hiddenRepLink.removeAttribute('hidden');
const commentId = e.target.getAttribute('data-commentid');
const divComment = document.getElementById(`div-comment-${commentId}`);
hiddenRepLink = divComment.querySelector('.comment-reply-link');
hiddenRepLink.setAttribute('hidden');
if (storeRespEl === null) {
const oldCancelLink = document.getElementById('cancel-comment-reply-link');
if (oldCancelLink)
oldCancelLink.parentNode.removeChild(oldCancelLink);
const respondEl = document.getElementById('amp-respond');
const form = respondEl.querySelector('form');
if (form) {
form.removeAttribute('amp-novalidate');
form.removeAttribute('class');
}
const noScript = respondEl.getElementsByTagName('noscript');
if (noScript[0])
noScript[0].parentNode.removeChild(noScript[0]);
replyEl = respondEl.cloneNode(true);
storeRespEl = respondEl;
const resp = replyEl.querySelector('#respond');
const fSubmit = resp.querySelector('#form-submit');
const cancelLink = document.createElement('a');
cancelLink.id = 'cancel-comment-reply-link';
cancelLink.setAttribute('rel', 'nofollow');
cancelLink.setAttribute('href', '#comments');
cancelLink.textContent = `Cancel Reply`;
resp.insertBefore(cancelLink, fSubmit);
cancelLink.addEventListener('click', replyFormCancel);
replyEl.querySelector('#submit-amp').addEventListener('click', sumbitComment);
}
document.getElementById('amp-respond').parentNode.removeChild(storeRespEl);
let replyName = divComment.querySelector('.fn');
replyName = replyName ? replyName.textContent : '';
const replyTitle = replyEl.querySelector('#reply-title');
replyTitle.textContent = `Reply to ${replyName}`;
const resp = replyEl.querySelector('#respond');
const inParent = resp.querySelector('#comment_parent');
inParent.setAttribute('value', commentId);
document.getElementById(`comment-${commentId}`).insertBefore(replyEl, divComment.nextSibling);
};
const replyFormCancel = () => {
delAlertBox();
const respondEl = document.getElementById('amp-respond');
respondEl.parentNode.removeChild(respondEl);
if (hiddenRepLink) {
hiddenRepLink.removeAttribute('hidden');
hiddenRepLink = null;
}
commentsEl.insertBefore(storeRespEl, commentsEl.firstChild);
};
const addReplyEvent = (comments) => {
for (const comment of comments) {
const links = comment.querySelectorAll('.comment-reply-link');
for (const link of links) {
//link.removeAttribute('href');
link.addEventListener('click', replyMoveForm);
}
}
};
postCommentBtn.addEventListener('click', sumbitComment);
showCommentsBtn.addEventListener('click', showCommentsFn);
})();
// This file is the comments.php form the template
<?php
if (post_password_required()) {
return;
}
$a309_no_comments = get_comments_number();
//global $current_user;
//if($current_user) echo $current_user->user_login;
?>
<?php if (a309_is_amp()): ?>
<amp-script id="comments-script" layout="container" src="<?php echo get_stylesheet_directory_uri() ?>/js/AMP/amp_comments.js" sandbox="allow-forms">
<?php endif; ?>
<div id="comments" data-post-id="<?php echo $post->ID; ?>" data-no-comments="<?php echo $a309_no_comments; ?>" data-post-slug="<?php echo $post->post_name; ?>" class="comments-area default-max-width <?php echo get_option('show_avatars') ? 'show-avatars' : ''; ?>">
<?php if (a309_is_amp()): ?>
<div id="amp-respond"> <?php
endif;
$aria_req = ($req) ? " aria-required='true'" : '';
$comments_args = array(
'comment_field' => '<p class="comment-form-comment"><textarea id="comment" name="comment" placeholder="Your Comment* " aria-required="true"></textarea></p>',
'comment_notes_before' => '',
'fields' =>
[
'author' =>
'<div class="comment-name-email-block"><p class="comment-form-author">' .
'<i class="icon-user-solid-square"></i><input id="author" class="blog-form-input" placeholder="Name* " name="author" type="text" value="' . esc_attr($commenter['comment_author']) .
'" size="30"' . $aria_req . ' /></p>',
'email' =>
'<p class="comment-form-email">' .
'<i class="icon-alternate_email"></i><input
id="email" class="blog-form-input" placeholder="Email Address* " name="email" type="text" value="' . esc_attr($commenter['comment_author_email']) .
'" size="30"' . $aria_req . ' /></p></div>',
'url' => '',
],
'logged_in_as' => null,
'title_reply' => esc_html__('Leave a comment', 'a309'),
'title_reply_before' => '<h2 id="reply-title" class="comment-reply-title">',
'title_reply_after' => '</h2>',
);
if (a309_is_amp()) {
$comments_args['cancel_reply_before'] = '';
$comments_args['cancel_reply_after'] = '';
$comments_args['cancel_reply_link'] = '';
$comments_args['submit_button'] = '<noscript data-ampdevmode><input name="%1$s" type="submit" id="%2$s" class="%3$s" value="%4$s" /></noscript>';
}
comment_form($comments_args);
if (a309_is_amp()):
?>
<p class="form-submit">
<button id="submit-amp" class="submit fade-in">Post Comment</button>
</p>
</div>
<?php
endif;
if (have_comments()) :
?>
<h2 class="comments-title"><?php
if ('1' === $a309_no_comments) :
esc_html_e('1 comment', 'a309');
else :
printf(
/* translators: %s: comment count number. */
esc_html(_nx('%s comment', '%s comments', $a309_no_comments, 'Comments title', 'a309')),
esc_html(number_format_i18n($a309_no_comments))
);
endif;
?></h2><!-- .comments-title -->
<button id="comments-show-btn" >
Show Comments
</button>
<!-- TODO: NO-JS page with comments later
<noscript data-ampdevmode><a id="comments-page" >
Go to comments page
</a>
</noscript>
-->
<?php if (!comments_open()) : ?>
<p class="no-comments"><?php esc_html_e('Comments are closed.', 'a309'); ?></p>
<?php endif; ?>
<?php endif; ?>
</div><!-- #comments -->
<?php if (a309_is_amp()): ?>
</amp-script>
<?php
endif;
<?php
//...
// The custom REST API for comments alos adding custom wp-comments-post.php file path
add_action('rest_api_init', 'change_rest_post' );
function change_rest_post(){
register_rest_route( 'a309/v1', '/get-comments-no/post/(?P<post_id>\d+)', array(
'methods' => 'GET',
'callback' => 'get_top_level_comments_number',
'permission_callback' => '__return_true',
) );
register_rest_route( 'a309/v1', '/get-comments/post/(?P<post_id>\d+)/page/(?P<page_no>\d+)', array(
'methods' => 'GET',
'callback' => 'get_comments_post',
'permission_callback' => '__return_true',
) );
}
function get_top_level_comments_number( $data ) {
global $wpdb;
$data['post_id'] = esc_sql($data['post_id']);
$noComments = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_parent = 0 AND comment_post_ID = '".$data['post_id']."'" );
wp_send_json([ 'commentNumber' => $noComments ]);
}
function get_comments_post($data){
// setup a fake POST to trick wp_list_comments
global $post;
$post = new stdClass();
$post->ID = $data['post_id'];
setup_postdata( $post );
$comment_args = array(
'avatar_size' => 60,
'reverse_top_level' => true,
'reverse_children' => true,
'style' => 'ol',
'short_ping' => true,
'max_depth' => 5,
'per_page' => 5,
'page' => $data['page_no'],
'echo' => false,
);
$template = wp_list_comments( $comment_args );
wp_reset_postdata();
wp_send_json([ 'comments' => $template, 'post_id' => $data['post_id'], 'page_no' => $data['page_no'] ]);
}
// Ajax Comment
function change_comment_action_url( $defaults ) {
$defaults['action'] = get_template_directory_uri().'/inc/comment-post.php';
return $defaults;
}
add_filter( 'comment_form_defaults', 'change_comment_action_url');
//...
<?php
// Mostly is the wp-comments-post.php but returns JSON instead of redirect
/**
* Handles Comment Post to WordPress and prevents duplicate comment posting.
*
* @package WordPress
*/
if ( 'POST' !== $_SERVER['REQUEST_METHOD'] ) {
$protocol = $_SERVER['SERVER_PROTOCOL'];
if ( ! in_array( $protocol, array( 'HTTP/1.1', 'HTTP/2', 'HTTP/2.0' ), true ) ) {
$protocol = 'HTTP/1.0';
}
header( 'Allow: POST' );
header( "$protocol 405 Method Not Allowed" );
header( 'Content-Type: text/plain' );
exit;
}
/** Sets up the WordPress Environment. */
require dirname(__DIR__, 4) . '/wp-load.php';
nocache_headers();
$comment = wp_handle_comment_submission( wp_unslash( $_POST ) );
if ( is_wp_error( $comment ) ) {
$data = (int) $comment->get_error_data();
if ( ! empty( $data ) ) {
/*wp_die(
'<p>' . $comment->get_error_message() . '</p>',
__( 'Comment Submission Failure' ),
array(
'response' => $data,
'back_link' => true,
)
);*/
wp_send_json([ 'error' => true, 'msg' => $comment->get_error_message() ]);
} else {
wp_send_json([ 'error' => true ]);
}
}
$user = wp_get_current_user();
$cookies_consent = ( isset( $_POST['wp-comment-cookies-consent'] ) );
/**
* Perform other actions when comment cookies are set.
*
* @since 3.4.0
* @since 4.9.6 The `$cookies_consent` parameter was added.
*
* @param WP_Comment $comment Comment object.
* @param WP_User $user Comment author's user object. The user may not exist.
* @param bool $cookies_consent Comment author's consent to store cookies.
*/
do_action( 'set_comment_cookies', $comment, $user, $cookies_consent );
$location = empty( $_POST['redirect_to'] ) ? get_comment_link( $comment ) : $_POST['redirect_to'] . '#comment-' . $comment->comment_ID;
// If user didn't consent to cookies, add specific query arguments to display the awaiting moderation message.
if ( ! $cookies_consent && 'unapproved' === wp_get_comment_status( $comment ) && ! empty( $comment->comment_author_email ) ) {
$location = add_query_arg(
array(
'unapproved' => $comment->comment_ID,
'moderation-hash' => wp_hash( $comment->comment_date_gmt ),
),
$location
);
}
/**
* Filters the location URI to send the commenter after posting.
*
* @since 2.0.5
*
* @param string $location The 'redirect_to' URI sent via $_POST.
* @param WP_Comment $comment Comment object.
*/
/*
$location = apply_filters( 'comment_post_redirect', $location, $comment );
wp_safe_redirect( $location );
exit;
*/
$comment->comment_avatar = get_avatar( $comment->comment_author_email, 64);
wp_send_json([ 'error' => false, 'comment' => $comment ]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment