1 var autosave, autosaveLast = '', autosavePeriodical, autosaveDelayPreview = false, notSaved = true, blockSave = false, fullscreen, autosaveLockRelease = true; |
1 /* global tinymce, wpCookies, autosaveL10n, switchEditors */ |
2 |
2 // Back-compat |
3 jQuery(document).ready( function($) { |
3 window.autosave = function() { |
4 |
4 return true; |
5 if ( $('#wp-content-wrap').hasClass('tmce-active') && typeof switchEditors != 'undefined' ) { |
5 }; |
6 autosaveLast = wp.autosave.getCompareString({ |
6 |
7 post_title : $('#title').val() || '', |
7 ( function( $, window ) { |
8 content : switchEditors.pre_wpautop( $('#content').val() ) || '', |
8 function autosave() { |
9 excerpt : $('#excerpt').val() || '' |
9 var initialCompareString, |
10 }); |
10 lastTriggerSave = 0, |
11 } else { |
11 $document = $(document); |
12 autosaveLast = wp.autosave.getCompareString(); |
12 |
13 } |
13 /** |
14 |
14 * Returns the data saved in both local and remote autosave |
15 autosavePeriodical = $.schedule({time: autosaveL10n.autosaveInterval * 1000, func: function() { autosave(); }, repeat: true, protect: true}); |
15 * |
16 |
16 * @return object Object containing the post data |
17 //Disable autosave after the form has been submitted |
17 */ |
18 $("#post").submit(function() { |
18 function getPostData( type ) { |
19 $.cancel(autosavePeriodical); |
19 var post_name, parent_id, data, |
20 autosaveLockRelease = false; |
20 time = ( new Date() ).getTime(), |
21 }); |
21 cats = [], |
22 |
22 editor = typeof tinymce !== 'undefined' && tinymce.get('content'); |
23 $('input[type="submit"], a.submitdelete', '#submitpost').click(function(){ |
23 |
24 blockSave = true; |
24 // Don't run editor.save() more often than every 3 sec. |
25 window.onbeforeunload = null; |
25 // It is resource intensive and might slow down typing in long posts on slow devices. |
26 $(':button, :submit', '#submitpost').each(function(){ |
26 if ( editor && ! editor.isHidden() && time - 3000 > lastTriggerSave ) { |
27 var t = $(this); |
27 editor.save(); |
28 if ( t.hasClass('button-primary') ) |
28 lastTriggerSave = time; |
29 t.addClass('button-primary-disabled'); |
29 } |
30 else |
30 |
31 t.addClass('button-disabled'); |
31 data = { |
32 }); |
32 post_id: $( '#post_ID' ).val() || 0, |
33 if ( $(this).attr('id') == 'publish' ) |
33 post_type: $( '#post_type' ).val() || '', |
34 $('#major-publishing-actions .spinner').show(); |
34 post_author: $( '#post_author' ).val() || '', |
35 else |
35 post_title: $( '#title' ).val() || '', |
36 $('#minor-publishing .spinner').show(); |
36 content: $( '#content' ).val() || '', |
37 }); |
37 excerpt: $( '#excerpt' ).val() || '' |
38 |
38 }; |
39 window.onbeforeunload = function(){ |
39 |
40 var editor = typeof(tinymce) != 'undefined' ? tinymce.activeEditor : false, compareString; |
40 if ( type === 'local' ) { |
41 |
41 return data; |
42 if ( editor && ! editor.isHidden() ) { |
42 } |
43 if ( editor.isDirty() ) |
43 |
44 return autosaveL10n.saveAlert; |
44 $( 'input[id^="in-category-"]:checked' ).each( function() { |
45 } else { |
45 cats.push( this.value ); |
46 if ( fullscreen && fullscreen.settings.visible ) { |
46 }); |
47 compareString = wp.autosave.getCompareString({ |
47 data.catslist = cats.join(','); |
48 post_title: $('#wp-fullscreen-title').val() || '', |
48 |
49 content: $('#wp_mce_fullscreen').val() || '', |
49 if ( post_name = $( '#post_name' ).val() ) { |
50 excerpt: $('#excerpt').val() || '' |
50 data.post_name = post_name; |
51 }); |
51 } |
52 } else { |
52 |
53 compareString = wp.autosave.getCompareString(); |
53 if ( parent_id = $( '#parent_id' ).val() ) { |
54 } |
54 data.parent_id = parent_id; |
55 |
55 } |
56 if ( compareString != autosaveLast ) |
56 |
57 return autosaveL10n.saveAlert; |
57 if ( $( '#comment_status' ).prop( 'checked' ) ) { |
|
58 data.comment_status = 'open'; |
|
59 } |
|
60 |
|
61 if ( $( '#ping_status' ).prop( 'checked' ) ) { |
|
62 data.ping_status = 'open'; |
|
63 } |
|
64 |
|
65 if ( $( '#auto_draft' ).val() === '1' ) { |
|
66 data.auto_draft = '1'; |
|
67 } |
|
68 |
|
69 return data; |
58 } |
70 } |
59 }; |
71 |
60 |
72 // Concatenate title, content and excerpt. Used to track changes when auto-saving. |
61 $(window).unload( function(e) { |
73 function getCompareString( postData ) { |
62 if ( ! autosaveLockRelease ) |
74 if ( typeof postData === 'object' ) { |
63 return; |
75 return ( postData.post_title || '' ) + '::' + ( postData.content || '' ) + '::' + ( postData.excerpt || '' ); |
64 |
76 } |
65 // unload fires (twice) on removing the Thickbox iframe. Make sure we process only the main document unload. |
77 |
66 if ( e.target && e.target.nodeName != '#document' ) |
78 return ( $('#title').val() || '' ) + '::' + ( $('#content').val() || '' ) + '::' + ( $('#excerpt').val() || '' ); |
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 } |
79 } |
89 doPreview(); |
80 |
90 return false; |
81 function disableButtons() { |
91 }); |
82 $document.trigger('autosave-disable-buttons'); |
92 |
83 // Re-enable 5 sec later. Just gives autosave a head start to avoid collisions. |
93 doPreview = function() { |
84 setTimeout( enableButtons, 5000 ); |
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 } |
85 } |
107 |
86 |
108 $('input#wp-preview').val(''); |
87 function enableButtons() { |
109 } |
88 $document.trigger( 'autosave-enable-buttons' ); |
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 } |
89 } |
132 }); |
90 |
133 |
91 // Autosave in localStorage |
134 // autosave new posts after a title is typed but not if Publish or Save Draft is clicked |
92 function autosaveLocal() { |
135 if ( '1' == $('#auto_draft').val() ) { |
93 var restorePostData, undoPostData, blog_id, post_id, hasStorage, intervalTimer, |
136 $('#title').blur( function() { |
94 lastCompareString, |
137 if ( !this.value || $('#auto_draft').val() != '1' ) |
95 isSuspended = false; |
138 return; |
96 |
139 delayed_autosave(); |
97 // Check if the browser supports sessionStorage and it's not disabled |
140 }); |
98 function checkStorage() { |
141 } |
99 var test = Math.random().toString(), |
142 |
100 result = false; |
143 // When connection is lost, keep user from submitting changes. |
101 |
144 $(document).on('heartbeat-connection-lost.autosave', function( e, error, status ) { |
102 try { |
145 if ( 'timeout' === error || 503 == status ) { |
103 window.sessionStorage.setItem( 'wp-test', test ); |
146 var notice = $('#lost-connection-notice'); |
104 result = window.sessionStorage.getItem( 'wp-test' ) === test; |
147 if ( ! wp.autosave.local.hasStorage ) { |
105 window.sessionStorage.removeItem( 'wp-test' ); |
148 notice.find('.hide-if-no-sessionstorage').hide(); |
106 } catch(e) {} |
149 } |
107 |
150 notice.show(); |
108 hasStorage = result; |
151 autosave_disable_buttons(); |
109 return result; |
152 } |
110 } |
153 }).on('heartbeat-connection-restored.autosave', function() { |
111 |
154 $('#lost-connection-notice').hide(); |
112 /** |
155 autosave_enable_buttons(); |
113 * Initialize the local storage |
156 }); |
114 * |
157 }); |
115 * @return mixed False if no sessionStorage in the browser or an Object containing all postData for this blog |
158 |
116 */ |
159 function autosave_parse_response( response ) { |
117 function getStorage() { |
160 var res = wpAjax.parseAjaxResponse(response, 'autosave'), post_id, sup; |
118 var stored_obj = false; |
161 |
119 // Separate local storage containers for each blog_id |
162 if ( res && res.responses && res.responses.length ) { |
120 if ( hasStorage && blog_id ) { |
163 if ( res.responses[0].supplemental ) { |
121 stored_obj = sessionStorage.getItem( 'wp-autosave-' + blog_id ); |
164 sup = res.responses[0].supplemental; |
122 |
165 |
123 if ( stored_obj ) { |
166 jQuery.each( sup, function( selector, value ) { |
124 stored_obj = JSON.parse( stored_obj ); |
167 if ( selector.match(/^replace-/) ) |
125 } else { |
168 jQuery( '#' + selector.replace('replace-', '') ).val( value ); |
126 stored_obj = {}; |
169 }); |
127 } |
170 } |
128 } |
171 |
129 |
172 // if no errors: add slug UI and update autosave-message |
130 return stored_obj; |
173 if ( !res.errors ) { |
131 } |
174 if ( post_id = parseInt( res.responses[0].id, 10 ) ) |
132 |
175 autosave_update_slug( post_id ); |
133 /** |
176 |
134 * Set the storage for this blog |
177 if ( res.responses[0].data ) // update autosave message |
135 * |
178 jQuery('.autosave-message').text( res.responses[0].data ); |
136 * Confirms that the data was saved successfully. |
179 } |
137 * |
180 } |
138 * @return bool |
181 |
139 */ |
182 return res; |
140 function setStorage( stored_obj ) { |
183 } |
141 var key; |
184 |
142 |
185 // called when autosaving pre-existing post |
143 if ( hasStorage && blog_id ) { |
186 function autosave_saved(response) { |
144 key = 'wp-autosave-' + blog_id; |
187 blockSave = false; |
145 sessionStorage.setItem( key, JSON.stringify( stored_obj ) ); |
188 autosave_parse_response(response); // parse the ajax response |
146 return sessionStorage.getItem( key ) !== null; |
189 autosave_enable_buttons(); // re-enable disabled form buttons |
147 } |
190 } |
148 |
191 |
149 return false; |
192 // called when autosaving new post |
150 } |
193 function autosave_saved_new(response) { |
151 |
194 blockSave = false; |
152 /** |
195 var res = autosave_parse_response(response), post_id; |
153 * Get the saved post data for the current post |
196 |
154 * |
197 if ( res && res.responses.length && !res.errors ) { |
155 * @return mixed False if no storage or no data or the postData as an Object |
198 // An ID is sent only for real auto-saves, not for autosave=0 "keepalive" saves |
156 */ |
199 post_id = parseInt( res.responses[0].id, 10 ); |
157 function getSavedPostData() { |
200 |
158 var stored = getStorage(); |
201 if ( post_id ) { |
159 |
202 notSaved = false; |
160 if ( ! stored || ! post_id ) { |
203 jQuery('#auto_draft').val('0'); // No longer an auto-draft |
161 return false; |
204 } |
162 } |
205 |
163 |
206 autosave_enable_buttons(); |
164 return stored[ 'post_' + post_id ] || false; |
207 |
165 } |
208 if ( autosaveDelayPreview ) { |
166 |
209 autosaveDelayPreview = false; |
167 /** |
210 doPreview(); |
168 * Set (save or delete) post data in the storage. |
211 } |
169 * |
212 } else { |
170 * If stored_data evaluates to 'false' the storage key for the current post will be removed |
213 autosave_enable_buttons(); // re-enable disabled form buttons |
171 * |
214 } |
172 * $param stored_data The post data to store or null/false/empty to delete the key |
215 } |
173 * @return bool |
216 |
174 */ |
217 function autosave_update_slug(post_id) { |
175 function setData( stored_data ) { |
218 // create slug area only if not already there |
176 var stored = getStorage(); |
219 if ( 'undefined' != makeSlugeditClickable && jQuery.isFunction(makeSlugeditClickable) && !jQuery('#edit-slug-box > *').size() ) { |
177 |
220 jQuery.post( ajaxurl, { |
178 if ( ! stored || ! post_id ) { |
221 action: 'sample-permalink', |
179 return false; |
222 post_id: post_id, |
180 } |
223 new_title: fullscreen && fullscreen.settings.visible ? jQuery('#wp-fullscreen-title').val() : jQuery('#title').val(), |
181 |
224 samplepermalinknonce: jQuery('#samplepermalinknonce').val() |
182 if ( stored_data ) { |
225 }, |
183 stored[ 'post_' + post_id ] = stored_data; |
226 function(data) { |
184 } else if ( stored.hasOwnProperty( 'post_' + post_id ) ) { |
227 if ( data !== '-1' ) { |
185 delete stored[ 'post_' + post_id ]; |
228 var box = jQuery('#edit-slug-box'); |
186 } else { |
229 box.html(data); |
187 return false; |
230 if (box.hasClass('hidden')) { |
188 } |
231 box.fadeIn('fast', function () { |
189 |
232 box.removeClass('hidden'); |
190 return setStorage( stored ); |
|
191 } |
|
192 |
|
193 function suspend() { |
|
194 isSuspended = true; |
|
195 } |
|
196 |
|
197 function resume() { |
|
198 isSuspended = false; |
|
199 } |
|
200 |
|
201 /** |
|
202 * Save post data for the current post |
|
203 * |
|
204 * Runs on a 15 sec. interval, saves when there are differences in the post title or content. |
|
205 * When the optional data is provided, updates the last saved post data. |
|
206 * |
|
207 * $param data optional Object The post data for saving, minimum 'post_title' and 'content' |
|
208 * @return bool |
|
209 */ |
|
210 function save( data ) { |
|
211 var postData, compareString, |
|
212 result = false; |
|
213 |
|
214 if ( isSuspended || ! hasStorage ) { |
|
215 return false; |
|
216 } |
|
217 |
|
218 if ( data ) { |
|
219 postData = getSavedPostData() || {}; |
|
220 $.extend( postData, data ); |
|
221 } else { |
|
222 postData = getPostData('local'); |
|
223 } |
|
224 |
|
225 compareString = getCompareString( postData ); |
|
226 |
|
227 if ( typeof lastCompareString === 'undefined' ) { |
|
228 lastCompareString = initialCompareString; |
|
229 } |
|
230 |
|
231 // If the content, title and excerpt did not change since the last save, don't save again |
|
232 if ( compareString === lastCompareString ) { |
|
233 return false; |
|
234 } |
|
235 |
|
236 postData.save_time = ( new Date() ).getTime(); |
|
237 postData.status = $( '#post_status' ).val() || ''; |
|
238 result = setData( postData ); |
|
239 |
|
240 if ( result ) { |
|
241 lastCompareString = compareString; |
|
242 } |
|
243 |
|
244 return result; |
|
245 } |
|
246 |
|
247 // Run on DOM ready |
|
248 function run() { |
|
249 post_id = $('#post_ID').val() || 0; |
|
250 |
|
251 // Check if the local post data is different than the loaded post data. |
|
252 if ( $( '#wp-content-wrap' ).hasClass( 'tmce-active' ) ) { |
|
253 // If TinyMCE loads first, check the post 1.5 sec. after it is ready. |
|
254 // By this time the content has been loaded in the editor and 'saved' to the textarea. |
|
255 // This prevents false positives. |
|
256 $document.on( 'tinymce-editor-init.autosave', function() { |
|
257 window.setTimeout( function() { |
|
258 checkPost(); |
|
259 }, 1500 ); |
|
260 }); |
|
261 } else { |
|
262 checkPost(); |
|
263 } |
|
264 |
|
265 // Save every 15 sec. |
|
266 intervalTimer = window.setInterval( save, 15000 ); |
|
267 |
|
268 $( 'form#post' ).on( 'submit.autosave-local', function() { |
|
269 var editor = typeof tinymce !== 'undefined' && tinymce.get('content'), |
|
270 post_id = $('#post_ID').val() || 0; |
|
271 |
|
272 if ( editor && ! editor.isHidden() ) { |
|
273 // Last onSubmit event in the editor, needs to run after the content has been moved to the textarea. |
|
274 editor.on( 'submit', function() { |
|
275 save({ |
|
276 post_title: $( '#title' ).val() || '', |
|
277 content: $( '#content' ).val() || '', |
|
278 excerpt: $( '#excerpt' ).val() || '' |
|
279 }); |
|
280 }); |
|
281 } else { |
|
282 save({ |
|
283 post_title: $( '#title' ).val() || '', |
|
284 content: $( '#content' ).val() || '', |
|
285 excerpt: $( '#excerpt' ).val() || '' |
233 }); |
286 }); |
234 } |
287 } |
235 makeSlugeditClickable(); |
288 |
236 } |
289 wpCookies.set( 'wp-saving-post', post_id + '-check', 24 * 60 * 60 ); |
237 } |
290 }); |
238 ); |
291 } |
|
292 |
|
293 // Strip whitespace and compare two strings |
|
294 function compare( str1, str2 ) { |
|
295 function removeSpaces( string ) { |
|
296 return string.toString().replace(/[\x20\t\r\n\f]+/g, ''); |
|
297 } |
|
298 |
|
299 return ( removeSpaces( str1 || '' ) === removeSpaces( str2 || '' ) ); |
|
300 } |
|
301 |
|
302 /** |
|
303 * Check if the saved data for the current post (if any) is different than the loaded post data on the screen |
|
304 * |
|
305 * Shows a standard message letting the user restore the post data if different. |
|
306 * |
|
307 * @return void |
|
308 */ |
|
309 function checkPost() { |
|
310 var content, post_title, excerpt, $notice, |
|
311 postData = getSavedPostData(), |
|
312 cookie = wpCookies.get( 'wp-saving-post' ); |
|
313 |
|
314 if ( cookie === post_id + '-saved' ) { |
|
315 wpCookies.remove( 'wp-saving-post' ); |
|
316 // The post was saved properly, remove old data and bail |
|
317 setData( false ); |
|
318 return; |
|
319 } |
|
320 |
|
321 if ( ! postData ) { |
|
322 return; |
|
323 } |
|
324 |
|
325 // There is a newer autosave. Don't show two "restore" notices at the same time. |
|
326 if ( $( '#has-newer-autosave' ).length ) { |
|
327 return; |
|
328 } |
|
329 |
|
330 content = $( '#content' ).val() || ''; |
|
331 post_title = $( '#title' ).val() || ''; |
|
332 excerpt = $( '#excerpt' ).val() || ''; |
|
333 |
|
334 if ( compare( content, postData.content ) && compare( post_title, postData.post_title ) && |
|
335 compare( excerpt, postData.excerpt ) ) { |
|
336 |
|
337 return; |
|
338 } |
|
339 |
|
340 restorePostData = postData; |
|
341 undoPostData = { |
|
342 content: content, |
|
343 post_title: post_title, |
|
344 excerpt: excerpt |
|
345 }; |
|
346 |
|
347 $notice = $( '#local-storage-notice' ); |
|
348 $('.wrap h2').first().after( $notice.addClass( 'notice-warning' ).show() ); |
|
349 |
|
350 $notice.on( 'click.autosave-local', function( event ) { |
|
351 var $target = $( event.target ); |
|
352 |
|
353 if ( $target.hasClass( 'restore-backup' ) ) { |
|
354 restorePost( restorePostData ); |
|
355 $target.parent().hide(); |
|
356 $(this).find( 'p.undo-restore' ).show(); |
|
357 $notice.removeClass( 'notice-warning' ).addClass( 'notice-success' ); |
|
358 } else if ( $target.hasClass( 'undo-restore-backup' ) ) { |
|
359 restorePost( undoPostData ); |
|
360 $target.parent().hide(); |
|
361 $(this).find( 'p.local-restore' ).show(); |
|
362 $notice.removeClass( 'notice-success' ).addClass( 'notice-warning' ); |
|
363 } |
|
364 |
|
365 event.preventDefault(); |
|
366 }); |
|
367 } |
|
368 |
|
369 // Restore the current title, content and excerpt from postData. |
|
370 function restorePost( postData ) { |
|
371 var editor; |
|
372 |
|
373 if ( postData ) { |
|
374 // Set the last saved data |
|
375 lastCompareString = getCompareString( postData ); |
|
376 |
|
377 if ( $( '#title' ).val() !== postData.post_title ) { |
|
378 $( '#title' ).focus().val( postData.post_title || '' ); |
|
379 } |
|
380 |
|
381 $( '#excerpt' ).val( postData.excerpt || '' ); |
|
382 editor = typeof tinymce !== 'undefined' && tinymce.get('content'); |
|
383 |
|
384 if ( editor && ! editor.isHidden() && typeof switchEditors !== 'undefined' ) { |
|
385 // Make sure there's an undo level in the editor |
|
386 editor.undoManager.add(); |
|
387 editor.setContent( postData.content ? switchEditors.wpautop( postData.content ) : '' ); |
|
388 } else { |
|
389 // Make sure the Text editor is selected |
|
390 $( '#content-html' ).click(); |
|
391 $( '#content' ).val( postData.content ); |
|
392 } |
|
393 |
|
394 return true; |
|
395 } |
|
396 |
|
397 return false; |
|
398 } |
|
399 |
|
400 blog_id = typeof window.autosaveL10n !== 'undefined' && window.autosaveL10n.blog_id; |
|
401 |
|
402 // Check if the browser supports sessionStorage and it's not disabled, |
|
403 // then initialize and run checkPost(). |
|
404 // Don't run if the post type supports neither 'editor' (textarea#content) nor 'excerpt'. |
|
405 if ( checkStorage() && blog_id && ( $('#content').length || $('#excerpt').length ) ) { |
|
406 $document.ready( run ); |
|
407 } |
|
408 |
|
409 return { |
|
410 hasStorage: hasStorage, |
|
411 getSavedPostData: getSavedPostData, |
|
412 save: save, |
|
413 suspend: suspend, |
|
414 resume: resume |
|
415 }; |
|
416 } |
|
417 |
|
418 // Autosave on the server |
|
419 function autosaveServer() { |
|
420 var _blockSave, _blockSaveTimer, previousCompareString, lastCompareString, |
|
421 nextRun = 0, |
|
422 isSuspended = false; |
|
423 |
|
424 // Block saving for the next 10 sec. |
|
425 function tempBlockSave() { |
|
426 _blockSave = true; |
|
427 window.clearTimeout( _blockSaveTimer ); |
|
428 |
|
429 _blockSaveTimer = window.setTimeout( function() { |
|
430 _blockSave = false; |
|
431 }, 10000 ); |
|
432 } |
|
433 |
|
434 function suspend() { |
|
435 isSuspended = true; |
|
436 } |
|
437 |
|
438 function resume() { |
|
439 isSuspended = false; |
|
440 } |
|
441 |
|
442 // Runs on heartbeat-response |
|
443 function response( data ) { |
|
444 _schedule(); |
|
445 _blockSave = false; |
|
446 lastCompareString = previousCompareString; |
|
447 previousCompareString = ''; |
|
448 |
|
449 $document.trigger( 'after-autosave', [data] ); |
|
450 enableButtons(); |
|
451 |
|
452 if ( data.success ) { |
|
453 // No longer an auto-draft |
|
454 $( '#auto_draft' ).val(''); |
|
455 } |
|
456 } |
|
457 |
|
458 /** |
|
459 * Save immediately |
|
460 * |
|
461 * Resets the timing and tells heartbeat to connect now |
|
462 * |
|
463 * @return void |
|
464 */ |
|
465 function triggerSave() { |
|
466 nextRun = 0; |
|
467 wp.heartbeat.connectNow(); |
|
468 } |
|
469 |
|
470 /** |
|
471 * Checks if the post content in the textarea has changed since page load. |
|
472 * |
|
473 * This also happens when TinyMCE is active and editor.save() is triggered by |
|
474 * wp.autosave.getPostData(). |
|
475 * |
|
476 * @return bool |
|
477 */ |
|
478 function postChanged() { |
|
479 return getCompareString() !== initialCompareString; |
|
480 } |
|
481 |
|
482 // Runs on 'heartbeat-send' |
|
483 function save() { |
|
484 var postData, compareString; |
|
485 |
|
486 // window.autosave() used for back-compat |
|
487 if ( isSuspended || _blockSave || ! window.autosave() ) { |
|
488 return false; |
|
489 } |
|
490 |
|
491 if ( ( new Date() ).getTime() < nextRun ) { |
|
492 return false; |
|
493 } |
|
494 |
|
495 postData = getPostData(); |
|
496 compareString = getCompareString( postData ); |
|
497 |
|
498 // First check |
|
499 if ( typeof lastCompareString === 'undefined' ) { |
|
500 lastCompareString = initialCompareString; |
|
501 } |
|
502 |
|
503 // No change |
|
504 if ( compareString === lastCompareString ) { |
|
505 return false; |
|
506 } |
|
507 |
|
508 previousCompareString = compareString; |
|
509 tempBlockSave(); |
|
510 disableButtons(); |
|
511 |
|
512 $document.trigger( 'wpcountwords', [ postData.content ] ) |
|
513 .trigger( 'before-autosave', [ postData ] ); |
|
514 |
|
515 postData._wpnonce = $( '#_wpnonce' ).val() || ''; |
|
516 |
|
517 return postData; |
|
518 } |
|
519 |
|
520 function _schedule() { |
|
521 nextRun = ( new Date() ).getTime() + ( autosaveL10n.autosaveInterval * 1000 ) || 60000; |
|
522 } |
|
523 |
|
524 $document.on( 'heartbeat-send.autosave', function( event, data ) { |
|
525 var autosaveData = save(); |
|
526 |
|
527 if ( autosaveData ) { |
|
528 data.wp_autosave = autosaveData; |
|
529 } |
|
530 }).on( 'heartbeat-tick.autosave', function( event, data ) { |
|
531 if ( data.wp_autosave ) { |
|
532 response( data.wp_autosave ); |
|
533 } |
|
534 }).on( 'heartbeat-connection-lost.autosave', function( event, error, status ) { |
|
535 // When connection is lost, keep user from submitting changes. |
|
536 if ( 'timeout' === error || 603 === status ) { |
|
537 var $notice = $('#lost-connection-notice'); |
|
538 |
|
539 if ( ! wp.autosave.local.hasStorage ) { |
|
540 $notice.find('.hide-if-no-sessionstorage').hide(); |
|
541 } |
|
542 |
|
543 $notice.show(); |
|
544 disableButtons(); |
|
545 } |
|
546 }).on( 'heartbeat-connection-restored.autosave', function() { |
|
547 $('#lost-connection-notice').hide(); |
|
548 enableButtons(); |
|
549 }).ready( function() { |
|
550 _schedule(); |
|
551 }); |
|
552 |
|
553 return { |
|
554 tempBlockSave: tempBlockSave, |
|
555 triggerSave: triggerSave, |
|
556 postChanged: postChanged, |
|
557 suspend: suspend, |
|
558 resume: resume |
|
559 }; |
|
560 } |
|
561 |
|
562 // Wait for TinyMCE to initialize plus 1 sec. for any external css to finish loading, |
|
563 // then 'save' to the textarea before setting initialCompareString. |
|
564 // This avoids any insignificant differences between the initial textarea content and the content |
|
565 // extracted from the editor. |
|
566 $document.on( 'tinymce-editor-init.autosave', function( event, editor ) { |
|
567 if ( editor.id === 'content' ) { |
|
568 window.setTimeout( function() { |
|
569 editor.save(); |
|
570 initialCompareString = getCompareString(); |
|
571 }, 1000 ); |
|
572 } |
|
573 }).ready( function() { |
|
574 // Set the initial compare string in case TinyMCE is not used or not loaded first |
|
575 initialCompareString = getCompareString(); |
|
576 }); |
|
577 |
|
578 return { |
|
579 getPostData: getPostData, |
|
580 getCompareString: getCompareString, |
|
581 disableButtons: disableButtons, |
|
582 enableButtons: enableButtons, |
|
583 local: autosaveLocal(), |
|
584 server: autosaveServer() |
|
585 }; |
239 } |
586 } |
240 } |
587 |
241 |
588 window.wp = window.wp || {}; |
242 function autosave_loading() { |
589 window.wp.autosave = autosave(); |
243 jQuery('.autosave-message').html(autosaveL10n.savingText); |
590 |
244 } |
591 }( jQuery, window )); |
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)); |
|