ru-se.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

comment-reply.js (12457B)


      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 		// Initialise 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 ) {
    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 );