| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 /** 2 * @output wp-includes/js/autosave.js 3 */ 4 5 /* global tinymce, wpCookies, autosaveL10n, switchEditors */ 6 // Back-compat. 7 window.autosave = function() { 8 return true; 9 }; 10 11 /** 12 * Adds autosave to the window object on dom ready. 13 * 14 * @since 3.9.0 15 * 16 * @param {JQueryStatic} $ The jQuery object. 17 * @param {Object} window The window object. 18 * 19 */ 20 ( function( $, window ) { 21 /** 22 * Auto saves the post. 23 * 24 * @since 3.9.0 25 * 26 * @return {Object} 27 * {{ 28 * getPostData: getPostData, 29 * getCompareString: getCompareString, 30 * disableButtons: disableButtons, 31 * enableButtons: enableButtons, 32 * local: ({hasStorage, getSavedPostData, save, suspend, resume}|*), 33 * server: ({tempBlockSave, triggerSave, postChanged, suspend, resume}|*) 34 * }} 35 * The object with all functions for autosave. 36 */ 37 function autosave() { 38 var initialCompareString, 39 initialCompareData = {}, 40 lastTriggerSave = 0, 41 $document = $( document ); 42 43 /** 44 * Sets the initial compare data. 45 * 46 * @since 5.6.1 47 */ 48 function setInitialCompare() { 49 initialCompareData = { 50 post_title: $( '#title' ).val() || '', 51 content: $( '#content' ).val() || '', 52 excerpt: $( '#excerpt' ).val() || '' 53 }; 54 55 initialCompareString = getCompareString( initialCompareData ); 56 } 57 58 /** 59 * Returns the data saved in both local and remote autosave. 60 * 61 * @since 3.9.0 62 * 63 * @param {string} type The type of autosave either local or remote. 64 * 65 * @return {Object} Object containing the post data. 66 */ 67 function getPostData( type ) { 68 var post_name, parent_id, data, 69 time = ( new Date() ).getTime(), 70 cats = [], 71 editor = getEditor(); 72 73 // Don't run editor.save() more often than every 3 seconds. 74 // It is resource intensive and might slow down typing in long posts on slow devices. 75 if ( editor && editor.isDirty() && ! editor.isHidden() && time - 3000 > lastTriggerSave ) { 76 editor.save(); 77 lastTriggerSave = time; 78 } 79 80 data = { 81 post_id: $( '#post_ID' ).val() || 0, 82 post_type: $( '#post_type' ).val() || '', 83 post_author: $( '#post_author' ).val() || '', 84 post_title: $( '#title' ).val() || '', 85 content: $( '#content' ).val() || '', 86 excerpt: $( '#excerpt' ).val() || '' 87 }; 88 89 if ( type === 'local' ) { 90 return data; 91 } 92 93 $( 'input[id^="in-category-"]:checked' ).each( function() { 94 cats.push( this.value ); 95 }); 96 data.catslist = cats.join(','); 97 98 if ( post_name = $( '#post_name' ).val() ) { 99 data.post_name = post_name; 100 } 101 102 if ( parent_id = $( '#parent_id' ).val() ) { 103 data.parent_id = parent_id; 104 } 105 106 if ( $( '#comment_status' ).prop( 'checked' ) ) { 107 data.comment_status = 'open'; 108 } 109 110 if ( $( '#ping_status' ).prop( 'checked' ) ) { 111 data.ping_status = 'open'; 112 } 113 114 if ( $( '#auto_draft' ).val() === '1' ) { 115 data.auto_draft = '1'; 116 } 117 118 return data; 119 } 120 121 /** 122 * Concatenates the title, content and excerpt. This is used to track changes 123 * when auto-saving. 124 * 125 * @since 3.9.0 126 * 127 * @param {Object} postData The object containing the post data. 128 * 129 * @return {string} A concatenated string with title, content and excerpt. 130 */ 131 function getCompareString( postData ) { 132 if ( typeof postData === 'object' ) { 133 return ( postData.post_title || '' ) + '::' + ( postData.content || '' ) + '::' + ( postData.excerpt || '' ); 134 } 135 136 return ( $('#title').val() || '' ) + '::' + ( $('#content').val() || '' ) + '::' + ( $('#excerpt').val() || '' ); 137 } 138 139 /** 140 * Disables save buttons. 141 * 142 * @since 3.9.0 143 * 144 * @return {void} 145 */ 146 function disableButtons() { 147 $document.trigger('autosave-disable-buttons'); 148 149 // Re-enable 5 sec later. Just gives autosave a head start to avoid collisions. 150 setTimeout( enableButtons, 5000 ); 151 } 152 153 /** 154 * Enables save buttons. 155 * 156 * @since 3.9.0 157 * 158 * @return {void} 159 */ 160 function enableButtons() { 161 $document.trigger( 'autosave-enable-buttons' ); 162 } 163 164 /** 165 * Gets the content editor. 166 * 167 * @since 4.6.0 168 * 169 * @return {boolean|*} Returns either false if the editor is undefined, 170 * or the instance of the content editor. 171 */ 172 function getEditor() { 173 return typeof tinymce !== 'undefined' && tinymce.get('content'); 174 } 175 176 /** 177 * Autosave in localStorage. 178 * 179 * @since 3.9.0 180 * 181 * @return { 182 * { 183 * hasStorage: *, 184 * getSavedPostData: getSavedPostData, 185 * save: save, 186 * suspend: suspend, 187 * resume: resume 188 * } 189 * } 190 * The object with all functions for local storage autosave. 191 */ 192 function autosaveLocal() { 193 var blog_id, post_id, hasStorage, intervalTimer, 194 lastCompareString, 195 isSuspended = false; 196 197 /** 198 * Checks if the browser supports sessionStorage and it's not disabled. 199 * 200 * @since 3.9.0 201 * 202 * @return {boolean} True if the sessionStorage is supported and enabled. 203 */ 204 function checkStorage() { 205 var test = Math.random().toString(), 206 result = false; 207 208 try { 209 window.sessionStorage.setItem( 'wp-test', test ); 210 result = window.sessionStorage.getItem( 'wp-test' ) === test; 211 window.sessionStorage.removeItem( 'wp-test' ); 212 } catch(e) {} 213 214 hasStorage = result; 215 return result; 216 } 217 218 /** 219 * Initializes the local storage. 220 * 221 * @since 3.9.0 222 * 223 * @return {boolean|Object} False if no sessionStorage in the browser or an Object 224 * containing all postData for this blog. 225 */ 226 function getStorage() { 227 var stored_obj = false; 228 // Separate local storage containers for each blog_id. 229 if ( hasStorage && blog_id ) { 230 stored_obj = sessionStorage.getItem( 'wp-autosave-' + blog_id ); 231 232 if ( stored_obj ) { 233 stored_obj = JSON.parse( stored_obj ); 234 } else { 235 stored_obj = {}; 236 } 237 } 238 239 return stored_obj; 240 } 241 242 /** 243 * Sets the storage for this blog. Confirms that the data was saved 244 * successfully. 245 * 246 * @param {Object} stored_obj The storage object to set. 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 ));
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Mon Sep 7 08:20:28 2026 | Cross-referenced by PHPXref |