[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/js/ -> comment-reply.js (source)

   1  /**
   2   * Handles the addition of the comment form.
   3   *
   4   * @since 2.7.0
   5   * @output wp-includes/js/comment-reply.js
   6   *
   7   * @namespace addComment
   8   *
   9   * @type {Object}
  10   */
  11  window.addComment = ( function( window ) {
  12      // Avoid scope lookups on commonly used variables.
  13      var document = window.document;
  14  
  15      // Settings.
  16      var config = {
  17          commentReplyClass   : 'comment-reply-link',
  18          commentReplyTitleId : 'reply-title',
  19          cancelReplyId       : 'cancel-comment-reply-link',
  20          commentFormId       : 'commentform',
  21          temporaryFormId     : 'wp-temp-form-div',
  22          parentIdFieldId     : 'comment_parent',
  23          postIdFieldId       : 'comment_post_ID'
  24      };
  25  
  26      // Cross browser MutationObserver.
  27      var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
  28  
  29      // Check browser cuts the mustard.
  30      var cutsTheMustard = 'querySelector' in document && 'addEventListener' in window;
  31  
  32      /*
  33       * Check browser supports dataset.
  34       * !! sets the variable to true if the property exists.
  35       */
  36      var supportsDataset = !! document.documentElement.dataset;
  37  
  38      // For holding the cancel element.
  39      var cancelElement;
  40  
  41      // For holding the comment form element.
  42      var commentFormElement;
  43  
  44      // The respond element.
  45      var respondElement;
  46  
  47      // The mutation observer.
  48      var observer;
  49  
  50      if ( cutsTheMustard && document.readyState !== 'loading' ) {
  51          ready();
  52      } else if ( cutsTheMustard ) {
  53          window.addEventListener( 'DOMContentLoaded', ready, false );
  54      }
  55  
  56      /**
  57       * Sets up object variables after the DOM is ready.
  58       *
  59       * @since 5.1.1
  60       */
  61  	function ready() {
  62          // Initialize the events.
  63          init();
  64  
  65          // Set up a MutationObserver to check for comments loaded late.
  66          observeChanges();
  67      }
  68  
  69      /**
  70       * Add events to links classed .comment-reply-link.
  71       *
  72       * Searches the context for reply links and adds the JavaScript events
  73       * required to move the comment form. To allow for lazy loading of
  74       * comments this method is exposed as window.commentReply.init().
  75       *
  76       * @since 5.1.0
  77       *
  78       * @memberOf addComment
  79       *
  80       * @param {HTMLElement} context The parent DOM element to search for links.
  81       */
  82  	function init( context ) {
  83          if ( ! cutsTheMustard ) {
  84              return;
  85          }
  86  
  87          // Get required elements.
  88          cancelElement = getElementById( config.cancelReplyId );
  89          commentFormElement = getElementById( config.commentFormId );
  90  
  91          // No cancel element, no replies.
  92          if ( ! cancelElement ) {
  93              return;
  94          }
  95  
  96          cancelElement.addEventListener( 'touchstart', cancelEvent );
  97          cancelElement.addEventListener( 'click',      cancelEvent );
  98  
  99          // Submit the comment form when the user types [Ctrl] or [Cmd] + [Enter].
 100          var submitFormHandler = function( e ) {
 101              if ( ( e.metaKey || e.ctrlKey ) && e.keyCode === 13 && document.activeElement.tagName.toLowerCase() !== 'a' ) {
 102                  commentFormElement.removeEventListener( 'keydown', submitFormHandler );
 103                  e.preventDefault();
 104                  // The submit button ID is 'submit' so we can't call commentFormElement.submit(). Click it instead.
 105                  commentFormElement.submit.click();
 106                  return false;
 107              }
 108          };
 109  
 110          if ( commentFormElement ) {
 111              commentFormElement.addEventListener( 'keydown', submitFormHandler );
 112          }
 113  
 114          var links = replyLinks( context );
 115          var element;
 116  
 117          for ( var i = 0, l = links.length; i < l; i++ ) {
 118              element = links[i];
 119  
 120              element.addEventListener( 'touchstart', clickEvent );
 121              element.addEventListener( 'click',      clickEvent );
 122          }
 123      }
 124  
 125      /**
 126       * Return all links classed .comment-reply-link.
 127       *
 128       * @since 5.1.0
 129       *
 130       * @param {HTMLElement} context The parent DOM element to search for links.
 131       *
 132       * @return {HTMLCollection|NodeList|Array}
 133       */
 134  	function replyLinks( context ) {
 135          var selectorClass = config.commentReplyClass;
 136          var allReplyLinks;
 137  
 138          // childNodes is a handy check to ensure the context is a HTMLElement.
 139          if ( ! context || ! context.childNodes ) {
 140              context = document;
 141          }
 142  
 143          if ( document.getElementsByClassName ) {
 144              // Fastest.
 145              allReplyLinks = context.getElementsByClassName( selectorClass );
 146          }
 147          else {
 148              // Fast.
 149              allReplyLinks = context.querySelectorAll( '.' + selectorClass );
 150          }
 151  
 152          return allReplyLinks;
 153      }
 154  
 155      /**
 156       * Cancel event handler.
 157       *
 158       * @since 5.1.0
 159       *
 160       * @param {Event} event The calling event.
 161       */
 162  	function cancelEvent( event ) {
 163          var cancelLink = this;
 164          var temporaryFormId  = config.temporaryFormId;
 165          var temporaryElement = getElementById( temporaryFormId );
 166  
 167          if ( ! temporaryElement || ! respondElement ) {
 168              // Conditions for cancel link fail.
 169              return;
 170          }
 171  
 172          getElementById( config.parentIdFieldId ).value = '0';
 173  
 174          // Move the respond form back in place of the temporary element.
 175          var headingText = temporaryElement.textContent;
 176          temporaryElement.parentNode.replaceChild( respondElement, temporaryElement );
 177          cancelLink.style.display = 'none';
 178  
 179          var replyHeadingElement  = getElementById( config.commentReplyTitleId );
 180          var replyHeadingTextNode = replyHeadingElement && replyHeadingElement.firstChild;
 181          var replyLinkToParent    = replyHeadingTextNode && replyHeadingTextNode.nextSibling;
 182  
 183          if ( replyHeadingTextNode && replyHeadingTextNode.nodeType === Node.TEXT_NODE && headingText ) {
 184              if ( replyLinkToParent && 'A' === replyLinkToParent.nodeName && replyLinkToParent.id !== config.cancelReplyId ) {
 185                  replyLinkToParent.style.display = '';
 186              }
 187  
 188              replyHeadingTextNode.textContent = headingText;
 189          }
 190  
 191          event.preventDefault();
 192      }
 193  
 194      /**
 195       * Click event handler.
 196       *
 197       * @since 5.1.0
 198       *
 199       * @param {Event} event The calling event.
 200       */
 201  	function clickEvent( event ) {
 202          var replyNode = getElementById( config.commentReplyTitleId );
 203          var defaultReplyHeading = replyNode && replyNode.firstChild.textContent;
 204          var replyLink = this,
 205              commId    = getDataAttribute( replyLink, 'belowelement' ),
 206              parentId  = getDataAttribute( replyLink, 'commentid' ),
 207              respondId = getDataAttribute( replyLink, 'respondelement' ),
 208              postId    = getDataAttribute( replyLink, 'postid' ),
 209              replyTo   = getDataAttribute( replyLink, 'replyto' ) || defaultReplyHeading,
 210              follow;
 211  
 212          if ( ! commId || ! parentId || ! respondId || ! postId ) {
 213              /*
 214               * Theme or plugin defines own link via custom `wp_list_comments()` callback
 215               * and calls `moveForm()` either directly or via a custom event hook.
 216               */
 217              return;
 218          }
 219  
 220          /*
 221           * Third party comments systems can hook into this function via the global scope,
 222           * therefore the click event needs to reference the global scope.
 223           */
 224          follow = window.addComment.moveForm( commId, parentId, respondId, postId, replyTo );
 225          if ( false === follow ) {
 226              event.preventDefault();
 227          }
 228      }
 229  
 230      /**
 231       * Creates a mutation observer to check for newly inserted comments.
 232       *
 233       * @since 5.1.0
 234       */
 235  	function observeChanges() {
 236          if ( ! MutationObserver ) {
 237              return;
 238          }
 239  
 240          var observerOptions = {
 241              childList: true,
 242              subtree: true
 243          };
 244  
 245          observer = new MutationObserver( handleChanges );
 246          observer.observe( document.body, observerOptions );
 247      }
 248  
 249      /**
 250       * Handles DOM changes, calling init() if any new nodes are added.
 251       *
 252       * @since 5.1.0
 253       *
 254       * @param {Array} mutationRecords Array of MutationRecord objects.
 255       */
 256  	function handleChanges( mutationRecords ) {
 257          var i = mutationRecords.length;
 258  
 259          while ( i-- ) {
 260              // Call init() once if any record in this set adds nodes.
 261              if ( mutationRecords[ i ].addedNodes.length ) {
 262                  init();
 263                  return;
 264              }
 265          }
 266      }
 267  
 268      /**
 269       * Backward compatible getter of data-* attribute.
 270       *
 271       * Uses element.dataset if it exists, otherwise uses getAttribute.
 272       *
 273       * @since 5.1.0
 274       *
 275       * @param {HTMLElement} Element DOM element with the attribute.
 276       * @param {string}      Attribute the attribute to get.
 277       *
 278       * @return {string}
 279       */
 280  	function getDataAttribute( element, attribute ) {
 281          if ( supportsDataset ) {
 282              return element.dataset[attribute];
 283          }
 284          else {
 285              return element.getAttribute( 'data-' + attribute );
 286          }
 287      }
 288  
 289      /**
 290       * Get element by ID.
 291       *
 292       * Local alias for document.getElementById.
 293       *
 294       * @since 5.1.0
 295       *
 296       * @param {HTMLElement} The requested element.
 297       */
 298  	function getElementById( elementId ) {
 299          return document.getElementById( elementId );
 300      }
 301  
 302      /**
 303       * Moves the reply form from its current position to the reply location.
 304       *
 305       * @since 2.7.0
 306       *
 307       * @memberOf addComment
 308       *
 309       * @param {string} addBelowId HTML ID of element the form follows.
 310       * @param {string} commentId  Database ID of comment being replied to.
 311       * @param {string} respondId  HTML ID of 'respond' element.
 312       * @param {string} postId     Database ID of the post.
 313       * @param {string} replyTo    Form heading content.
 314       */
 315  	function moveForm( addBelowId, commentId, respondId, postId, replyTo ) {
 316          // Get elements based on their IDs.
 317          var addBelowElement = getElementById( addBelowId );
 318          respondElement  = getElementById( respondId );
 319  
 320          // Get the hidden fields.
 321          var parentIdField   = getElementById( config.parentIdFieldId );
 322          var postIdField     = getElementById( config.postIdFieldId );
 323          var element, cssHidden, style;
 324  
 325          var replyHeading         = getElementById( config.commentReplyTitleId );
 326          var replyHeadingTextNode = replyHeading && replyHeading.firstChild;
 327          var replyLinkToParent    = replyHeadingTextNode && replyHeadingTextNode.nextSibling;
 328  
 329          if ( ! addBelowElement || ! respondElement || ! parentIdField ) {
 330              // Missing key elements, fail.
 331              return;
 332          }
 333  
 334          if ( 'undefined' === typeof replyTo ) {
 335              replyTo = replyHeadingTextNode && replyHeadingTextNode.textContent;
 336          }
 337  
 338          addPlaceHolder( respondElement );
 339  
 340          // Set the value of the post.
 341          if ( postId && postIdField ) {
 342              postIdField.value = postId;
 343          }
 344  
 345          parentIdField.value = commentId;
 346  
 347          cancelElement.style.display = '';
 348          addBelowElement.parentNode.insertBefore( respondElement, addBelowElement.nextSibling );
 349  
 350          if ( replyHeadingTextNode && replyHeadingTextNode.nodeType === Node.TEXT_NODE ) {
 351              if ( replyLinkToParent && 'A' === replyLinkToParent.nodeName && replyLinkToParent.id !== config.cancelReplyId ) {
 352                  replyLinkToParent.style.display = 'none';
 353              }
 354  
 355              replyHeadingTextNode.textContent = replyTo;
 356          }
 357  
 358          /*
 359           * This is for backward compatibility with third party commenting systems
 360           * hooking into the event using older techniques.
 361           */
 362          cancelElement.onclick = function() {
 363              return false;
 364          };
 365  
 366          // Focus on the first field in the comment form.
 367          try {
 368              for ( var i = 0; i < commentFormElement.elements.length; i++ ) {
 369                  element = commentFormElement.elements[i];
 370                  cssHidden = false;
 371  
 372                  // Get elements computed style.
 373                  if ( 'getComputedStyle' in window ) {
 374                      // Modern browsers.
 375                      style = window.getComputedStyle( element );
 376                  } else if ( document.documentElement.currentStyle ) {
 377                      // IE 8.
 378                      style = element.currentStyle;
 379                  }
 380  
 381                  /*
 382                   * For display none, do the same thing jQuery does. For visibility,
 383                   * check the element computed style since browsers are already doing
 384                   * the job for us. In fact, the visibility computed style is the actual
 385                   * computed value and already takes into account the element ancestors.
 386                   */
 387                  if ( ( element.offsetWidth <= 0 && element.offsetHeight <= 0 ) || style.visibility === 'hidden' ) {
 388                      cssHidden = true;
 389                  }
 390  
 391                  // Skip form elements that are hidden or disabled.
 392                  if ( 'hidden' === element.type || element.disabled || cssHidden ) {
 393                      continue;
 394                  }
 395  
 396                  element.focus();
 397                  // Stop after the first focusable element.
 398                  break;
 399              }
 400          }
 401          catch(e) {
 402  
 403          }
 404  
 405          /*
 406           * false is returned for backward compatibility with third party commenting systems
 407           * hooking into this function.
 408           */
 409          return false;
 410      }
 411  
 412      /**
 413       * Add placeholder element.
 414       *
 415       * Places a place holder element above the #respond element for
 416       * the form to be returned to if needs be.
 417       *
 418       * @since 2.7.0
 419       *
 420       * @param {HTMLelement} respondElement the #respond element holding comment form.
 421       */
 422  	function addPlaceHolder( respondElement ) {
 423          var temporaryFormId  = config.temporaryFormId;
 424          var temporaryElement = getElementById( temporaryFormId );
 425          var replyElement = getElementById( config.commentReplyTitleId );
 426          var initialHeadingText = replyElement ? replyElement.firstChild.textContent : '';
 427  
 428          if ( temporaryElement ) {
 429              // The element already exists, no need to recreate.
 430              return;
 431          }
 432  
 433          temporaryElement = document.createElement( 'div' );
 434          temporaryElement.id = temporaryFormId;
 435          temporaryElement.style.display = 'none';
 436          temporaryElement.textContent = initialHeadingText;
 437          respondElement.parentNode.insertBefore( temporaryElement, respondElement );
 438      }
 439  
 440      return {
 441          init: init,
 442          moveForm: moveForm
 443      };
 444  })( window );


Generated : Fri Nov 15 08:20:01 2024 Cross-referenced by PHPXref