wp/wp-admin/js/post.js
changeset 7 cf61fcea0001
parent 5 5e2f62d02dcd
child 9 177826044cd9
--- a/wp/wp-admin/js/post.js	Tue Jun 09 11:14:17 2015 +0000
+++ b/wp/wp-admin/js/post.js	Mon Oct 14 17:39:30 2019 +0200
@@ -1,168 +1,251 @@
 /* global postL10n, ajaxurl, wpAjax, setPostThumbnailL10n, postboxes, pagenow, tinymce, alert, deleteUserSetting */
-/* global theList:true, theExtraList:true, getUserSetting, setUserSetting */
+/* global theList:true, theExtraList:true, getUserSetting, setUserSetting, commentReply */
+
+/**
+ * Contains all dynamic functionality needed on post and term pages.
+ *
+ * @summary Control page and term functionality.
+ */
 
 var commentsBox, WPSetThumbnailHTML, WPSetThumbnailID, WPRemoveThumbnail, wptitlehint, makeSlugeditClickable, editPermalink;
-// Back-compat: prevent fatal errors
+// Backwards compatibility: prevent fatal errors.
 makeSlugeditClickable = editPermalink = function(){};
 
+// Make sure the wp object exists.
 window.wp = window.wp || {};
 
 ( function( $ ) {
 	var titleHasFocus = false;
 
-commentsBox = {
-	st : 0,
+	/**
+	 * Control loading of comments on the post and term edit pages.
+	 *
+	 * @type {{st: number, get: commentsBox.get, load: commentsBox.load}}
+	 *
+	 * @namespace commentsBox
+	 */
+	commentsBox = {
+		// Comment offset to use when fetching new comments.
+		st : 0,
 
-	get : function(total, num) {
-		var st = this.st, data;
-		if ( ! num )
-			num = 20;
-
-		this.st += num;
-		this.total = total;
-		$( '#commentsdiv .spinner' ).addClass( 'is-active' );
+		/**
+		 * Fetch comments using AJAX and display them in the box.
+		 *
+		 * @param {int} total Total number of comments for this post.
+		 * @param {int} num   Optional. Number of comments to fetch, defaults to 20.
+		 * @returns {boolean} Always returns false.
+		 *
+		 * @memberof commentsBox
+		 */
+		get : function(total, num) {
+			var st = this.st, data;
+			if ( ! num )
+				num = 20;
 
-		data = {
-			'action' : 'get-comments',
-			'mode' : 'single',
-			'_ajax_nonce' : $('#add_comment_nonce').val(),
-			'p' : $('#post_ID').val(),
-			'start' : st,
-			'number' : num
-		};
+			this.st += num;
+			this.total = total;
+			$( '#commentsdiv .spinner' ).addClass( 'is-active' );
+
+			data = {
+				'action' : 'get-comments',
+				'mode' : 'single',
+				'_ajax_nonce' : $('#add_comment_nonce').val(),
+				'p' : $('#post_ID').val(),
+				'start' : st,
+				'number' : num
+			};
 
-		$.post(ajaxurl, data,
-			function(r) {
-				r = wpAjax.parseAjaxResponse(r);
-				$('#commentsdiv .widefat').show();
-				$( '#commentsdiv .spinner' ).removeClass( 'is-active' );
+			$.post(
+				ajaxurl,
+				data,
+				function(r) {
+					r = wpAjax.parseAjaxResponse(r);
+					$('#commentsdiv .widefat').show();
+					$( '#commentsdiv .spinner' ).removeClass( 'is-active' );
 
-				if ( 'object' == typeof r && r.responses[0] ) {
-					$('#the-comment-list').append( r.responses[0].data );
+					if ( 'object' == typeof r && r.responses[0] ) {
+						$('#the-comment-list').append( r.responses[0].data );
 
-					theList = theExtraList = null;
-					$( 'a[className*=\':\']' ).unbind();
+						theList = theExtraList = null;
+						$( 'a[className*=\':\']' ).unbind();
 
-					if ( commentsBox.st > commentsBox.total )
-						$('#show-comments').hide();
-					else
-						$('#show-comments').show().children('a').html(postL10n.showcomm);
+						// If the offset is over the total number of comments we cannot fetch any more, so hide the button.
+						if ( commentsBox.st > commentsBox.total )
+							$('#show-comments').hide();
+						else
+							$('#show-comments').show().children('a').html(postL10n.showcomm);
 
-					return;
-				} else if ( 1 == r ) {
-					$('#show-comments').html(postL10n.endcomm);
-					return;
-				}
+						return;
+					} else if ( 1 == r ) {
+						$('#show-comments').html(postL10n.endcomm);
+						return;
+					}
 
-				$('#the-comment-list').append('<tr><td colspan="2">'+wpAjax.broken+'</td></tr>');
-			}
-		);
+					$('#the-comment-list').append('<tr><td colspan="2">'+wpAjax.broken+'</td></tr>');
+				}
+			);
 
-		return false;
-	}
-};
+			return false;
+		},
 
-WPSetThumbnailHTML = function(html){
-	$('.inside', '#postimagediv').html(html);
-};
+		/**
+		 * Load the next batch of comments.
+		 *
+		 * @param {int} total Total number of comments to load.
+		 *
+		 * @memberof commentsBox
+		 */
+		load: function(total){
+			this.st = jQuery('#the-comment-list tr.comment:visible').length;
+			this.get(total);
+		}
+	};
 
-WPSetThumbnailID = function(id){
-	var field = $('input[value="_thumbnail_id"]', '#list-table');
-	if ( field.size() > 0 ) {
-		$('#meta\\[' + field.attr('id').match(/[0-9]+/) + '\\]\\[value\\]').text(id);
-	}
-};
+	/**
+	 * Overwrite the content of the Featured Image postbox
+	 *
+	 * @param {string} html New HTML to be displayed in the content area of the postbox.
+	 *
+	 * @global
+	 */
+	WPSetThumbnailHTML = function(html){
+		$('.inside', '#postimagediv').html(html);
+	};
 
-WPRemoveThumbnail = function(nonce){
-	$.post(ajaxurl, {
-		action: 'set-post-thumbnail', post_id: $( '#post_ID' ).val(), thumbnail_id: -1, _ajax_nonce: nonce, cookie: encodeURIComponent( document.cookie )
-	}, function(str){
-		if ( str == '0' ) {
-			alert( setPostThumbnailL10n.error );
-		} else {
-			WPSetThumbnailHTML(str);
+	/**
+	 * Set the Image ID of the Featured Image
+	 *
+	 * @param {int} id The post_id of the image to use as Featured Image.
+	 *
+	 * @global
+	 */
+	WPSetThumbnailID = function(id){
+		var field = $('input[value="_thumbnail_id"]', '#list-table');
+		if ( field.length > 0 ) {
+			$('#meta\\[' + field.attr('id').match(/[0-9]+/) + '\\]\\[value\\]').text(id);
 		}
-	}
-	);
-};
-
-$(document).on( 'heartbeat-send.refresh-lock', function( e, data ) {
-	var lock = $('#active_post_lock').val(),
-		post_id = $('#post_ID').val(),
-		send = {};
-
-	if ( ! post_id || ! $('#post-lock-dialog').length )
-		return;
-
-	send.post_id = post_id;
+	};
 
-	if ( lock )
-		send.lock = lock;
-
-	data['wp-refresh-post-lock'] = send;
+	/**
+	 * Remove the Featured Image
+	 *
+	 * @param {string} nonce Nonce to use in the request.
+	 *
+	 * @global
+	 */
+	WPRemoveThumbnail = function(nonce){
+		$.post(ajaxurl, {
+			action: 'set-post-thumbnail', post_id: $( '#post_ID' ).val(), thumbnail_id: -1, _ajax_nonce: nonce, cookie: encodeURIComponent( document.cookie )
+		},
+			/**
+			 * Handle server response
+			 *
+			 * @param {string} str Response, will be '0' when an error occurred otherwise contains link to add Featured Image.
+			 */
+			function(str){
+			if ( str == '0' ) {
+				alert( setPostThumbnailL10n.error );
+			} else {
+				WPSetThumbnailHTML(str);
+			}
+		}
+		);
+	};
 
-}).on( 'heartbeat-tick.refresh-lock', function( e, data ) {
-	// Post locks: update the lock string or show the dialog if somebody has taken over editing
-	var received, wrap, avatar;
+	/**
+	 * Heartbeat locks.
+	 *
+	 * Used to lock editing of an object by only one user at a time.
+	 *
+	 * When the user does not send a heartbeat in a heartbeat-time
+	 * the user is no longer editing and another user can start editing.
+	 */
+	$(document).on( 'heartbeat-send.refresh-lock', function( e, data ) {
+		var lock = $('#active_post_lock').val(),
+			post_id = $('#post_ID').val(),
+			send = {};
 
-	if ( data['wp-refresh-post-lock'] ) {
-		received = data['wp-refresh-post-lock'];
+		if ( ! post_id || ! $('#post-lock-dialog').length )
+			return;
+
+		send.post_id = post_id;
 
-		if ( received.lock_error ) {
-			// show "editing taken over" message
-			wrap = $('#post-lock-dialog');
+		if ( lock )
+			send.lock = lock;
+
+		data['wp-refresh-post-lock'] = send;
+
+	}).on( 'heartbeat-tick.refresh-lock', function( e, data ) {
+		// Post locks: update the lock string or show the dialog if somebody has taken over editing.
+		var received, wrap, avatar;
 
-			if ( wrap.length && ! wrap.is(':visible') ) {
-				if ( wp.autosave ) {
-					// Save the latest changes and disable
-					$(document).one( 'heartbeat-tick', function() {
-						wp.autosave.server.suspend();
-						wrap.removeClass('saving').addClass('saved');
-						$(window).off( 'beforeunload.edit-post' );
-					});
+		if ( data['wp-refresh-post-lock'] ) {
+			received = data['wp-refresh-post-lock'];
+
+			if ( received.lock_error ) {
+				// Show "editing taken over" message.
+				wrap = $('#post-lock-dialog');
 
-					wrap.addClass('saving');
-					wp.autosave.server.triggerSave();
-				}
+				if ( wrap.length && ! wrap.is(':visible') ) {
+					if ( wp.autosave ) {
+						// Save the latest changes and disable.
+						$(document).one( 'heartbeat-tick', function() {
+							wp.autosave.server.suspend();
+							wrap.removeClass('saving').addClass('saved');
+							$(window).off( 'beforeunload.edit-post' );
+						});
 
-				if ( received.lock_error.avatar_src ) {
-					avatar = $('<img class="avatar avatar-64 photo" width="64" height="64" />').attr( 'src', received.lock_error.avatar_src.replace(/&amp;/g, '&') );
-					wrap.find('div.post-locked-avatar').empty().append( avatar );
+						wrap.addClass('saving');
+						wp.autosave.server.triggerSave();
+					}
+
+					if ( received.lock_error.avatar_src ) {
+						avatar = $( '<img class="avatar avatar-64 photo" width="64" height="64" alt="" />' ).attr( 'src', received.lock_error.avatar_src.replace( /&amp;/g, '&' ) );
+						wrap.find('div.post-locked-avatar').empty().append( avatar );
+					}
+
+					wrap.show().find('.currently-editing').text( received.lock_error.text );
+					wrap.find('.wp-tab-first').focus();
 				}
-
-				wrap.show().find('.currently-editing').text( received.lock_error.text );
-				wrap.find('.wp-tab-first').focus();
+			} else if ( received.new_lock ) {
+				$('#active_post_lock').val( received.new_lock );
 			}
-		} else if ( received.new_lock ) {
-			$('#active_post_lock').val( received.new_lock );
 		}
-	}
-}).on( 'before-autosave.update-post-slug', function() {
-	titleHasFocus = document.activeElement && document.activeElement.id === 'title';
-}).on( 'after-autosave.update-post-slug', function() {
-	// Create slug area only if not already there
-	// and the title field was not focused (user was not typing a title) when autosave ran
-	if ( ! $('#edit-slug-box > *').length && ! titleHasFocus ) {
-		$.post( ajaxurl, {
-				action: 'sample-permalink',
-				post_id: $('#post_ID').val(),
-				new_title: $('#title').val(),
-				samplepermalinknonce: $('#samplepermalinknonce').val()
-			},
-			function( data ) {
-				if ( data != '-1' ) {
-					$('#edit-slug-box').html(data);
+	}).on( 'before-autosave.update-post-slug', function() {
+		titleHasFocus = document.activeElement && document.activeElement.id === 'title';
+	}).on( 'after-autosave.update-post-slug', function() {
+
+		/*
+		 * Create slug area only if not already there
+		 * and the title field was not focused (user was not typing a title) when autosave ran.
+		 */
+		if ( ! $('#edit-slug-box > *').length && ! titleHasFocus ) {
+			$.post( ajaxurl, {
+					action: 'sample-permalink',
+					post_id: $('#post_ID').val(),
+					new_title: $('#title').val(),
+					samplepermalinknonce: $('#samplepermalinknonce').val()
+				},
+				function( data ) {
+					if ( data != '-1' ) {
+						$('#edit-slug-box').html(data);
+					}
 				}
-			}
-		);
-	}
-});
+			);
+		}
+	});
 
 }(jQuery));
 
+/**
+ * Heartbeat refresh nonces.
+ */
 (function($) {
 	var check, timeout;
 
+	/**
+	 * Only allow to check for nonce refresh every 30 seconds.
+	 */
 	function schedule() {
 		check = false;
 		window.clearTimeout( timeout );
@@ -170,13 +253,13 @@
 	}
 
 	$(document).on( 'heartbeat-send.wp-refresh-nonces', function( e, data ) {
-		var nonce, post_id;
+		var post_id,
+			$authCheck = $('#wp-auth-check-wrap');
 
-		if ( check ) {
-			if ( ( post_id = $('#post_ID').val() ) && ( nonce = $('#_wpnonce').val() ) ) {
+		if ( check || ( $authCheck.length && ! $authCheck.hasClass( 'hidden' ) ) ) {
+			if ( ( post_id = $('#post_ID').val() ) && $('#_wpnonce').val() ) {
 				data['wp-refresh-post-nonces'] = {
-					post_id: post_id,
-					post_nonce: nonce
+					post_id: post_id
 				};
 			}
 		}
@@ -200,48 +283,55 @@
 	});
 }(jQuery));
 
+/**
+ * All post and postbox controls and functionality.
+ */
 jQuery(document).ready( function($) {
 	var stamp, visibility, $submitButtons, updateVisibility, updateText,
 		sticky = '',
-		last = 0,
-		co = $('#content'),
+		$textarea = $('#content'),
 		$document = $(document),
-		$editSlugWrap = $('#edit-slug-box'),
 		postId = $('#post_ID').val() || 0,
 		$submitpost = $('#submitpost'),
 		releaseLock = true,
 		$postVisibilitySelect = $('#post-visibility-select'),
 		$timestampdiv = $('#timestampdiv'),
-		$postStatusSelect = $('#post-status-select');
+		$postStatusSelect = $('#post-status-select'),
+		isMac = window.navigator.platform ? window.navigator.platform.indexOf( 'Mac' ) !== -1 : false;
 
 	postboxes.add_postbox_toggles(pagenow);
 
-	// Clear the window name. Otherwise if this is a former preview window where the user navigated to edit another post,
-	// and the first post is still being edited, clicking Preview there will use this window to show the preview.
+	/*
+	 * Clear the window name. Otherwise if this is a former preview window where the user navigated to edit another post,
+	 * and the first post is still being edited, clicking Preview there will use this window to show the preview.
+	 */
 	window.name = '';
 
 	// Post locks: contain focus inside the dialog. If the dialog is shown, focus the first item.
 	$('#post-lock-dialog .notification-dialog').on( 'keydown', function(e) {
+		// Don't do anything when [tab] is pressed.
 		if ( e.which != 9 )
 			return;
 
 		var target = $(e.target);
 
+		// [shift] + [tab] on first tab cycles back to last tab.
 		if ( target.hasClass('wp-tab-first') && e.shiftKey ) {
 			$(this).find('.wp-tab-last').focus();
 			e.preventDefault();
+		// [tab] on last tab cycles back to first tab.
 		} else if ( target.hasClass('wp-tab-last') && ! e.shiftKey ) {
 			$(this).find('.wp-tab-first').focus();
 			e.preventDefault();
 		}
 	}).filter(':visible').find('.wp-tab-first').focus();
 
-	// Set the heartbeat interval to 15 sec. if post lock dialogs are enabled
+	// Set the heartbeat interval to 15 sec. if post lock dialogs are enabled.
 	if ( wp.heartbeat && $('#post-lock-dialog').length ) {
 		wp.heartbeat.interval( 15 );
 	}
 
-	// The form is being submitted by the user
+	// The form is being submitted by the user.
 	$submitButtons = $submitpost.find( ':submit, a.submitdelete, #post-preview' ).on( 'click.edit-post', function( event ) {
 		var $button = $(this);
 
@@ -261,11 +351,27 @@
 				return;
 			}
 
-			// Stop autosave
+			// Stop auto save.
 			if ( wp.autosave ) {
 				wp.autosave.server.suspend();
 			}
 
+			if ( typeof commentReply !== 'undefined' ) {
+				/*
+				 * Warn the user they have an unsaved comment before submitting
+				 * the post data for update.
+				 */
+				if ( ! commentReply.discardCommentChanges() ) {
+					return false;
+				}
+
+				/*
+				 * Close the comment edit/reply form if open to stop the form
+				 * action from interfering with the post's form action.
+				 */
+				commentReply.close();
+			}
+
 			releaseLock = false;
 			$(window).off( 'beforeunload.edit-post' );
 
@@ -313,11 +419,10 @@
 
 	// This code is meant to allow tabbing from Title to Post content.
 	$('#title').on( 'keydown.editor-focus', function( event ) {
-		var editor, $textarea;
+		var editor;
 
 		if ( event.keyCode === 9 && ! event.ctrlKey && ! event.altKey && ! event.shiftKey ) {
 			editor = typeof tinymce != 'undefined' && tinymce.get('content');
-			$textarea = $('#content');
 
 			if ( editor && ! editor.isHidden() ) {
 				editor.focus();
@@ -331,7 +436,7 @@
 		}
 	});
 
-	// Autosave new posts after a title is typed
+	// Auto save new posts after a title is typed.
 	if ( $( '#auto_draft' ).val() ) {
 		$( '#title' ).blur( function() {
 			var cancel;
@@ -340,7 +445,7 @@
 				return;
 			}
 
-			// Cancel the autosave when the blur was triggered by the user submitting the form
+			// Cancel the auto save when the blur was triggered by the user submitting the form.
 			$('form#post').one( 'submit', function() {
 				cancel = true;
 			});
@@ -363,8 +468,16 @@
 		$( '.autosave-message' ).text( postL10n.savingText );
 	}).on( 'after-autosave.edit-post', function( event, data ) {
 		$( '.autosave-message' ).text( data.message );
+
+		if ( $( document.body ).hasClass( 'post-new-php' ) ) {
+			$( '.submitbox .submitdelete' ).show();
+		}
 	});
 
+	/*
+	 * When the user is trying to load another page, or reloads current page
+	 * show a confirmation dialog when there are unsaved changes.
+	 */
 	$(window).on( 'beforeunload.edit-post', function() {
 		var editor = typeof tinymce !== 'undefined' && tinymce.get('content');
 
@@ -378,30 +491,54 @@
 			return;
 		}
 
-		// Unload is triggered (by hand) on removing the Thickbox iframe.
-		// Make sure we process only the main document unload.
+		/*
+		 * Unload is triggered (by hand) on removing the Thickbox iframe.
+		 * Make sure we process only the main document unload.
+		 */
 		if ( event.target && event.target.nodeName != '#document' ) {
 			return;
 		}
 
-		$.ajax({
-			type: 'POST',
-			url: ajaxurl,
+		var postID = $('#post_ID').val();
+		var postLock = $('#active_post_lock').val();
+
+		if ( ! postID || ! postLock ) {
+			return;
+		}
+
+		var data = {
+			action: 'wp-remove-post-lock',
+			_wpnonce: $('#_wpnonce').val(),
+			post_ID: postID,
+			active_post_lock: postLock
+		};
+
+		if ( window.FormData && window.navigator.sendBeacon ) {
+			var formData = new window.FormData();
+
+			$.each( data, function( key, value ) {
+				formData.append( key, value );
+			});
+
+			if ( window.navigator.sendBeacon( ajaxurl, formData ) ) {
+				return;
+			}
+		}
+
+		// Fall back to a synchronous POST request.
+		// See https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon
+		$.post({
 			async: false,
-			data: {
-				action: 'wp-remove-post-lock',
-				_wpnonce: $('#_wpnonce').val(),
-				post_ID: $('#post_ID').val(),
-				active_post_lock: $('#active_post_lock').val()
-			}
+			data: data,
+			url: ajaxurl
 		});
 	});
 
-	// multi-taxonomies
+	// Multiple Taxonomies.
 	if ( $('#tagsdiv-post_tag').length ) {
 		window.tagBox && window.tagBox.init();
 	} else {
-		$('#side-sortables, #normal-sortables, #advanced-sortables').children('div.postbox').each(function(){
+		$('.meta-box-sortables').children('div.postbox').each(function(){
 			if ( this.id.indexOf('tagsdiv-') === 0 ) {
 				window.tagBox && window.tagBox.init();
 				return false;
@@ -409,7 +546,7 @@
 		});
 	}
 
-	// categories
+	// Handle categories.
 	$('.categorydiv').each( function(){
 		var this_id = $(this).attr('id'), catAddBefore, catAddAfter, taxonomyParts, taxonomy, settingName;
 
@@ -417,44 +554,74 @@
 		taxonomyParts.shift();
 		taxonomy = taxonomyParts.join('-');
 		settingName = taxonomy + '_tab';
-		if ( taxonomy == 'category' )
+
+		if ( taxonomy == 'category' ) {
 			settingName = 'cats';
+		}
 
 		// TODO: move to jQuery 1.3+, support for multiple hierarchical taxonomies, see wp-lists.js
-		$('a', '#' + taxonomy + '-tabs').click( function(){
+		$('a', '#' + taxonomy + '-tabs').click( function( e ) {
+			e.preventDefault();
 			var t = $(this).attr('href');
 			$(this).parent().addClass('tabs').siblings('li').removeClass('tabs');
 			$('#' + taxonomy + '-tabs').siblings('.tabs-panel').hide();
 			$(t).show();
-			if ( '#' + taxonomy + '-all' == t )
+			if ( '#' + taxonomy + '-all' == t ) {
 				deleteUserSetting( settingName );
-			else
+			} else {
 				setUserSetting( settingName, 'pop' );
-			return false;
+			}
 		});
 
 		if ( getUserSetting( settingName ) )
 			$('a[href="#' + taxonomy + '-pop"]', '#' + taxonomy + '-tabs').click();
 
-		// Ajax Cat
-		$( '#new' + taxonomy ).one( 'focus', function() { $( this ).val( '' ).removeClass( 'form-input-tip' ); } );
+		// Add category button controls.
+		$('#new' + taxonomy).one( 'focus', function() {
+			$( this ).val( '' ).removeClass( 'form-input-tip' );
+		});
 
+		// On [enter] submit the taxonomy.
 		$('#new' + taxonomy).keypress( function(event){
 			if( 13 === event.keyCode ) {
 				event.preventDefault();
 				$('#' + taxonomy + '-add-submit').click();
 			}
 		});
-		$('#' + taxonomy + '-add-submit').click( function(){ $('#new' + taxonomy).focus(); });
+
+		// After submitting a new taxonomy, re-focus the input field.
+		$('#' + taxonomy + '-add-submit').click( function() {
+			$('#new' + taxonomy).focus();
+		});
 
+		/**
+		 * Before adding a new taxonomy, disable submit button.
+		 *
+		 * @param {Object} s Taxonomy object which will be added.
+		 *
+		 * @returns {Object}
+		 */
 		catAddBefore = function( s ) {
-			if ( !$('#new'+taxonomy).val() )
+			if ( !$('#new'+taxonomy).val() ) {
 				return false;
+			}
+
 			s.data += '&' + $( ':checked', '#'+taxonomy+'checklist' ).serialize();
 			$( '#' + taxonomy + '-add-submit' ).prop( 'disabled', true );
 			return s;
 		};
 
+		/**
+		 * Re-enable submit button after a taxonomy has been added.
+		 *
+		 * Re-enable submit button.
+		 * If the taxonomy has a parent place the taxonomy underneath the parent.
+		 *
+		 * @param {Object} r Response.
+		 * @param {Object} s Taxonomy data.
+		 *
+		 * @returns void
+		 */
 		catAddAfter = function( r, s ) {
 			var sup, drop = $('#new'+taxonomy+'_parent');
 
@@ -472,13 +639,15 @@
 			addAfter: catAddAfter
 		});
 
-		$('#' + taxonomy + '-add-toggle').click( function() {
+		// Add new taxonomy button toggles input form visibility.
+		$('#' + taxonomy + '-add-toggle').click( function( e ) {
+			e.preventDefault();
 			$('#' + taxonomy + '-adder').toggleClass( 'wp-hidden-children' );
 			$('a[href="#' + taxonomy + '-all"]', '#' + taxonomy + '-tabs').click();
 			$('#new'+taxonomy).focus();
-			return false;
 		});
 
+		// Sync checked items between "All {taxonomy}" and "Most used" lists.
 		$('#' + taxonomy + 'checklist, #' + taxonomy + 'checklist-pop').on( 'click', 'li.popular-category > label input[type="checkbox"]', function() {
 			var t = $(this), c = t.is(':checked'), id = t.val();
 			if ( id && t.parents('#taxonomy-'+taxonomy).length )
@@ -487,29 +656,51 @@
 
 	}); // end cats
 
-	// Custom Fields
+	// Custom Fields postbox.
 	if ( $('#postcustom').length ) {
-		$( '#the-list' ).wpList( { addAfter: function() {
-			$('table#list-table').show();
-		}, addBefore: function( s ) {
-			s.data += '&post_id=' + $('#post_ID').val();
-			return s;
-		}
+		$( '#the-list' ).wpList( {
+			/**
+			 * Add current post_ID to request to fetch custom fields
+			 *
+			 * @param {Object} s Request object.
+			 *
+			 * @returns {Object} Data modified with post_ID attached.
+			 */
+			addBefore: function( s ) {
+				s.data += '&post_id=' + $('#post_ID').val();
+				return s;
+			},
+			/**
+			 * Show the listing of custom fields after fetching.
+			 */
+			addAfter: function() {
+				$('table#list-table').show();
+			}
 		});
 	}
 
-	// submitdiv
+	/*
+	 * Publish Post box (#submitdiv)
+	 */
 	if ( $('#submitdiv').length ) {
 		stamp = $('#timestamp').html();
 		visibility = $('#post-visibility-display').html();
 
+		/**
+		 * When the visibility of a post changes sub-options should be shown or hidden.
+		 *
+		 * @returns void
+		 */
 		updateVisibility = function() {
+			// Show sticky for public posts.
 			if ( $postVisibilitySelect.find('input:radio:checked').val() != 'public' ) {
 				$('#sticky').prop('checked', false);
 				$('#sticky-span').hide();
 			} else {
 				$('#sticky-span').show();
 			}
+
+			// Show password input field for password protected post.
 			if ( $postVisibilitySelect.find('input:radio:checked').val() != 'password' ) {
 				$('#password-span').hide();
 			} else {
@@ -517,6 +708,11 @@
 			}
 		};
 
+		/**
+		 * Make sure all labels represent the current settings.
+		 *
+		 * @returns {boolean} False when an invalid timestamp has been selected, otherwise True.
+		 */
 		updateText = function() {
 
 			if ( ! $timestampdiv.length )
@@ -530,6 +726,7 @@
 			originalDate = new Date( $('#hidden_aa').val(), $('#hidden_mm').val() -1, $('#hidden_jj').val(), $('#hidden_hh').val(), $('#hidden_mn').val() );
 			currentDate = new Date( $('#cur_aa').val(), $('#cur_mm').val() -1, $('#cur_jj').val(), $('#cur_hh').val(), $('#cur_mn').val() );
 
+			// Catch unexpected date problems.
 			if ( attemptedDate.getFullYear() != aa || (1 + attemptedDate.getMonth()) != mm || attemptedDate.getDate() != jj || attemptedDate.getMinutes() != mn ) {
 				$timestampdiv.find('.timestamp-wrap').addClass('form-invalid');
 				return false;
@@ -537,6 +734,7 @@
 				$timestampdiv.find('.timestamp-wrap').removeClass('form-invalid');
 			}
 
+			// Determine what the publish should be depending on the date and post status.
 			if ( attemptedDate > currentDate && $('#original_post_status').val() != 'future' ) {
 				publishOn = postL10n.publishOnFuture;
 				$('#publish').val( postL10n.schedule );
@@ -547,20 +745,25 @@
 				publishOn = postL10n.publishOnPast;
 				$('#publish').val( postL10n.update );
 			}
-			if ( originalDate.toUTCString() == attemptedDate.toUTCString() ) { //hack
+
+			// If the date is the same, set it to trigger update events.
+			if ( originalDate.toUTCString() == attemptedDate.toUTCString() ) {
+				// Re-set to the current value.
 				$('#timestamp').html(stamp);
 			} else {
 				$('#timestamp').html(
-					publishOn + ' <b>' +
-					postL10n.dateFormat.replace( '%1$s', $('option[value="' + $('#mm').val() + '"]', '#mm').text() )
-						.replace( '%2$s', jj )
+					'\n' + publishOn + ' <b>' +
+					postL10n.dateFormat
+						.replace( '%1$s', $( 'option[value="' + mm + '"]', '#mm' ).attr( 'data-text' ) )
+						.replace( '%2$s', parseInt( jj, 10 ) )
 						.replace( '%3$s', aa )
-						.replace( '%4$s', hh )
-						.replace( '%5$s', mn ) +
+						.replace( '%4$s', ( '00' + hh ).slice( -2 ) )
+						.replace( '%5$s', ( '00' + mn ).slice( -2 ) ) +
 						'</b> '
 				);
 			}
 
+			// Add "privately published" to post status when applies.
 			if ( $postVisibilitySelect.find('input:radio:checked').val() == 'private' ) {
 				$('#publish').val( postL10n.update );
 				if ( 0 === optPublish.length ) {
@@ -582,7 +785,11 @@
 				if ( postStatus.is(':hidden') )
 					$('#misc-publishing-actions .edit-post-status').show();
 			}
+
+			// Update "Status:" to currently selected status.
 			$('#post-status-display').html($('option:selected', postStatus).text());
+
+			// Show or hide the "Save Draft" button.
 			if ( $('option:selected', postStatus).val() == 'private' || $('option:selected', postStatus).val() == 'publish' ) {
 				$('#save-post').hide();
 			} else {
@@ -596,15 +803,19 @@
 			return true;
 		};
 
-		$( '#visibility .edit-visibility').click( function () {
+		// Show the visibility options and hide the toggle button when opened.
+		$( '#visibility .edit-visibility').click( function( e ) {
+			e.preventDefault();
 			if ( $postVisibilitySelect.is(':hidden') ) {
 				updateVisibility();
-				$postVisibilitySelect.slideDown('fast').find('input[type="radio"]').first().focus();
+				$postVisibilitySelect.slideDown( 'fast', function() {
+					$postVisibilitySelect.find( 'input[type="radio"]' ).first().focus();
+				} );
 				$(this).hide();
 			}
-			return false;
 		});
 
+		// Cancel visibility selection area and hide it from view.
 		$postVisibilitySelect.find('.cancel-post-visibility').click( function( event ) {
 			$postVisibilitySelect.slideUp('fast');
 			$('#visibility-radio-' + $('#hidden-post-visibility').val()).prop('checked', true);
@@ -616,14 +827,15 @@
 			event.preventDefault();
 		});
 
+		// Set the selected visibility as current.
 		$postVisibilitySelect.find('.save-post-visibility').click( function( event ) { // crazyhorse - multiple ok cancels
 			$postVisibilitySelect.slideUp('fast');
-			$('#visibility .edit-visibility').show();
+			$('#visibility .edit-visibility').show().focus();
 			updateText();
 
 			if ( $postVisibilitySelect.find('input:radio:checked').val() != 'public' ) {
 				$('#sticky').prop('checked', false);
-			} // WEAPON LOCKED
+			}
 
 			if ( $('#sticky').prop('checked') ) {
 				sticky = 'Sticky';
@@ -635,19 +847,23 @@
 			event.preventDefault();
 		});
 
+		// When the selection changes, update labels.
 		$postVisibilitySelect.find('input:radio').change( function() {
 			updateVisibility();
 		});
 
+		// Edit publish time click.
 		$timestampdiv.siblings('a.edit-timestamp').click( function( event ) {
 			if ( $timestampdiv.is( ':hidden' ) ) {
-				$timestampdiv.slideDown('fast');
-				$('#mm').focus();
+				$timestampdiv.slideDown( 'fast', function() {
+					$( 'input, select', $timestampdiv.find( '.timestamp-wrap' ) ).first().focus();
+				} );
 				$(this).hide();
 			}
 			event.preventDefault();
 		});
 
+		// Cancel editing the publish time and hide the settings.
 		$timestampdiv.find('.cancel-timestamp').click( function( event ) {
 			$timestampdiv.slideUp('fast').siblings('a.edit-timestamp').show().focus();
 			$('#mm').val($('#hidden_mm').val());
@@ -659,14 +875,16 @@
 			event.preventDefault();
 		});
 
+		// Save the changed timestamp.
 		$timestampdiv.find('.save-timestamp').click( function( event ) { // crazyhorse - multiple ok cancels
 			if ( updateText() ) {
 				$timestampdiv.slideUp('fast');
-				$timestampdiv.siblings('a.edit-timestamp').show();
+				$timestampdiv.siblings('a.edit-timestamp').show().focus();
 			}
 			event.preventDefault();
 		});
 
+		// Cancel submit when an invalid timestamp has been selected.
 		$('#post').on( 'submit', function( event ) {
 			if ( ! updateText() ) {
 				event.preventDefault();
@@ -680,132 +898,151 @@
 			}
 		});
 
+		// Post Status edit click.
 		$postStatusSelect.siblings('a.edit-post-status').click( function( event ) {
 			if ( $postStatusSelect.is( ':hidden' ) ) {
-				$postStatusSelect.slideDown('fast').find('select').focus();
+				$postStatusSelect.slideDown( 'fast', function() {
+					$postStatusSelect.find('select').focus();
+				} );
 				$(this).hide();
 			}
 			event.preventDefault();
 		});
 
+		// Save the Post Status changes and hide the options.
 		$postStatusSelect.find('.save-post-status').click( function( event ) {
-			$postStatusSelect.slideUp('fast').siblings('a.edit-post-status').show();
+			$postStatusSelect.slideUp( 'fast' ).siblings( 'a.edit-post-status' ).show().focus();
 			updateText();
 			event.preventDefault();
 		});
 
+		// Cancel Post Status editing and hide the options.
 		$postStatusSelect.find('.cancel-post-status').click( function( event ) {
-			$('#post-status-select').slideUp('fast').siblings( 'a.edit-post-status' ).show().focus();
+			$postStatusSelect.slideUp( 'fast' ).siblings( 'a.edit-post-status' ).show().focus();
 			$('#post_status').val( $('#hidden_post_status').val() );
 			updateText();
 			event.preventDefault();
 		});
-	} // end submitdiv
+	}
 
-	// permalink
+	/**
+	 * Handle the editing of the post_name. Create the required HTML elements and update the changes via AJAX.
+	 *
+	 * @summary Permalink aka slug aka post_name editing
+	 *
+	 * @global
+	 *
+	 * @returns void
+	 */
 	function editPermalink() {
 		var i, slug_value,
+			$el, revert_e,
 			c = 0,
-			e = $('#editable-post-name'),
-			revert_e = e.html(),
 			real_slug = $('#post_name'),
 			revert_slug = real_slug.val(),
-			b = $('#edit-slug-buttons'),
-			revert_b = b.html(),
+			permalink = $( '#sample-permalink' ),
+			permalinkOrig = permalink.html(),
+			permalinkInner = $( '#sample-permalink a' ).html(),
+			buttons = $('#edit-slug-buttons'),
+			buttonsOrig = buttons.html(),
 			full = $('#editable-post-name-full');
 
-		// Deal with Twemoji in the post-name
+		// Deal with Twemoji in the post-name.
 		full.find( 'img' ).replaceWith( function() { return this.alt; } );
 		full = full.html();
 
-		$('#view-post-btn').hide();
-		b.html('<a href="#" class="save button button-small">'+postL10n.ok+'</a> <a class="cancel" href="#">'+postL10n.cancel+'</a>');
-		b.children('.save').click(function() {
-			var new_slug = e.children('input').val();
+		permalink.html( permalinkInner );
+
+		// Save current content to revert to when cancelling.
+		$el = $( '#editable-post-name' );
+		revert_e = $el.html();
+
+		buttons.html( '<button type="button" class="save button button-small">' + postL10n.ok + '</button> <button type="button" class="cancel button-link">' + postL10n.cancel + '</button>' );
+
+		// Save permalink changes.
+		buttons.children( '.save' ).click( function() {
+			var new_slug = $el.children( 'input' ).val();
+
 			if ( new_slug == $('#editable-post-name-full').text() ) {
-				b.children('.cancel').click();
-				return false;
+				buttons.children('.cancel').click();
+				return;
 			}
-			$.post(ajaxurl, {
-				action: 'sample-permalink',
-				post_id: postId,
-				new_slug: new_slug,
-				new_title: $('#title').val(),
-				samplepermalinknonce: $('#samplepermalinknonce').val()
-			}, function(data) {
-				var box = $('#edit-slug-box');
-				box.html(data);
-				if (box.hasClass('hidden')) {
-					box.fadeIn('fast', function () {
-						box.removeClass('hidden');
-					});
+
+			$.post(
+				ajaxurl,
+				{
+					action: 'sample-permalink',
+					post_id: postId,
+					new_slug: new_slug,
+					new_title: $('#title').val(),
+					samplepermalinknonce: $('#samplepermalinknonce').val()
+				},
+				function(data) {
+					var box = $('#edit-slug-box');
+					box.html(data);
+					if (box.hasClass('hidden')) {
+						box.fadeIn('fast', function () {
+							box.removeClass('hidden');
+						});
+					}
+
+					buttons.html(buttonsOrig);
+					permalink.html(permalinkOrig);
+					real_slug.val(new_slug);
+					$( '.edit-slug' ).focus();
+					wp.a11y.speak( postL10n.permalinkSaved );
 				}
-
-				b.html(revert_b);
-				real_slug.val(new_slug);
-				$('#view-post-btn').show();
-			});
-			return false;
+			);
 		});
 
-		b.children('.cancel').click(function() {
+		// Cancel editing of permalink.
+		buttons.children( '.cancel' ).click( function() {
 			$('#view-post-btn').show();
-			e.html(revert_e);
-			b.html(revert_b);
+			$el.html(revert_e);
+			buttons.html(buttonsOrig);
+			permalink.html(permalinkOrig);
 			real_slug.val(revert_slug);
-			return false;
+			$( '.edit-slug' ).focus();
 		});
 
+		// If more than 1/4th of 'full' is '%', make it empty.
 		for ( i = 0; i < full.length; ++i ) {
 			if ( '%' == full.charAt(i) )
 				c++;
 		}
+		slug_value = ( c > full.length / 4 ) ? '' : full;
 
-		slug_value = ( c > full.length / 4 ) ? '' : full;
-		e.html('<input type="text" id="new-post-slug" value="'+slug_value+'" />').children('input').keypress(function(e) {
-			var key = e.keyCode || 0;
-			// on enter, just save the new slug, don't save the post
-			if ( 13 == key ) {
-				b.children('.save').click();
-				return false;
+		$el.html( '<input type="text" id="new-post-slug" value="' + slug_value + '" autocomplete="off" />' ).children( 'input' ).keydown( function( e ) {
+			var key = e.which;
+			// On [enter], just save the new slug, don't save the post.
+			if ( 13 === key ) {
+				e.preventDefault();
+				buttons.children( '.save' ).click();
 			}
-			if ( 27 == key ) {
-				b.children('.cancel').click();
-				return false;
+			// On [esc] cancel the editing.
+			if ( 27 === key ) {
+				buttons.children( '.cancel' ).click();
 			}
 		} ).keyup( function() {
-			real_slug.val(this.value);
+			real_slug.val( this.value );
 		}).focus();
 	}
 
-	if ( $editSlugWrap.length ) {
-		$editSlugWrap.on( 'click', function( event ) {
-			var $target = $( event.target );
-
-			if ( $target.is('#editable-post-name') || $target.hasClass('edit-slug') ) {
-				editPermalink();
-			}
-		});
-	}
-
-	// word count
-	if ( typeof(wpWordCount) != 'undefined' ) {
-		$document.triggerHandler('wpcountwords', [ co.val() ]);
+	$( '#titlediv' ).on( 'click', '.edit-slug', function() {
+		editPermalink();
+	});
 
-		co.keyup( function(e) {
-			var k = e.keyCode || e.charCode;
-
-			if ( k == last )
-				return true;
-
-			if ( 13 == k || 8 == last || 46 == last )
-				$document.triggerHandler('wpcountwords', [ co.val() ]);
-
-			last = k;
-			return true;
-		});
-	}
-
+	/**
+	 * Add screen reader text to the title prompt when needed.
+	 *
+	 * @summary Title screen reader text handler.
+	 *
+	 * @param {string} id Optional. HTML ID to add the screen reader helper text to.
+	 *
+	 * @global
+	 *
+	 * @returns void
+	 */
 	wptitlehint = function(id) {
 		id = id || 'title';
 
@@ -832,20 +1069,24 @@
 
 	wptitlehint();
 
-	// Resize the visual and text editors
+	// Resize the WYSIWYG and plain text editors.
 	( function() {
 		var editor, offset, mce,
-			$textarea = $('textarea#content'),
 			$handle = $('#post-status-info'),
 			$postdivrich = $('#postdivrich');
 
-		// No point for touch devices
+		// If there are no textareas or we are on a touch device, we can't do anything.
 		if ( ! $textarea.length || 'ontouchstart' in window ) {
-			// Hide the resize handle
+			// Hide the resize handle.
 			$('#content-resize-handle').hide();
 			return;
 		}
 
+		/**
+		 * Handle drag event.
+		 *
+		 * @param {Object} event Event containing details about the drag.
+		 */
 		function dragging( event ) {
 			if ( $postdivrich.hasClass( 'wp-editor-expand' ) ) {
 				return;
@@ -860,6 +1101,9 @@
 			event.preventDefault();
 		}
 
+		/**
+		 * When the dragging stopped make sure we return focus and do a sanity check on the height.
+		 */
 		function endDrag() {
 			var height, toolbarHeight;
 
@@ -883,7 +1127,7 @@
 
 			$document.off( '.wp-editor-resize' );
 
-			// sanity check
+			// Sanity check: normalize height to stay within acceptable ranges.
 			if ( height && height > 50 && height < 5000 ) {
 				setUserSetting( 'ed_size', height );
 			}
@@ -910,8 +1154,9 @@
 		}).on( 'mouseup.wp-editor-resize', endDrag );
 	})();
 
+	// TinyMCE specific handling of Post Format changes to reflect in the editor.
 	if ( typeof tinymce !== 'undefined' ) {
-		// When changing post formats, change the editor body class
+		// When changing post formats, change the editor body class.
 		$( '#post-formats-select input.post-format' ).on( 'change.set-editor-class', function() {
 			var editor, body, format = this.id;
 
@@ -922,5 +1167,102 @@
 				$( document ).trigger( 'editor-classchange' );
 			}
 		});
+
+		// When changing page template, change the editor body class
+		$( '#page_template' ).on( 'change.set-editor-class', function() {
+			var editor, body, pageTemplate = $( this ).val() || '';
+
+			pageTemplate = pageTemplate.substr( pageTemplate.lastIndexOf( '/' ) + 1, pageTemplate.length )
+				.replace( /\.php$/, '' )
+				.replace( /\./g, '-' );
+
+			if ( pageTemplate && ( editor = tinymce.get( 'content' ) ) ) {
+				body = editor.getBody();
+				body.className = body.className.replace( /\bpage-template-[^ ]+/, '' );
+				editor.dom.addClass( body, 'page-template-' + pageTemplate );
+				$( document ).trigger( 'editor-classchange' );
+			}
+		});
+
+	}
+
+	// Save on pressing [ctrl]/[command] + [s] in the Text editor.
+	$textarea.on( 'keydown.wp-autosave', function( event ) {
+		// Key [s] has code 83.
+		if ( event.which === 83 ) {
+			if ( event.shiftKey || event.altKey || ( isMac && ( ! event.metaKey || event.ctrlKey ) ) || ( ! isMac && ! event.ctrlKey ) ) {
+				return;
+			}
+
+			wp.autosave && wp.autosave.server.triggerSave();
+			event.preventDefault();
+		}
+	});
+
+	// If the last status was auto-draft and the save is triggered, edit the current URL.
+	if ( $( '#original_post_status' ).val() === 'auto-draft' && window.history.replaceState ) {
+		var location;
+
+		$( '#publish' ).on( 'click', function() {
+			location = window.location.href;
+			location += ( location.indexOf( '?' ) !== -1 ) ? '&' : '?';
+			location += 'wp-post-new-reload=true';
+
+			window.history.replaceState( null, null, location );
+		});
 	}
 });
+
+/**
+ * TinyMCE word count display
+ */
+( function( $, counter ) {
+	$( function() {
+		var $content = $( '#content' ),
+			$count = $( '#wp-word-count' ).find( '.word-count' ),
+			prevCount = 0,
+			contentEditor;
+
+		/**
+		 * Get the word count from TinyMCE and display it
+		 */
+		function update() {
+			var text, count;
+
+			if ( ! contentEditor || contentEditor.isHidden() ) {
+				text = $content.val();
+			} else {
+				text = contentEditor.getContent( { format: 'raw' } );
+			}
+
+			count = counter.count( text );
+
+			if ( count !== prevCount ) {
+				$count.text( count );
+			}
+
+			prevCount = count;
+		}
+
+		/**
+		 * Bind the word count update triggers.
+		 *
+		 * When a node change in the main TinyMCE editor has been triggered.
+		 * When a key has been released in the plain text content editor.
+		 */
+		$( document ).on( 'tinymce-editor-init', function( event, editor ) {
+			if ( editor.id !== 'content' ) {
+				return;
+			}
+
+			contentEditor = editor;
+
+			editor.on( 'nodechange keyup', _.debounce( update, 1000 ) );
+		} );
+
+		$content.on( 'input keyup', _.debounce( update, 1000 ) );
+
+		update();
+	} );
+
+} )( jQuery, new wp.utils.WordCounter() );