X-Git-Url: https://scripts.mit.edu/gitweb/autoinstalls/wordpress.git/blobdiff_plain/f9001779751f83dc8a10e478bfecb4d8dd5f964c..4713a14935b83517997f3c88f808eb41da55033d:/wp-includes/js/autosave.js diff --git a/wp-includes/js/autosave.js b/wp-includes/js/autosave.js index 14529d9d..def54298 100644 --- a/wp-includes/js/autosave.js +++ b/wp-includes/js/autosave.js @@ -1 +1,599 @@ -var autosave,autosaveLast="",autosavePeriodical,autosaveOldMessage="",autosaveDelayPreview=false,notSaved=true,blockSave=false,interimLogin=false;jQuery(document).ready(function(b){var a=true;autosaveLast=b("#post #title").val()+b("#post #content").val();autosavePeriodical=b.schedule({time:autosaveL10n.autosaveInterval*1000,func:function(){autosave()},repeat:true,protect:true});b("#post").submit(function(){b.cancel(autosavePeriodical)});b('input[type="submit"], a.submitdelete',"#submitpost").click(function(){blockSave=true;window.onbeforeunload=null;b(":button, :submit","#submitpost").each(function(){var c=b(this);if(c.hasClass("button-primary")){c.addClass("button-primary-disabled")}else{c.addClass("button-disabled")}});if(b(this).attr("id")=="publish"){b("#ajax-loading").css("visibility","visible")}else{b("#draft-ajax-loading").css("visibility","visible")}});window.onbeforeunload=function(){var c=typeof(tinyMCE)!="undefined"?tinyMCE.activeEditor:false,e,d;if(c&&!c.isHidden()){if(c.isDirty()){return autosaveL10n.saveAlert}}else{e=b("#post #title").val(),d=b("#post #content").val();if((e||d)&&e+d!=autosaveLast){return autosaveL10n.saveAlert}}};b("#post-preview").click(function(){if(b("#auto_draft").val()=="1"&¬Saved){autosaveDelayPreview=true;autosave();return false}doPreview();return false});doPreview=function(){b("input#wp-preview").val("dopreview");b("form#post").attr("target","wp-preview").submit().attr("target","");b("input#wp-preview").val("")};if(typeof tinyMCE!="undefined"){b("#title")[b.browser.opera?"keypress":"keydown"](function(c){if(c.which==9&&!c.shiftKey&&!c.controlKey&&!c.altKey){if((b("#auto_draft").val()=="1")&&(b("#title").val().length>0)){autosave()}if(tinyMCE.activeEditor&&!tinyMCE.activeEditor.isHidden()&&a){c.preventDefault();a=false;tinyMCE.activeEditor.focus();return false}}})}if("1"==b("#auto_draft").val()){b("#title").blur(function(){if(!this.value||b("#auto_draft").val()!="1"){return}delayed_autosave()})}});function autosave_parse_response(c){var e=wpAjax.parseAjaxResponse(c,"autosave"),f="",a,b,d;if(e&&e.responses&&e.responses.length){f=e.responses[0].data;if(e.responses[0].supplemental){b=e.responses[0].supplemental;if("disable"==b.disable_autosave){autosave=function(){};e={errors:true}}if(b.session_expired&&(d=b.session_expired)){if(!interimLogin||interimLogin.closed){interimLogin=window.open(d,"login","width=600,height=450,resizable=yes,scrollbars=yes,status=yes");interimLogin.focus()}delete b.session_expired}jQuery.each(b,function(g,h){if(g.match(/^replace-/)){jQuery("#"+g.replace("replace-","")).val(h)}})}if(!e.errors){a=parseInt(e.responses[0].id,10);if(!isNaN(a)&&a>0){autosave_update_slug(a)}}}if(f){jQuery("#autosave").html(f)}else{if(autosaveOldMessage&&e){jQuery("#autosave").html(autosaveOldMessage)}}return e}function autosave_saved(a){blockSave=false;autosave_parse_response(a);autosave_enable_buttons()}function autosave_saved_new(b){blockSave=false;var d=autosave_parse_response(b),c,a;if(d&&d.responses.length&&!d.errors){a=parseInt(d.responses[0].id,10);if(!isNaN(a)&&a>0){notSaved=false;jQuery("#auto_draft").val("0")}autosave_enable_buttons();if(autosaveDelayPreview){autosaveDelayPreview=false;doPreview()}}else{autosave_enable_buttons()}}function autosave_update_slug(a){if("undefined"!=makeSlugeditClickable&&jQuery.isFunction(makeSlugeditClickable)&&!jQuery("#edit-slug-box > *").size()){jQuery.post(ajaxurl,{action:"sample-permalink",post_id:a,new_title:jQuery("#title").val(),samplepermalinknonce:jQuery("#samplepermalinknonce").val()},function(b){jQuery("#edit-slug-box").html(b);makeSlugeditClickable()})}}function autosave_loading(){jQuery("#autosave").html(autosaveL10n.savingText)}function autosave_enable_buttons(){setTimeout(function(){jQuery(":button, :submit","#submitpost").removeAttr("disabled");jQuery(".ajax-loading").css("visibility","hidden")},500)}function autosave_disable_buttons(){jQuery(":button, :submit","#submitpost").attr("disabled","disabled");setTimeout(autosave_enable_buttons,5000)}function delayed_autosave(){setTimeout(function(){if(blockSave){return}autosave()},200)}autosave=function(){blockSave=true;var c=(typeof tinyMCE!="undefined")&&tinyMCE.activeEditor&&!tinyMCE.activeEditor.isHidden(),d,f,b,e,a;autosave_disable_buttons();d={action:"autosave",post_ID:jQuery("#post_ID").val()||0,post_title:jQuery("#title").val()||"",autosavenonce:jQuery("#autosavenonce").val(),post_type:jQuery("#post_type").val()||"",autosave:1};jQuery(".tags-input").each(function(){d[this.name]=this.value});f=true;if(jQuery("#TB_window").css("display")=="block"){f=false}if(c&&f){b=tinyMCE.activeEditor;if(b.plugins.spellchecker&&b.plugins.spellchecker.active){f=false}else{if("mce_fullscreen"==b.id){tinyMCE.get("content").setContent(b.getContent({format:"raw"}),{format:"raw"})}tinyMCE.get("content").save()}}d.content=jQuery("#content").val();if(jQuery("#post_name").val()){d.post_name=jQuery("#post_name").val()}if((d.post_title.length==0&&d.content.length==0)||d.post_title+d.content==autosaveLast){f=false}e=jQuery("#original_post_status").val();goodcats=([]);jQuery("[name='post_category[]']:checked").each(function(g){goodcats.push(this.value)});d.catslist=goodcats.join(",");if(jQuery("#comment_status").attr("checked")){d.comment_status="open"}if(jQuery("#ping_status").attr("checked")){d.ping_status="open"}if(jQuery("#excerpt").size()){d.excerpt=jQuery("#excerpt").val()}if(jQuery("#post_author").size()){d.post_author=jQuery("#post_author").val()}if(jQuery("#parent_id").val()){d.parent_id=jQuery("#parent_id").val()}d.user_ID=jQuery("#user-id").val();if(jQuery("#auto_draft").val()=="1"){d.auto_draft="1"}if(f){autosaveLast=jQuery("#title").val()+jQuery("#content").val()}else{d.autosave=0}if(d.auto_draft=="1"){a=autosave_saved_new}else{a=autosave_saved}autosaveOldMessage=jQuery("#autosave").html();jQuery.ajax({data:d,beforeSend:f?autosave_loading:null,type:"POST",url:autosaveL10n.requestFile,success:a})}; \ No newline at end of file +/* global tinymce, wpCookies, autosaveL10n, switchEditors */ +// Back-compat +window.autosave = function() { + return true; +}; + +( function( $, window ) { + function autosave() { + var initialCompareString, + lastTriggerSave = 0, + $document = $(document); + + /** + * 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; + } + + 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; + } + + $( 'input[id^="in-category-"]:checked' ).each( function() { + cats.push( this.value ); + }); + data.catslist = cats.join(','); + + if ( post_name = $( '#post_name' ).val() ) { + data.post_name = post_name; + } + + if ( parent_id = $( '#parent_id' ).val() ) { + data.parent_id = parent_id; + } + + if ( $( '#comment_status' ).prop( 'checked' ) ) { + data.comment_status = 'open'; + } + + if ( $( '#ping_status' ).prop( 'checked' ) ) { + data.ping_status = 'open'; + } + + if ( $( '#auto_draft' ).val() === '1' ) { + data.auto_draft = '1'; + } + + return data; + } + + // 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 || '' ); + } + + return ( $('#title').val() || '' ) + '::' + ( $('#content').val() || '' ) + '::' + ( $('#excerpt').val() || '' ); + } + + function disableButtons() { + $document.trigger('autosave-disable-buttons'); + // Re-enable 5 sec later. Just gives autosave a head start to avoid collisions. + setTimeout( enableButtons, 5000 ); + } + + function enableButtons() { + $document.trigger( 'autosave-enable-buttons' ); + } + + // 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; + } + + /** + * 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 = {}; + } + } + + return stored_obj; + } + + /** + * 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; + } + + return false; + } + + /** + * 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 ( ! 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 + */ + function setData( stored_data ) { + var stored = getStorage(); + + 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 setStorage( stored ); + } + + function suspend() { + isSuspended = true; + } + + function resume() { + isSuspended = false; + } + + /** + * 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 ) { + return false; + } + + if ( data ) { + postData = getSavedPostData() || {}; + $.extend( postData, data ); + } else { + postData = getPostData('local'); + } + + compareString = getCompareString( postData ); + + if ( typeof lastCompareString === 'undefined' ) { + lastCompareString = initialCompareString; + } + + // If the content, title and excerpt did not change since the last save, don't save again + if ( compareString === lastCompareString ) { + return false; + } + + postData.save_time = ( new Date() ).getTime(); + postData.status = $( '#post_status' ).val() || ''; + result = setData( postData ); + + if ( result ) { + lastCompareString = compareString; + } + + return result; + } + + // 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(); + } + + // 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() || '' + }); + } + + wpCookies.set( 'wp-saving-post-' + post_id, 'check' ); + }); + } + + // Strip whitespace and compare two strings + function compare( str1, str2 ) { + function removeSpaces( string ) { + return string.toString().replace(/[\x20\t\r\n\f]+/g, ''); + } + + return ( removeSpaces( str1 || '' ) === removeSpaces( 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 + */ + function checkPost() { + var content, post_title, excerpt, $notice, + postData = getSavedPostData(), + cookie = wpCookies.get( 'wp-saving-post-' + post_id ); + + if ( ! postData ) { + return; + } + + if ( cookie ) { + wpCookies.remove( 'wp-saving-post-' + post_id ); + + if ( cookie === 'saved' ) { + // The post was saved properly, remove old data and bail + setData( false ); + 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() || ''; + + // cookie == 'check' means the post was not saved properly, always show #local-storage-notice + if ( cookie !== 'check' && compare( content, postData.content ) && + compare( post_title, postData.post_title ) && compare( excerpt, postData.excerpt ) ) { + + return; + } + + restorePostData = postData; + undoPostData = { + content: content, + post_title: post_title, + excerpt: excerpt + }; + + $notice = $( '#local-storage-notice' ); + $('.wrap h2').first().after( $notice.addClass( 'updated' ).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(); + } else if ( $target.hasClass( 'undo-restore-backup' ) ) { + restorePost( undoPostData ); + $target.parent().hide(); + $(this).find( 'p.local-restore' ).show(); + } + + event.preventDefault(); + }); + } + + // Restore the current title, content and excerpt from postData. + function restorePost( postData ) { + var editor; + + if ( postData ) { + // Set the last saved data + lastCompareString = getCompareString( postData ); + + if ( $( '#title' ).val() !== postData.post_title ) { + $( '#title' ).focus().val( postData.post_title || '' ); + } + + $( '#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 ); + } + + return true; + } + + return false; + } + + // Initialize and run checkPost() on loading the script (before TinyMCE init) + blog_id = typeof window.autosaveL10n !== 'undefined' && window.autosaveL10n.blog_id; + + // Check if the browser supports sessionStorage and it's not disabled + if ( ! checkStorage() ) { + return; + } + + // Don't run if the post type supports neither 'editor' (textarea#content) nor 'excerpt'. + if ( ! blog_id || ( ! $('#content').length && ! $('#excerpt').length ) ) { + return; + } + + $document.ready( run ); + + 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 ); + } + + function suspend() { + isSuspended = true; + } + + function resume() { + isSuspended = false; + } + + // Runs on heartbeat-response + function response( data ) { + _schedule(); + _blockSave = false; + lastCompareString = previousCompareString; + previousCompareString = ''; + + $document.trigger( 'after-autosave', [data] ); + enableButtons(); + + if ( data.success ) { + // No longer an auto-draft + $( '#auto_draft' ).val(''); + } + } + + /** + * Save immediately + * + * Resets the timing and tells heartbeat to connect now + * + * @return void + */ + function triggerSave() { + nextRun = 0; + wp.heartbeat.connectNow(); + } + + /** + * 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; + } + + // Runs on 'heartbeat-send' + function save() { + var postData, compareString; + + // window.autosave() used for back-compat + if ( isSuspended || _blockSave || ! window.autosave() ) { + return false; + } + + 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; + } + + previousCompareString = compareString; + tempBlockSave(); + disableButtons(); + + $document.trigger( 'wpcountwords', [ postData.content ] ) + .trigger( 'before-autosave', [ postData ] ); + + postData._wpnonce = $( '#_wpnonce' ).val() || ''; + + return postData; + } + + function _schedule() { + nextRun = ( new Date() ).getTime() + ( autosaveL10n.autosaveInterval * 1000 ) || 60000; + } + + $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 + }; + } + + // 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() + }; + } + + window.wp = window.wp || {}; + window.wp.autosave = autosave(); + +}( jQuery, window ));