* Handles updating and editing comments.
* @file This file contains functionality for the admin comments page.
* @output wp-admin/js/edit-comments.js
/* global adminCommentsSettings, thousandsSeparator, list_args, QTags, ajaxurl, wpAjax */
/* global commentReply, theExtraList, theList, setCommentsList */
var getCount, updateCount, updateCountText, updatePending, updateApproved,
updateHtmlTitle, updateDashboardText, updateInModerationText, adminTitle = document.title,
isDashboard = $('#dashboard_right_now').length,
* Extracts a number from the content of a jQuery element.
* @param {jQuery} el jQuery element.
* @return {number} The number found in the given element.
getCount = function(el) {
var n = parseInt( el.html().replace(/[^0-9]+/g, ''), 10 );
* Updates an html element with a localized number string.
* @param {jQuery} el The jQuery element to update.
* @param {number} n Number to be put in the element.
updateCount = function(el, n) {
n = n < 1 ? '0' : n.toString();
n1 = thousandsSeparator + n.substr(n.length - 3) + n1;
n = n.substr(0, n.length - 3);
* Updates the number of approved comments on a specific post and the filter bar.
* @param {number} diff The amount to lower or raise the approved count with.
* @param {number} commentPostId The ID of the post to be updated.
updateApproved = function( diff, commentPostId ) {
var postSelector = '.post-com-count-' + commentPostId,
noClass = 'comment-count-no-comments',
approvedClass = 'comment-count-approved',
updateCountText( 'span.approved-count', diff );
// Cache selectors to not get duplicates.
approved = $( 'span.' + approvedClass, postSelector );
noComments = $( 'span.' + noClass, postSelector );
approved.each(function() {
var a = $(this), n = getCount(a) + diff;
a.removeClass( approvedClass ).addClass( noClass );
a.addClass( approvedClass ).removeClass( noClass );
noComments.each(function() {
a.removeClass( noClass ).addClass( approvedClass );
a.addClass( noClass ).removeClass( approvedClass );
* Updates a number count in all matched HTML elements
* @param {string} selector The jQuery selector for elements to update a count
* @param {number} diff The amount to lower or raise the count with.
updateCountText = function( selector, diff ) {
$( selector ).each(function() {
var a = $(this), n = getCount(a) + diff;
* Updates a text about comment count on the dashboard.
* @param {Object} response Ajax response from the server that includes a
* translated "comment count" message.
updateDashboardText = function( response ) {
if ( ! isDashboard || ! response || ! response.i18n_comments_text ) {
$( '.comment-count a', '#dashboard_right_now' ).text( response.i18n_comments_text );
* Updates the "comments in moderation" text across the UI.
* @param {Object} response Ajax response from the server that includes a
* translated "comments in moderation" message.
updateInModerationText = function( response ) {
if ( ! response || ! response.i18n_moderation_text ) {
// Update the "comment in moderation" text across the UI.
$( '.comments-in-moderation-text' ).text( response.i18n_moderation_text );
// Hide the "comment in moderation" text in the Dashboard "At a Glance" widget.
if ( isDashboard && response.in_moderation ) {
$( '.comment-mod-count', '#dashboard_right_now' )
[ response.in_moderation > 0 ? 'removeClass' : 'addClass' ]( 'hidden' );
* Updates the title of the document with the number comments to be approved.
* @param {number} diff The amount to lower or raise the number of to be
* approved comments with.
updateHtmlTitle = function( diff ) {
var newTitle, regExMatch, titleCount, commentFrag;
/* translators: %s: Comments count. */
titleRegEx = titleRegEx || new RegExp( __( 'Comments (%s)' ).replace( '%s', '\\([0-9' + thousandsSeparator + ']+\\)' ) + '?' );
// Count funcs operate on a $'d element.
titleDiv = titleDiv || $( '<div />' );
commentFrag = titleRegEx.exec( document.title );
commentFrag = commentFrag[0];
titleDiv.html( commentFrag );
titleCount = getCount( titleDiv ) + diff;
updateCount( titleDiv, titleCount );
regExMatch = titleRegEx.exec( document.title );
/* translators: %s: Comments count. */
newTitle = document.title.replace( regExMatch[0], __( 'Comments (%s)' ).replace( '%s', titleDiv.text() ) + ' ' );
regExMatch = titleRegEx.exec( newTitle );
newTitle = newTitle.replace( regExMatch[0], __( 'Comments' ) );
document.title = newTitle;
* Updates the number of pending comments on a specific post and the filter bar.
* @param {number} diff The amount to lower or raise the pending count with.
* @param {number} commentPostId The ID of the post to be updated.
updatePending = function( diff, commentPostId ) {
var postSelector = '.post-com-count-' + commentPostId,
noClass = 'comment-count-no-pending',
noParentClass = 'post-com-count-no-pending',
pendingClass = 'comment-count-pending',
$( 'span.pending-count' ).each(function() {
var a = $(this), n = getCount(a) + diff;
a.closest('.awaiting-mod')[ 0 === n ? 'addClass' : 'removeClass' ]('count-0');
// Cache selectors to not get dupes.
pending = $( 'span.' + pendingClass, postSelector );
noPending = $( 'span.' + noClass, postSelector );
pending.each(function() {
var a = $(this), n = getCount(a) + diff;
a.parent().addClass( noParentClass );
a.removeClass( pendingClass ).addClass( noClass );
a.parent().removeClass( noParentClass );
a.addClass( pendingClass ).removeClass( noClass );
noPending.each(function() {
a.parent().removeClass( noParentClass );
a.removeClass( noClass ).addClass( pendingClass );
a.parent().addClass( noParentClass );
a.addClass( noClass ).removeClass( pendingClass );
* Initializes the comments list.
window.setCommentsList = function() {
var totalInput, perPageInput, pageInput, dimAfter, delBefore, updateTotalCount, delAfter, refillTheExtraList, diff,
totalInput = $('input[name="_total"]', '#comments-form');
perPageInput = $('input[name="_per_page"]', '#comments-form');
pageInput = $('input[name="_page"]', '#comments-form');
* Updates the total with the latest count.
* The time parameter makes sure that we only update the total if this value is
* a newer value than we previously received.
* The time and setConfidentTime parameters make sure that we only update the
* total when necessary. So a value that has been generated earlier will not
* @param {number} total Total number of comments.
* @param {number} time Unix timestamp of response.
* @param {boolean} setConfidentTime Whether to update the last confident time
updateTotalCount = function( total, time, setConfidentTime ) {
if ( time < lastConfidentTime )
lastConfidentTime = time;
totalInput.val( total.toString() );
* Changes DOM that need to be changed after a list item has been dimmed.
* @param {Object} r Ajax response object.
* @param {Object} settings Settings for the wpList object.
dimAfter = function( r, settings ) {
var editRow, replyID, replyButton, response,
c = $( '#' + settings.element );
if ( true !== settings.parsed ) {
response = settings.parsed.responses[0];
editRow = $('#replyrow');
replyID = $('#comment_ID', editRow).val();
replyButton = $('#replybtn', editRow);
if ( c.is('.unapproved') ) {
if ( settings.data.id == replyID )
replyButton.text( __( 'Approve and Reply' ) );
c.find( '.row-actions span.view' ).addClass( 'hidden' ).end()
.find( 'div.comment_status' ).html( '0' );
if ( settings.data.id == replyID )
replyButton.text( __( 'Reply' ) );
c.find( '.row-actions span.view' ).removeClass( 'hidden' ).end()
.find( 'div.comment_status' ).html( '1' );
diff = $('#' + settings.element).is('.' + settings.dimClass) ? 1 : -1;
updateDashboardText( response.supplemental );
updateInModerationText( response.supplemental );
updatePending( diff, response.supplemental.postId );
updateApproved( -1 * diff, response.supplemental.postId );
updateApproved( -1 * diff );
* Handles marking a comment as spam or trashing the comment.
* Is executed in the list delBefore hook.
* @param {Object} settings Settings for the wpList object.
* @param {HTMLElement} list Comments table element.
* @return {Object} The settings object.
delBefore = function( settings, list ) {
var note, id, el, n, h, a, author,
wpListsData = $( settings.target ).attr( 'data-wp-lists' );
settings.data._total = totalInput.val() || 0;
settings.data._per_page = perPageInput.val() || 0;
settings.data._page = pageInput.val() || 0;
settings.data._url = document.location.href;
settings.data.comment_status = $('input[name="comment_status"]', '#comments-form').val();
if ( wpListsData.indexOf(':trash=1') != -1 )
else if ( wpListsData.indexOf(':spam=1') != -1 )
id = wpListsData.replace(/.*?comment-([0-9]+).*/, '$1');
el = $('#comment-' + id);
note = $('#' + action + '-undo-holder').html();
el.find('.check-column :checkbox').prop('checked', false); // Uncheck the row so as not to be affected by Bulk Edits.
if ( el.siblings('#replyrow').length && commentReply.cid == id )
n = el.children(':visible').length;
author = $('.author strong', el).text();
h = $('<tr id="undo-' + id + '" class="undo un' + action + '" style="display:none;"><td colspan="' + n + '">' + note + '</td></tr>');
author = $('.comment-author', el).text();
h = $('<div id="undo-' + id + '" style="display:none;" class="undo un' + action + '">' + note + '</div>');
$('strong', '#undo-' + id).text(author);
a = $('.undo a', '#undo-' + id);
a.attr('href', 'comment.php?action=un' + action + 'comment&c=' + id + '&_wpnonce=' + settings.data._ajax_nonce);
a.attr('data-wp-lists', 'delete:the-comment-list:comment-' + id + '::un' + action + '=1');
a.attr('class', 'vim-z vim-destructive aria-button-if-js');
$('.avatar', el).first().clone().prependTo('#undo-' + id + ' .' + action + '-undo-inside');
a.on( 'click', function( e ){
e.stopPropagation(); // Ticket #35904.
$('#undo-' + id).css( {backgroundColor:'#ceb'} ).fadeOut(350, function(){
$('#comment-' + id).css('backgroundColor', '').fadeIn(300, function(){ $(this).show(); });
* Handles actions that need to be done after marking as spam or thrashing a
* The ajax requests return the unix time stamp a comment was marked as spam or
* trashed. We use this to have a correct total amount of comments.
* @param {Object} r Ajax response object.
* @param {Object} settings Settings for the wpList object.
delAfter = function( r, settings ) {
var total_items_i18n, total, animated, animatedCallback,
response = true === settings.parsed ? {} : settings.parsed.responses[0],
commentStatus = true === settings.parsed ? '' : response.supplemental.status,
commentPostId = true === settings.parsed ? '' : response.supplemental.postId,
newTotal = true === settings.parsed ? '' : response.supplemental,
targetParent = $( settings.target ).parent(),
commentRow = $('#' + settings.element),
spamDiff, trashDiff, pendingDiff, approvedDiff,
* As `wpList` toggles only the `unapproved` class, the approved comment
* rows can have both the `approved` and `unapproved` classes.
approved = commentRow.hasClass( 'approved' ) && ! commentRow.hasClass( 'unapproved' ),
unapproved = commentRow.hasClass( 'unapproved' ),
spammed = commentRow.hasClass( 'spam' ),
trashed = commentRow.hasClass( 'trash' ),
undoing = false; // Ticket #35904.
updateDashboardText( newTotal );
updateInModerationText( newTotal );