edit-comments.js 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342
  1. /**
  2. * Handles updating and editing comments.
  3. *
  4. * @file This file contains functionality for the admin comments page.
  5. * @since 2.1.0
  6. * @output wp-admin/js/edit-comments.js
  7. */
  8. /* global adminCommentsL10n, thousandsSeparator, list_args, QTags, ajaxurl, wpAjax */
  9. /* global commentReply, theExtraList, theList, setCommentsList */
  10. (function($) {
  11. var getCount, updateCount, updateCountText, updatePending, updateApproved,
  12. updateHtmlTitle, updateDashboardText, updateInModerationText, adminTitle = document.title,
  13. isDashboard = $('#dashboard_right_now').length,
  14. titleDiv, titleRegEx;
  15. /**
  16. * Extracts a number from the content of a jQuery element.
  17. *
  18. * @since 2.9.0
  19. * @access private
  20. *
  21. * @param {jQuery} el jQuery element.
  22. *
  23. * @return {number} The number found in the given element.
  24. */
  25. getCount = function(el) {
  26. var n = parseInt( el.html().replace(/[^0-9]+/g, ''), 10 );
  27. if ( isNaN(n) ) {
  28. return 0;
  29. }
  30. return n;
  31. };
  32. /**
  33. * Updates an html element with a localized number string.
  34. *
  35. * @since 2.9.0
  36. * @access private
  37. *
  38. * @param {jQuery} el The jQuery element to update.
  39. * @param {number} n Number to be put in the element.
  40. *
  41. * @return {void}
  42. */
  43. updateCount = function(el, n) {
  44. var n1 = '';
  45. if ( isNaN(n) ) {
  46. return;
  47. }
  48. n = n < 1 ? '0' : n.toString();
  49. if ( n.length > 3 ) {
  50. while ( n.length > 3 ) {
  51. n1 = thousandsSeparator + n.substr(n.length - 3) + n1;
  52. n = n.substr(0, n.length - 3);
  53. }
  54. n = n + n1;
  55. }
  56. el.html(n);
  57. };
  58. /**
  59. * Updates the number of approved comments on a specific post and the filter bar.
  60. *
  61. * @since 4.4.0
  62. * @access private
  63. *
  64. * @param {number} diff The amount to lower or raise the approved count with.
  65. * @param {number} commentPostId The ID of the post to be updated.
  66. *
  67. * @return {void}
  68. */
  69. updateApproved = function( diff, commentPostId ) {
  70. var postSelector = '.post-com-count-' + commentPostId,
  71. noClass = 'comment-count-no-comments',
  72. approvedClass = 'comment-count-approved',
  73. approved,
  74. noComments;
  75. updateCountText( 'span.approved-count', diff );
  76. if ( ! commentPostId ) {
  77. return;
  78. }
  79. // Cache selectors to not get duplicates.
  80. approved = $( 'span.' + approvedClass, postSelector );
  81. noComments = $( 'span.' + noClass, postSelector );
  82. approved.each(function() {
  83. var a = $(this), n = getCount(a) + diff;
  84. if ( n < 1 )
  85. n = 0;
  86. if ( 0 === n ) {
  87. a.removeClass( approvedClass ).addClass( noClass );
  88. } else {
  89. a.addClass( approvedClass ).removeClass( noClass );
  90. }
  91. updateCount( a, n );
  92. });
  93. noComments.each(function() {
  94. var a = $(this);
  95. if ( diff > 0 ) {
  96. a.removeClass( noClass ).addClass( approvedClass );
  97. } else {
  98. a.addClass( noClass ).removeClass( approvedClass );
  99. }
  100. updateCount( a, diff );
  101. });
  102. };
  103. /**
  104. * Updates a number count in all matched HTML elements
  105. *
  106. * @since 4.4.0
  107. * @access private
  108. *
  109. * @param {string} selector The jQuery selector for elements to update a count
  110. * for.
  111. * @param {number} diff The amount to lower or raise the count with.
  112. *
  113. * @return {void}
  114. */
  115. updateCountText = function( selector, diff ) {
  116. $( selector ).each(function() {
  117. var a = $(this), n = getCount(a) + diff;
  118. if ( n < 1 ) {
  119. n = 0;
  120. }
  121. updateCount( a, n );
  122. });
  123. };
  124. /**
  125. * Updates a text about comment count on the dashboard.
  126. *
  127. * @since 4.4.0
  128. * @access private
  129. *
  130. * @param {Object} response Ajax response from the server that includes a
  131. * translated "comment count" message.
  132. *
  133. * @return {void}
  134. */
  135. updateDashboardText = function( response ) {
  136. if ( ! isDashboard || ! response || ! response.i18n_comments_text ) {
  137. return;
  138. }
  139. $( '.comment-count a', '#dashboard_right_now' ).text( response.i18n_comments_text );
  140. };
  141. /**
  142. * Updates the "comments in moderation" text across the UI.
  143. *
  144. * @since 5.2.0
  145. *
  146. * @param {object} response Ajax response from the server that includes a
  147. * translated "comments in moderation" message.
  148. *
  149. * @return {void}
  150. */
  151. updateInModerationText = function( response ) {
  152. if ( ! response || ! response.i18n_moderation_text ) {
  153. return;
  154. }
  155. // Update the "comment in moderation" text across the UI.
  156. $( '.comments-in-moderation-text' ).text( response.i18n_moderation_text );
  157. // Hide the "comment in moderation" text in the Dashboard "At a Glance" widget.
  158. if ( isDashboard && response.in_moderation ) {
  159. $( '.comment-mod-count', '#dashboard_right_now' )
  160. [ response.in_moderation > 0 ? 'removeClass' : 'addClass' ]( 'hidden' );
  161. }
  162. };
  163. /**
  164. * Updates the title of the document with the number comments to be approved.
  165. *
  166. * @since 4.4.0
  167. * @access private
  168. *
  169. * @param {number} diff The amount to lower or raise the number of to be
  170. * approved comments with.
  171. *
  172. * @return {void}
  173. */
  174. updateHtmlTitle = function( diff ) {
  175. var newTitle, regExMatch, titleCount, commentFrag;
  176. titleRegEx = titleRegEx || new RegExp( adminCommentsL10n.docTitleCommentsCount.replace( '%s', '\\([0-9' + thousandsSeparator + ']+\\)' ) + '?' );
  177. // count funcs operate on a $'d element
  178. titleDiv = titleDiv || $( '<div />' );
  179. newTitle = adminTitle;
  180. commentFrag = titleRegEx.exec( document.title );
  181. if ( commentFrag ) {
  182. commentFrag = commentFrag[0];
  183. titleDiv.html( commentFrag );
  184. titleCount = getCount( titleDiv ) + diff;
  185. } else {
  186. titleDiv.html( 0 );
  187. titleCount = diff;
  188. }
  189. if ( titleCount >= 1 ) {
  190. updateCount( titleDiv, titleCount );
  191. regExMatch = titleRegEx.exec( document.title );
  192. if ( regExMatch ) {
  193. newTitle = document.title.replace( regExMatch[0], adminCommentsL10n.docTitleCommentsCount.replace( '%s', titleDiv.text() ) + ' ' );
  194. }
  195. } else {
  196. regExMatch = titleRegEx.exec( newTitle );
  197. if ( regExMatch ) {
  198. newTitle = newTitle.replace( regExMatch[0], adminCommentsL10n.docTitleComments );
  199. }
  200. }
  201. document.title = newTitle;
  202. };
  203. /**
  204. * Updates the number of pending comments on a specific post and the filter bar.
  205. *
  206. * @since 3.2.0
  207. * @access private
  208. *
  209. * @param {number} diff The amount to lower or raise the pending count with.
  210. * @param {number} commentPostId The ID of the post to be updated.
  211. *
  212. * @return {void}
  213. */
  214. updatePending = function( diff, commentPostId ) {
  215. var postSelector = '.post-com-count-' + commentPostId,
  216. noClass = 'comment-count-no-pending',
  217. noParentClass = 'post-com-count-no-pending',
  218. pendingClass = 'comment-count-pending',
  219. pending,
  220. noPending;
  221. if ( ! isDashboard ) {
  222. updateHtmlTitle( diff );
  223. }
  224. $( 'span.pending-count' ).each(function() {
  225. var a = $(this), n = getCount(a) + diff;
  226. if ( n < 1 )
  227. n = 0;
  228. a.closest('.awaiting-mod')[ 0 === n ? 'addClass' : 'removeClass' ]('count-0');
  229. updateCount( a, n );
  230. });
  231. if ( ! commentPostId ) {
  232. return;
  233. }
  234. // cache selectors to not get dupes
  235. pending = $( 'span.' + pendingClass, postSelector );
  236. noPending = $( 'span.' + noClass, postSelector );
  237. pending.each(function() {
  238. var a = $(this), n = getCount(a) + diff;
  239. if ( n < 1 )
  240. n = 0;
  241. if ( 0 === n ) {
  242. a.parent().addClass( noParentClass );
  243. a.removeClass( pendingClass ).addClass( noClass );
  244. } else {
  245. a.parent().removeClass( noParentClass );
  246. a.addClass( pendingClass ).removeClass( noClass );
  247. }
  248. updateCount( a, n );
  249. });
  250. noPending.each(function() {
  251. var a = $(this);
  252. if ( diff > 0 ) {
  253. a.parent().removeClass( noParentClass );
  254. a.removeClass( noClass ).addClass( pendingClass );
  255. } else {
  256. a.parent().addClass( noParentClass );
  257. a.addClass( noClass ).removeClass( pendingClass );
  258. }
  259. updateCount( a, diff );
  260. });
  261. };
  262. /**
  263. * Initializes the comments list.
  264. *
  265. * @since 4.4.0
  266. *
  267. * @global
  268. *
  269. * @return {void}
  270. */
  271. window.setCommentsList = function() {
  272. var totalInput, perPageInput, pageInput, dimAfter, delBefore, updateTotalCount, delAfter, refillTheExtraList, diff,
  273. lastConfidentTime = 0;
  274. totalInput = $('input[name="_total"]', '#comments-form');
  275. perPageInput = $('input[name="_per_page"]', '#comments-form');
  276. pageInput = $('input[name="_page"]', '#comments-form');
  277. /**
  278. * Updates the total with the latest count.
  279. *
  280. * The time parameter makes sure that we only update the total if this value is
  281. * a newer value than we previously received.
  282. *
  283. * The time and setConfidentTime parameters make sure that we only update the
  284. * total when necessary. So a value that has been generated earlier will not
  285. * update the total.
  286. *
  287. * @since 2.8.0
  288. * @access private
  289. *
  290. * @param {number} total Total number of comments.
  291. * @param {number} time Unix timestamp of response.
  292. * @param {boolean} setConfidentTime Whether to update the last confident time
  293. * with the given time.
  294. *
  295. * @return {void}
  296. */
  297. updateTotalCount = function( total, time, setConfidentTime ) {
  298. if ( time < lastConfidentTime )
  299. return;
  300. if ( setConfidentTime )
  301. lastConfidentTime = time;
  302. totalInput.val( total.toString() );
  303. };
  304. /**
  305. * Changes DOM that need to be changed after a list item has been dimmed.
  306. *
  307. * @since 2.5.0
  308. * @access private
  309. *
  310. * @param {Object} r Ajax response object.
  311. * @param {Object} settings Settings for the wpList object.
  312. *
  313. * @return {void}
  314. */
  315. dimAfter = function( r, settings ) {
  316. var editRow, replyID, replyButton, response,
  317. c = $( '#' + settings.element );
  318. if ( true !== settings.parsed ) {
  319. response = settings.parsed.responses[0];
  320. }
  321. editRow = $('#replyrow');
  322. replyID = $('#comment_ID', editRow).val();
  323. replyButton = $('#replybtn', editRow);
  324. if ( c.is('.unapproved') ) {
  325. if ( settings.data.id == replyID )
  326. replyButton.text(adminCommentsL10n.replyApprove);
  327. c.find( '.row-actions span.view' ).addClass( 'hidden' ).end()
  328. .find( 'div.comment_status' ).html( '0' );
  329. } else {
  330. if ( settings.data.id == replyID )
  331. replyButton.text(adminCommentsL10n.reply);
  332. c.find( '.row-actions span.view' ).removeClass( 'hidden' ).end()
  333. .find( 'div.comment_status' ).html( '1' );
  334. }
  335. diff = $('#' + settings.element).is('.' + settings.dimClass) ? 1 : -1;
  336. if ( response ) {
  337. updateDashboardText( response.supplemental );
  338. updateInModerationText( response.supplemental );
  339. updatePending( diff, response.supplemental.postId );
  340. updateApproved( -1 * diff, response.supplemental.postId );
  341. } else {
  342. updatePending( diff );
  343. updateApproved( -1 * diff );
  344. }
  345. };
  346. /**
  347. * Handles marking a comment as spam or trashing the comment.
  348. *
  349. * Is executed in the list delBefore hook.
  350. *
  351. * @since 2.8.0
  352. * @access private
  353. *
  354. * @param {Object} settings Settings for the wpList object.
  355. * @param {HTMLElement} list Comments table element.
  356. *
  357. * @return {Object} The settings object.
  358. */
  359. delBefore = function( settings, list ) {
  360. var note, id, el, n, h, a, author,
  361. action = false,
  362. wpListsData = $( settings.target ).attr( 'data-wp-lists' );
  363. settings.data._total = totalInput.val() || 0;
  364. settings.data._per_page = perPageInput.val() || 0;
  365. settings.data._page = pageInput.val() || 0;
  366. settings.data._url = document.location.href;
  367. settings.data.comment_status = $('input[name="comment_status"]', '#comments-form').val();
  368. if ( wpListsData.indexOf(':trash=1') != -1 )
  369. action = 'trash';
  370. else if ( wpListsData.indexOf(':spam=1') != -1 )
  371. action = 'spam';
  372. if ( action ) {
  373. id = wpListsData.replace(/.*?comment-([0-9]+).*/, '$1');
  374. el = $('#comment-' + id);
  375. note = $('#' + action + '-undo-holder').html();
  376. el.find('.check-column :checkbox').prop('checked', false); // Uncheck the row so as not to be affected by Bulk Edits.
  377. if ( el.siblings('#replyrow').length && commentReply.cid == id )
  378. commentReply.close();
  379. if ( el.is('tr') ) {
  380. n = el.children(':visible').length;
  381. author = $('.author strong', el).text();
  382. h = $('<tr id="undo-' + id + '" class="undo un' + action + '" style="display:none;"><td colspan="' + n + '">' + note + '</td></tr>');
  383. } else {
  384. author = $('.comment-author', el).text();
  385. h = $('<div id="undo-' + id + '" style="display:none;" class="undo un' + action + '">' + note + '</div>');
  386. }
  387. el.before(h);
  388. $('strong', '#undo-' + id).text(author);
  389. a = $('.undo a', '#undo-' + id);
  390. a.attr('href', 'comment.php?action=un' + action + 'comment&c=' + id + '&_wpnonce=' + settings.data._ajax_nonce);
  391. a.attr('data-wp-lists', 'delete:the-comment-list:comment-' + id + '::un' + action + '=1');
  392. a.attr('class', 'vim-z vim-destructive aria-button-if-js');
  393. $('.avatar', el).first().clone().prependTo('#undo-' + id + ' .' + action + '-undo-inside');
  394. a.click(function( e ){
  395. e.preventDefault();
  396. e.stopPropagation(); // ticket #35904
  397. list.wpList.del(this);
  398. $('#undo-' + id).css( {backgroundColor:'#ceb'} ).fadeOut(350, function(){
  399. $(this).remove();
  400. $('#comment-' + id).css('backgroundColor', '').fadeIn(300, function(){ $(this).show(); });
  401. });
  402. });
  403. }
  404. return settings;
  405. };
  406. /**
  407. * Handles actions that need to be done after marking as spam or thrashing a
  408. * comment.
  409. *
  410. * The ajax requests return the unix time stamp a comment was marked as spam or
  411. * trashed. We use this to have a correct total amount of comments.
  412. *
  413. * @since 2.5.0
  414. * @access private
  415. *
  416. * @param {Object} r Ajax response object.
  417. * @param {Object} settings Settings for the wpList object.
  418. *
  419. * @return {void}
  420. */
  421. delAfter = function( r, settings ) {
  422. var total_items_i18n, total, animated, animatedCallback,
  423. response = true === settings.parsed ? {} : settings.parsed.responses[0],
  424. commentStatus = true === settings.parsed ? '' : response.supplemental.status,
  425. commentPostId = true === settings.parsed ? '' : response.supplemental.postId,
  426. newTotal = true === settings.parsed ? '' : response.supplemental,
  427. targetParent = $( settings.target ).parent(),
  428. commentRow = $('#' + settings.element),
  429. spamDiff, trashDiff, pendingDiff, approvedDiff,
  430. /*
  431. * As `wpList` toggles only the `unapproved` class, the approved comment
  432. * rows can have both the `approved` and `unapproved` classes.
  433. */
  434. approved = commentRow.hasClass( 'approved' ) && ! commentRow.hasClass( 'unapproved' ),
  435. unapproved = commentRow.hasClass( 'unapproved' ),
  436. spammed = commentRow.hasClass( 'spam' ),
  437. trashed = commentRow.hasClass( 'trash' ),
  438. undoing = false; // ticket #35904
  439. updateDashboardText( newTotal );
  440. updateInModerationText( newTotal );
  441. // the order of these checks is important
  442. // .unspam can also have .approve or .unapprove
  443. // .untrash can also have .approve or .unapprove
  444. if ( targetParent.is( 'span.undo' ) ) {
  445. // the comment was spammed
  446. if ( targetParent.hasClass( 'unspam' ) ) {
  447. spamDiff = -1;
  448. if ( 'trash' === commentStatus ) {
  449. trashDiff = 1;
  450. } else if ( '1' === commentStatus ) {
  451. approvedDiff = 1;
  452. } else if ( '0' === commentStatus ) {
  453. pendingDiff = 1;
  454. }
  455. // the comment was trashed
  456. } else if ( targetParent.hasClass( 'untrash' ) ) {
  457. trashDiff = -1;
  458. if ( 'spam' === commentStatus ) {
  459. spamDiff = 1;
  460. } else if ( '1' === commentStatus ) {
  461. approvedDiff = 1;
  462. } else if ( '0' === commentStatus ) {
  463. pendingDiff = 1;
  464. }
  465. }
  466. undoing = true;
  467. // user clicked "Spam"
  468. } else if ( targetParent.is( 'span.spam' ) ) {
  469. // the comment is currently approved
  470. if ( approved ) {
  471. approvedDiff = -1;
  472. // the comment is currently pending
  473. } else if ( unapproved ) {
  474. pendingDiff = -1;
  475. // the comment was in the trash
  476. } else if ( trashed ) {
  477. trashDiff = -1;
  478. }
  479. // you can't spam an item on the spam screen
  480. spamDiff = 1;
  481. // user clicked "Unspam"
  482. } else if ( targetParent.is( 'span.unspam' ) ) {
  483. if ( approved ) {
  484. pendingDiff = 1;
  485. } else if ( unapproved ) {
  486. approvedDiff = 1;
  487. } else if ( trashed ) {
  488. // the comment was previously approved
  489. if ( targetParent.hasClass( 'approve' ) ) {
  490. approvedDiff = 1;
  491. // the comment was previously pending
  492. } else if ( targetParent.hasClass( 'unapprove' ) ) {
  493. pendingDiff = 1;
  494. }
  495. } else if ( spammed ) {
  496. if ( targetParent.hasClass( 'approve' ) ) {
  497. approvedDiff = 1;
  498. } else if ( targetParent.hasClass( 'unapprove' ) ) {
  499. pendingDiff = 1;
  500. }
  501. }
  502. // you can Unspam an item on the spam screen
  503. spamDiff = -1;
  504. // user clicked "Trash"
  505. } else if ( targetParent.is( 'span.trash' ) ) {
  506. if ( approved ) {
  507. approvedDiff = -1;
  508. } else if ( unapproved ) {
  509. pendingDiff = -1;
  510. // the comment was in the spam queue
  511. } else if ( spammed ) {
  512. spamDiff = -1;
  513. }
  514. // you can't trash an item on the trash screen
  515. trashDiff = 1;
  516. // user clicked "Restore"
  517. } else if ( targetParent.is( 'span.untrash' ) ) {
  518. if ( approved ) {
  519. pendingDiff = 1;
  520. } else if ( unapproved ) {
  521. approvedDiff = 1;
  522. } else if ( trashed ) {
  523. if ( targetParent.hasClass( 'approve' ) ) {
  524. approvedDiff = 1;
  525. } else if ( targetParent.hasClass( 'unapprove' ) ) {
  526. pendingDiff = 1;
  527. }
  528. }
  529. // you can't go from trash to spam
  530. // you can untrash on the trash screen
  531. trashDiff = -1;
  532. // User clicked "Approve"
  533. } else if ( targetParent.is( 'span.approve:not(.unspam):not(.untrash)' ) ) {
  534. approvedDiff = 1;
  535. pendingDiff = -1;
  536. // User clicked "Unapprove"
  537. } else if ( targetParent.is( 'span.unapprove:not(.unspam):not(.untrash)' ) ) {
  538. approvedDiff = -1;
  539. pendingDiff = 1;
  540. // User clicked "Delete Permanently"
  541. } else if ( targetParent.is( 'span.delete' ) ) {
  542. if ( spammed ) {
  543. spamDiff = -1;
  544. } else if ( trashed ) {
  545. trashDiff = -1;
  546. }
  547. }
  548. if ( pendingDiff ) {
  549. updatePending( pendingDiff, commentPostId );
  550. updateCountText( 'span.all-count', pendingDiff );
  551. }
  552. if ( approvedDiff ) {
  553. updateApproved( approvedDiff, commentPostId );
  554. updateCountText( 'span.all-count', approvedDiff );
  555. }
  556. if ( spamDiff ) {
  557. updateCountText( 'span.spam-count', spamDiff );
  558. }
  559. if ( trashDiff ) {
  560. updateCountText( 'span.trash-count', trashDiff );
  561. }
  562. if (
  563. ( ( 'trash' === settings.data.comment_status ) && !getCount( $( 'span.trash-count' ) ) ) ||
  564. ( ( 'spam' === settings.data.comment_status ) && !getCount( $( 'span.spam-count' ) ) )
  565. ) {
  566. $( '#delete_all' ).hide();
  567. }
  568. if ( ! isDashboard ) {
  569. total = totalInput.val() ? parseInt( totalInput.val(), 10 ) : 0;
  570. if ( $(settings.target).parent().is('span.undo') )
  571. total++;
  572. else
  573. total--;
  574. if ( total < 0 )
  575. total = 0;
  576. if ( 'object' === typeof r ) {
  577. if ( response.supplemental.total_items_i18n && lastConfidentTime < response.supplemental.time ) {
  578. total_items_i18n = response.supplemental.total_items_i18n || '';
  579. if ( total_items_i18n ) {
  580. $('.displaying-num').text( total_items_i18n.replace( '&nbsp;', String.fromCharCode( 160 ) ) );
  581. $('.total-pages').text( response.supplemental.total_pages_i18n.replace( '&nbsp;', String.fromCharCode( 160 ) ) );
  582. $('.tablenav-pages').find('.next-page, .last-page').toggleClass('disabled', response.supplemental.total_pages == $('.current-page').val());
  583. }
  584. updateTotalCount( total, response.supplemental.time, true );
  585. } else if ( response.supplemental.time ) {
  586. updateTotalCount( total, response.supplemental.time, false );
  587. }
  588. } else {
  589. updateTotalCount( total, r, false );
  590. }
  591. }
  592. if ( ! theExtraList || theExtraList.length === 0 || theExtraList.children().length === 0 || undoing ) {
  593. return;
  594. }
  595. theList.get(0).wpList.add( theExtraList.children( ':eq(0):not(.no-items)' ).remove().clone() );
  596. refillTheExtraList();
  597. animated = $( ':animated', '#the-comment-list' );
  598. animatedCallback = function() {
  599. if ( ! $( '#the-comment-list tr:visible' ).length ) {
  600. theList.get(0).wpList.add( theExtraList.find( '.no-items' ).clone() );
  601. }
  602. };
  603. if ( animated.length ) {
  604. animated.promise().done( animatedCallback );
  605. } else {
  606. animatedCallback();
  607. }
  608. };
  609. /**
  610. * Retrieves additional comments to populate the extra list.
  611. *
  612. * @since 3.1.0
  613. * @access private
  614. *
  615. * @param {boolean} [ev] Repopulate the extra comments list if true.
  616. *
  617. * @return {void}
  618. */
  619. refillTheExtraList = function(ev) {
  620. var args = $.query.get(), total_pages = $('.total-pages').text(), per_page = $('input[name="_per_page"]', '#comments-form').val();
  621. if (! args.paged)
  622. args.paged = 1;
  623. if (args.paged > total_pages) {
  624. return;
  625. }
  626. if (ev) {
  627. theExtraList.empty();
  628. args.number = Math.min(8, per_page); // see WP_Comments_List_Table::prepare_items() @ class-wp-comments-list-table.php
  629. } else {
  630. args.number = 1;
  631. args.offset = Math.min(8, per_page) - 1; // fetch only the next item on the extra list
  632. }
  633. args.no_placeholder = true;
  634. args.paged ++;
  635. // $.query.get() needs some correction to be sent into an ajax request
  636. if ( true === args.comment_type )
  637. args.comment_type = '';
  638. args = $.extend(args, {
  639. 'action': 'fetch-list',
  640. 'list_args': list_args,
  641. '_ajax_fetch_list_nonce': $('#_ajax_fetch_list_nonce').val()
  642. });
  643. $.ajax({
  644. url: ajaxurl,
  645. global: false,
  646. dataType: 'json',
  647. data: args,
  648. success: function(response) {
  649. theExtraList.get(0).wpList.add( response.rows );
  650. }
  651. });
  652. };
  653. /**
  654. * Globally available jQuery object referring to the extra comments list.
  655. *
  656. * @global
  657. */
  658. window.theExtraList = $('#the-extra-comment-list').wpList( { alt: '', delColor: 'none', addColor: 'none' } );
  659. /**
  660. * Globally available jQuery object referring to the comments list.
  661. *
  662. * @global
  663. */
  664. window.theList = $('#the-comment-list').wpList( { alt: '', delBefore: delBefore, dimAfter: dimAfter, delAfter: delAfter, addColor: 'none' } )
  665. .bind('wpListDelEnd', function(e, s){
  666. var wpListsData = $(s.target).attr('data-wp-lists'), id = s.element.replace(/[^0-9]+/g, '');
  667. if ( wpListsData.indexOf(':trash=1') != -1 || wpListsData.indexOf(':spam=1') != -1 )
  668. $('#undo-' + id).fadeIn(300, function(){ $(this).show(); });
  669. });
  670. };
  671. /**
  672. * Object containing functionality regarding the comment quick editor and reply
  673. * editor.
  674. *
  675. * @since 2.7.0
  676. *
  677. * @global
  678. */
  679. window.commentReply = {
  680. cid : '',
  681. act : '',
  682. originalContent : '',
  683. /**
  684. * Initializes the comment reply functionality.
  685. *
  686. * @memberof commentReply
  687. *
  688. * @since 2.7.0
  689. */
  690. init : function() {
  691. var row = $('#replyrow');
  692. $( '.cancel', row ).click( function() { return commentReply.revert(); } );
  693. $( '.save', row ).click( function() { return commentReply.send(); } );
  694. $( 'input#author-name, input#author-email, input#author-url', row ).keypress( function( e ) {
  695. if ( e.which == 13 ) {
  696. commentReply.send();
  697. e.preventDefault();
  698. return false;
  699. }
  700. });
  701. // add events
  702. $('#the-comment-list .column-comment > p').dblclick(function(){
  703. commentReply.toggle($(this).parent());
  704. });
  705. $('#doaction, #doaction2, #post-query-submit').click(function(){
  706. if ( $('#the-comment-list #replyrow').length > 0 )
  707. commentReply.close();
  708. });
  709. this.comments_listing = $('#comments-form > input[name="comment_status"]').val() || '';
  710. },
  711. /**
  712. * Adds doubleclick event handler to the given comment list row.
  713. *
  714. * The double-click event will toggle the comment edit or reply form.
  715. *
  716. * @since 2.7.0
  717. *
  718. * @memberof commentReply
  719. *
  720. * @param {Object} r The row to add double click handlers to.
  721. *
  722. * @return {void}
  723. */
  724. addEvents : function(r) {
  725. r.each(function() {
  726. $(this).find('.column-comment > p').dblclick(function(){
  727. commentReply.toggle($(this).parent());
  728. });
  729. });
  730. },
  731. /**
  732. * Opens the quick edit for the given element.
  733. *
  734. * @since 2.7.0
  735. *
  736. * @memberof commentReply
  737. *
  738. * @param {HTMLElement} el The element you want to open the quick editor for.
  739. *
  740. * @return {void}
  741. */
  742. toggle : function(el) {
  743. if ( 'none' !== $( el ).css( 'display' ) && ( $( '#replyrow' ).parent().is('#com-reply') || window.confirm( adminCommentsL10n.warnQuickEdit ) ) ) {
  744. $( el ).find( 'button.vim-q' ).click();
  745. }
  746. },
  747. /**
  748. * Closes the comment quick edit or reply form and undoes any changes.
  749. *
  750. * @since 2.7.0
  751. *
  752. * @memberof commentReply
  753. *
  754. * @return {void}
  755. */
  756. revert : function() {
  757. if ( $('#the-comment-list #replyrow').length < 1 )
  758. return false;
  759. $('#replyrow').fadeOut('fast', function(){
  760. commentReply.close();
  761. });
  762. },
  763. /**
  764. * Closes the comment quick edit or reply form and undoes any changes.
  765. *
  766. * @since 2.7.0
  767. *
  768. * @memberof commentReply
  769. *
  770. * @return {void}
  771. */
  772. close : function() {
  773. var commentRow = $(),
  774. replyRow = $( '#replyrow' );
  775. // Return if the replyrow is not showing.
  776. if ( replyRow.parent().is( '#com-reply' ) ) {
  777. return;
  778. }
  779. if ( this.cid ) {
  780. commentRow = $( '#comment-' + this.cid );
  781. }
  782. /*
  783. * When closing the Quick Edit form, show the comment row and move focus
  784. * back to the Quick Edit button.
  785. */
  786. if ( 'edit-comment' === this.act ) {
  787. commentRow.fadeIn( 300, function() {
  788. commentRow
  789. .show()
  790. .find( '.vim-q' )
  791. .attr( 'aria-expanded', 'false' )
  792. .focus();
  793. } ).css( 'backgroundColor', '' );
  794. }
  795. // When closing the Reply form, move focus back to the Reply button.
  796. if ( 'replyto-comment' === this.act ) {
  797. commentRow.find( '.vim-r' )
  798. .attr( 'aria-expanded', 'false' )
  799. .focus();
  800. }
  801. // reset the Quicktags buttons
  802. if ( typeof QTags != 'undefined' )
  803. QTags.closeAllTags('replycontent');
  804. $('#add-new-comment').css('display', '');
  805. replyRow.hide();
  806. $( '#com-reply' ).append( replyRow );
  807. $('#replycontent').css('height', '').val('');
  808. $('#edithead input').val('');
  809. $( '.notice-error', replyRow )
  810. .addClass( 'hidden' )
  811. .find( '.error' ).empty();
  812. $( '.spinner', replyRow ).removeClass( 'is-active' );
  813. this.cid = '';
  814. this.originalContent = '';
  815. },
  816. /**
  817. * Opens the comment quick edit or reply form.
  818. *
  819. * @since 2.7.0
  820. *
  821. * @memberof commentReply
  822. *
  823. * @param {number} comment_id The comment id to open an editor for.
  824. * @param {number} post_id The post id to open an editor for.
  825. * @param {string} action The action to perform. Either 'edit' or 'replyto'.
  826. *
  827. * @return {boolean} Always false.
  828. */
  829. open : function(comment_id, post_id, action) {
  830. var editRow, rowData, act, replyButton, editHeight,
  831. t = this,
  832. c = $('#comment-' + comment_id),
  833. h = c.height(),
  834. colspanVal = 0;
  835. if ( ! this.discardCommentChanges() ) {
  836. return false;
  837. }
  838. t.close();
  839. t.cid = comment_id;
  840. editRow = $('#replyrow');
  841. rowData = $('#inline-'+comment_id);
  842. action = action || 'replyto';
  843. act = 'edit' == action ? 'edit' : 'replyto';
  844. act = t.act = act + '-comment';
  845. t.originalContent = $('textarea.comment', rowData).val();
  846. colspanVal = $( '> th:visible, > td:visible', c ).length;
  847. // Make sure it's actually a table and there's a `colspan` value to apply.
  848. if ( editRow.hasClass( 'inline-edit-row' ) && 0 !== colspanVal ) {
  849. $( 'td', editRow ).attr( 'colspan', colspanVal );
  850. }
  851. $('#action', editRow).val(act);
  852. $('#comment_post_ID', editRow).val(post_id);
  853. $('#comment_ID', editRow).val(comment_id);
  854. if ( action == 'edit' ) {
  855. $( '#author-name', editRow ).val( $( 'div.author', rowData ).text() );
  856. $('#author-email', editRow).val( $('div.author-email', rowData).text() );
  857. $('#author-url', editRow).val( $('div.author-url', rowData).text() );
  858. $('#status', editRow).val( $('div.comment_status', rowData).text() );
  859. $('#replycontent', editRow).val( $('textarea.comment', rowData).val() );
  860. $( '#edithead, #editlegend, #savebtn', editRow ).show();
  861. $('#replyhead, #replybtn, #addhead, #addbtn', editRow).hide();
  862. if ( h > 120 ) {
  863. // Limit the maximum height when editing very long comments to make it more manageable.
  864. // The textarea is resizable in most browsers, so the user can adjust it if needed.
  865. editHeight = h > 500 ? 500 : h;
  866. $('#replycontent', editRow).css('height', editHeight + 'px');
  867. }
  868. c.after( editRow ).fadeOut('fast', function(){
  869. $('#replyrow').fadeIn(300, function(){ $(this).show(); });
  870. });
  871. } else if ( action == 'add' ) {
  872. $('#addhead, #addbtn', editRow).show();
  873. $( '#replyhead, #replybtn, #edithead, #editlegend, #savebtn', editRow ) .hide();
  874. $('#the-comment-list').prepend(editRow);
  875. $('#replyrow').fadeIn(300);
  876. } else {
  877. replyButton = $('#replybtn', editRow);
  878. $( '#edithead, #editlegend, #savebtn, #addhead, #addbtn', editRow ).hide();
  879. $('#replyhead, #replybtn', editRow).show();
  880. c.after(editRow);
  881. if ( c.hasClass('unapproved') ) {
  882. replyButton.text(adminCommentsL10n.replyApprove);
  883. } else {
  884. replyButton.text(adminCommentsL10n.reply);
  885. }
  886. $('#replyrow').fadeIn(300, function(){ $(this).show(); });
  887. }
  888. setTimeout(function() {
  889. var rtop, rbottom, scrollTop, vp, scrollBottom;
  890. rtop = $('#replyrow').offset().top;
  891. rbottom = rtop + $('#replyrow').height();
  892. scrollTop = window.pageYOffset || document.documentElement.scrollTop;
  893. vp = document.documentElement.clientHeight || window.innerHeight || 0;
  894. scrollBottom = scrollTop + vp;
  895. if ( scrollBottom - 20 < rbottom )
  896. window.scroll(0, rbottom - vp + 35);
  897. else if ( rtop - 20 < scrollTop )
  898. window.scroll(0, rtop - 35);
  899. $('#replycontent').focus().keyup(function(e){
  900. if ( e.which == 27 )
  901. commentReply.revert(); // close on Escape
  902. });
  903. }, 600);
  904. return false;
  905. },
  906. /**
  907. * Submits the comment quick edit or reply form.
  908. *
  909. * @since 2.7.0
  910. *
  911. * @memberof commentReply
  912. *
  913. * @return {void}
  914. */
  915. send : function() {
  916. var post = {},
  917. $errorNotice = $( '#replysubmit .error-notice' );
  918. $errorNotice.addClass( 'hidden' );
  919. $( '#replysubmit .spinner' ).addClass( 'is-active' );
  920. $('#replyrow input').not(':button').each(function() {
  921. var t = $(this);
  922. post[ t.attr('name') ] = t.val();
  923. });
  924. post.content = $('#replycontent').val();
  925. post.id = post.comment_post_ID;
  926. post.comments_listing = this.comments_listing;
  927. post.p = $('[name="p"]').val();
  928. if ( $('#comment-' + $('#comment_ID').val()).hasClass('unapproved') )
  929. post.approve_parent = 1;
  930. $.ajax({
  931. type : 'POST',
  932. url : ajaxurl,
  933. data : post,
  934. success : function(x) { commentReply.show(x); },
  935. error : function(r) { commentReply.error(r); }
  936. });
  937. },
  938. /**
  939. * Shows the new or updated comment or reply.
  940. *
  941. * This function needs to be passed the ajax result as received from the server.
  942. * It will handle the response and show the comment that has just been saved to
  943. * the server.
  944. *
  945. * @since 2.7.0
  946. *
  947. * @memberof commentReply
  948. *
  949. * @param {Object} xml Ajax response object.
  950. *
  951. * @return {void}
  952. */
  953. show : function(xml) {
  954. var t = this, r, c, id, bg, pid;
  955. if ( typeof(xml) == 'string' ) {
  956. t.error({'responseText': xml});
  957. return false;
  958. }
  959. r = wpAjax.parseAjaxResponse(xml);
  960. if ( r.errors ) {
  961. t.error({'responseText': wpAjax.broken});
  962. return false;
  963. }
  964. t.revert();
  965. r = r.responses[0];
  966. id = '#comment-' + r.id;
  967. if ( 'edit-comment' == t.act )
  968. $(id).remove();
  969. if ( r.supplemental.parent_approved ) {
  970. pid = $('#comment-' + r.supplemental.parent_approved);
  971. updatePending( -1, r.supplemental.parent_post_id );
  972. if ( this.comments_listing == 'moderated' ) {
  973. pid.animate( { 'backgroundColor':'#CCEEBB' }, 400, function(){
  974. pid.fadeOut();
  975. });
  976. return;
  977. }
  978. }
  979. if ( r.supplemental.i18n_comments_text ) {
  980. updateDashboardText( r.supplemental );
  981. updateInModerationText( r.supplemental );
  982. updateApproved( 1, r.supplemental.parent_post_id );
  983. updateCountText( 'span.all-count', 1 );
  984. }
  985. c = $.trim(r.data); // Trim leading whitespaces
  986. $(c).hide();
  987. $('#replyrow').after(c);
  988. id = $(id);
  989. t.addEvents(id);
  990. bg = id.hasClass('unapproved') ? '#FFFFE0' : id.closest('.widefat, .postbox').css('backgroundColor');
  991. id.animate( { 'backgroundColor':'#CCEEBB' }, 300 )
  992. .animate( { 'backgroundColor': bg }, 300, function() {
  993. if ( pid && pid.length ) {
  994. pid.animate( { 'backgroundColor':'#CCEEBB' }, 300 )
  995. .animate( { 'backgroundColor': bg }, 300 )
  996. .removeClass('unapproved').addClass('approved')
  997. .find('div.comment_status').html('1');
  998. }
  999. });
  1000. },
  1001. /**
  1002. * Shows an error for the failed comment update or reply.
  1003. *
  1004. * @since 2.7.0
  1005. *
  1006. * @memberof commentReply
  1007. *
  1008. * @param {string} r The Ajax response.
  1009. *
  1010. * @return {void}
  1011. */
  1012. error : function(r) {
  1013. var er = r.statusText,
  1014. $errorNotice = $( '#replysubmit .notice-error' ),
  1015. $error = $errorNotice.find( '.error' );
  1016. $( '#replysubmit .spinner' ).removeClass( 'is-active' );
  1017. if ( r.responseText )
  1018. er = r.responseText.replace( /<.[^<>]*?>/g, '' );
  1019. if ( er ) {
  1020. $errorNotice.removeClass( 'hidden' );
  1021. $error.html( er );
  1022. }
  1023. },
  1024. /**
  1025. * Opens the add comments form in the comments metabox on the post edit page.
  1026. *
  1027. * @since 3.4.0
  1028. *
  1029. * @memberof commentReply
  1030. *
  1031. * @param {number} post_id The post id.
  1032. *
  1033. * @return {void}
  1034. */
  1035. addcomment: function(post_id) {
  1036. var t = this;
  1037. $('#add-new-comment').fadeOut(200, function(){
  1038. t.open(0, post_id, 'add');
  1039. $('table.comments-box').css('display', '');
  1040. $('#no-comments').remove();
  1041. });
  1042. },
  1043. /**
  1044. * Alert the user if they have unsaved changes on a comment that will be lost if
  1045. * they proceed with the intended action.
  1046. *
  1047. * @since 4.6.0
  1048. *
  1049. * @memberof commentReply
  1050. *
  1051. * @return {boolean} Whether it is safe the continue with the intended action.
  1052. */
  1053. discardCommentChanges: function() {
  1054. var editRow = $( '#replyrow' );
  1055. if ( this.originalContent === $( '#replycontent', editRow ).val() ) {
  1056. return true;
  1057. }
  1058. return window.confirm( adminCommentsL10n.warnCommentChanges );
  1059. }
  1060. };
  1061. $(document).ready(function(){
  1062. var make_hotkeys_redirect, edit_comment, toggle_all, make_bulk;
  1063. setCommentsList();
  1064. commentReply.init();
  1065. $(document).on( 'click', 'span.delete a.delete', function( e ) {
  1066. e.preventDefault();
  1067. });
  1068. if ( typeof $.table_hotkeys != 'undefined' ) {
  1069. /**
  1070. * Creates a function that navigates to a previous or next page.
  1071. *
  1072. * @since 2.7.0
  1073. * @access private
  1074. *
  1075. * @param {string} which What page to navigate to: either next or prev.
  1076. *
  1077. * @return {Function} The function that executes the navigation.
  1078. */
  1079. make_hotkeys_redirect = function(which) {
  1080. return function() {
  1081. var first_last, l;
  1082. first_last = 'next' == which? 'first' : 'last';
  1083. l = $('.tablenav-pages .'+which+'-page:not(.disabled)');
  1084. if (l.length)
  1085. window.location = l[0].href.replace(/\&hotkeys_highlight_(first|last)=1/g, '')+'&hotkeys_highlight_'+first_last+'=1';
  1086. };
  1087. };
  1088. /**
  1089. * Navigates to the edit page for the selected comment.
  1090. *
  1091. * @since 2.7.0
  1092. * @access private
  1093. *
  1094. * @param {Object} event The event that triggered this action.
  1095. * @param {Object} current_row A jQuery object of the selected row.
  1096. *
  1097. * @return {void}
  1098. */
  1099. edit_comment = function(event, current_row) {
  1100. window.location = $('span.edit a', current_row).attr('href');
  1101. };
  1102. /**
  1103. * Toggles all comments on the screen, for bulk actions.
  1104. *
  1105. * @since 2.7.0
  1106. * @access private
  1107. *
  1108. * @return {void}
  1109. */
  1110. toggle_all = function() {
  1111. $('#cb-select-all-1').data( 'wp-toggle', 1 ).trigger( 'click' ).removeData( 'wp-toggle' );
  1112. };
  1113. /**
  1114. * Creates a bulk action function that is executed on all selected comments.
  1115. *
  1116. * @since 2.7.0
  1117. * @access private
  1118. *
  1119. * @param {string} value The name of the action to execute.
  1120. *
  1121. * @return {Function} The function that executes the bulk action.
  1122. */
  1123. make_bulk = function(value) {
  1124. return function() {
  1125. var scope = $('select[name="action"]');
  1126. $('option[value="' + value + '"]', scope).prop('selected', true);
  1127. $('#doaction').click();
  1128. };
  1129. };
  1130. $.table_hotkeys(
  1131. $('table.widefat'),
  1132. [
  1133. 'a', 'u', 's', 'd', 'r', 'q', 'z',
  1134. ['e', edit_comment],
  1135. ['shift+x', toggle_all],
  1136. ['shift+a', make_bulk('approve')],
  1137. ['shift+s', make_bulk('spam')],
  1138. ['shift+d', make_bulk('delete')],
  1139. ['shift+t', make_bulk('trash')],
  1140. ['shift+z', make_bulk('untrash')],
  1141. ['shift+u', make_bulk('unapprove')]
  1142. ],
  1143. {
  1144. highlight_first: adminCommentsL10n.hotkeys_highlight_first,
  1145. highlight_last: adminCommentsL10n.hotkeys_highlight_last,
  1146. prev_page_link_cb: make_hotkeys_redirect('prev'),
  1147. next_page_link_cb: make_hotkeys_redirect('next'),
  1148. hotkeys_opts: {
  1149. disableInInput: true,
  1150. type: 'keypress',
  1151. noDisable: '.check-column input[type="checkbox"]'
  1152. },
  1153. cycle_expr: '#the-comment-list tr',
  1154. start_row_index: 0
  1155. }
  1156. );
  1157. }
  1158. // Quick Edit and Reply have an inline comment editor.
  1159. $( '#the-comment-list' ).on( 'click', '.comment-inline', function() {
  1160. var $el = $( this ),
  1161. action = 'replyto';
  1162. if ( 'undefined' !== typeof $el.data( 'action' ) ) {
  1163. action = $el.data( 'action' );
  1164. }
  1165. $( this ).attr( 'aria-expanded', 'true' );
  1166. commentReply.open( $el.data( 'commentId' ), $el.data( 'postId' ), action );
  1167. } );
  1168. });
  1169. })(jQuery);