wp/wp-includes/js/autosave.js
changeset 7 cf61fcea0001
parent 5 5e2f62d02dcd
child 9 177826044cd9
equal deleted inserted replaced
6:490d5cc509ed 7:cf61fcea0001
     2 // Back-compat
     2 // Back-compat
     3 window.autosave = function() {
     3 window.autosave = function() {
     4 	return true;
     4 	return true;
     5 };
     5 };
     6 
     6 
       
     7 /**
       
     8  * @summary Adds autosave to the window object on dom ready.
       
     9  *
       
    10  * @since 3.9.0
       
    11  *
       
    12  * @param {jQuery} $ jQuery object.
       
    13  * @param {window} The window object.
       
    14  *
       
    15  */
     7 ( function( $, window ) {
    16 ( function( $, window ) {
       
    17 	/**
       
    18 	 * @summary Auto saves the post.
       
    19 	 *
       
    20 	 * @since 3.9.0
       
    21 	 *
       
    22 	 * @returns {Object}
       
    23 	 * 	{{
       
    24 	 * 		getPostData: getPostData,
       
    25 	 * 		getCompareString: getCompareString,
       
    26 	 * 		disableButtons: disableButtons,
       
    27 	 * 		enableButtons: enableButtons,
       
    28 	 * 		local: ({hasStorage, getSavedPostData, save, suspend, resume}|*),
       
    29 	 * 		server: ({tempBlockSave, triggerSave, postChanged, suspend, resume}|*)}
       
    30 	 * 	}
       
    31 	 * 	The object with all functions for autosave.
       
    32 	 */
     8 	function autosave() {
    33 	function autosave() {
     9 		var initialCompareString,
    34 		var initialCompareString,
    10 		lastTriggerSave = 0,
    35 			lastTriggerSave = 0,
    11 		$document = $(document);
    36 			$document = $(document);
    12 
    37 
    13 		/**
    38 		/**
    14 		 * Returns the data saved in both local and remote autosave
    39 		 * @summary Returns the data saved in both local and remote autosave.
    15 		 *
    40 		 *
    16 		 * @return object Object containing the post data
    41 		 * @since 3.9.0
       
    42 		 *
       
    43 		 * @param {string} type The type of autosave either local or remote.
       
    44 		 *
       
    45 		 * @returns {Object} Object containing the post data.
    17 		 */
    46 		 */
    18 		function getPostData( type ) {
    47 		function getPostData( type ) {
    19 			var post_name, parent_id, data,
    48 			var post_name, parent_id, data,
    20 				time = ( new Date() ).getTime(),
    49 				time = ( new Date() ).getTime(),
    21 				cats = [],
    50 				cats = [],
    22 				editor = typeof tinymce !== 'undefined' && tinymce.get('content');
    51 				editor = getEditor();
    23 
    52 
    24 			// Don't run editor.save() more often than every 3 sec.
    53 			// Don't run editor.save() more often than every 3 seconds.
    25 			// It is resource intensive and might slow down typing in long posts on slow devices.
    54 			// It is resource intensive and might slow down typing in long posts on slow devices.
    26 			if ( editor && ! editor.isHidden() && time - 3000 > lastTriggerSave ) {
    55 			if ( editor && editor.isDirty() && ! editor.isHidden() && time - 3000 > lastTriggerSave ) {
    27 				editor.save();
    56 				editor.save();
    28 				lastTriggerSave = time;
    57 				lastTriggerSave = time;
    29 			}
    58 			}
    30 
    59 
    31 			data = {
    60 			data = {
    67 			}
    96 			}
    68 
    97 
    69 			return data;
    98 			return data;
    70 		}
    99 		}
    71 
   100 
    72 		// Concatenate title, content and excerpt. Used to track changes when auto-saving.
   101 		/**
       
   102 		 * @summary Concatenates the title, content and excerpt.
       
   103 		 *
       
   104 		 * This is used to track changes when auto-saving.
       
   105 		 *
       
   106 		 * @since 3.9.0
       
   107 		 *
       
   108 		 * @param {Object} postData The object containing the post data.
       
   109 		 *
       
   110 		 * @returns {string} A concatenated string with title, content and excerpt.
       
   111 		 */
    73 		function getCompareString( postData ) {
   112 		function getCompareString( postData ) {
    74 			if ( typeof postData === 'object' ) {
   113 			if ( typeof postData === 'object' ) {
    75 				return ( postData.post_title || '' ) + '::' + ( postData.content || '' ) + '::' + ( postData.excerpt || '' );
   114 				return ( postData.post_title || '' ) + '::' + ( postData.content || '' ) + '::' + ( postData.excerpt || '' );
    76 			}
   115 			}
    77 
   116 
    78 			return ( $('#title').val() || '' ) + '::' + ( $('#content').val() || '' ) + '::' + ( $('#excerpt').val() || '' );
   117 			return ( $('#title').val() || '' ) + '::' + ( $('#content').val() || '' ) + '::' + ( $('#excerpt').val() || '' );
    79 		}
   118 		}
    80 
   119 
       
   120 		/**
       
   121 		 * @summary Disables save buttons.
       
   122 		 *
       
   123 		 * @since 3.9.0
       
   124 		 *
       
   125 		 * @returns {void}
       
   126 		 */
    81 		function disableButtons() {
   127 		function disableButtons() {
    82 			$document.trigger('autosave-disable-buttons');
   128 			$document.trigger('autosave-disable-buttons');
       
   129 
    83 			// Re-enable 5 sec later. Just gives autosave a head start to avoid collisions.
   130 			// Re-enable 5 sec later. Just gives autosave a head start to avoid collisions.
    84 			setTimeout( enableButtons, 5000 );
   131 			setTimeout( enableButtons, 5000 );
    85 		}
   132 		}
    86 
   133 
       
   134 		/**
       
   135 		 * @summary Enables save buttons.
       
   136 		 *
       
   137 		 * @since 3.9.0
       
   138 		 *
       
   139 		 * @returns {void}
       
   140 		 */
    87 		function enableButtons() {
   141 		function enableButtons() {
    88 			$document.trigger( 'autosave-enable-buttons' );
   142 			$document.trigger( 'autosave-enable-buttons' );
    89 		}
   143 		}
    90 
   144 
    91 		// Autosave in localStorage
   145 		/**
       
   146 		 * @summary Gets the content editor.
       
   147 		 *
       
   148 		 * @since 4.6.0
       
   149 		 *
       
   150 		 * @returns {boolean|*} Returns either false if the editor is undefined,
       
   151 		 * 						or the instance of the content editor.
       
   152 		 */
       
   153 		function getEditor() {
       
   154 			return typeof tinymce !== 'undefined' && tinymce.get('content');
       
   155 		}
       
   156 
       
   157 		/**
       
   158 		 * @summary Autosave in localStorage.
       
   159 		 *
       
   160 		 * @since 3.9.0
       
   161 		 *
       
   162 		 * @returns {
       
   163 		 * {
       
   164 		 * 	hasStorage: *,
       
   165 		 * 	getSavedPostData: getSavedPostData,
       
   166 		 * 	save: save,
       
   167 		 * 	suspend: suspend,
       
   168 		 * 	resume: resume
       
   169 		 * 	}
       
   170 		 * }
       
   171 		 * The object with all functions for local storage autosave.
       
   172 		 */
    92 		function autosaveLocal() {
   173 		function autosaveLocal() {
    93 			var restorePostData, undoPostData, blog_id, post_id, hasStorage, intervalTimer,
   174 			var blog_id, post_id, hasStorage, intervalTimer,
    94 				lastCompareString,
   175 				lastCompareString,
    95 				isSuspended = false;
   176 				isSuspended = false;
    96 
   177 
    97 			// Check if the browser supports sessionStorage and it's not disabled
   178 			/**
       
   179 			 * @summary Checks if the browser supports sessionStorage and it's not disabled.
       
   180 			 *
       
   181 			 * @since 3.9.0
       
   182 			 *
       
   183 			 * @returns {boolean} True if the sessionStorage is supported and enabled.
       
   184 			 */
    98 			function checkStorage() {
   185 			function checkStorage() {
    99 				var test = Math.random().toString(),
   186 				var test = Math.random().toString(),
   100 					result = false;
   187 					result = false;
   101 
   188 
   102 				try {
   189 				try {
   108 				hasStorage = result;
   195 				hasStorage = result;
   109 				return result;
   196 				return result;
   110 			}
   197 			}
   111 
   198 
   112 			/**
   199 			/**
   113 			 * Initialize the local storage
   200 			 * @summary Initializes the local storage.
   114 			 *
   201 			 *
   115 			 * @return mixed False if no sessionStorage in the browser or an Object containing all postData for this blog
   202 			 * @since 3.9.0
       
   203 			 *
       
   204 			 * @returns {boolean|Object} False if no sessionStorage in the browser or an Object
       
   205 			 *                           containing all postData for this blog.
   116 			 */
   206 			 */
   117 			function getStorage() {
   207 			function getStorage() {
   118 				var stored_obj = false;
   208 				var stored_obj = false;
   119 				// Separate local storage containers for each blog_id
   209 				// Separate local storage containers for each blog_id
   120 				if ( hasStorage && blog_id ) {
   210 				if ( hasStorage && blog_id ) {
   129 
   219 
   130 				return stored_obj;
   220 				return stored_obj;
   131 			}
   221 			}
   132 
   222 
   133 			/**
   223 			/**
   134 			 * Set the storage for this blog
   224 			 * @summary Sets the storage for this blog.
   135 			 *
   225 			 *
   136 			 * Confirms that the data was saved successfully.
   226 			 * Confirms that the data was saved successfully.
   137 			 *
   227 			 *
   138 			 * @return bool
   228 			 * @since 3.9.0
       
   229 			 *
       
   230 			 * @returns {boolean} True if the data was saved successfully, false if it wasn't saved.
   139 			 */
   231 			 */
   140 			function setStorage( stored_obj ) {
   232 			function setStorage( stored_obj ) {
   141 				var key;
   233 				var key;
   142 
   234 
   143 				if ( hasStorage && blog_id ) {
   235 				if ( hasStorage && blog_id ) {
   148 
   240 
   149 				return false;
   241 				return false;
   150 			}
   242 			}
   151 
   243 
   152 			/**
   244 			/**
   153 			 * Get the saved post data for the current post
   245 			 * @summary Gets the saved post data for the current post.
   154 			 *
   246 			 *
   155 			 * @return mixed False if no storage or no data or the postData as an Object
   247 			 * @since 3.9.0
       
   248 			 *
       
   249 			 * @returns {boolean|Object} False if no storage or no data or the postData as an Object.
   156 			 */
   250 			 */
   157 			function getSavedPostData() {
   251 			function getSavedPostData() {
   158 				var stored = getStorage();
   252 				var stored = getStorage();
   159 
   253 
   160 				if ( ! stored || ! post_id ) {
   254 				if ( ! stored || ! post_id ) {
   163 
   257 
   164 				return stored[ 'post_' + post_id ] || false;
   258 				return stored[ 'post_' + post_id ] || false;
   165 			}
   259 			}
   166 
   260 
   167 			/**
   261 			/**
   168 			 * Set (save or delete) post data in the storage.
   262 			 * @summary Sets (save or delete) post data in the storage.
   169 			 *
   263 			 *
   170 			 * If stored_data evaluates to 'false' the storage key for the current post will be removed
   264 			 * If stored_data evaluates to 'false' the storage key for the current post will be removed.
   171 			 *
   265 			 *
   172 			 * $param stored_data The post data to store or null/false/empty to delete the key
   266 			 * @since 3.9.0
   173 			 * @return bool
   267 			 *
       
   268 			 * @param {Object|boolean|null} stored_data The post data to store or null/false/empty to delete the key.
       
   269 			 *
       
   270 			 * @returns {boolean} True if data is stored, false if data was removed.
   174 			 */
   271 			 */
   175 			function setData( stored_data ) {
   272 			function setData( stored_data ) {
   176 				var stored = getStorage();
   273 				var stored = getStorage();
   177 
   274 
   178 				if ( ! stored || ! post_id ) {
   275 				if ( ! stored || ! post_id ) {
   188 				}
   285 				}
   189 
   286 
   190 				return setStorage( stored );
   287 				return setStorage( stored );
   191 			}
   288 			}
   192 
   289 
       
   290 			/**
       
   291 			 * @summary Sets isSuspended to true.
       
   292 			 *
       
   293 			 * @since 3.9.0
       
   294 			 *
       
   295 			 * @returns {void}
       
   296 			 */
   193 			function suspend() {
   297 			function suspend() {
   194 				isSuspended = true;
   298 				isSuspended = true;
   195 			}
   299 			}
   196 
   300 
       
   301 			/**
       
   302 			 * @summary Sets isSuspended to false.
       
   303 			 *
       
   304 			 * @since 3.9.0
       
   305 			 *
       
   306 			 * @returns {void}
       
   307 			 */
   197 			function resume() {
   308 			function resume() {
   198 				isSuspended = false;
   309 				isSuspended = false;
   199 			}
   310 			}
   200 
   311 
   201 			/**
   312 			/**
   202 			 * Save post data for the current post
   313 			 * @summary Saves post data for the current post.
   203 			 *
   314 			 *
   204 			 * Runs on a 15 sec. interval, saves when there are differences in the post title or content.
   315 			 * Runs on a 15 sec. interval, saves when there are differences in the post title or content.
   205 			 * When the optional data is provided, updates the last saved post data.
   316 			 * When the optional data is provided, updates the last saved post data.
   206 			 *
   317 			 *
   207 			 * $param data optional Object The post data for saving, minimum 'post_title' and 'content'
   318 			 * @since 3.9.0
   208 			 * @return bool
   319 			 *
       
   320 			 * @param {Object} data The post data for saving, minimum 'post_title' and 'content'.
       
   321 			 *
       
   322 			 * @returns {boolean} Returns true when data has been saved, otherwise it returns false.
   209 			 */
   323 			 */
   210 			function save( data ) {
   324 			function save( data ) {
   211 				var postData, compareString,
   325 				var postData, compareString,
   212 					result = false;
   326 					result = false;
   213 
   327 
   226 
   340 
   227 				if ( typeof lastCompareString === 'undefined' ) {
   341 				if ( typeof lastCompareString === 'undefined' ) {
   228 					lastCompareString = initialCompareString;
   342 					lastCompareString = initialCompareString;
   229 				}
   343 				}
   230 
   344 
   231 				// If the content, title and excerpt did not change since the last save, don't save again
   345 				// If the content, title and excerpt did not change since the last save, don't save again.
   232 				if ( compareString === lastCompareString ) {
   346 				if ( compareString === lastCompareString ) {
   233 					return false;
   347 					return false;
   234 				}
   348 				}
   235 
   349 
   236 				postData.save_time = ( new Date() ).getTime();
   350 				postData.save_time = ( new Date() ).getTime();
   242 				}
   356 				}
   243 
   357 
   244 				return result;
   358 				return result;
   245 			}
   359 			}
   246 
   360 
   247 			// Run on DOM ready
   361 			/**
       
   362 			 * @summary Initializes the auto save function.
       
   363 			 *
       
   364 			 * Checks whether the editor is active or not to use the editor events
       
   365 			 * to autosave, or uses the values from the elements to autosave.
       
   366 			 *
       
   367 			 * Runs on DOM ready.
       
   368 			 *
       
   369 			 * @since 3.9.0
       
   370 			 *
       
   371 			 * @returns {void}
       
   372 			 */
   248 			function run() {
   373 			function run() {
   249 				post_id = $('#post_ID').val() || 0;
   374 				post_id = $('#post_ID').val() || 0;
   250 
   375 
   251 				// Check if the local post data is different than the loaded post data.
   376 				// Check if the local post data is different than the loaded post data.
   252 				if ( $( '#wp-content-wrap' ).hasClass( 'tmce-active' ) ) {
   377 				if ( $( '#wp-content-wrap' ).hasClass( 'tmce-active' ) ) {
       
   378 
   253 					// If TinyMCE loads first, check the post 1.5 sec. after it is ready.
   379 					// If TinyMCE loads first, check the post 1.5 sec. after it is ready.
   254 					// By this time the content has been loaded in the editor and 'saved' to the textarea.
   380 					// By this time the content has been loaded in the editor and 'saved' to the textarea.
   255 					// This prevents false positives.
   381 					// This prevents false positives.
   256 					$document.on( 'tinymce-editor-init.autosave', function() {
   382 					$document.on( 'tinymce-editor-init.autosave', function() {
   257 						window.setTimeout( function() {
   383 						window.setTimeout( function() {
   264 
   390 
   265 				// Save every 15 sec.
   391 				// Save every 15 sec.
   266 				intervalTimer = window.setInterval( save, 15000 );
   392 				intervalTimer = window.setInterval( save, 15000 );
   267 
   393 
   268 				$( 'form#post' ).on( 'submit.autosave-local', function() {
   394 				$( 'form#post' ).on( 'submit.autosave-local', function() {
   269 					var editor = typeof tinymce !== 'undefined' && tinymce.get('content'),
   395 					var editor = getEditor(),
   270 						post_id = $('#post_ID').val() || 0;
   396 						post_id = $('#post_ID').val() || 0;
   271 
   397 
   272 					if ( editor && ! editor.isHidden() ) {
   398 					if ( editor && ! editor.isHidden() ) {
       
   399 
   273 						// Last onSubmit event in the editor, needs to run after the content has been moved to the textarea.
   400 						// Last onSubmit event in the editor, needs to run after the content has been moved to the textarea.
   274 						editor.on( 'submit', function() {
   401 						editor.on( 'submit', function() {
   275 							save({
   402 							save({
   276 								post_title: $( '#title' ).val() || '',
   403 								post_title: $( '#title' ).val() || '',
   277 								content: $( '#content' ).val() || '',
   404 								content: $( '#content' ).val() || '',
   284 							content: $( '#content' ).val() || '',
   411 							content: $( '#content' ).val() || '',
   285 							excerpt: $( '#excerpt' ).val() || ''
   412 							excerpt: $( '#excerpt' ).val() || ''
   286 						});
   413 						});
   287 					}
   414 					}
   288 
   415 
   289 					wpCookies.set( 'wp-saving-post', post_id + '-check', 24 * 60 * 60 );
   416 					var secure = ( 'https:' === window.location.protocol );
       
   417 					wpCookies.set( 'wp-saving-post', post_id + '-check', 24 * 60 * 60, false, false, secure );
   290 				});
   418 				});
   291 			}
   419 			}
   292 
   420 
   293 			// Strip whitespace and compare two strings
   421 			/**
       
   422 			 * @summary Compares 2 strings.
       
   423 			 *
       
   424 			 * Removes whitespaces in the strings before comparing them.
       
   425 			 *
       
   426 			 * @since 3.9.0
       
   427 			 *
       
   428 			 * @param {string} str1 The first string.
       
   429 			 * @param {string} str2 The second string.
       
   430 			 * @returns {boolean} True if the strings are the same.
       
   431 			 */
   294 			function compare( str1, str2 ) {
   432 			function compare( str1, str2 ) {
   295 				function removeSpaces( string ) {
   433 				function removeSpaces( string ) {
   296 					return string.toString().replace(/[\x20\t\r\n\f]+/g, '');
   434 					return string.toString().replace(/[\x20\t\r\n\f]+/g, '');
   297 				}
   435 				}
   298 
   436 
   299 				return ( removeSpaces( str1 || '' ) === removeSpaces( str2 || '' ) );
   437 				return ( removeSpaces( str1 || '' ) === removeSpaces( str2 || '' ) );
   300 			}
   438 			}
   301 
   439 
   302 			/**
   440 			/**
   303 			 * Check if the saved data for the current post (if any) is different than the loaded post data on the screen
   441 			 * @summary Checks if the saved data for the current post (if any) is different
       
   442 			 * than the loaded post data on the screen.
   304 			 *
   443 			 *
   305 			 * Shows a standard message letting the user restore the post data if different.
   444 			 * Shows a standard message letting the user restore the post data if different.
   306 			 *
   445 			 *
   307 			 * @return void
   446 			 * @since 3.9.0
       
   447 			 *
       
   448 			 * @returns {void}
   308 			 */
   449 			 */
   309 			function checkPost() {
   450 			function checkPost() {
   310 				var content, post_title, excerpt, $notice,
   451 				var content, post_title, excerpt, $notice,
   311 					postData = getSavedPostData(),
   452 					postData = getSavedPostData(),
   312 					cookie = wpCookies.get( 'wp-saving-post' );
   453 					cookie = wpCookies.get( 'wp-saving-post' ),
       
   454 					$newerAutosaveNotice = $( '#has-newer-autosave' ).parent( '.notice' ),
       
   455 					$headerEnd = $( '.wp-header-end' );
   313 
   456 
   314 				if ( cookie === post_id + '-saved' ) {
   457 				if ( cookie === post_id + '-saved' ) {
   315 					wpCookies.remove( 'wp-saving-post' );
   458 					wpCookies.remove( 'wp-saving-post' );
   316 					// The post was saved properly, remove old data and bail
   459 					// The post was saved properly, remove old data and bail
   317 					setData( false );
   460 					setData( false );
   320 
   463 
   321 				if ( ! postData ) {
   464 				if ( ! postData ) {
   322 					return;
   465 					return;
   323 				}
   466 				}
   324 
   467 
   325 				// There is a newer autosave. Don't show two "restore" notices at the same time.
       
   326 				if ( $( '#has-newer-autosave' ).length ) {
       
   327 					return;
       
   328 				}
       
   329 
       
   330 				content = $( '#content' ).val() || '';
   468 				content = $( '#content' ).val() || '';
   331 				post_title = $( '#title' ).val() || '';
   469 				post_title = $( '#title' ).val() || '';
   332 				excerpt = $( '#excerpt' ).val() || '';
   470 				excerpt = $( '#excerpt' ).val() || '';
   333 
   471 
   334 				if ( compare( content, postData.content ) && compare( post_title, postData.post_title ) &&
   472 				if ( compare( content, postData.content ) && compare( post_title, postData.post_title ) &&
   335 					compare( excerpt, postData.excerpt ) ) {
   473 					compare( excerpt, postData.excerpt ) ) {
   336 
   474 
   337 					return;
   475 					return;
   338 				}
   476 				}
   339 
   477 
   340 				restorePostData = postData;
   478 				/*
   341 				undoPostData = {
   479 				 * If '.wp-header-end' is found, append the notices after it otherwise
   342 					content: content,
   480 				 * after the first h1 or h2 heading found within the main content.
   343 					post_title: post_title,
   481 				 */
   344 					excerpt: excerpt
   482 				if ( ! $headerEnd.length ) {
   345 				};
   483 					$headerEnd = $( '.wrap h1, .wrap h2' ).first();
   346 
   484 				}
   347 				$notice = $( '#local-storage-notice' );
   485 
   348 				$('.wrap h2').first().after( $notice.addClass( 'notice-warning' ).show() );
   486 				$notice = $( '#local-storage-notice' )
   349 
   487 					.insertAfter( $headerEnd )
   350 				$notice.on( 'click.autosave-local', function( event ) {
   488 					.addClass( 'notice-warning' );
   351 					var $target = $( event.target );
   489 
   352 
   490 				if ( $newerAutosaveNotice.length ) {
   353 					if ( $target.hasClass( 'restore-backup' ) ) {
   491 
   354 						restorePost( restorePostData );
   492 					// If there is a "server" autosave notice, hide it.
   355 						$target.parent().hide();
   493 					// The data in the session storage is either the same or newer.
   356 						$(this).find( 'p.undo-restore' ).show();
   494 					$newerAutosaveNotice.slideUp( 150, function() {
   357 						$notice.removeClass( 'notice-warning' ).addClass( 'notice-success' );
   495 						$notice.slideDown( 150 );
   358 					} else if ( $target.hasClass( 'undo-restore-backup' ) ) {
   496 					});
   359 						restorePost( undoPostData );
   497 				} else {
   360 						$target.parent().hide();
   498 					$notice.slideDown( 200 );
   361 						$(this).find( 'p.local-restore' ).show();
   499 				}
   362 						$notice.removeClass( 'notice-success' ).addClass( 'notice-warning' );
   500 
   363 					}
   501 				$notice.find( '.restore-backup' ).on( 'click.autosave-local', function() {
   364 
   502 					restorePost( postData );
   365 					event.preventDefault();
   503 					$notice.fadeTo( 250, 0, function() {
       
   504 						$notice.slideUp( 150 );
       
   505 					});
   366 				});
   506 				});
   367 			}
   507 			}
   368 
   508 
   369 			// Restore the current title, content and excerpt from postData.
   509 			/**
       
   510 			 * @summary Restores the current title, content and excerpt from postData.
       
   511 			 *
       
   512 			 * @since 3.9.0
       
   513 			 *
       
   514 			 * @param {Object} postData The object containing all post data.
       
   515 			 *
       
   516 			 * @returns {boolean} True if the post is restored.
       
   517 			 */
   370 			function restorePost( postData ) {
   518 			function restorePost( postData ) {
   371 				var editor;
   519 				var editor;
   372 
   520 
   373 				if ( postData ) {
   521 				if ( postData ) {
   374 					// Set the last saved data
   522 					// Set the last saved data
   377 					if ( $( '#title' ).val() !== postData.post_title ) {
   525 					if ( $( '#title' ).val() !== postData.post_title ) {
   378 						$( '#title' ).focus().val( postData.post_title || '' );
   526 						$( '#title' ).focus().val( postData.post_title || '' );
   379 					}
   527 					}
   380 
   528 
   381 					$( '#excerpt' ).val( postData.excerpt || '' );
   529 					$( '#excerpt' ).val( postData.excerpt || '' );
   382 					editor = typeof tinymce !== 'undefined' && tinymce.get('content');
   530 					editor = getEditor();
   383 
   531 
   384 					if ( editor && ! editor.isHidden() && typeof switchEditors !== 'undefined' ) {
   532 					if ( editor && ! editor.isHidden() && typeof switchEditors !== 'undefined' ) {
       
   533 						if ( editor.settings.wpautop && postData.content ) {
       
   534 							postData.content = switchEditors.wpautop( postData.content );
       
   535 						}
       
   536 
   385 						// Make sure there's an undo level in the editor
   537 						// Make sure there's an undo level in the editor
   386 						editor.undoManager.add();
   538 						editor.undoManager.transact( function() {
   387 						editor.setContent( postData.content ? switchEditors.wpautop( postData.content ) : '' );
   539 							editor.setContent( postData.content || '' );
       
   540 							editor.nodeChanged();
       
   541 						});
   388 					} else {
   542 					} else {
       
   543 
   389 						// Make sure the Text editor is selected
   544 						// Make sure the Text editor is selected
   390 						$( '#content-html' ).click();
   545 						$( '#content-html' ).click();
   391 						$( '#content' ).val( postData.content );
   546 						$( '#content' ).focus();
       
   547 
       
   548 						// Using document.execCommand() will let the user undo.
       
   549 						document.execCommand( 'selectAll' );
       
   550 						document.execCommand( 'insertText', false, postData.content || '' );
   392 					}
   551 					}
   393 
   552 
   394 					return true;
   553 					return true;
   395 				}
   554 				}
   396 
   555 
   413 				suspend: suspend,
   572 				suspend: suspend,
   414 				resume: resume
   573 				resume: resume
   415 			};
   574 			};
   416 		}
   575 		}
   417 
   576 
   418 		// Autosave on the server
   577 		/**
       
   578 		 * @summary Auto saves the post on the server.
       
   579 		 *
       
   580 		 * @since 3.9.0
       
   581 		 *
       
   582 		 * @returns {Object} {
       
   583 		 * 	{
       
   584 		 * 		tempBlockSave: tempBlockSave,
       
   585 		 * 		triggerSave: triggerSave,
       
   586 		 * 		postChanged: postChanged,
       
   587 		 * 		suspend: suspend,
       
   588 		 * 		resume: resume
       
   589 		 * 		}
       
   590 		 * 	} The object all functions for autosave.
       
   591 		 */
   419 		function autosaveServer() {
   592 		function autosaveServer() {
   420 			var _blockSave, _blockSaveTimer, previousCompareString, lastCompareString,
   593 			var _blockSave, _blockSaveTimer, previousCompareString, lastCompareString,
   421 				nextRun = 0,
   594 				nextRun = 0,
   422 				isSuspended = false;
   595 				isSuspended = false;
   423 
   596 
   424 			// Block saving for the next 10 sec.
   597 
       
   598 			/**
       
   599 			 * @summary  Blocks saving for the next 10 seconds.
       
   600 			 *
       
   601 			 * @since 3.9.0
       
   602 			 *
       
   603 			 * @returns {void}
       
   604 			 */
   425 			function tempBlockSave() {
   605 			function tempBlockSave() {
   426 				_blockSave = true;
   606 				_blockSave = true;
   427 				window.clearTimeout( _blockSaveTimer );
   607 				window.clearTimeout( _blockSaveTimer );
   428 
   608 
   429 				_blockSaveTimer = window.setTimeout( function() {
   609 				_blockSaveTimer = window.setTimeout( function() {
   430 					_blockSave = false;
   610 					_blockSave = false;
   431 				}, 10000 );
   611 				}, 10000 );
   432 			}
   612 			}
   433 
   613 
       
   614 			/**
       
   615 			 * @summary Sets isSuspended to true.
       
   616 			 *
       
   617 			 * @since 3.9.0
       
   618 			 *
       
   619 			 * @returns {void}
       
   620 			 */
   434 			function suspend() {
   621 			function suspend() {
   435 				isSuspended = true;
   622 				isSuspended = true;
   436 			}
   623 			}
   437 
   624 
       
   625 			/**
       
   626 			 * @summary Sets isSuspended to false.
       
   627 			 *
       
   628 			 * @since 3.9.0
       
   629 			 *
       
   630 			 * @returns {void}
       
   631 			 */
   438 			function resume() {
   632 			function resume() {
   439 				isSuspended = false;
   633 				isSuspended = false;
   440 			}
   634 			}
   441 
   635 
   442 			// Runs on heartbeat-response
   636 			/**
       
   637 			 * @summary Triggers the autosave with the post data.
       
   638 			 *
       
   639 			 * @since 3.9.0
       
   640 			 *
       
   641 			 * @param {Object} data The post data.
       
   642 			 *
       
   643 			 * @returns {void}
       
   644 			 */
   443 			function response( data ) {
   645 			function response( data ) {
   444 				_schedule();
   646 				_schedule();
   445 				_blockSave = false;
   647 				_blockSave = false;
   446 				lastCompareString = previousCompareString;
   648 				lastCompareString = previousCompareString;
   447 				previousCompareString = '';
   649 				previousCompareString = '';
   454 					$( '#auto_draft' ).val('');
   656 					$( '#auto_draft' ).val('');
   455 				}
   657 				}
   456 			}
   658 			}
   457 
   659 
   458 			/**
   660 			/**
   459 			 * Save immediately
   661 			 * @summary Saves immediately.
   460 			 *
   662 			 *
   461 			 * Resets the timing and tells heartbeat to connect now
   663 			 * Resets the timing and tells heartbeat to connect now.
   462 			 *
   664 			 *
   463 			 * @return void
   665 			 * @since 3.9.0
       
   666 			 *
       
   667 			 * @returns {void}
   464 			 */
   668 			 */
   465 			function triggerSave() {
   669 			function triggerSave() {
   466 				nextRun = 0;
   670 				nextRun = 0;
   467 				wp.heartbeat.connectNow();
   671 				wp.heartbeat.connectNow();
   468 			}
   672 			}
   469 
   673 
   470 			/**
   674 			/**
   471 			 * Checks if the post content in the textarea has changed since page load.
   675 			 * @summary Checks if the post content in the textarea has changed since page load.
   472 			 *
   676 			 *
   473 			 * This also happens when TinyMCE is active and editor.save() is triggered by
   677 			 * This also happens when TinyMCE is active and editor.save() is triggered by
   474 			 * wp.autosave.getPostData().
   678 			 * wp.autosave.getPostData().
   475 			 *
   679 			 *
   476 			 * @return bool
   680 			 * @since 3.9.0
       
   681 			 *
       
   682 			 * @return {boolean} True if the post has been changed.
   477 			 */
   683 			 */
   478 			function postChanged() {
   684 			function postChanged() {
   479 				return getCompareString() !== initialCompareString;
   685 				return getCompareString() !== initialCompareString;
   480 			}
   686 			}
   481 
   687 
   482 			// Runs on 'heartbeat-send'
   688 			/**
       
   689 			 * @summary Checks if the post can be saved or not.
       
   690 			 *
       
   691 			 * If the post hasn't changed or it cannot be updated,
       
   692 			 * because the autosave is blocked or suspended, the function returns false.
       
   693 			 *
       
   694 			 * @since 3.9.0
       
   695 			 *
       
   696 			 * @returns {Object} Returns the post data.
       
   697 			 */
   483 			function save() {
   698 			function save() {
   484 				var postData, compareString;
   699 				var postData, compareString;
   485 
   700 
   486 				// window.autosave() used for back-compat
   701 				// window.autosave() used for back-compat
   487 				if ( isSuspended || _blockSave || ! window.autosave() ) {
   702 				if ( isSuspended || _blockSave || ! window.autosave() ) {
   515 				postData._wpnonce = $( '#_wpnonce' ).val() || '';
   730 				postData._wpnonce = $( '#_wpnonce' ).val() || '';
   516 
   731 
   517 				return postData;
   732 				return postData;
   518 			}
   733 			}
   519 
   734 
       
   735 			/**
       
   736 			 * @summary Sets the next run, based on the autosave interval.
       
   737 			 *
       
   738 			 * @private
       
   739 			 *
       
   740 			 * @since 3.9.0
       
   741 			 *
       
   742 			 * @returns {void}
       
   743 			 */
   520 			function _schedule() {
   744 			function _schedule() {
   521 				nextRun = ( new Date() ).getTime() + ( autosaveL10n.autosaveInterval * 1000 ) || 60000;
   745 				nextRun = ( new Date() ).getTime() + ( autosaveL10n.autosaveInterval * 1000 ) || 60000;
   522 			}
   746 			}
   523 
   747 
       
   748 			/**
       
   749 			 * @summary Sets the autosaveData on the autosave heartbeat.
       
   750 			 *
       
   751 			 * @since 3.9.0
       
   752 			 *
       
   753 			 * @returns {void}
       
   754 			 */
   524 			$document.on( 'heartbeat-send.autosave', function( event, data ) {
   755 			$document.on( 'heartbeat-send.autosave', function( event, data ) {
   525 				var autosaveData = save();
   756 				var autosaveData = save();
   526 
   757 
   527 				if ( autosaveData ) {
   758 				if ( autosaveData ) {
   528 					data.wp_autosave = autosaveData;
   759 					data.wp_autosave = autosaveData;
   529 				}
   760 				}
       
   761 
       
   762 				/**
       
   763 				 * @summary Triggers the autosave of the post with the autosave data
       
   764 				 * on the autosave heartbeat.
       
   765 				 *
       
   766 				 * @since 3.9.0
       
   767 				 *
       
   768 				 * @returns {void}
       
   769 				 */
   530 			}).on( 'heartbeat-tick.autosave', function( event, data ) {
   770 			}).on( 'heartbeat-tick.autosave', function( event, data ) {
   531 				if ( data.wp_autosave ) {
   771 				if ( data.wp_autosave ) {
   532 					response( data.wp_autosave );
   772 					response( data.wp_autosave );
   533 				}
   773 				}
       
   774 				/**
       
   775 				 * @summary Disables buttons and throws a notice when the connection is lost.
       
   776 				 *
       
   777 				 * @since 3.9.0
       
   778 				 *
       
   779 				 * @returns {void}
       
   780 				 */
   534 			}).on( 'heartbeat-connection-lost.autosave', function( event, error, status ) {
   781 			}).on( 'heartbeat-connection-lost.autosave', function( event, error, status ) {
       
   782 
   535 				// When connection is lost, keep user from submitting changes.
   783 				// When connection is lost, keep user from submitting changes.
   536 				if ( 'timeout' === error || 603 === status ) {
   784 				if ( 'timeout' === error || 603 === status ) {
   537 					var $notice = $('#lost-connection-notice');
   785 					var $notice = $('#lost-connection-notice');
   538 
   786 
   539 					if ( ! wp.autosave.local.hasStorage ) {
   787 					if ( ! wp.autosave.local.hasStorage ) {
   541 					}
   789 					}
   542 
   790 
   543 					$notice.show();
   791 					$notice.show();
   544 					disableButtons();
   792 					disableButtons();
   545 				}
   793 				}
       
   794 
       
   795 				/**
       
   796 				 * @summary Enables buttons when the connection is restored.
       
   797 				 *
       
   798 				 * @since 3.9.0
       
   799 				 *
       
   800 				 * @returns {void}
       
   801 				 */
   546 			}).on( 'heartbeat-connection-restored.autosave', function() {
   802 			}).on( 'heartbeat-connection-restored.autosave', function() {
   547 				$('#lost-connection-notice').hide();
   803 				$('#lost-connection-notice').hide();
   548 				enableButtons();
   804 				enableButtons();
   549 			}).ready( function() {
   805 			}).ready( function() {
   550 				_schedule();
   806 				_schedule();
   557 				suspend: suspend,
   813 				suspend: suspend,
   558 				resume: resume
   814 				resume: resume
   559 			};
   815 			};
   560 		}
   816 		}
   561 
   817 
   562 		// Wait for TinyMCE to initialize plus 1 sec. for any external css to finish loading,
   818 		/**
   563 		// then 'save' to the textarea before setting initialCompareString.
   819 		 * @summary Sets the autosave time out.
   564 		// This avoids any insignificant differences between the initial textarea content and the content
   820 		 *
   565 		// extracted from the editor.
   821 		 * Wait for TinyMCE to initialize plus 1 second. for any external css to finish loading,
       
   822 		 * then save to the textarea before setting initialCompareString.
       
   823 		 * This avoids any insignificant differences between the initial textarea content and the content
       
   824 		 * extracted from the editor.
       
   825 		 *
       
   826 		 * @since 3.9.0
       
   827 		 *
       
   828 		 * @returns {void}
       
   829 		 */
   566 		$document.on( 'tinymce-editor-init.autosave', function( event, editor ) {
   830 		$document.on( 'tinymce-editor-init.autosave', function( event, editor ) {
   567 			if ( editor.id === 'content' ) {
   831 			if ( editor.id === 'content' ) {
   568 				window.setTimeout( function() {
   832 				window.setTimeout( function() {
   569 					editor.save();
   833 					editor.save();
   570 					initialCompareString = getCompareString();
   834 					initialCompareString = getCompareString();
   571 				}, 1000 );
   835 				}, 1000 );
   572 			}
   836 			}
   573 		}).ready( function() {
   837 		}).ready( function() {
       
   838 
   574 			// Set the initial compare string in case TinyMCE is not used or not loaded first
   839 			// Set the initial compare string in case TinyMCE is not used or not loaded first
   575 			initialCompareString = getCompareString();
   840 			initialCompareString = getCompareString();
   576 		});
   841 		});
   577 
   842 
   578 		return {
   843 		return {
   583 			local: autosaveLocal(),
   848 			local: autosaveLocal(),
   584 			server: autosaveServer()
   849 			server: autosaveServer()
   585 		};
   850 		};
   586 	}
   851 	}
   587 
   852 
       
   853 	/** @namespace wp */
   588 	window.wp = window.wp || {};
   854 	window.wp = window.wp || {};
   589 	window.wp.autosave = autosave();
   855 	window.wp.autosave = autosave();
   590 
   856 
   591 }( jQuery, window ));
   857 }( jQuery, window ));