]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/js/autosave.js
Wordpress 3.7
[autoinstalls/wordpress.git] / wp-includes / js / autosave.js
1 var autosave, autosaveLast = '', autosavePeriodical, autosaveDelayPreview = false, notSaved = true, blockSave = false, fullscreen, autosaveLockRelease = true;
2
3 jQuery(document).ready( function($) {
4
5         if ( $('#wp-content-wrap').hasClass('tmce-active') && typeof switchEditors != 'undefined' ) {
6                 autosaveLast = wp.autosave.getCompareString({
7                         post_title : $('#title').val() || '',
8                         content : switchEditors.pre_wpautop( $('#content').val() ) || '',
9                         excerpt : $('#excerpt').val() || ''
10                 });
11         } else {
12                 autosaveLast = wp.autosave.getCompareString();
13         }
14
15         autosavePeriodical = $.schedule({time: autosaveL10n.autosaveInterval * 1000, func: function() { autosave(); }, repeat: true, protect: true});
16
17         //Disable autosave after the form has been submitted
18         $("#post").submit(function() {
19                 $.cancel(autosavePeriodical);
20                 autosaveLockRelease = false;
21         });
22
23         $('input[type="submit"], a.submitdelete', '#submitpost').click(function(){
24                 blockSave = true;
25                 window.onbeforeunload = null;
26                 $(':button, :submit', '#submitpost').each(function(){
27                         var t = $(this);
28                         if ( t.hasClass('button-primary') )
29                                 t.addClass('button-primary-disabled');
30                         else
31                                 t.addClass('button-disabled');
32                 });
33                 if ( $(this).attr('id') == 'publish' )
34                         $('#major-publishing-actions .spinner').show();
35                 else
36                         $('#minor-publishing .spinner').show();
37         });
38
39         window.onbeforeunload = function(){
40                 var editor = typeof(tinymce) != 'undefined' ? tinymce.activeEditor : false, compareString;
41
42                 if ( editor && ! editor.isHidden() ) {
43                         if ( editor.isDirty() )
44                                 return autosaveL10n.saveAlert;
45                 } else {
46                         if ( fullscreen && fullscreen.settings.visible ) {
47                                 compareString = wp.autosave.getCompareString({
48                                         post_title: $('#wp-fullscreen-title').val() || '',
49                                         content: $('#wp_mce_fullscreen').val() || '',
50                                         excerpt: $('#excerpt').val() || ''
51                                 });
52                         } else {
53                                 compareString = wp.autosave.getCompareString();
54                         }
55
56                         if ( compareString != autosaveLast )
57                                 return autosaveL10n.saveAlert;
58                 }
59         };
60
61         $(window).unload( function(e) {
62                 if ( ! autosaveLockRelease )
63                         return;
64
65                 // unload fires (twice) on removing the Thickbox iframe. Make sure we process only the main document unload.
66                 if ( e.target && e.target.nodeName != '#document' )
67                         return;
68
69                 $.ajax({
70                         type: 'POST',
71                         url: ajaxurl,
72                         async: false,
73                         data: {
74                                 action: 'wp-remove-post-lock',
75                                 _wpnonce: $('#_wpnonce').val(),
76                                 post_ID: $('#post_ID').val(),
77                                 active_post_lock: $('#active_post_lock').val()
78                         }
79                 });
80         } );
81
82         // preview
83         $('#post-preview').click(function(){
84                 if ( $('#auto_draft').val() == '1' && notSaved ) {
85                         autosaveDelayPreview = true;
86                         autosave();
87                         return false;
88                 }
89                 doPreview();
90                 return false;
91         });
92
93         doPreview = function() {
94                 $('input#wp-preview').val('dopreview');
95                 $('form#post').attr('target', 'wp-preview').submit().attr('target', '');
96
97                 /*
98                  * Workaround for WebKit bug preventing a form submitting twice to the same action.
99                  * https://bugs.webkit.org/show_bug.cgi?id=28633
100                  */
101                 var ua = navigator.userAgent.toLowerCase();
102                 if ( ua.indexOf('safari') != -1 && ua.indexOf('chrome') == -1 ) {
103                         $('form#post').attr('action', function(index, value) {
104                                 return value + '?t=' + new Date().getTime();
105                         });
106                 }
107
108                 $('input#wp-preview').val('');
109         }
110
111         // This code is meant to allow tabbing from Title to Post content.
112         $('#title').on('keydown.editor-focus', function(e) {
113                 var ed;
114
115                 if ( e.which != 9 )
116                         return;
117
118                 if ( !e.ctrlKey && !e.altKey && !e.shiftKey ) {
119                         if ( typeof(tinymce) != 'undefined' )
120                                 ed = tinymce.get('content');
121
122                         if ( ed && !ed.isHidden() ) {
123                                 $(this).one('keyup', function(e){
124                                         $('#content_tbl td.mceToolbar > a').focus();
125                                 });
126                         } else {
127                                 $('#content').focus();
128                         }
129
130                         e.preventDefault();
131                 }
132         });
133
134         // autosave new posts after a title is typed but not if Publish or Save Draft is clicked
135         if ( '1' == $('#auto_draft').val() ) {
136                 $('#title').blur( function() {
137                         if ( !this.value || $('#auto_draft').val() != '1' )
138                                 return;
139                         delayed_autosave();
140                 });
141         }
142
143         // When connection is lost, keep user from submitting changes.
144         $(document).on('heartbeat-connection-lost.autosave', function( e, error, status ) {
145                 if ( 'timeout' === error || 503 == status ) {
146                         var notice = $('#lost-connection-notice');
147                         if ( ! wp.autosave.local.hasStorage ) {
148                                 notice.find('.hide-if-no-sessionstorage').hide();
149                         }
150                         notice.show();
151                         autosave_disable_buttons();
152                 }
153         }).on('heartbeat-connection-restored.autosave', function() {
154                 $('#lost-connection-notice').hide();
155                 autosave_enable_buttons();
156         });
157 });
158
159 function autosave_parse_response( response ) {
160         var res = wpAjax.parseAjaxResponse(response, 'autosave'), post_id, sup;
161
162         if ( res && res.responses && res.responses.length ) {
163                 if ( res.responses[0].supplemental ) {
164                         sup = res.responses[0].supplemental;
165
166                         jQuery.each( sup, function( selector, value ) {
167                                 if ( selector.match(/^replace-/) )
168                                         jQuery( '#' + selector.replace('replace-', '') ).val( value );
169                         });
170                 }
171
172                 // if no errors: add slug UI and update autosave-message
173                 if ( !res.errors ) {
174                         if ( post_id = parseInt( res.responses[0].id, 10 ) )
175                                 autosave_update_slug( post_id );
176
177                         if ( res.responses[0].data ) // update autosave message
178                                 jQuery('.autosave-message').text( res.responses[0].data );
179                 }
180         }
181
182         return res;
183 }
184
185 // called when autosaving pre-existing post
186 function autosave_saved(response) {
187         blockSave = false;
188         autosave_parse_response(response); // parse the ajax response
189         autosave_enable_buttons(); // re-enable disabled form buttons
190 }
191
192 // called when autosaving new post
193 function autosave_saved_new(response) {
194         blockSave = false;
195         var res = autosave_parse_response(response), post_id;
196
197         if ( res && res.responses.length && !res.errors ) {
198                 // An ID is sent only for real auto-saves, not for autosave=0 "keepalive" saves
199                 post_id = parseInt( res.responses[0].id, 10 );
200
201                 if ( post_id ) {
202                         notSaved = false;
203                         jQuery('#auto_draft').val('0'); // No longer an auto-draft
204                 }
205
206                 autosave_enable_buttons();
207
208                 if ( autosaveDelayPreview ) {
209                         autosaveDelayPreview = false;
210                         doPreview();
211                 }
212         } else {
213                 autosave_enable_buttons(); // re-enable disabled form buttons
214         }
215 }
216
217 function autosave_update_slug(post_id) {
218         // create slug area only if not already there
219         if ( 'undefined' != makeSlugeditClickable && jQuery.isFunction(makeSlugeditClickable) && !jQuery('#edit-slug-box > *').size() ) {
220                 jQuery.post( ajaxurl, {
221                                 action: 'sample-permalink',
222                                 post_id: post_id,
223                                 new_title: fullscreen && fullscreen.settings.visible ? jQuery('#wp-fullscreen-title').val() : jQuery('#title').val(),
224                                 samplepermalinknonce: jQuery('#samplepermalinknonce').val()
225                         },
226                         function(data) {
227                                 if ( data !== '-1' ) {
228                                         var box = jQuery('#edit-slug-box');
229                                         box.html(data);
230                                         if (box.hasClass('hidden')) {
231                                                 box.fadeIn('fast', function () {
232                                                         box.removeClass('hidden');
233                                                 });
234                                         }
235                                         makeSlugeditClickable();
236                                 }
237                         }
238                 );
239         }
240 }
241
242 function autosave_loading() {
243         jQuery('.autosave-message').html(autosaveL10n.savingText);
244 }
245
246 function autosave_enable_buttons() {
247         jQuery(document).trigger('autosave-enable-buttons');
248         if ( ! wp.heartbeat || ! wp.heartbeat.hasConnectionError() ) {
249                 // delay that a bit to avoid some rare collisions while the DOM is being updated.
250                 setTimeout(function(){
251                         var parent = jQuery('#submitpost');
252                         parent.find(':button, :submit').removeAttr('disabled');
253                         parent.find('.spinner').hide();
254                 }, 500);
255         }
256 }
257
258 function autosave_disable_buttons() {
259         jQuery(document).trigger('autosave-disable-buttons');
260         jQuery('#submitpost').find(':button, :submit').prop('disabled', true);
261         // Re-enable 5 sec later. Just gives autosave a head start to avoid collisions.
262         setTimeout( autosave_enable_buttons, 5000 );
263 }
264
265 function delayed_autosave() {
266         setTimeout(function(){
267                 if ( blockSave )
268                         return;
269                 autosave();
270         }, 200);
271 }
272
273 autosave = function() {
274         var post_data = wp.autosave.getPostData(),
275                 compareString,
276                 successCallback;
277
278         blockSave = true;
279
280         // post_data.content cannot be retrieved at the moment
281         if ( ! post_data.autosave )
282                 return false;
283
284         // No autosave while thickbox is open (media buttons)
285         if ( jQuery("#TB_window").css('display') == 'block' )
286                 return false;
287
288         compareString = wp.autosave.getCompareString( post_data );
289
290         // Nothing to save or no change.
291         if ( compareString == autosaveLast )
292                 return false;
293
294         autosaveLast = compareString;
295         jQuery(document).triggerHandler('wpcountwords', [ post_data["content"] ]);
296
297         // Disable buttons until we know the save completed.
298         autosave_disable_buttons();
299
300         if ( post_data["auto_draft"] == '1' ) {
301                 successCallback = autosave_saved_new; // new post
302         } else {
303                 successCallback = autosave_saved; // pre-existing post
304         }
305
306         jQuery.ajax({
307                 data: post_data,
308                 beforeSend: autosave_loading,
309                 type: "POST",
310                 url: ajaxurl,
311                 success: successCallback
312         });
313
314         return true;
315 }
316
317 // Autosave in localStorage
318 // set as simple object/mixin for now
319 window.wp = window.wp || {};
320 wp.autosave = wp.autosave || {};
321
322 (function($){
323 // Returns the data for saving in both localStorage and autosaves to the server
324 wp.autosave.getPostData = function() {
325         var ed = typeof tinymce != 'undefined' ? tinymce.activeEditor : null, post_name, parent_id, cats = [],
326                 data = {
327                         action: 'autosave',
328                         autosave: true,
329                         post_id: $('#post_ID').val() || 0,
330                         autosavenonce: $('#autosavenonce').val() || '',
331                         post_type: $('#post_type').val() || '',
332                         post_author: $('#post_author').val() || '',
333                         excerpt: $('#excerpt').val() || ''
334                 };
335
336         if ( ed && !ed.isHidden() ) {
337                 // Don't run while the tinymce spellcheck is on. It resets all found words.
338                 if ( ed.plugins.spellchecker && ed.plugins.spellchecker.active ) {
339                         data.autosave = false;
340                         return data;
341                 } else {
342                         if ( 'mce_fullscreen' == ed.id )
343                                 tinymce.get('content').setContent(ed.getContent({format : 'raw'}), {format : 'raw'});
344
345                         tinymce.triggerSave();
346                 }
347         }
348
349         if ( typeof fullscreen != 'undefined' && fullscreen.settings.visible ) {
350                 data['post_title'] = $('#wp-fullscreen-title').val() || '';
351                 data['content'] = $('#wp_mce_fullscreen').val() || '';
352         } else {
353                 data['post_title'] = $('#title').val() || '';
354                 data['content'] = $('#content').val() || '';
355         }
356
357         /*
358         // We haven't been saving tags with autosave since 2.8... Start again?
359         $('.the-tags').each( function() {
360                 data[this.name] = this.value;
361         });
362         */
363
364         $('input[id^="in-category-"]:checked').each( function() {
365                 cats.push(this.value);
366         });
367         data['catslist'] = cats.join(',');
368
369         if ( post_name = $('#post_name').val() )
370                 data['post_name'] = post_name;
371
372         if ( parent_id = $('#parent_id').val() )
373                 data['parent_id'] = parent_id;
374
375         if ( $('#comment_status').prop('checked') )
376                 data['comment_status'] = 'open';
377
378         if ( $('#ping_status').prop('checked') )
379                 data['ping_status'] = 'open';
380
381         if ( $('#auto_draft').val() == '1' )
382                 data['auto_draft'] = '1';
383
384         return data;
385 };
386
387 // Concatenate title, content and excerpt. Used to track changes when auto-saving.
388 wp.autosave.getCompareString = function( post_data ) {
389         if ( typeof post_data === 'object' ) {
390                 return ( post_data.post_title || '' ) + '::' + ( post_data.content || '' ) + '::' + ( post_data.excerpt || '' );
391         }
392
393         return ( $('#title').val() || '' ) + '::' + ( $('#content').val() || '' ) + '::' + ( $('#excerpt').val() || '' );
394 };
395
396 wp.autosave.local = {
397
398         lastSavedData: '',
399         blog_id: 0,
400         hasStorage: false,
401
402         // Check if the browser supports sessionStorage and it's not disabled
403         checkStorage: function() {
404                 var test = Math.random(), result = false;
405
406                 try {
407                         sessionStorage.setItem('wp-test', test);
408                         result = sessionStorage.getItem('wp-test') == test;
409                         sessionStorage.removeItem('wp-test');
410                 } catch(e) {}
411
412                 this.hasStorage = result;
413                 return result;
414     },
415
416         /**
417          * Initialize the local storage
418          *
419          * @return mixed False if no sessionStorage in the browser or an Object containing all post_data for this blog
420          */
421         getStorage: function() {
422                 var stored_obj = false;
423                 // Separate local storage containers for each blog_id
424                 if ( this.hasStorage && this.blog_id ) {
425                         stored_obj = sessionStorage.getItem( 'wp-autosave-' + this.blog_id );
426
427                         if ( stored_obj )
428                                 stored_obj = JSON.parse( stored_obj );
429                         else
430                                 stored_obj = {};
431                 }
432
433                 return stored_obj;
434         },
435
436         /**
437          * Set the storage for this blog
438          *
439          * Confirms that the data was saved successfully.
440          *
441          * @return bool
442          */
443         setStorage: function( stored_obj ) {
444                 var key;
445
446                 if ( this.hasStorage && this.blog_id ) {
447                         key = 'wp-autosave-' + this.blog_id;
448                         sessionStorage.setItem( key, JSON.stringify( stored_obj ) );
449                         return sessionStorage.getItem( key ) !== null;
450                 }
451
452                 return false;
453         },
454
455         /**
456          * Get the saved post data for the current post
457          *
458          * @return mixed False if no storage or no data or the post_data as an Object
459          */
460         getData: function() {
461                 var stored = this.getStorage(), post_id = $('#post_ID').val();
462
463                 if ( !stored || !post_id )
464                         return false;
465
466                 return stored[ 'post_' + post_id ] || false;
467         },
468
469         /**
470          * Set (save or delete) post data in the storage.
471          *
472          * If stored_data evaluates to 'false' the storage key for the current post will be removed
473          *
474          * $param stored_data The post data to store or null/false/empty to delete the key
475          * @return bool
476          */
477         setData: function( stored_data ) {
478                 var stored = this.getStorage(), post_id = $('#post_ID').val();
479
480                 if ( !stored || !post_id )
481                         return false;
482
483                 if ( stored_data )
484                         stored[ 'post_' + post_id ] = stored_data;
485                 else if ( stored.hasOwnProperty( 'post_' + post_id ) )
486                         delete stored[ 'post_' + post_id ];
487                 else
488                         return false;
489
490                 return this.setStorage(stored);
491         },
492
493         /**
494          * Save post data for the current post
495          *
496          * Runs on a 15 sec. schedule, saves when there are differences in the post title or content.
497          * When the optional data is provided, updates the last saved post data.
498          *
499          * $param data optional Object The post data for saving, minimum 'post_title' and 'content'
500          * @return bool
501          */
502         save: function( data ) {
503                 var result = false, post_data, compareString;
504
505                 if ( ! data ) {
506                         post_data = wp.autosave.getPostData();
507                 } else {
508                         post_data = this.getData() || {};
509                         $.extend( post_data, data );
510                         post_data.autosave = true;
511                 }
512
513                 // Cannot get the post data at the moment
514                 if ( ! post_data.autosave )
515                         return false;
516
517                 compareString = wp.autosave.getCompareString( post_data );
518
519                 // If the content, title and excerpt did not change since the last save, don't save again
520                 if ( compareString == this.lastSavedData )
521                         return false;
522
523                 post_data['save_time'] = (new Date()).getTime();
524                 post_data['status'] = $('#post_status').val() || '';
525                 result = this.setData( post_data );
526
527                 if ( result )
528                         this.lastSavedData = compareString;
529
530                 return result;
531         },
532
533         // Initialize and run checkPost() on loading the script (before TinyMCE init)
534         init: function( settings ) {
535                 var self = this;
536
537                 // Check if the browser supports sessionStorage and it's not disabled
538                 if ( ! this.checkStorage() )
539                         return;
540
541                 // Don't run if the post type supports neither 'editor' (textarea#content) nor 'excerpt'.
542                 if ( ! $('#content').length && ! $('#excerpt').length )
543                         return;
544
545                 if ( settings )
546                         $.extend( this, settings );
547
548                 if ( !this.blog_id )
549                         this.blog_id = typeof window.autosaveL10n != 'undefined' ? window.autosaveL10n.blog_id : 0;
550
551                 $(document).ready( function(){ self.run(); } );
552         },
553
554         // Run on DOM ready
555         run: function() {
556                 var self = this;
557
558                 // Check if the local post data is different than the loaded post data.
559                 this.checkPost();
560
561                 // Set the schedule
562                 this.schedule = $.schedule({
563                         time: 15 * 1000,
564                         func: function() { wp.autosave.local.save(); },
565                         repeat: true,
566                         protect: true
567                 });
568
569                 $('form#post').on('submit.autosave-local', function() {
570                         var editor = typeof tinymce != 'undefined' && tinymce.get('content'), post_id = $('#post_ID').val() || 0;
571
572                         if ( editor && ! editor.isHidden() ) {
573                                 // Last onSubmit event in the editor, needs to run after the content has been moved to the textarea.
574                                 editor.onSubmit.add( function() {
575                                         wp.autosave.local.save({
576                                                 post_title: $('#title').val() || '',
577                                                 content: $('#content').val() || '',
578                                                 excerpt: $('#excerpt').val() || ''
579                                         });
580                                 });
581                         } else {
582                                 self.save({
583                                         post_title: $('#title').val() || '',
584                                         content: $('#content').val() || '',
585                                         excerpt: $('#excerpt').val() || ''
586                                 });
587                         }
588
589                         wpCookies.set( 'wp-saving-post-' + post_id, 'check' );
590                 });
591         },
592
593         // Strip whitespace and compare two strings
594         compare: function( str1, str2 ) {
595                 function remove( string ) {
596                         return string.toString().replace(/[\x20\t\r\n\f]+/g, '');
597                 }
598
599                 return ( remove( str1 || '' ) == remove( str2 || '' ) );
600         },
601
602         /**
603          * Check if the saved data for the current post (if any) is different than the loaded post data on the screen
604          *
605          * Shows a standard message letting the user restore the post data if different.
606          *
607          * @return void
608          */
609         checkPost: function() {
610                 var self = this, post_data = this.getData(), content, post_title, excerpt, notice,
611                         post_id = $('#post_ID').val() || 0, cookie = wpCookies.get( 'wp-saving-post-' + post_id );
612
613                 if ( ! post_data )
614                         return;
615
616                 if ( cookie ) {
617                         wpCookies.remove( 'wp-saving-post-' + post_id );
618
619                         if ( cookie == 'saved' ) {
620                                 // The post was saved properly, remove old data and bail
621                                 this.setData( false );
622                                 return;
623                         }
624                 }
625
626                 // There is a newer autosave. Don't show two "restore" notices at the same time.
627                 if ( $('#has-newer-autosave').length )
628                         return;
629
630                 content = $('#content').val() || '';
631                 post_title = $('#title').val() || '';
632                 excerpt = $('#excerpt').val() || '';
633
634                 if ( $('#wp-content-wrap').hasClass('tmce-active') && typeof switchEditors != 'undefined' )
635                         content = switchEditors.pre_wpautop( content );
636
637                 // cookie == 'check' means the post was not saved properly, always show #local-storage-notice
638                 if ( cookie != 'check' && this.compare( content, post_data.content ) && this.compare( post_title, post_data.post_title ) && this.compare( excerpt, post_data.excerpt ) ) {
639                         return;
640                 }
641
642                 this.restore_post_data = post_data;
643                 this.undo_post_data = {
644                         content: content,
645                         post_title: post_title,
646                         excerpt: excerpt
647                 };
648
649                 notice = $('#local-storage-notice');
650                 $('.wrap h2').first().after( notice.addClass('updated').show() );
651
652                 notice.on( 'click', function(e) {
653                         var target = $( e.target );
654
655                         if ( target.hasClass('restore-backup') ) {
656                                 self.restorePost( self.restore_post_data );
657                                 target.parent().hide();
658                                 $(this).find('p.undo-restore').show();
659                         } else if ( target.hasClass('undo-restore-backup') ) {
660                                 self.restorePost( self.undo_post_data );
661                                 target.parent().hide();
662                                 $(this).find('p.local-restore').show();
663                         }
664
665                         e.preventDefault();
666                 });
667         },
668
669         // Restore the current title, content and excerpt from post_data.
670         restorePost: function( post_data ) {
671                 var editor;
672
673                 if ( post_data ) {
674                         // Set the last saved data
675                         this.lastSavedData = wp.autosave.getCompareString( post_data );
676
677                         if ( $('#title').val() != post_data.post_title )
678                                 $('#title').focus().val( post_data.post_title || '' );
679
680                         $('#excerpt').val( post_data.excerpt || '' );
681                         editor = typeof tinymce != 'undefined' && tinymce.get('content');
682
683                         if ( editor && ! editor.isHidden() && typeof switchEditors != 'undefined' ) {
684                                 // Make sure there's an undo level in the editor
685                                 editor.undoManager.add();
686                                 editor.setContent( post_data.content ? switchEditors.wpautop( post_data.content ) : '' );
687                         } else {
688                                 // Make sure the Text editor is selected
689                                 $('#content-html').click();
690                                 $('#content').val( post_data.content );
691                         }
692
693                         return true;
694                 }
695
696                 return false;
697         }
698 };
699
700 wp.autosave.local.init();
701
702 }(jQuery));