[ Index ] |
PHP Cross Reference of WordPress |
[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 {jQuery} $ jQuery object. 17 * @param {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 * @since 3.9.0 247 * 248 * @return {boolean} True if the data was saved successfully, false if it wasn't saved. 249 */ 250 function setStorage( stored_obj ) { 251 var key; 252 253 if ( hasStorage && blog_id ) { 254 key = 'wp-autosave-' + blog_id; 255 sessionStorage.setItem( key, JSON.stringify( stored_obj ) ); 256 return sessionStorage.getItem( key ) !== null; 257 } 258 259 return false; 260 } 261 262 /** 263 * Gets the saved post data for the current post. 264 * 265 * @since 3.9.0 266 * 267 * @return {boolean|Object} False if no storage or no data or the postData as an Object. 268 */ 269 function getSavedPostData() { 270 var stored = getStorage(); 271 272 if ( ! stored || ! post_id ) { 273 return false; 274 } 275 276 return stored[ 'post_' + post_id ] || false; 277 } 278 279 /** 280 * Sets (save or delete) post data in the storage. 281 * 282 * If stored_data evaluates to 'false' the storage key for the current post will be removed. 283 * 284 * @since 3.9.0 285 * 286 * @param {Object|boolean|null} stored_data The post data to store or null/false/empty to delete the key. 287 * 288 * @return {boolean} True if data is stored, false if data was removed. 289 */ 290 function setData( stored_data ) { 291 var stored = getStorage(); 292 293 if ( ! stored || ! post_id ) { 294 return false; 295 } 296 297 if ( stored_data ) { 298 stored[ 'post_' + post_id ] = stored_data; 299 } else if ( stored.hasOwnProperty( 'post_' + post_id ) ) { 300 delete stored[ 'post_' + post_id ]; 301 } else { 302 return false; 303 } 304 305 return setStorage( stored ); 306 } 307 308 /** 309 * Sets isSuspended to true. 310 * 311 * @since 3.9.0 312 * 313 * @return {void} 314 */ 315 function suspend() { 316 isSuspended = true; 317 } 318 319 /** 320 * Sets isSuspended to false. 321 * 322 * @since 3.9.0 323 * 324 * @return {void} 325 */ 326 function resume() { 327 isSuspended = false; 328 } 329 330 /** 331 * Saves post data for the current post. 332 * 333 * Runs on a 15 seconds interval, saves when there are differences in the post title or content. 334 * When the optional data is provided, updates the last saved post data. 335 * 336 * @since 3.9.0 337 * 338 * @param {Object} data The post data for saving, minimum 'post_title' and 'content'. 339 * 340 * @return {boolean} Returns true when data has been saved, otherwise it returns false. 341 */ 342 function save( data ) { 343 var postData, compareString, 344 result = false; 345 346 if ( isSuspended || ! hasStorage ) { 347 return false; 348 } 349 350 if ( data ) { 351 postData = getSavedPostData() || {}; 352 $.extend( postData, data ); 353 } else { 354 postData = getPostData('local'); 355 } 356 357 compareString = getCompareString( postData ); 358 359 if ( typeof lastCompareString === 'undefined' ) { 360 lastCompareString = initialCompareString; 361 } 362 363 // If the content, title and excerpt did not change since the last save, don't save again. 364 if ( compareString === lastCompareString ) { 365 return false; 366 } 367 368 postData.save_time = ( new Date() ).getTime(); 369 postData.status = $( '#post_status' ).val() || ''; 370 result = setData( postData ); 371 372 if ( result ) { 373 lastCompareString = compareString; 374 } 375 376 return result; 377 } 378 379 /** 380 * Initializes the auto save function. 381 * 382 * Checks whether the editor is active or not to use the editor events 383 * to autosave, or uses the values from the elements to autosave. 384 * 385 * Runs on DOM ready. 386 * 387 * @since 3.9.0 388 * 389 * @return {void} 390 */ 391 function run() { 392 post_id = $('#post_ID').val() || 0; 393 394 // Check if the local post data is different than the loaded post data. 395 if ( $( '#wp-content-wrap' ).hasClass( 'tmce-active' ) ) { 396 397 /* 398 * If TinyMCE loads first, check the post 1.5 seconds after it is ready. 399 * By this time the content has been loaded in the editor and 'saved' to the textarea. 400 * This prevents false positives. 401 */ 402 $document.on( 'tinymce-editor-init.autosave', function() { 403 window.setTimeout( function() { 404 checkPost(); 405 }, 1500 ); 406 }); 407 } else { 408 checkPost(); 409 } 410 411 // Save every 15 seconds. 412 intervalTimer = window.setInterval( save, 15000 ); 413 414 $( 'form#post' ).on( 'submit.autosave-local', function() { 415 var editor = getEditor(), 416 post_id = $('#post_ID').val() || 0; 417 418 if ( editor && ! editor.isHidden() ) { 419 420 // Last onSubmit event in the editor, needs to run after the content has been moved to the textarea. 421 editor.on( 'submit', function() { 422 save({ 423 post_title: $( '#title' ).val() || '', 424 content: $( '#content' ).val() || '', 425 excerpt: $( '#excerpt' ).val() || '' 426 }); 427 }); 428 } else { 429 save({ 430 post_title: $( '#title' ).val() || '', 431 content: $( '#content' ).val() || '', 432 excerpt: $( '#excerpt' ).val() || '' 433 }); 434 } 435 436 var secure = ( 'https:' === window.location.protocol ); 437 wpCookies.set( 'wp-saving-post', post_id + '-check', 24 * 60 * 60, false, false, secure ); 438 }); 439 } 440 441 /** 442 * Compares 2 strings. Removes whitespaces in the strings before comparing them. 443 * 444 * @since 3.9.0 445 * 446 * @param {string} str1 The first string. 447 * @param {string} str2 The second string. 448 * @return {boolean} True if the strings are the same. 449 */ 450 function compare( str1, str2 ) { 451 function removeSpaces( string ) { 452 return string.toString().replace(/[\x20\t\r\n\f]+/g, ''); 453 } 454 455 return ( removeSpaces( str1 || '' ) === removeSpaces( str2 || '' ) ); 456 } 457 458 /** 459 * Checks if the saved data for the current post (if any) is different than the 460 * loaded post data on the screen. 461 * 462 * Shows a standard message letting the user restore the post data if different. 463 * 464 * @since 3.9.0 465 * 466 * @return {void} 467 */ 468 function checkPost() { 469 var content, post_title, excerpt, $notice, 470 postData = getSavedPostData(), 471 cookie = wpCookies.get( 'wp-saving-post' ), 472 $newerAutosaveNotice = $( '#has-newer-autosave' ).parent( '.notice' ), 473 $headerEnd = $( '.wp-header-end' ); 474 475 if ( cookie === post_id + '-saved' ) { 476 wpCookies.remove( 'wp-saving-post' ); 477 // The post was saved properly, remove old data and bail. 478 setData( false ); 479 return; 480 } 481 482 if ( ! postData ) { 483 return; 484 } 485 486 content = $( '#content' ).val() || ''; 487 post_title = $( '#title' ).val() || ''; 488 excerpt = $( '#excerpt' ).val() || ''; 489 490 if ( compare( content, postData.content ) && compare( post_title, postData.post_title ) && 491 compare( excerpt, postData.excerpt ) ) { 492 493 return; 494 } 495 496 /* 497 * If '.wp-header-end' is found, append the notices after it otherwise 498 * after the first h1 or h2 heading found within the main content. 499 */ 500 if ( ! $headerEnd.length ) { 501 $headerEnd = $( '.wrap h1, .wrap h2' ).first(); 502 } 503 504 $notice = $( '#local-storage-notice' ) 505 .insertAfter( $headerEnd ) 506 .addClass( 'notice-warning' ); 507 508 if ( $newerAutosaveNotice.length ) { 509 510 // If there is a "server" autosave notice, hide it. 511 // The data in the session storage is either the same or newer. 512 $newerAutosaveNotice.slideUp( 150, function() { 513 $notice.slideDown( 150 ); 514 }); 515 } else { 516 $notice.slideDown( 200 ); 517 } 518 519 $notice.find( '.restore-backup' ).on( 'click.autosave-local', function() { 520 restorePost( postData ); 521 $notice.fadeTo( 250, 0, function() { 522 $notice.slideUp( 150 ); 523 }); 524 }); 525 } 526 527 /** 528 * Restores the current title, content and excerpt from postData. 529 * 530 * @since 3.9.0 531 * 532 * @param {Object} postData The object containing all post data. 533 * 534 * @return {boolean} True if the post is restored. 535 */ 536 function restorePost( postData ) { 537 var editor; 538 539 if ( postData ) { 540 // Set the last saved data. 541 lastCompareString = getCompareString( postData ); 542 543 if ( $( '#title' ).val() !== postData.post_title ) { 544 $( '#title' ).trigger( 'focus' ).val( postData.post_title || '' ); 545 } 546 547 $( '#excerpt' ).val( postData.excerpt || '' ); 548 editor = getEditor(); 549 550 if ( editor && ! editor.isHidden() && typeof switchEditors !== 'undefined' ) { 551 if ( editor.settings.wpautop && postData.content ) { 552 postData.content = switchEditors.wpautop( postData.content ); 553 } 554 555 // Make sure there's an undo level in the editor. 556 editor.undoManager.transact( function() { 557 editor.setContent( postData.content || '' ); 558 editor.nodeChanged(); 559 }); 560 } else { 561 562 // Make sure the Text editor is selected. 563 $( '#content-html' ).trigger( 'click' ); 564 $( '#content' ).trigger( 'focus' ); 565 566 // Using document.execCommand() will let the user undo. 567 document.execCommand( 'selectAll' ); 568 document.execCommand( 'insertText', false, postData.content || '' ); 569 } 570 571 return true; 572 } 573 574 return false; 575 } 576 577 blog_id = typeof window.autosaveL10n !== 'undefined' && window.autosaveL10n.blog_id; 578 579 /* 580 * Check if the browser supports sessionStorage and it's not disabled, 581 * then initialize and run checkPost(). 582 * Don't run if the post type supports neither 'editor' (textarea#content) nor 'excerpt'. 583 */ 584 if ( checkStorage() && blog_id && ( $('#content').length || $('#excerpt').length ) ) { 585 $( run ); 586 } 587 588 return { 589 hasStorage: hasStorage, 590 getSavedPostData: getSavedPostData, 591 save: save, 592 suspend: suspend, 593 resume: resume 594 }; 595 } 596 597 /** 598 * Auto saves the post on the server. 599 * 600 * @since 3.9.0 601 * 602 * @return {Object} { 603 * { 604 * tempBlockSave: tempBlockSave, 605 * triggerSave: triggerSave, 606 * postChanged: postChanged, 607 * suspend: suspend, 608 * resume: resume 609 * } 610 * } The object all functions for autosave. 611 */ 612 function autosaveServer() { 613 var _blockSave, _blockSaveTimer, previousCompareString, lastCompareString, 614 nextRun = 0, 615 isSuspended = false; 616 617 618 /** 619 * Blocks saving for the next 10 seconds. 620 * 621 * @since 3.9.0 622 * 623 * @return {void} 624 */ 625 function tempBlockSave() { 626 _blockSave = true; 627 window.clearTimeout( _blockSaveTimer ); 628 629 _blockSaveTimer = window.setTimeout( function() { 630 _blockSave = false; 631 }, 10000 ); 632 } 633 634 /** 635 * Sets isSuspended to true. 636 * 637 * @since 3.9.0 638 * 639 * @return {void} 640 */ 641 function suspend() { 642 isSuspended = true; 643 } 644 645 /** 646 * Sets isSuspended to false. 647 * 648 * @since 3.9.0 649 * 650 * @return {void} 651 */ 652 function resume() { 653 isSuspended = false; 654 } 655 656 /** 657 * Triggers the autosave with the post data. 658 * 659 * @since 3.9.0 660 * 661 * @param {Object} data The post data. 662 * 663 * @return {void} 664 */ 665 function response( data ) { 666 _schedule(); 667 _blockSave = false; 668 lastCompareString = previousCompareString; 669 previousCompareString = ''; 670 671 $document.trigger( 'after-autosave', [data] ); 672 enableButtons(); 673 674 if ( data.success ) { 675 // No longer an auto-draft. 676 $( '#auto_draft' ).val(''); 677 } 678 } 679 680 /** 681 * Saves immediately. 682 * 683 * Resets the timing and tells heartbeat to connect now. 684 * 685 * @since 3.9.0 686 * 687 * @return {void} 688 */ 689 function triggerSave() { 690 nextRun = 0; 691 wp.heartbeat.connectNow(); 692 } 693 694 /** 695 * Checks if the post content in the textarea has changed since page load. 696 * 697 * This also happens when TinyMCE is active and editor.save() is triggered by 698 * wp.autosave.getPostData(). 699 * 700 * @since 3.9.0 701 * 702 * @return {boolean} True if the post has been changed. 703 */ 704 function postChanged() { 705 var changed = false; 706 707 // If there are TinyMCE instances, loop through them. 708 if ( window.tinymce ) { 709 window.tinymce.each( [ 'content', 'excerpt' ], function( field ) { 710 var editor = window.tinymce.get( field ); 711 712 if ( ! editor || editor.isHidden() ) { 713 if ( ( $( '#' + field ).val() || '' ) !== initialCompareData[ field ] ) { 714 changed = true; 715 // Break. 716 return false; 717 } 718 } else if ( editor.isDirty() ) { 719 changed = true; 720 return false; 721 } 722 } ); 723 724 if ( ( $( '#title' ).val() || '' ) !== initialCompareData.post_title ) { 725 changed = true; 726 } 727 728 return changed; 729 } 730 731 return getCompareString() !== initialCompareString; 732 } 733 734 /** 735 * Checks if the post can be saved or not. 736 * 737 * If the post hasn't changed or it cannot be updated, 738 * because the autosave is blocked or suspended, the function returns false. 739 * 740 * @since 3.9.0 741 * 742 * @return {Object} Returns the post data. 743 */ 744 function save() { 745 var postData, compareString; 746 747 // window.autosave() used for back-compat. 748 if ( isSuspended || _blockSave || ! window.autosave() ) { 749 return false; 750 } 751 752 if ( ( new Date() ).getTime() < nextRun ) { 753 return false; 754 } 755 756 postData = getPostData(); 757 compareString = getCompareString( postData ); 758 759 // First check. 760 if ( typeof lastCompareString === 'undefined' ) { 761 lastCompareString = initialCompareString; 762 } 763 764 // No change. 765 if ( compareString === lastCompareString ) { 766 return false; 767 } 768 769 previousCompareString = compareString; 770 tempBlockSave(); 771 disableButtons(); 772 773 $document.trigger( 'wpcountwords', [ postData.content ] ) 774 .trigger( 'before-autosave', [ postData ] ); 775 776 postData._wpnonce = $( '#_wpnonce' ).val() || ''; 777 778 return postData; 779 } 780 781 /** 782 * Sets the next run, based on the autosave interval. 783 * 784 * @private 785 * 786 * @since 3.9.0 787 * 788 * @return {void} 789 */ 790 function _schedule() { 791 nextRun = ( new Date() ).getTime() + ( autosaveL10n.autosaveInterval * 1000 ) || 60000; 792 } 793 794 /** 795 * Sets the autosaveData on the autosave heartbeat. 796 * 797 * @since 3.9.0 798 * 799 * @return {void} 800 */ 801 $( function() { 802 _schedule(); 803 }).on( 'heartbeat-send.autosave', function( event, data ) { 804 var autosaveData = save(); 805 806 if ( autosaveData ) { 807 data.wp_autosave = autosaveData; 808 } 809 810 /** 811 * Triggers the autosave of the post with the autosave data on the autosave 812 * heartbeat. 813 * 814 * @since 3.9.0 815 * 816 * @return {void} 817 */ 818 }).on( 'heartbeat-tick.autosave', function( event, data ) { 819 if ( data.wp_autosave ) { 820 response( data.wp_autosave ); 821 } 822 /** 823 * Disables buttons and throws a notice when the connection is lost. 824 * 825 * @since 3.9.0 826 * 827 * @return {void} 828 */ 829 }).on( 'heartbeat-connection-lost.autosave', function( event, error, status ) { 830 831 // When connection is lost, keep user from submitting changes. 832 if ( 'timeout' === error || 603 === status ) { 833 var $notice = $('#lost-connection-notice'); 834 835 if ( ! wp.autosave.local.hasStorage ) { 836 $notice.find('.hide-if-no-sessionstorage').hide(); 837 } 838 839 $notice.show(); 840 disableButtons(); 841 } 842 843 /** 844 * Enables buttons when the connection is restored. 845 * 846 * @since 3.9.0 847 * 848 * @return {void} 849 */ 850 }).on( 'heartbeat-connection-restored.autosave', function() { 851 $('#lost-connection-notice').hide(); 852 enableButtons(); 853 }); 854 855 return { 856 tempBlockSave: tempBlockSave, 857 triggerSave: triggerSave, 858 postChanged: postChanged, 859 suspend: suspend, 860 resume: resume 861 }; 862 } 863 864 /** 865 * Sets the autosave time out. 866 * 867 * Wait for TinyMCE to initialize plus 1 second. for any external css to finish loading, 868 * then save to the textarea before setting initialCompareString. 869 * This avoids any insignificant differences between the initial textarea content and the content 870 * extracted from the editor. 871 * 872 * @since 3.9.0 873 * 874 * @return {void} 875 */ 876 $( function() { 877 // Set the initial compare string in case TinyMCE is not used or not loaded first. 878 setInitialCompare(); 879 }).on( 'tinymce-editor-init.autosave', function( event, editor ) { 880 // Reset the initialCompare data after the TinyMCE instances have been initialized. 881 if ( 'content' === editor.id || 'excerpt' === editor.id ) { 882 window.setTimeout( function() { 883 editor.save(); 884 setInitialCompare(); 885 }, 1000 ); 886 } 887 }); 888 889 return { 890 getPostData: getPostData, 891 getCompareString: getCompareString, 892 disableButtons: disableButtons, 893 enableButtons: enableButtons, 894 local: autosaveLocal(), 895 server: autosaveServer() 896 }; 897 } 898 899 /** @namespace wp */ 900 window.wp = window.wp || {}; 901 window.wp.autosave = autosave(); 902 903 }( jQuery, window ));
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Dec 25 01:00:02 2024 | Cross-referenced by PHPXref 0.7.1 |