X-Git-Url: https://scripts.mit.edu/gitweb/autoinstalls/wordpress.git/blobdiff_plain/fa11948979fd6a4ea5705dc613b239699a459db3..fa6ee2c363cdfdebcb4b76e4d9c4347a4cb19065:/wp-includes/js/autosave.js diff --git a/wp-includes/js/autosave.js b/wp-includes/js/autosave.js index f339c949..4e6834a1 100644 --- a/wp-includes/js/autosave.js +++ b/wp-includes/js/autosave.js @@ -1,702 +1,591 @@ -var autosave, autosaveLast = '', autosavePeriodical, autosaveDelayPreview = false, notSaved = true, blockSave = false, fullscreen, autosaveLockRelease = true; - -jQuery(document).ready( function($) { +/* global tinymce, wpCookies, autosaveL10n, switchEditors */ +// Back-compat +window.autosave = function() { + return true; +}; - if ( $('#wp-content-wrap').hasClass('tmce-active') && typeof switchEditors != 'undefined' ) { - autosaveLast = wp.autosave.getCompareString({ - post_title : $('#title').val() || '', - content : switchEditors.pre_wpautop( $('#content').val() ) || '', - excerpt : $('#excerpt').val() || '' - }); - } else { - autosaveLast = wp.autosave.getCompareString(); - } +( function( $, window ) { + function autosave() { + var initialCompareString, + lastTriggerSave = 0, + $document = $(document); - autosavePeriodical = $.schedule({time: autosaveL10n.autosaveInterval * 1000, func: function() { autosave(); }, repeat: true, protect: true}); - - //Disable autosave after the form has been submitted - $("#post").submit(function() { - $.cancel(autosavePeriodical); - autosaveLockRelease = false; - }); - - $('input[type="submit"], a.submitdelete', '#submitpost').click(function(){ - blockSave = true; - window.onbeforeunload = null; - $(':button, :submit', '#submitpost').each(function(){ - var t = $(this); - if ( t.hasClass('button-primary') ) - t.addClass('button-primary-disabled'); - else - t.addClass('button-disabled'); - }); - if ( $(this).attr('id') == 'publish' ) - $('#major-publishing-actions .spinner').show(); - else - $('#minor-publishing .spinner').show(); - }); - - window.onbeforeunload = function(){ - var editor = typeof(tinymce) != 'undefined' ? tinymce.activeEditor : false, compareString; - - if ( editor && ! editor.isHidden() ) { - if ( editor.isDirty() ) - return autosaveL10n.saveAlert; - } else { - if ( fullscreen && fullscreen.settings.visible ) { - compareString = wp.autosave.getCompareString({ - post_title: $('#wp-fullscreen-title').val() || '', - content: $('#wp_mce_fullscreen').val() || '', - excerpt: $('#excerpt').val() || '' - }); - } else { - compareString = wp.autosave.getCompareString(); + /** + * Returns the data saved in both local and remote autosave + * + * @return 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'); + + // Don't run editor.save() more often than every 3 sec. + // It is resource intensive and might slow down typing in long posts on slow devices. + if ( editor && ! editor.isHidden() && time - 3000 > lastTriggerSave ) { + editor.save(); + lastTriggerSave = time; } - if ( compareString != autosaveLast ) - return autosaveL10n.saveAlert; - } - }; - - $(window).unload( function(e) { - if ( ! autosaveLockRelease ) - return; - - // unload fires (twice) on removing the Thickbox iframe. Make sure we process only the main document unload. - if ( e.target && e.target.nodeName != '#document' ) - return; - - $.ajax({ - type: 'POST', - url: ajaxurl, - async: false, - data: { - action: 'wp-remove-post-lock', - _wpnonce: $('#_wpnonce').val(), - post_ID: $('#post_ID').val(), - active_post_lock: $('#active_post_lock').val() + data = { + post_id: $( '#post_ID' ).val() || 0, + post_type: $( '#post_type' ).val() || '', + post_author: $( '#post_author' ).val() || '', + post_title: $( '#title' ).val() || '', + content: $( '#content' ).val() || '', + excerpt: $( '#excerpt' ).val() || '' + }; + + if ( type === 'local' ) { + return data; } - }); - } ); - - // preview - $('#post-preview').click(function(){ - if ( $('#auto_draft').val() == '1' && notSaved ) { - autosaveDelayPreview = true; - autosave(); - return false; - } - doPreview(); - return false; - }); - doPreview = function() { - $('input#wp-preview').val('dopreview'); - $('form#post').attr('target', 'wp-preview').submit().attr('target', ''); - - /* - * Workaround for WebKit bug preventing a form submitting twice to the same action. - * https://bugs.webkit.org/show_bug.cgi?id=28633 - */ - var ua = navigator.userAgent.toLowerCase(); - if ( ua.indexOf('safari') != -1 && ua.indexOf('chrome') == -1 ) { - $('form#post').attr('action', function(index, value) { - return value + '?t=' + new Date().getTime(); + $( 'input[id^="in-category-"]:checked' ).each( function() { + cats.push( this.value ); }); - } + data.catslist = cats.join(','); - $('input#wp-preview').val(''); - } + if ( post_name = $( '#post_name' ).val() ) { + data.post_name = post_name; + } - // This code is meant to allow tabbing from Title to Post content. - $('#title').on('keydown.editor-focus', function(e) { - var ed; + if ( parent_id = $( '#parent_id' ).val() ) { + data.parent_id = parent_id; + } - if ( e.which != 9 ) - return; + if ( $( '#comment_status' ).prop( 'checked' ) ) { + data.comment_status = 'open'; + } - if ( !e.ctrlKey && !e.altKey && !e.shiftKey ) { - if ( typeof(tinymce) != 'undefined' ) - ed = tinymce.get('content'); + if ( $( '#ping_status' ).prop( 'checked' ) ) { + data.ping_status = 'open'; + } - if ( ed && !ed.isHidden() ) { - $(this).one('keyup', function(e){ - $('#content_tbl td.mceToolbar > a').focus(); - }); - } else { - $('#content').focus(); + if ( $( '#auto_draft' ).val() === '1' ) { + data.auto_draft = '1'; } - e.preventDefault(); + return data; } - }); - - // autosave new posts after a title is typed but not if Publish or Save Draft is clicked - if ( '1' == $('#auto_draft').val() ) { - $('#title').blur( function() { - if ( !this.value || $('#auto_draft').val() != '1' ) - return; - delayed_autosave(); - }); - } - // When connection is lost, keep user from submitting changes. - $(document).on('heartbeat-connection-lost.autosave', function( e, error, status ) { - if ( 'timeout' === error || 503 == status ) { - var notice = $('#lost-connection-notice'); - if ( ! wp.autosave.local.hasStorage ) { - notice.find('.hide-if-no-sessionstorage').hide(); + // Concatenate title, content and excerpt. Used to track changes when auto-saving. + function getCompareString( postData ) { + if ( typeof postData === 'object' ) { + return ( postData.post_title || '' ) + '::' + ( postData.content || '' ) + '::' + ( postData.excerpt || '' ); } - notice.show(); - autosave_disable_buttons(); - } - }).on('heartbeat-connection-restored.autosave', function() { - $('#lost-connection-notice').hide(); - autosave_enable_buttons(); - }); -}); - -function autosave_parse_response( response ) { - var res = wpAjax.parseAjaxResponse(response, 'autosave'), post_id, sup; - - if ( res && res.responses && res.responses.length ) { - if ( res.responses[0].supplemental ) { - sup = res.responses[0].supplemental; - - jQuery.each( sup, function( selector, value ) { - if ( selector.match(/^replace-/) ) - jQuery( '#' + selector.replace('replace-', '') ).val( value ); - }); + + return ( $('#title').val() || '' ) + '::' + ( $('#content').val() || '' ) + '::' + ( $('#excerpt').val() || '' ); } - // if no errors: add slug UI and update autosave-message - if ( !res.errors ) { - if ( post_id = parseInt( res.responses[0].id, 10 ) ) - autosave_update_slug( post_id ); + function disableButtons() { + $document.trigger('autosave-disable-buttons'); + // Re-enable 5 sec later. Just gives autosave a head start to avoid collisions. + setTimeout( enableButtons, 5000 ); + } - if ( res.responses[0].data ) // update autosave message - jQuery('.autosave-message').text( res.responses[0].data ); + function enableButtons() { + $document.trigger( 'autosave-enable-buttons' ); } - } - return res; -} + // Autosave in localStorage + function autosaveLocal() { + var restorePostData, undoPostData, blog_id, post_id, hasStorage, intervalTimer, + lastCompareString, + isSuspended = false; + + // Check if the browser supports sessionStorage and it's not disabled + function checkStorage() { + var test = Math.random().toString(), + result = false; + + try { + window.sessionStorage.setItem( 'wp-test', test ); + result = window.sessionStorage.getItem( 'wp-test' ) === test; + window.sessionStorage.removeItem( 'wp-test' ); + } catch(e) {} + + hasStorage = result; + return result; + } -// called when autosaving pre-existing post -function autosave_saved(response) { - blockSave = false; - autosave_parse_response(response); // parse the ajax response - autosave_enable_buttons(); // re-enable disabled form buttons -} + /** + * Initialize the local storage + * + * @return mixed False if no sessionStorage in the browser or an Object containing all postData for this blog + */ + function getStorage() { + var stored_obj = false; + // Separate local storage containers for each blog_id + if ( hasStorage && blog_id ) { + stored_obj = sessionStorage.getItem( 'wp-autosave-' + blog_id ); + + if ( stored_obj ) { + stored_obj = JSON.parse( stored_obj ); + } else { + stored_obj = {}; + } + } -// called when autosaving new post -function autosave_saved_new(response) { - blockSave = false; - var res = autosave_parse_response(response), post_id; + return stored_obj; + } - if ( res && res.responses.length && !res.errors ) { - // An ID is sent only for real auto-saves, not for autosave=0 "keepalive" saves - post_id = parseInt( res.responses[0].id, 10 ); + /** + * Set the storage for this blog + * + * Confirms that the data was saved successfully. + * + * @return bool + */ + function setStorage( stored_obj ) { + var key; + + if ( hasStorage && blog_id ) { + key = 'wp-autosave-' + blog_id; + sessionStorage.setItem( key, JSON.stringify( stored_obj ) ); + return sessionStorage.getItem( key ) !== null; + } - if ( post_id ) { - notSaved = false; - jQuery('#auto_draft').val('0'); // No longer an auto-draft - } + return false; + } - autosave_enable_buttons(); + /** + * Get the saved post data for the current post + * + * @return mixed False if no storage or no data or the postData as an Object + */ + function getSavedPostData() { + var stored = getStorage(); - if ( autosaveDelayPreview ) { - autosaveDelayPreview = false; - doPreview(); - } - } else { - autosave_enable_buttons(); // re-enable disabled form buttons - } -} - -function autosave_update_slug(post_id) { - // create slug area only if not already there - if ( 'undefined' != makeSlugeditClickable && jQuery.isFunction(makeSlugeditClickable) && !jQuery('#edit-slug-box > *').size() ) { - jQuery.post( ajaxurl, { - action: 'sample-permalink', - post_id: post_id, - new_title: fullscreen && fullscreen.settings.visible ? jQuery('#wp-fullscreen-title').val() : jQuery('#title').val(), - samplepermalinknonce: jQuery('#samplepermalinknonce').val() - }, - function(data) { - if ( data !== '-1' ) { - var box = jQuery('#edit-slug-box'); - box.html(data); - if (box.hasClass('hidden')) { - box.fadeIn('fast', function () { - box.removeClass('hidden'); - }); - } - makeSlugeditClickable(); + if ( ! stored || ! post_id ) { + return false; } + + return stored[ 'post_' + post_id ] || false; } - ); - } -} - -function autosave_loading() { - jQuery('.autosave-message').html(autosaveL10n.savingText); -} - -function autosave_enable_buttons() { - jQuery(document).trigger('autosave-enable-buttons'); - if ( ! wp.heartbeat || ! wp.heartbeat.hasConnectionError() ) { - // delay that a bit to avoid some rare collisions while the DOM is being updated. - setTimeout(function(){ - var parent = jQuery('#submitpost'); - parent.find(':button, :submit').removeAttr('disabled'); - parent.find('.spinner').hide(); - }, 500); - } -} - -function autosave_disable_buttons() { - jQuery(document).trigger('autosave-disable-buttons'); - jQuery('#submitpost').find(':button, :submit').prop('disabled', true); - // Re-enable 5 sec later. Just gives autosave a head start to avoid collisions. - setTimeout( autosave_enable_buttons, 5000 ); -} - -function delayed_autosave() { - setTimeout(function(){ - if ( blockSave ) - return; - autosave(); - }, 200); -} - -autosave = function() { - var post_data = wp.autosave.getPostData(), - compareString, - successCallback; - - blockSave = true; - - // post_data.content cannot be retrieved at the moment - if ( ! post_data.autosave ) - return false; - - // No autosave while thickbox is open (media buttons) - if ( jQuery("#TB_window").css('display') == 'block' ) - return false; - - compareString = wp.autosave.getCompareString( post_data ); - - // Nothing to save or no change. - if ( compareString == autosaveLast ) - return false; - - autosaveLast = compareString; - jQuery(document).triggerHandler('wpcountwords', [ post_data["content"] ]); - - // Disable buttons until we know the save completed. - autosave_disable_buttons(); - - if ( post_data["auto_draft"] == '1' ) { - successCallback = autosave_saved_new; // new post - } else { - successCallback = autosave_saved; // pre-existing post - } - jQuery.ajax({ - data: post_data, - beforeSend: autosave_loading, - type: "POST", - url: ajaxurl, - success: successCallback - }); + /** + * Set (save or delete) post data in the storage. + * + * If stored_data evaluates to 'false' the storage key for the current post will be removed + * + * $param stored_data The post data to store or null/false/empty to delete the key + * @return bool + */ + function setData( stored_data ) { + var stored = getStorage(); + + if ( ! stored || ! post_id ) { + return false; + } - return true; -} - -// Autosave in localStorage -// set as simple object/mixin for now -window.wp = window.wp || {}; -wp.autosave = wp.autosave || {}; - -(function($){ -// Returns the data for saving in both localStorage and autosaves to the server -wp.autosave.getPostData = function() { - var ed = typeof tinymce != 'undefined' ? tinymce.activeEditor : null, post_name, parent_id, cats = [], - data = { - action: 'autosave', - autosave: true, - post_id: $('#post_ID').val() || 0, - autosavenonce: $('#autosavenonce').val() || '', - post_type: $('#post_type').val() || '', - post_author: $('#post_author').val() || '', - excerpt: $('#excerpt').val() || '' - }; + if ( stored_data ) { + stored[ 'post_' + post_id ] = stored_data; + } else if ( stored.hasOwnProperty( 'post_' + post_id ) ) { + delete stored[ 'post_' + post_id ]; + } else { + return false; + } - if ( ed && !ed.isHidden() ) { - // Don't run while the tinymce spellcheck is on. It resets all found words. - if ( ed.plugins.spellchecker && ed.plugins.spellchecker.active ) { - data.autosave = false; - return data; - } else { - if ( 'mce_fullscreen' == ed.id ) - tinymce.get('content').setContent(ed.getContent({format : 'raw'}), {format : 'raw'}); + return setStorage( stored ); + } - tinymce.triggerSave(); - } - } + function suspend() { + isSuspended = true; + } - if ( typeof fullscreen != 'undefined' && fullscreen.settings.visible ) { - data['post_title'] = $('#wp-fullscreen-title').val() || ''; - data['content'] = $('#wp_mce_fullscreen').val() || ''; - } else { - data['post_title'] = $('#title').val() || ''; - data['content'] = $('#content').val() || ''; - } + function resume() { + isSuspended = false; + } - /* - // We haven't been saving tags with autosave since 2.8... Start again? - $('.the-tags').each( function() { - data[this.name] = this.value; - }); - */ + /** + * Save 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 + */ + function save( data ) { + var postData, compareString, + result = false; + + if ( isSuspended || ! hasStorage ) { + return false; + } - $('input[id^="in-category-"]:checked').each( function() { - cats.push(this.value); - }); - data['catslist'] = cats.join(','); + if ( data ) { + postData = getSavedPostData() || {}; + $.extend( postData, data ); + } else { + postData = getPostData('local'); + } - if ( post_name = $('#post_name').val() ) - data['post_name'] = post_name; + compareString = getCompareString( postData ); - if ( parent_id = $('#parent_id').val() ) - data['parent_id'] = parent_id; + if ( typeof lastCompareString === 'undefined' ) { + lastCompareString = initialCompareString; + } - if ( $('#comment_status').prop('checked') ) - data['comment_status'] = 'open'; + // If the content, title and excerpt did not change since the last save, don't save again + if ( compareString === lastCompareString ) { + return false; + } - if ( $('#ping_status').prop('checked') ) - data['ping_status'] = 'open'; + postData.save_time = ( new Date() ).getTime(); + postData.status = $( '#post_status' ).val() || ''; + result = setData( postData ); - if ( $('#auto_draft').val() == '1' ) - data['auto_draft'] = '1'; + if ( result ) { + lastCompareString = compareString; + } - return data; -}; + return result; + } -// Concatenate title, content and excerpt. Used to track changes when auto-saving. -wp.autosave.getCompareString = function( post_data ) { - if ( typeof post_data === 'object' ) { - return ( post_data.post_title || '' ) + '::' + ( post_data.content || '' ) + '::' + ( post_data.excerpt || '' ); - } + // Run on DOM ready + 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. + $document.on( 'tinymce-editor-init.autosave', function() { + window.setTimeout( function() { + checkPost(); + }, 1500 ); + }); + } else { + checkPost(); + } - return ( $('#title').val() || '' ) + '::' + ( $('#content').val() || '' ) + '::' + ( $('#excerpt').val() || '' ); -}; + // Save every 15 sec. + intervalTimer = window.setInterval( save, 15000 ); + + $( 'form#post' ).on( 'submit.autosave-local', function() { + var editor = typeof tinymce !== 'undefined' && tinymce.get('content'), + 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({ + post_title: $( '#title' ).val() || '', + content: $( '#content' ).val() || '', + excerpt: $( '#excerpt' ).val() || '' + }); + }); + } else { + save({ + post_title: $( '#title' ).val() || '', + content: $( '#content' ).val() || '', + excerpt: $( '#excerpt' ).val() || '' + }); + } -wp.autosave.local = { - - lastSavedData: '', - blog_id: 0, - hasStorage: false, - - // Check if the browser supports sessionStorage and it's not disabled - checkStorage: function() { - var test = Math.random(), result = false; - - try { - sessionStorage.setItem('wp-test', test); - result = sessionStorage.getItem('wp-test') == test; - sessionStorage.removeItem('wp-test'); - } catch(e) {} - - this.hasStorage = result; - return result; - }, - - /** - * Initialize the local storage - * - * @return mixed False if no sessionStorage in the browser or an Object containing all post_data for this blog - */ - getStorage: function() { - var stored_obj = false; - // Separate local storage containers for each blog_id - if ( this.hasStorage && this.blog_id ) { - stored_obj = sessionStorage.getItem( 'wp-autosave-' + this.blog_id ); - - if ( stored_obj ) - stored_obj = JSON.parse( stored_obj ); - else - stored_obj = {}; - } + wpCookies.set( 'wp-saving-post', post_id + '-check', 24 * 60 * 60 ); + }); + } - return stored_obj; - }, - - /** - * Set the storage for this blog - * - * Confirms that the data was saved successfully. - * - * @return bool - */ - setStorage: function( stored_obj ) { - var key; - - if ( this.hasStorage && this.blog_id ) { - key = 'wp-autosave-' + this.blog_id; - sessionStorage.setItem( key, JSON.stringify( stored_obj ) ); - return sessionStorage.getItem( key ) !== null; - } + // Strip whitespace and compare two strings + function compare( str1, str2 ) { + function removeSpaces( string ) { + return string.toString().replace(/[\x20\t\r\n\f]+/g, ''); + } - return false; - }, - - /** - * Get the saved post data for the current post - * - * @return mixed False if no storage or no data or the post_data as an Object - */ - getData: function() { - var stored = this.getStorage(), post_id = $('#post_ID').val(); - - if ( !stored || !post_id ) - return false; - - return stored[ 'post_' + post_id ] || false; - }, - - /** - * Set (save or delete) post data in the storage. - * - * If stored_data evaluates to 'false' the storage key for the current post will be removed - * - * $param stored_data The post data to store or null/false/empty to delete the key - * @return bool - */ - setData: function( stored_data ) { - var stored = this.getStorage(), post_id = $('#post_ID').val(); - - if ( !stored || !post_id ) - return false; - - if ( stored_data ) - stored[ 'post_' + post_id ] = stored_data; - else if ( stored.hasOwnProperty( 'post_' + post_id ) ) - delete stored[ 'post_' + post_id ]; - else - return false; - - return this.setStorage(stored); - }, - - /** - * Save post data for the current post - * - * Runs on a 15 sec. schedule, 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 - */ - save: function( data ) { - var result = false, post_data, compareString; - - if ( ! data ) { - post_data = wp.autosave.getPostData(); - } else { - post_data = this.getData() || {}; - $.extend( post_data, data ); - post_data.autosave = true; - } + return ( removeSpaces( str1 || '' ) === removeSpaces( str2 || '' ) ); + } - // Cannot get the post data at the moment - if ( ! post_data.autosave ) - return false; + /** + * Check 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 + */ + function checkPost() { + var content, post_title, excerpt, $notice, + postData = getSavedPostData(), + cookie = wpCookies.get( 'wp-saving-post' ); + + if ( cookie === post_id + '-saved' ) { + wpCookies.remove( 'wp-saving-post' ); + // The post was saved properly, remove old data and bail + setData( false ); + return; + } - compareString = wp.autosave.getCompareString( post_data ); + if ( ! postData ) { + return; + } - // If the content, title and excerpt did not change since the last save, don't save again - if ( compareString == this.lastSavedData ) - return false; + // There is a newer autosave. Don't show two "restore" notices at the same time. + if ( $( '#has-newer-autosave' ).length ) { + return; + } - post_data['save_time'] = (new Date()).getTime(); - post_data['status'] = $('#post_status').val() || ''; - result = this.setData( post_data ); + content = $( '#content' ).val() || ''; + post_title = $( '#title' ).val() || ''; + excerpt = $( '#excerpt' ).val() || ''; - if ( result ) - this.lastSavedData = compareString; + if ( compare( content, postData.content ) && compare( post_title, postData.post_title ) && + compare( excerpt, postData.excerpt ) ) { - return result; - }, + return; + } - // Initialize and run checkPost() on loading the script (before TinyMCE init) - init: function( settings ) { - var self = this; + restorePostData = postData; + undoPostData = { + content: content, + post_title: post_title, + excerpt: excerpt + }; + + $notice = $( '#local-storage-notice' ); + $('.wrap h2').first().after( $notice.addClass( 'notice-warning' ).show() ); + + $notice.on( 'click.autosave-local', function( event ) { + var $target = $( event.target ); + + 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' ); + } - // Check if the browser supports sessionStorage and it's not disabled - if ( ! this.checkStorage() ) - return; + event.preventDefault(); + }); + } - // Don't run if the post type supports neither 'editor' (textarea#content) nor 'excerpt'. - if ( ! $('#content').length && ! $('#excerpt').length ) - return; + // Restore the current title, content and excerpt from postData. + function restorePost( postData ) { + var editor; - if ( settings ) - $.extend( this, settings ); + if ( postData ) { + // Set the last saved data + lastCompareString = getCompareString( postData ); - if ( !this.blog_id ) - this.blog_id = typeof window.autosaveL10n != 'undefined' ? window.autosaveL10n.blog_id : 0; + if ( $( '#title' ).val() !== postData.post_title ) { + $( '#title' ).focus().val( postData.post_title || '' ); + } - $(document).ready( function(){ self.run(); } ); - }, + $( '#excerpt' ).val( postData.excerpt || '' ); + editor = typeof tinymce !== 'undefined' && tinymce.get('content'); + + if ( editor && ! editor.isHidden() && typeof switchEditors !== 'undefined' ) { + // Make sure there's an undo level in the editor + editor.undoManager.add(); + editor.setContent( postData.content ? switchEditors.wpautop( postData.content ) : '' ); + } else { + // Make sure the Text editor is selected + $( '#content-html' ).click(); + $( '#content' ).val( postData.content ); + } - // Run on DOM ready - run: function() { - var self = this; + return true; + } - // Check if the local post data is different than the loaded post data. - this.checkPost(); + return false; + } - // Set the schedule - this.schedule = $.schedule({ - time: 15 * 1000, - func: function() { wp.autosave.local.save(); }, - repeat: true, - protect: true - }); + blog_id = typeof window.autosaveL10n !== 'undefined' && window.autosaveL10n.blog_id; - $('form#post').on('submit.autosave-local', function() { - var editor = typeof tinymce != 'undefined' && tinymce.get('content'), post_id = $('#post_ID').val() || 0; + // Check if the browser supports sessionStorage and it's not disabled, + // then initialize and run checkPost(). + // Don't run if the post type supports neither 'editor' (textarea#content) nor 'excerpt'. + if ( checkStorage() && blog_id && ( $('#content').length || $('#excerpt').length ) ) { + $document.ready( run ); + } - if ( editor && ! editor.isHidden() ) { - // Last onSubmit event in the editor, needs to run after the content has been moved to the textarea. - editor.onSubmit.add( function() { - wp.autosave.local.save({ - post_title: $('#title').val() || '', - content: $('#content').val() || '', - excerpt: $('#excerpt').val() || '' - }); - }); - } else { - self.save({ - post_title: $('#title').val() || '', - content: $('#content').val() || '', - excerpt: $('#excerpt').val() || '' - }); + return { + hasStorage: hasStorage, + getSavedPostData: getSavedPostData, + save: save, + suspend: suspend, + resume: resume + }; + } + + // Autosave on the server + function autosaveServer() { + var _blockSave, _blockSaveTimer, previousCompareString, lastCompareString, + nextRun = 0, + isSuspended = false; + + // Block saving for the next 10 sec. + function tempBlockSave() { + _blockSave = true; + window.clearTimeout( _blockSaveTimer ); + + _blockSaveTimer = window.setTimeout( function() { + _blockSave = false; + }, 10000 ); } - wpCookies.set( 'wp-saving-post-' + post_id, 'check' ); - }); - }, + function suspend() { + isSuspended = true; + } - // Strip whitespace and compare two strings - compare: function( str1, str2 ) { - function remove( string ) { - return string.toString().replace(/[\x20\t\r\n\f]+/g, ''); - } + function resume() { + isSuspended = false; + } + + // Runs on heartbeat-response + function response( data ) { + _schedule(); + _blockSave = false; + lastCompareString = previousCompareString; + previousCompareString = ''; - return ( remove( str1 || '' ) == remove( str2 || '' ) ); - }, - - /** - * Check 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 - */ - checkPost: function() { - var self = this, post_data = this.getData(), content, post_title, excerpt, notice, - post_id = $('#post_ID').val() || 0, cookie = wpCookies.get( 'wp-saving-post-' + post_id ); - - if ( ! post_data ) - return; - - if ( cookie ) { - wpCookies.remove( 'wp-saving-post-' + post_id ); - - if ( cookie == 'saved' ) { - // The post was saved properly, remove old data and bail - this.setData( false ); - return; + $document.trigger( 'after-autosave', [data] ); + enableButtons(); + + if ( data.success ) { + // No longer an auto-draft + $( '#auto_draft' ).val(''); + } } - } - // There is a newer autosave. Don't show two "restore" notices at the same time. - if ( $('#has-newer-autosave').length ) - return; + /** + * Save immediately + * + * Resets the timing and tells heartbeat to connect now + * + * @return void + */ + function triggerSave() { + nextRun = 0; + wp.heartbeat.connectNow(); + } - content = $('#content').val() || ''; - post_title = $('#title').val() || ''; - excerpt = $('#excerpt').val() || ''; + /** + * 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 + */ + function postChanged() { + return getCompareString() !== initialCompareString; + } - if ( $('#wp-content-wrap').hasClass('tmce-active') && typeof switchEditors != 'undefined' ) - content = switchEditors.pre_wpautop( content ); + // Runs on 'heartbeat-send' + function save() { + var postData, compareString; - // cookie == 'check' means the post was not saved properly, always show #local-storage-notice - if ( cookie != 'check' && this.compare( content, post_data.content ) && this.compare( post_title, post_data.post_title ) && this.compare( excerpt, post_data.excerpt ) ) { - return; - } + // window.autosave() used for back-compat + if ( isSuspended || _blockSave || ! window.autosave() ) { + return false; + } - this.restore_post_data = post_data; - this.undo_post_data = { - content: content, - post_title: post_title, - excerpt: excerpt - }; + if ( ( new Date() ).getTime() < nextRun ) { + return false; + } + + postData = getPostData(); + compareString = getCompareString( postData ); + + // First check + if ( typeof lastCompareString === 'undefined' ) { + lastCompareString = initialCompareString; + } + + // No change + if ( compareString === lastCompareString ) { + return false; + } - notice = $('#local-storage-notice'); - $('.wrap h2').first().after( notice.addClass('updated').show() ); + previousCompareString = compareString; + tempBlockSave(); + disableButtons(); - notice.on( 'click', function(e) { - var target = $( e.target ); + $document.trigger( 'wpcountwords', [ postData.content ] ) + .trigger( 'before-autosave', [ postData ] ); - if ( target.hasClass('restore-backup') ) { - self.restorePost( self.restore_post_data ); - target.parent().hide(); - $(this).find('p.undo-restore').show(); - } else if ( target.hasClass('undo-restore-backup') ) { - self.restorePost( self.undo_post_data ); - target.parent().hide(); - $(this).find('p.local-restore').show(); + postData._wpnonce = $( '#_wpnonce' ).val() || ''; + + return postData; } - e.preventDefault(); - }); - }, - - // Restore the current title, content and excerpt from post_data. - restorePost: function( post_data ) { - var editor; - - if ( post_data ) { - // Set the last saved data - this.lastSavedData = wp.autosave.getCompareString( post_data ); - - if ( $('#title').val() != post_data.post_title ) - $('#title').focus().val( post_data.post_title || '' ); - - $('#excerpt').val( post_data.excerpt || '' ); - editor = typeof tinymce != 'undefined' && tinymce.get('content'); - - if ( editor && ! editor.isHidden() && typeof switchEditors != 'undefined' ) { - // Make sure there's an undo level in the editor - editor.undoManager.add(); - editor.setContent( post_data.content ? switchEditors.wpautop( post_data.content ) : '' ); - } else { - // Make sure the Text editor is selected - $('#content-html').click(); - $('#content').val( post_data.content ); + function _schedule() { + nextRun = ( new Date() ).getTime() + ( autosaveL10n.autosaveInterval * 1000 ) || 60000; } - return true; + $document.on( 'heartbeat-send.autosave', function( event, data ) { + var autosaveData = save(); + + if ( autosaveData ) { + data.wp_autosave = autosaveData; + } + }).on( 'heartbeat-tick.autosave', function( event, data ) { + if ( data.wp_autosave ) { + response( data.wp_autosave ); + } + }).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'); + + if ( ! wp.autosave.local.hasStorage ) { + $notice.find('.hide-if-no-sessionstorage').hide(); + } + + $notice.show(); + disableButtons(); + } + }).on( 'heartbeat-connection-restored.autosave', function() { + $('#lost-connection-notice').hide(); + enableButtons(); + }).ready( function() { + _schedule(); + }); + + return { + tempBlockSave: tempBlockSave, + triggerSave: triggerSave, + postChanged: postChanged, + suspend: suspend, + resume: resume + }; } - return false; + // 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. + $document.on( 'tinymce-editor-init.autosave', function( event, editor ) { + if ( editor.id === 'content' ) { + window.setTimeout( function() { + editor.save(); + initialCompareString = getCompareString(); + }, 1000 ); + } + }).ready( function() { + // Set the initial compare string in case TinyMCE is not used or not loaded first + initialCompareString = getCompareString(); + }); + + return { + getPostData: getPostData, + getCompareString: getCompareString, + disableButtons: disableButtons, + enableButtons: enableButtons, + local: autosaveLocal(), + server: autosaveServer() + }; } -}; -wp.autosave.local.init(); + window.wp = window.wp || {}; + window.wp.autosave = autosave(); -}(jQuery)); +}( jQuery, window ));