wp/wp-includes/js/autosave.js
changeset 7 cf61fcea0001
parent 5 5e2f62d02dcd
child 9 177826044cd9
--- a/wp/wp-includes/js/autosave.js	Tue Jun 09 11:14:17 2015 +0000
+++ b/wp/wp-includes/js/autosave.js	Mon Oct 14 17:39:30 2019 +0200
@@ -4,26 +4,55 @@
 	return true;
 };
 
+/**
+ * @summary Adds autosave to the window object on dom ready.
+ *
+ * @since 3.9.0
+ *
+ * @param {jQuery} $ jQuery object.
+ * @param {window} The window object.
+ *
+ */
 ( function( $, window ) {
+	/**
+	 * @summary Auto saves the post.
+	 *
+	 * @since 3.9.0
+	 *
+	 * @returns {Object}
+	 * 	{{
+	 * 		getPostData: getPostData,
+	 * 		getCompareString: getCompareString,
+	 * 		disableButtons: disableButtons,
+	 * 		enableButtons: enableButtons,
+	 * 		local: ({hasStorage, getSavedPostData, save, suspend, resume}|*),
+	 * 		server: ({tempBlockSave, triggerSave, postChanged, suspend, resume}|*)}
+	 * 	}
+	 * 	The object with all functions for autosave.
+	 */
 	function autosave() {
 		var initialCompareString,
-		lastTriggerSave = 0,
-		$document = $(document);
+			lastTriggerSave = 0,
+			$document = $(document);
 
 		/**
-		 * Returns the data saved in both local and remote autosave
+		 * @summary Returns the data saved in both local and remote autosave.
+		 *
+		 * @since 3.9.0
 		 *
-		 * @return object Object containing the post data
+		 * @param {string} type The type of autosave either local or remote.
+		 *
+		 * @returns {Object} Object containing the post data.
 		 */
 		function getPostData( type ) {
 			var post_name, parent_id, data,
 				time = ( new Date() ).getTime(),
 				cats = [],
-				editor = typeof tinymce !== 'undefined' && tinymce.get('content');
+				editor = getEditor();
 
-			// Don't run editor.save() more often than every 3 sec.
+			// Don't run editor.save() more often than every 3 seconds.
 			// It is resource intensive and might slow down typing in long posts on slow devices.
-			if ( editor && ! editor.isHidden() && time - 3000 > lastTriggerSave ) {
+			if ( editor && editor.isDirty() && ! editor.isHidden() && time - 3000 > lastTriggerSave ) {
 				editor.save();
 				lastTriggerSave = time;
 			}
@@ -69,7 +98,17 @@
 			return data;
 		}
 
-		// Concatenate title, content and excerpt. Used to track changes when auto-saving.
+		/**
+		 * @summary Concatenates the title, content and excerpt.
+		 *
+		 * This is used to track changes when auto-saving.
+		 *
+		 * @since 3.9.0
+		 *
+		 * @param {Object} postData The object containing the post data.
+		 *
+		 * @returns {string} A concatenated string with title, content and excerpt.
+		 */
 		function getCompareString( postData ) {
 			if ( typeof postData === 'object' ) {
 				return ( postData.post_title || '' ) + '::' + ( postData.content || '' ) + '::' + ( postData.excerpt || '' );
@@ -78,23 +117,71 @@
 			return ( $('#title').val() || '' ) + '::' + ( $('#content').val() || '' ) + '::' + ( $('#excerpt').val() || '' );
 		}
 
+		/**
+		 * @summary Disables save buttons.
+		 *
+		 * @since 3.9.0
+		 *
+		 * @returns {void}
+		 */
 		function disableButtons() {
 			$document.trigger('autosave-disable-buttons');
+
 			// Re-enable 5 sec later. Just gives autosave a head start to avoid collisions.
 			setTimeout( enableButtons, 5000 );
 		}
 
+		/**
+		 * @summary Enables save buttons.
+		 *
+		 * @since 3.9.0
+		 *
+		 * @returns {void}
+		 */
 		function enableButtons() {
 			$document.trigger( 'autosave-enable-buttons' );
 		}
 
-		// Autosave in localStorage
+		/**
+		 * @summary Gets the content editor.
+		 *
+		 * @since 4.6.0
+		 *
+		 * @returns {boolean|*} Returns either false if the editor is undefined,
+		 * 						or the instance of the content editor.
+		 */
+		function getEditor() {
+			return typeof tinymce !== 'undefined' && tinymce.get('content');
+		}
+
+		/**
+		 * @summary Autosave in localStorage.
+		 *
+		 * @since 3.9.0
+		 *
+		 * @returns {
+		 * {
+		 * 	hasStorage: *,
+		 * 	getSavedPostData: getSavedPostData,
+		 * 	save: save,
+		 * 	suspend: suspend,
+		 * 	resume: resume
+		 * 	}
+		 * }
+		 * The object with all functions for local storage autosave.
+		 */
 		function autosaveLocal() {
-			var restorePostData, undoPostData, blog_id, post_id, hasStorage, intervalTimer,
+			var blog_id, post_id, hasStorage, intervalTimer,
 				lastCompareString,
 				isSuspended = false;
 
-			// Check if the browser supports sessionStorage and it's not disabled
+			/**
+			 * @summary Checks if the browser supports sessionStorage and it's not disabled.
+			 *
+			 * @since 3.9.0
+			 *
+			 * @returns {boolean} True if the sessionStorage is supported and enabled.
+			 */
 			function checkStorage() {
 				var test = Math.random().toString(),
 					result = false;
@@ -110,9 +197,12 @@
 			}
 
 			/**
-			 * Initialize the local storage
+			 * @summary Initializes the local storage.
 			 *
-			 * @return mixed False if no sessionStorage in the browser or an Object containing all postData for this blog
+			 * @since 3.9.0
+			 *
+			 * @returns {boolean|Object} False if no sessionStorage in the browser or an Object
+			 *                           containing all postData for this blog.
 			 */
 			function getStorage() {
 				var stored_obj = false;
@@ -131,11 +221,13 @@
 			}
 
 			/**
-			 * Set the storage for this blog
+			 * @summary Sets the storage for this blog.
 			 *
 			 * Confirms that the data was saved successfully.
 			 *
-			 * @return bool
+			 * @since 3.9.0
+			 *
+			 * @returns {boolean} True if the data was saved successfully, false if it wasn't saved.
 			 */
 			function setStorage( stored_obj ) {
 				var key;
@@ -150,9 +242,11 @@
 			}
 
 			/**
-			 * Get the saved post data for the current post
+			 * @summary Gets the saved post data for the current post.
 			 *
-			 * @return mixed False if no storage or no data or the postData as an Object
+			 * @since 3.9.0
+			 *
+			 * @returns {boolean|Object} False if no storage or no data or the postData as an Object.
 			 */
 			function getSavedPostData() {
 				var stored = getStorage();
@@ -165,12 +259,15 @@
 			}
 
 			/**
-			 * Set (save or delete) post data in the storage.
+			 * @summary Sets (save or delete) post data in the storage.
+			 *
+			 * If stored_data evaluates to 'false' the storage key for the current post will be removed.
 			 *
-			 * If stored_data evaluates to 'false' the storage key for the current post will be removed
+			 * @since 3.9.0
 			 *
-			 * $param stored_data The post data to store or null/false/empty to delete the key
-			 * @return bool
+			 * @param {Object|boolean|null} stored_data The post data to store or null/false/empty to delete the key.
+			 *
+			 * @returns {boolean} True if data is stored, false if data was removed.
 			 */
 			function setData( stored_data ) {
 				var stored = getStorage();
@@ -190,22 +287,39 @@
 				return setStorage( stored );
 			}
 
+			/**
+			 * @summary Sets isSuspended to true.
+			 *
+			 * @since 3.9.0
+			 *
+			 * @returns {void}
+			 */
 			function suspend() {
 				isSuspended = true;
 			}
 
+			/**
+			 * @summary Sets isSuspended to false.
+			 *
+			 * @since 3.9.0
+			 *
+			 * @returns {void}
+			 */
 			function resume() {
 				isSuspended = false;
 			}
 
 			/**
-			 * Save post data for the current post
+			 * @summary Saves post data for the current post.
 			 *
 			 * Runs on a 15 sec. interval, saves when there are differences in the post title or content.
 			 * When the optional data is provided, updates the last saved post data.
 			 *
-			 * $param data optional Object The post data for saving, minimum 'post_title' and 'content'
-			 * @return bool
+			 * @since 3.9.0
+			 *
+			 * @param {Object} data The post data for saving, minimum 'post_title' and 'content'.
+			 *
+			 * @returns {boolean} Returns true when data has been saved, otherwise it returns false.
 			 */
 			function save( data ) {
 				var postData, compareString,
@@ -228,7 +342,7 @@
 					lastCompareString = initialCompareString;
 				}
 
-				// If the content, title and excerpt did not change since the last save, don't save again
+				// If the content, title and excerpt did not change since the last save, don't save again.
 				if ( compareString === lastCompareString ) {
 					return false;
 				}
@@ -244,12 +358,24 @@
 				return result;
 			}
 
-			// Run on DOM ready
+			/**
+			 * @summary Initializes the auto save function.
+			 *
+			 * Checks whether the editor is active or not to use the editor events
+			 * to autosave, or uses the values from the elements to autosave.
+			 *
+			 * Runs on DOM ready.
+			 *
+			 * @since 3.9.0
+			 *
+			 * @returns {void}
+			 */
 			function run() {
 				post_id = $('#post_ID').val() || 0;
 
 				// Check if the local post data is different than the loaded post data.
 				if ( $( '#wp-content-wrap' ).hasClass( 'tmce-active' ) ) {
+
 					// If TinyMCE loads first, check the post 1.5 sec. after it is ready.
 					// By this time the content has been loaded in the editor and 'saved' to the textarea.
 					// This prevents false positives.
@@ -266,10 +392,11 @@
 				intervalTimer = window.setInterval( save, 15000 );
 
 				$( 'form#post' ).on( 'submit.autosave-local', function() {
-					var editor = typeof tinymce !== 'undefined' && tinymce.get('content'),
+					var editor = getEditor(),
 						post_id = $('#post_ID').val() || 0;
 
 					if ( editor && ! editor.isHidden() ) {
+
 						// Last onSubmit event in the editor, needs to run after the content has been moved to the textarea.
 						editor.on( 'submit', function() {
 							save({
@@ -286,11 +413,22 @@
 						});
 					}
 
-					wpCookies.set( 'wp-saving-post', post_id + '-check', 24 * 60 * 60 );
+					var secure = ( 'https:' === window.location.protocol );
+					wpCookies.set( 'wp-saving-post', post_id + '-check', 24 * 60 * 60, false, false, secure );
 				});
 			}
 
-			// Strip whitespace and compare two strings
+			/**
+			 * @summary Compares 2 strings.
+			 *
+			 * Removes whitespaces in the strings before comparing them.
+			 *
+			 * @since 3.9.0
+			 *
+			 * @param {string} str1 The first string.
+			 * @param {string} str2 The second string.
+			 * @returns {boolean} True if the strings are the same.
+			 */
 			function compare( str1, str2 ) {
 				function removeSpaces( string ) {
 					return string.toString().replace(/[\x20\t\r\n\f]+/g, '');
@@ -300,16 +438,21 @@
 			}
 
 			/**
-			 * Check if the saved data for the current post (if any) is different than the loaded post data on the screen
+			 * @summary Checks if the saved data for the current post (if any) is different
+			 * than the loaded post data on the screen.
 			 *
 			 * Shows a standard message letting the user restore the post data if different.
 			 *
-			 * @return void
+			 * @since 3.9.0
+			 *
+			 * @returns {void}
 			 */
 			function checkPost() {
 				var content, post_title, excerpt, $notice,
 					postData = getSavedPostData(),
-					cookie = wpCookies.get( 'wp-saving-post' );
+					cookie = wpCookies.get( 'wp-saving-post' ),
+					$newerAutosaveNotice = $( '#has-newer-autosave' ).parent( '.notice' ),
+					$headerEnd = $( '.wp-header-end' );
 
 				if ( cookie === post_id + '-saved' ) {
 					wpCookies.remove( 'wp-saving-post' );
@@ -322,11 +465,6 @@
 					return;
 				}
 
-				// There is a newer autosave. Don't show two "restore" notices at the same time.
-				if ( $( '#has-newer-autosave' ).length ) {
-					return;
-				}
-
 				content = $( '#content' ).val() || '';
 				post_title = $( '#title' ).val() || '';
 				excerpt = $( '#excerpt' ).val() || '';
@@ -337,36 +475,46 @@
 					return;
 				}
 
-				restorePostData = postData;
-				undoPostData = {
-					content: content,
-					post_title: post_title,
-					excerpt: excerpt
-				};
+				/*
+				 * If '.wp-header-end' is found, append the notices after it otherwise
+				 * after the first h1 or h2 heading found within the main content.
+				 */
+				if ( ! $headerEnd.length ) {
+					$headerEnd = $( '.wrap h1, .wrap h2' ).first();
+				}
 
-				$notice = $( '#local-storage-notice' );
-				$('.wrap h2').first().after( $notice.addClass( 'notice-warning' ).show() );
+				$notice = $( '#local-storage-notice' )
+					.insertAfter( $headerEnd )
+					.addClass( 'notice-warning' );
 
-				$notice.on( 'click.autosave-local', function( event ) {
-					var $target = $( event.target );
+				if ( $newerAutosaveNotice.length ) {
 
-					if ( $target.hasClass( 'restore-backup' ) ) {
-						restorePost( restorePostData );
-						$target.parent().hide();
-						$(this).find( 'p.undo-restore' ).show();
-						$notice.removeClass( 'notice-warning' ).addClass( 'notice-success' );
-					} else if ( $target.hasClass( 'undo-restore-backup' ) ) {
-						restorePost( undoPostData );
-						$target.parent().hide();
-						$(this).find( 'p.local-restore' ).show();
-						$notice.removeClass( 'notice-success' ).addClass( 'notice-warning' );
-					}
+					// If there is a "server" autosave notice, hide it.
+					// The data in the session storage is either the same or newer.
+					$newerAutosaveNotice.slideUp( 150, function() {
+						$notice.slideDown( 150 );
+					});
+				} else {
+					$notice.slideDown( 200 );
+				}
 
-					event.preventDefault();
+				$notice.find( '.restore-backup' ).on( 'click.autosave-local', function() {
+					restorePost( postData );
+					$notice.fadeTo( 250, 0, function() {
+						$notice.slideUp( 150 );
+					});
 				});
 			}
 
-			// Restore the current title, content and excerpt from postData.
+			/**
+			 * @summary Restores the current title, content and excerpt from postData.
+			 *
+			 * @since 3.9.0
+			 *
+			 * @param {Object} postData The object containing all post data.
+			 *
+			 * @returns {boolean} True if the post is restored.
+			 */
 			function restorePost( postData ) {
 				var editor;
 
@@ -379,16 +527,27 @@
 					}
 
 					$( '#excerpt' ).val( postData.excerpt || '' );
-					editor = typeof tinymce !== 'undefined' && tinymce.get('content');
+					editor = getEditor();
 
 					if ( editor && ! editor.isHidden() && typeof switchEditors !== 'undefined' ) {
+						if ( editor.settings.wpautop && postData.content ) {
+							postData.content = switchEditors.wpautop( postData.content );
+						}
+
 						// Make sure there's an undo level in the editor
-						editor.undoManager.add();
-						editor.setContent( postData.content ? switchEditors.wpautop( postData.content ) : '' );
+						editor.undoManager.transact( function() {
+							editor.setContent( postData.content || '' );
+							editor.nodeChanged();
+						});
 					} else {
+
 						// Make sure the Text editor is selected
 						$( '#content-html' ).click();
-						$( '#content' ).val( postData.content );
+						$( '#content' ).focus();
+
+						// Using document.execCommand() will let the user undo.
+						document.execCommand( 'selectAll' );
+						document.execCommand( 'insertText', false, postData.content || '' );
 					}
 
 					return true;
@@ -415,13 +574,34 @@
 			};
 		}
 
-		// Autosave on the server
+		/**
+		 * @summary Auto saves the post on the server.
+		 *
+		 * @since 3.9.0
+		 *
+		 * @returns {Object} {
+		 * 	{
+		 * 		tempBlockSave: tempBlockSave,
+		 * 		triggerSave: triggerSave,
+		 * 		postChanged: postChanged,
+		 * 		suspend: suspend,
+		 * 		resume: resume
+		 * 		}
+		 * 	} The object all functions for autosave.
+		 */
 		function autosaveServer() {
 			var _blockSave, _blockSaveTimer, previousCompareString, lastCompareString,
 				nextRun = 0,
 				isSuspended = false;
 
-			// Block saving for the next 10 sec.
+
+			/**
+			 * @summary  Blocks saving for the next 10 seconds.
+			 *
+			 * @since 3.9.0
+			 *
+			 * @returns {void}
+			 */
 			function tempBlockSave() {
 				_blockSave = true;
 				window.clearTimeout( _blockSaveTimer );
@@ -431,15 +611,37 @@
 				}, 10000 );
 			}
 
+			/**
+			 * @summary Sets isSuspended to true.
+			 *
+			 * @since 3.9.0
+			 *
+			 * @returns {void}
+			 */
 			function suspend() {
 				isSuspended = true;
 			}
 
+			/**
+			 * @summary Sets isSuspended to false.
+			 *
+			 * @since 3.9.0
+			 *
+			 * @returns {void}
+			 */
 			function resume() {
 				isSuspended = false;
 			}
 
-			// Runs on heartbeat-response
+			/**
+			 * @summary Triggers the autosave with the post data.
+			 *
+			 * @since 3.9.0
+			 *
+			 * @param {Object} data The post data.
+			 *
+			 * @returns {void}
+			 */
 			function response( data ) {
 				_schedule();
 				_blockSave = false;
@@ -456,11 +658,13 @@
 			}
 
 			/**
-			 * Save immediately
+			 * @summary Saves immediately.
+			 *
+			 * Resets the timing and tells heartbeat to connect now.
 			 *
-			 * Resets the timing and tells heartbeat to connect now
+			 * @since 3.9.0
 			 *
-			 * @return void
+			 * @returns {void}
 			 */
 			function triggerSave() {
 				nextRun = 0;
@@ -468,18 +672,29 @@
 			}
 
 			/**
-			 * Checks if the post content in the textarea has changed since page load.
+			 * @summary Checks if the post content in the textarea has changed since page load.
 			 *
 			 * This also happens when TinyMCE is active and editor.save() is triggered by
 			 * wp.autosave.getPostData().
 			 *
-			 * @return bool
+			 * @since 3.9.0
+			 *
+			 * @return {boolean} True if the post has been changed.
 			 */
 			function postChanged() {
 				return getCompareString() !== initialCompareString;
 			}
 
-			// Runs on 'heartbeat-send'
+			/**
+			 * @summary Checks if the post can be saved or not.
+			 *
+			 * If the post hasn't changed or it cannot be updated,
+			 * because the autosave is blocked or suspended, the function returns false.
+			 *
+			 * @since 3.9.0
+			 *
+			 * @returns {Object} Returns the post data.
+			 */
 			function save() {
 				var postData, compareString;
 
@@ -517,21 +732,54 @@
 				return postData;
 			}
 
+			/**
+			 * @summary Sets the next run, based on the autosave interval.
+			 *
+			 * @private
+			 *
+			 * @since 3.9.0
+			 *
+			 * @returns {void}
+			 */
 			function _schedule() {
 				nextRun = ( new Date() ).getTime() + ( autosaveL10n.autosaveInterval * 1000 ) || 60000;
 			}
 
+			/**
+			 * @summary Sets the autosaveData on the autosave heartbeat.
+			 *
+			 * @since 3.9.0
+			 *
+			 * @returns {void}
+			 */
 			$document.on( 'heartbeat-send.autosave', function( event, data ) {
 				var autosaveData = save();
 
 				if ( autosaveData ) {
 					data.wp_autosave = autosaveData;
 				}
+
+				/**
+				 * @summary Triggers the autosave of the post with the autosave data
+				 * on the autosave heartbeat.
+				 *
+				 * @since 3.9.0
+				 *
+				 * @returns {void}
+				 */
 			}).on( 'heartbeat-tick.autosave', function( event, data ) {
 				if ( data.wp_autosave ) {
 					response( data.wp_autosave );
 				}
+				/**
+				 * @summary Disables buttons and throws a notice when the connection is lost.
+				 *
+				 * @since 3.9.0
+				 *
+				 * @returns {void}
+				 */
 			}).on( 'heartbeat-connection-lost.autosave', function( event, error, status ) {
+
 				// When connection is lost, keep user from submitting changes.
 				if ( 'timeout' === error || 603 === status ) {
 					var $notice = $('#lost-connection-notice');
@@ -543,6 +791,14 @@
 					$notice.show();
 					disableButtons();
 				}
+
+				/**
+				 * @summary Enables buttons when the connection is restored.
+				 *
+				 * @since 3.9.0
+				 *
+				 * @returns {void}
+				 */
 			}).on( 'heartbeat-connection-restored.autosave', function() {
 				$('#lost-connection-notice').hide();
 				enableButtons();
@@ -559,10 +815,18 @@
 			};
 		}
 
-		// Wait for TinyMCE to initialize plus 1 sec. for any external css to finish loading,
-		// then 'save' to the textarea before setting initialCompareString.
-		// This avoids any insignificant differences between the initial textarea content and the content
-		// extracted from the editor.
+		/**
+		 * @summary Sets the autosave time out.
+		 *
+		 * Wait for TinyMCE to initialize plus 1 second. for any external css to finish loading,
+		 * then save to the textarea before setting initialCompareString.
+		 * This avoids any insignificant differences between the initial textarea content and the content
+		 * extracted from the editor.
+		 *
+		 * @since 3.9.0
+		 *
+		 * @returns {void}
+		 */
 		$document.on( 'tinymce-editor-init.autosave', function( event, editor ) {
 			if ( editor.id === 'content' ) {
 				window.setTimeout( function() {
@@ -571,6 +835,7 @@
 				}, 1000 );
 			}
 		}).ready( function() {
+
 			// Set the initial compare string in case TinyMCE is not used or not loaded first
 			initialCompareString = getCompareString();
 		});
@@ -585,6 +850,7 @@
 		};
 	}
 
+	/** @namespace wp */
 	window.wp = window.wp || {};
 	window.wp.autosave = autosave();