Ticket #38342: 38342.6.diff
File 38342.6.diff, 39.2 KB (added by , 8 years ago) |
---|
-
src/wp-admin/css/dashboard.css
diff --git src/wp-admin/css/dashboard.css src/wp-admin/css/dashboard.css index d467f0c..80f0292 100644
form.initial-form.quickpress-open input#title { 518 518 resize: none; 519 519 } 520 520 521 #quick-press.is-saving .spinner { 522 visibility: inherit; 523 } 524 521 525 /* Dashboard Quick Draft - Drafts list */ 522 526 523 527 .js #dashboard_quick_press .drafts { … … form.initial-form.quickpress-open input#title { 541 545 margin: 0 12px; 542 546 } 543 547 548 #dashboard_quick_press .drafts ul.is-placeholder li { 549 padding: 3px 0; 550 color: transparent; 551 } 552 553 @-webkit-keyframes loading-fade { 554 555 0% { 556 opacity: 0.5; 557 } 558 559 50% { 560 opacity: 1; 561 } 562 563 100% { 564 opacity: 0.5; 565 } 566 } 567 568 @keyframes loading-fade { 569 570 0% { 571 opacity: 0.5; 572 } 573 574 50% { 575 opacity: 1; 576 } 577 578 100% { 579 opacity: 0.5; 580 } 581 } 582 583 #dashboard_quick_press .drafts ul.is-placeholder li:before, 584 #dashboard_quick_press .drafts ul.is-placeholder li:after { 585 content: ""; 586 display: block; 587 height: 13px; 588 background: #eee; 589 -webkit-animation: loading-fade 1.6s ease-in-out infinite; 590 animation: loading-fade 1.6s ease-in-out infinite; 591 } 592 593 #dashboard_quick_press .drafts ul.is-placeholder li:before { 594 margin-bottom: 5px; 595 width: 40%; 596 } 597 598 #dashboard_quick_press .drafts ul.is-placeholder li:after { 599 width: 80%; 600 } 601 544 602 #dashboard_quick_press .drafts li { 545 603 margin-bottom: 1em; 546 604 } 605 606 #dashboard_quick_press .drafts li.is-new { 607 background-color: #fffbe5; 608 } 609 547 610 #dashboard_quick_press .drafts li time { 548 611 color: #72777c; 549 612 } -
src/wp-admin/includes/dashboard.php
diff --git src/wp-admin/includes/dashboard.php src/wp-admin/includes/dashboard.php index 40035f8..7c0f4b3 100644
function wp_dashboard_quick_press( $error_msg = false ) { 494 494 $post_ID = (int) $post->ID; 495 495 ?> 496 496 497 <form name="post" action="<?php echo esc_url( admin_url( 'post.php' ) ); ?>" method="post" id="quick-press" class="initial-form hide-if-no-js"> 498 499 <?php if ( $error_msg ) : ?> 500 <div class="error"><?php echo $error_msg; ?></div> 501 <?php endif; ?> 502 497 <form name="post" method="post" id="quick-press" class="initial-form hide-if-no-js"> 498 <div class="notice notice-error notice-alt inline hidden"><p></p></div> 503 499 <div class="input-text-wrap" id="title-wrap"> 504 <label class=" screen-reader-textprompt" for="title" id="title-prompt-text">500 <label class="prompt" for="title" id="title-prompt-text"> 505 501 506 502 <?php 507 503 /** This filter is documented in wp-admin/edit-form-advanced.php */ 508 504 echo apply_filters( 'enter_title_here', __( 'Title' ), $post ); 509 505 ?> 510 506 </label> 511 <input type="text" name=" post_title" id="title" autocomplete="off" />507 <input type="text" name="title" id="title" autocomplete="off" /> 512 508 </div> 513 509 514 510 <div class="textarea-wrap" id="description-wrap"> 515 <label class=" screen-reader-textprompt" for="content" id="content-prompt-text"><?php _e( 'What’s on your mind?' ); ?></label>511 <label class="prompt" for="content" id="content-prompt-text"><?php _e( 'What’s on your mind?' ); ?></label> 516 512 <textarea name="content" id="content" class="mceEditor" rows="3" cols="15" autocomplete="off"></textarea> 517 513 </div> 518 519 514 <p class="submit"> 520 <input type="hidden" name="action" id="quickpost-action" value="post-quickdraft-save" /> 521 <input type="hidden" name="post_ID" value="<?php echo $post_ID; ?>" /> 522 <input type="hidden" name="post_type" value="post" /> 523 <?php wp_nonce_field( 'add-post' ); ?> 515 <div class="spinner no-float"></div> 524 516 <?php submit_button( __( 'Save Draft' ), 'primary', 'save', false, array( 'id' => 'save-post' ) ); ?> 525 517 <br class="clear" /> 526 518 </p> 527 519 528 520 </form> 521 <div id="quick-press-drafts" class="drafts"> 522 <p class="view-all" style="display: none;"> 523 <a href="<?php echo esc_url( admin_url( 'edit.php?post_status=draft' ) ) ?>" aria-label="<?php esc_attr_e( 'View all drafts' ) ?>"><?php _ex( 'View all', 'drafts' ) ?></a> 524 </p> 525 <h2 class="hide-if-no-js"><?php _e( 'Drafts' ) ?></h2> 526 <ul class="drafts-list is-placeholder"> 527 <li><span class="screen-reader-text"><?php _e( 'Loading…' ) ?></span></li> 528 </ul> 529 </div> 530 <script id="tmpl-item-quick-press-draft" type="text/template"> 531 <div class="draft-title"> 532 <a href="<?php echo ( esc_url( admin_url( 'post.php?post={{ data.id }}&action=edit' ) ) ); ?>" aria-label="<?php esc_attr_e( 'Edit Post' ) ?>">{{ data.formattedTitle }}</a> 533 <time datetime="{{ data.date }}">{{ data.formattedDate }}</time> 534 </div> 535 {{{ data.formattedContent }}} 536 </script> 529 537 <?php 530 wp_dashboard_recent_drafts();531 538 } 532 539 533 540 /** 534 541 * Show recent drafts of the user on the dashboard. 535 542 * 536 543 * @since 2.7.0 544 * @deprecated 4.8.0 537 545 * 538 546 * @param array $drafts 539 547 */ … … function wp_dashboard_recent_drafts( $drafts = false ) { 548 556 'order' => 'DESC' 549 557 ); 550 558 551 /** 552 * Filters the post query arguments for the 'Recent Drafts' dashboard widget. 553 * 554 * @since 4.4.0 555 * 556 * @param array $query_args The query arguments for the 'Recent Drafts' dashboard widget. 557 */ 559 /** This filter is documented in wp-includes/rest-api.php */ 558 560 $query_args = apply_filters( 'dashboard_recent_drafts_query_args', $query_args ); 559 561 560 562 $drafts = get_posts( $query_args ); -
src/wp-admin/js/dashboard.js
diff --git src/wp-admin/js/dashboard.js src/wp-admin/js/dashboard.js index fa100dd..6dee942 100644
1 /* global pagenow, ajaxurl, postboxes, wpActiveEditor:true*/2 var ajaxWidgets, ajaxPopulateWidgets, quickPressLoad;1 /* global _, wp, quickDraft, pagenow, ajaxurl, postboxes */ 2 var ajaxWidgets, ajaxPopulateWidgets, QuickDraft = {}; 3 3 4 4 jQuery(document).ready( function($) { 5 5 var welcomePanel = $( '#welcome-panel' ), … … jQuery(document).ready( function($) { 59 59 }; 60 60 ajaxPopulateWidgets(); 61 61 62 postboxes.add_postbox_toggles(pagenow, { pbshow: ajaxPopulateWidgets } ); 63 64 /* QuickPress */ 65 quickPressLoad = function() { 66 var act = $('#quickpost-action'), t; 67 68 $( '#quick-press .submit input[type="submit"], #quick-press .submit input[type="reset"]' ).prop( 'disabled' , false ); 69 70 t = $('#quick-press').submit( function( e ) { 71 e.preventDefault(); 72 $('#dashboard_quick_press #publishing-action .spinner').show(); 73 $('#quick-press .submit input[type="submit"], #quick-press .submit input[type="reset"]').prop('disabled', true); 74 75 $.post( t.attr( 'action' ), t.serializeArray(), function( data ) { 76 // Replace the form, and prepend the published post. 77 $('#dashboard_quick_press .inside').html( data ); 78 $('#quick-press').removeClass('initial-form'); 79 quickPressLoad(); 80 highlightLatestPost(); 81 $('#title').focus(); 82 }); 83 84 function highlightLatestPost () { 85 var latestPost = $('.drafts ul li').first(); 86 latestPost.css('background', '#fffbe5'); 87 setTimeout(function () { 88 latestPost.css('background', 'none'); 89 }, 1000); 90 } 91 } ); 92 93 $('#publish').click( function() { act.val( 'post-quickpress-publish' ); } ); 94 95 $('#title, #tags-input, #content').each( function() { 96 var input = $(this), prompt = $('#' + this.id + '-prompt-text'); 97 98 if ( '' === this.value ) { 99 prompt.removeClass('screen-reader-text'); 100 } 101 102 prompt.click( function() { 103 $(this).addClass('screen-reader-text'); 104 input.focus(); 105 }); 62 postboxes.add_postbox_toggles(pagenow, { pbshow: function( id ) { 63 ajaxPopulateWidgets(); 106 64 107 input.blur( function() { 108 if ( '' === this.value ) { 109 prompt.removeClass('screen-reader-text'); 110 } 111 }); 112 113 input.focus( function() { 114 prompt.addClass('screen-reader-text'); 115 }); 116 }); 117 118 $('#quick-press').on( 'click focusin', function() { 119 wpActiveEditor = 'content'; 120 }); 121 122 autoResizeTextarea(); 123 }; 124 quickPressLoad(); 65 if ( 'dashboard_quick_press' === id ) { 66 QuickDraft.init(); 67 } 68 } } ); 125 69 126 70 $( '.meta-box-sortables' ).sortable( 'option', 'containment', '#wpwrap' ); 127 71 … … jQuery(document).ready( function($) { 186 130 }); 187 131 } 188 132 133 autoResizeTextarea(); 134 135 if ( jQuery( '#dashboard_quick_press' ).is( ':visible' ) ) { 136 QuickDraft.init(); 137 } 138 }); 139 140 // Set up the QuickDraft views. 141 QuickDraft.Views = {}; 142 143 /** 144 * Set up a view object for the quick draft form. 145 * 146 * @since 4.8.0 147 * 148 * @augments wp.Backbone.View 149 */ 150 QuickDraft.Views.Form = wp.Backbone.View.extend( { 151 152 // Set up our default action handlers. 153 events: { 154 155 // Hide the prompt whena field receives focus. 156 'focus :input': 'hidePrompt', 157 158 // Possibly re-display the prompt when a field looses focus. 159 'blur :input': 'showPrompt', 160 161 // Listen for a reset event, showing all prompts. 162 'reset': 'showAllPrompts', 163 164 // Listed for and handle form submissions. 165 'submit': 'handleFormSubmission' 166 }, 167 168 // Initialize the QuickDraft Form view. 169 initialize: function() { 170 171 // Show all prompts in the form to begin. 172 this.showAllPrompts(); 173 174 // Rerender if the error state changes. 175 quickDraft.state.on( 'change:errorState', _.bind( this.render, this ) ); 176 }, 177 178 /** 179 * Handle toggling of the helper prompts shown inside each field. 180 * 181 * Will only show the prompt if the field is empty. 182 * 183 * @param {object} element Field element containing the helper prompt. 184 * @param {boolean} visible Should the prompt be visible? 185 */ 186 togglePrompt: function( element, visible ) { 187 var $input = jQuery( element ), 188 hasContent = $input.val().length > 0; 189 190 // Set the visibility of the elements nearest prompt to passed 'visible' value. 191 jQuery( element ).siblings( '.prompt' ).toggleClass( 'screen-reader-text', ! visible || hasContent ); 192 }, 193 194 // Show all of the field promts. 195 showAllPrompts: function() { 196 197 // Show all of the field prompts. 198 this.$el.find( ':input' ).each( _.bind( function( i, input ) { 199 _.defer( _.bind( this.togglePrompt, this, input, true ) ); 200 }, this ) ); 201 }, 202 203 /** 204 * Show the prompt inside a field. 205 * 206 * @param {object} event The event triggering this show request. 207 */ 208 showPrompt: function( event ) { 209 this.togglePrompt( event.target, true ); 210 }, 211 212 /** 213 * Hide the prompt inside a field. 214 * 215 * @param {object} event The event triggering this hide request. 216 */ 217 hidePrompt: function( event ) { 218 this.togglePrompt( event.target, false ); 219 }, 220 221 /** 222 * Handle error conditions. 223 * 224 * {string} error The error condition, or false to reset. 225 */ 226 setErrorState: function( error ) { 227 228 // Set or reset the app state error condition. 229 quickDraft.state.set( 'errorState', error ); 230 231 if ( false !== error ) { 232 233 // Alert screen readers that an error occurred. 234 wp.a11y.speak( error, 'assertive' ); 235 } 236 237 }, 238 239 /** 240 * Handle the form submission event. 241 * 242 * @param {object} event The form submission event. 243 */ 244 handleFormSubmission: function( event ) { 245 var values, 246 hasValuesToSave = false; 247 248 // Prevent the browser's default form submission handling. 249 event.preventDefault(); 250 251 // Prevent double submissions by checking the submitting state. 252 if ( quickDraft.state.get( 'submitting' ) ) { 253 return; 254 } 255 256 // Reset the error state. 257 this.setErrorState( false ); 258 259 // Extract the form field values. 260 values = _.reduce( this.$el.serializeArray(), function( memo, field ) { 261 memo[ field.name ] = field.value; 262 hasValuesToSave = hasValuesToSave || ( '' !== field.value ); 263 return memo; 264 }, {} ); 265 266 // If the values are all blank, show an error. 267 if ( ! hasValuesToSave ) { 268 269 // Set the error. 270 this.setErrorState( quickDraft.l10n.errorEmptyFields ); 271 return; 272 } 273 274 // Save the new values to the model and confirm they are valid. 275 this.model.set( values ); 276 if ( ! this.model.isValid() ) { 277 return; 278 } 279 280 // Show a spinner during the callback. 281 this.$el.addClass( 'is-saving' ); 282 283 284 // Set the state sbmitting to avoid double saves 285 quickDraft.state.set( 'submitting', true ); 286 287 // Trigger the model save. 288 this.model.save() 289 290 // Always remove the spinner. 291 .always( 292 _.bind( function() { 293 this.$el.removeClass( 'is-saving' ); 294 295 // Submission complete 296 quickDraft.state.set( 'submitting', false ); 297 298 }, this ) 299 ) 300 301 // Handle save success. 302 .success( 303 _.bind( function() { 304 // Success! Clear any previous error state. 305 this.render(); 306 307 // Add the post model to the head of our collection. 308 this.collection.add( this.model, { at: 0 } ); 309 310 // Create a new post model to contain the form data and reset the form. 311 this.model = new wp.api.models.Post(); 312 this.model.on( 'change:errorState', _.bind( this.render, this ) ); 313 314 this.el.reset(); 315 }, this ) 316 ) 317 318 // Handle save failure. 319 .error( 320 _.bind( function( model, error ) { 321 var message = ''; 322 323 // Try to parse and use the response message. 324 try { 325 message = JSON.parse( error.responseText ).message; 326 } catch( e ) { 327 328 // Fall back to a default error string if the parse fails. 329 message = quickDraft.l10n.error; 330 } 331 332 // Set the app error condition. 333 this.setErrorState( message ); 334 }, this ) 335 ); 336 }, 337 338 // Render the form view. 339 render: function() { 340 var $error = this.$el.find( '.notice-alt' ), 341 errorText = quickDraft.state.get( 'errorState' ); 342 343 // Error notice is only visible if error text is set. 344 $error.toggleClass( 'hidden', ! errorText ); 345 if ( errorText ) { 346 347 // Note: The inner text transform prevents XSS via html(). 348 $error.html( jQuery( '<p />', { text: errorText } ) ); 349 } 350 } 351 } ); 352 353 /** 354 * Set up a view object for the Quick Draft list of drafts. 355 * 356 * @since 4.8.0 357 * 358 * @augments wp.Backbone.View 359 */ 360 QuickDraft.Views.DraftList = wp.Backbone.View.extend( { 361 362 // Initialize the draft list view. 363 initialize: function() { 364 365 // Render the view once the drafts have loaded. 366 this.listenTo( this.collection, 'sync', this.onDraftsLoaded ); 367 }, 368 369 // Once the drafts have loaded, complete the setup. 370 onDraftsLoaded: function() { 371 372 // Add a listener for new items added to the underlying (draft) post collection. 373 this.listenTo( this.collection, 'add', this.renderNew ); 374 375 // Render the view! 376 this.render(); 377 }, 378 379 // Handle a new item being added to the collection. 380 renderNew: function() { 381 382 // Display highlight effect to first (added) item for one second. 383 var $newEl = this.render().$el.find( 'li:first' ).addClass( 'is-new' ); 384 setTimeout( function() { 385 $newEl.removeClass( 'is-new' ); 386 }, 1000 ); 387 388 // Alert screen readers that a new draft has been added. 389 wp.a11y.speak( quickDraft.l10n.newDraftCreated, 'assertive' ); 390 }, 391 392 // Render the draft post list view. 393 render: function() { 394 395 // Hide drafts list entirely if no drafts exist. 396 this.$el.toggle( this.collection.length > 0 ); 397 398 // Display a 'View All' link if there are more drafts available. 399 this.$el.find( '.view-all' ).toggle( this.collection.hasMore() ); 400 401 // Remove the placeholder class and render the models. 402 this.$el.find( '.drafts-list' ) 403 .removeClass( 'is-placeholder' ) 404 .html( 405 _.map( this.collection.models, function( draft ) { 406 return new QuickDraft.Views.DraftListItem( { 407 model: draft 408 } ).render().el; 409 } ) 410 ); 411 412 return this; 413 } 189 414 } ); 415 416 /** 417 * Set up a view object an individual draft in the draft list. 418 * 419 * @since 4.8.0 420 * 421 * @augments wp.Backbone.View 422 */ 423 QuickDraft.Views.DraftListItem = wp.Backbone.View.extend( { 424 tagName: 'li', 425 426 // Render beased on the passed template. 427 template: wp.template( 'item-quick-press-draft' ), 428 429 // Render a single draft list item. 430 render: function() { 431 432 // Clone the original model attributes, so we can leave the model untouched. 433 var attributes = _.clone( this.model.attributes ); 434 435 // Trim the content to 10 words. 436 attributes.formattedContent = wp.formatting.trimWords( attributes.content.rendered, 10 ); 437 438 // If the title is missing entirely, add a no title placeholder. 439 attributes.formattedTitle = attributes.title.rendered.length > 0 ? attributes.title.rendered : quickDraft.l10n.noTitle; 440 441 // Format the data using Intl.DateTimeFormat with a fallback to date.toLocaleDateString. 442 var date = new Date( wp.api.utils.parseISO8601( attributes.date + quickDraft.timezoneOffset ) ); 443 if ( 'undefined' !== typeof Intl && Intl.DateTimeFormat ) { 444 attributes.formattedDate = new Intl.DateTimeFormat( undefined, { 445 month: 'long', 446 day: 'numeric', 447 year: 'numeric' 448 } ).format( date ); 449 } else { 450 attributes.formattedDate = date.toLocaleDateString(); 451 } 452 453 // Output the rendered template. 454 this.$el.html( this.template( attributes ) ); 455 456 // Continue the rendering chain. 457 return this; 458 } 459 } ); 460 461 462 /** 463 * Initialize the Quick Draft feature. 464 * 465 * @since 4.8.0 466 * 467 */ 468 QuickDraft.init = function() { 469 470 // Set up a state model to track the application state. 471 quickDraft.state = new Backbone.Model({ 472 'errorState': false 473 }); 474 475 // Wait for the wp-api client to initialize. 476 wp.api.loadPromise.done( function() { 477 478 // Fetch up to 4 of the current user's recent drafts by extending wp.api.collections.Posts. 479 var draftsCollection = new wp.api.collections.Posts(); 480 draftsCollection.fetch( { 481 data: wp.hooks.applyFilters( 482 'dashboard_recent_drafts_fetch_args', 483 { 484 status: 'draft', 485 author: quickDraft.currentUserId, 486 per_page: 4, 487 order_by: 'date', 488 } 489 ) 490 } ); 491 492 // Drafts list is initialized but not rendered until drafts load. 493 new QuickDraft.Views.DraftList( { 494 el: '#quick-press-drafts', 495 collection: draftsCollection 496 } ); 497 498 new QuickDraft.Views.Form( { 499 el: '#quick-press', 500 model: new wp.api.models.Post(), 501 collection: draftsCollection 502 } ).render(); 503 }); 504 }; -
new file src/wp-includes/js/wp-hooks.js
diff --git src/wp-includes/js/wp-hooks.js src/wp-includes/js/wp-hooks.js new file mode 100644 index 0000000..c7b2d79
- + 1 ( function( wp ) { 2 'use strict'; 3 4 /** 5 * Contains the registered hooks, keyed by hook type. Each hook type is an 6 * array of objects with priority and callback of each registered hook. 7 */ 8 var HOOKS = {}; 9 10 /** 11 * Returns a function which, when invoked, will add a hook. 12 * 13 * @param {string} type Type for which hooks are to be added 14 * @return {Function} Hook added 15 */ 16 function createAddHookByType( type ) { 17 /** 18 * Adds the hook to the appropriate hooks container 19 * 20 * @param {string} hook Name of hook to add 21 * @param {Function} callback Function to call when the hook is run 22 * @param {?number} priority Priority of this hook (default=10) 23 */ 24 return function( hook, callback, priority ) { 25 var hookObject, hooks; 26 if ( typeof hook !== 'string' || typeof callback !== 'function' ) { 27 return; 28 } 29 30 // Assign default priority 31 if ( 'undefined' === typeof priority ) { 32 priority = 10; 33 } else { 34 priority = parseInt( priority, 10 ); 35 } 36 37 // Validate numeric priority 38 if ( isNaN( priority ) ) { 39 return; 40 } 41 42 // Check if adding first of type 43 if ( ! HOOKS[ type ] ) { 44 HOOKS[ type ] = {}; 45 } 46 47 hookObject = { 48 callback: callback, 49 priority: priority 50 }; 51 52 if ( HOOKS[ type ].hasOwnProperty( hook ) ) { 53 // Append and re-sort amongst existing 54 hooks = HOOKS[ type ][ hook ]; 55 hooks.push( hookObject ); 56 hooks = sortHooks( hooks ); 57 } else { 58 // First of its type needs no sort 59 hooks = [ hookObject ]; 60 } 61 62 HOOKS[ type ][ hook ] = hooks; 63 }; 64 } 65 66 /** 67 * Returns a function which, when invoked, will remove a specified hook. 68 * 69 * @param {string} type Type for which hooks are to be removed 70 * @return {Function} Hook remover 71 */ 72 function createRemoveHookByType( type ) { 73 /** 74 * Removes the specified hook by resetting its value. 75 * 76 * @param {string} hook Name of hook to remove 77 * @param {?Function} callback The specific callback to be removed. If 78 * omitted, clears all callbacks. 79 */ 80 return function( hook, callback ) { 81 var handlers, i; 82 83 // Baily early if no hooks exist by this name 84 if ( ! HOOKS[ type ] || ! HOOKS[ type ].hasOwnProperty( hook ) ) { 85 return; 86 } 87 88 if ( callback ) { 89 // Try to find specified callback to remove 90 handlers = HOOKS[ type ][ hook ]; 91 for ( i = handlers.length - 1; i >= 0; i-- ) { 92 if ( handlers[ i ].callback === callback ) { 93 handlers.splice( i, 1 ); 94 } 95 } 96 } else { 97 // Reset hooks to empty 98 delete HOOKS[ type ][ hook ]; 99 } 100 }; 101 } 102 103 /** 104 * Returns a function which, when invoked, will execute all registered 105 * hooks of the specified type by calling upon runner with its hook name 106 * and arguments. 107 * 108 * @param {string} type Type for which hooks are to be run, one of 'action' or 'filter'. 109 * @param {Function} runner Function to invoke for each hook callback 110 * @return {Function} Hook runner 111 */ 112 function createRunHookByType( type, runner ) { 113 /** 114 * Runs the specified hook. 115 * 116 * @param {string} hook The hook to run 117 * @param {...*} args Arguments to pass to the action/filter 118 * @return {*} Return value of runner, if applicable 119 * @private 120 */ 121 return function( /* hook, ...args */ ) { 122 var args, hook; 123 124 args = Array.prototype.slice.call( arguments ); 125 hook = args.shift(); 126 127 if ( typeof hook === 'string' ) { 128 return runner( hook, args ); 129 } 130 }; 131 } 132 133 /** 134 * Performs an action if it exists. 135 * 136 * @param {string} action The action to perform. 137 * @param {...*} args Optional args to pass to the action. 138 * @private 139 */ 140 function runDoAction( action, args ) { 141 var handlers, i; 142 if ( HOOKS.actions ) { 143 handlers = HOOKS.actions[ action ]; 144 } 145 146 if ( ! handlers ) { 147 return; 148 } 149 150 HOOKS.actions.current = action; 151 152 for ( i = 0; i < handlers.length; i++ ) { 153 handlers[ i ].callback.apply( null, args ); 154 HOOKS.actions[ action ].runs = HOOKS.actions[ action ].runs ? HOOKS.actions[ action ].runs + 1 : 1; 155 } 156 } 157 158 /** 159 * Performs a filter if it exists. 160 * 161 * @param {string} filter The filter to apply. 162 * @param {...*} args Optional args to pass to the filter. 163 * @return {*} The filtered value 164 * @private 165 */ 166 function runApplyFilters( filter, args ) { 167 var handlers, i; 168 if ( HOOKS.filters ) { 169 handlers = HOOKS.filters[ filter ]; 170 } 171 172 if ( ! handlers ) { 173 return args[ 0 ]; 174 } 175 176 HOOKS.filters.current = filter; 177 HOOKS.filters[ filter ].runs = HOOKS.filters[ filter ].runs ? HOOKS.filters[ filter ].runs + 1 : 1; 178 179 for ( i = 0; i < handlers.length; i++ ) { 180 args[ 0 ] = handlers[ i ].callback.apply( null, args ); 181 } 182 183 return args[ 0 ]; 184 } 185 186 /** 187 * Use an insert sort for keeping our hooks organized based on priority. 188 * 189 * @see http://jsperf.com/javascript-sort 190 * 191 * @param {Array} hooks Array of the hooks to sort 192 * @return {Array} The sorted array 193 * @private 194 */ 195 function sortHooks( hooks ) { 196 var i, tmpHook, j, prevHook; 197 for ( i = 1; i < hooks.length; i++ ) { 198 tmpHook = hooks[ i ]; 199 j = i; 200 while ( ( prevHook = hooks[ j - 1 ] ) && prevHook.priority > tmpHook.priority ) { 201 hooks[ j ] = hooks[ j - 1 ]; 202 --j; 203 } 204 hooks[ j ] = tmpHook; 205 } 206 207 return hooks; 208 } 209 210 /** 211 * Checks to see if an action is currently being executed. 212 * 213 * @param {string} type Type of hooks to check, one of 'action' or 'filter'. 214 * @param {string} action The name of the action to check for, if omitted will check for any action being performed. 215 * 216 * @return {[type]} [description] 217 */ 218 function createDoingHookByType( type ) { 219 return function( action ) { 220 221 // If the action was not passed, check for any current hook. 222 if ( 'undefined' === typeof action ) { 223 return 'undefined' !== typeof HOOKS[ type ].current; 224 } 225 226 // Return the current hook. 227 return HOOKS[ type ] && HOOKS[ type ].current ? 228 action === HOOKS[ type ].current : 229 false; 230 }; 231 } 232 233 /** 234 * Retrieve the number of times an action is fired. 235 * 236 * @param {string} type Type for which hooks to check, one of 'action' or 'filter'. 237 * @param {string} action The action to check. 238 * 239 * @return {[type]} [description] 240 */ 241 function createDidHookByType( type ) { 242 return function( action ) { 243 return HOOKS[ type ] && HOOKS[ type ][ action ] && HOOKS[ type ][ action ].runs ? 244 HOOKS[ type ][ action ].runs : 245 0; 246 }; 247 } 248 249 /** 250 * Check to see if an action is registered for a hook. 251 * 252 * @param {string} type Type for which hooks to check, one of 'action' or 'filter'. 253 * @param {string} action The action to check. 254 * 255 * @return {bool} Whether an action has been registered for a hook. 256 */ 257 function createHasHookByType( type ) { 258 return function( action ) { 259 return HOOKS[ type ] && HOOKS[ type ][ action ] ? 260 !! HOOKS[ type ][ action ] : 261 false; 262 }; 263 } 264 265 wp.hooks = { 266 removeFilter: createRemoveHookByType( 'filters' ), 267 applyFilters: createRunHookByType( 'filters', runApplyFilters ), 268 addFilter: createAddHookByType( 'filters' ), 269 removeAction: createRemoveHookByType( 'actions' ), 270 doAction: createRunHookByType( 'actions', runDoAction ), 271 addAction: createAddHookByType( 'actions' ), 272 doingAction: createDoingHookByType( 'actions' ), 273 didAction: createDidHookByType( 'actions' ), 274 hasAction: createHasHookByType( 'actions' ) 275 }; 276 } )( window.wp = window.wp || {} ); -
src/wp-includes/js/wp-util.js
diff --git src/wp-includes/js/wp-util.js src/wp-includes/js/wp-util.js index 527441d..bdc7531 100644
window.wp = window.wp || {}; 121 121 } 122 122 }; 123 123 124 // wp.formatting 125 // ------ 126 // 127 // Tools for formatting strings 128 wp.formatting = { 129 settings: settings.formatting || {}, 130 131 /** 132 * Trims text to a certain number of words. 133 * 134 * @see wp_trim_words 135 * 136 * @param {string} text Text to trim. 137 * @param {number} numWords Number of words. Optional, default is 55. 138 * @param {string} more What to append if text needs to be trimmed. Optional, default is '…'. 139 * @return {string} Trimmed text. 140 */ 141 trimWords: function( text, numWords, more ) { 142 var words, separator; 143 144 if ( 'undefined' === typeof numWords ) { 145 numWords = 55; 146 } 147 148 if ( 'undefined' === typeof more ) { 149 more = wp.formatting.settings.trimWordsMore; 150 } 151 152 text = text.replace( /[\n\r\t ]+/g, ' ' ).replace( /^ | $/g, '' ); 153 154 if ( wp.formatting.settings.trimWordsByCharacter ) { 155 separator = ''; 156 } else { 157 separator = ' '; 158 } 159 160 words = text.split( separator ); 161 162 if ( words.length <= numWords ) { 163 return words.join( separator ); 164 } 165 166 return words.slice( 0, numWords ).join( separator ) + more; 167 } 168 }; 169 124 170 }(jQuery)); -
src/wp-includes/plugin.php
diff --git src/wp-includes/plugin.php src/wp-includes/plugin.php index 86f1c3b..86f9db8 100644
function doing_filter( $filter = null ) { 363 363 } 364 364 365 365 /** 366 * Retrieve the name of anaction currently being processed.366 * Retrieve whether action currently being processed. 367 367 * 368 368 * @since 3.9.0 369 369 * -
src/wp-includes/script-loader.php
diff --git src/wp-includes/script-loader.php src/wp-includes/script-loader.php index def438c..1067e4b 100644
function wp_default_scripts( &$scripts ) { 85 85 86 86 $scripts->add( 'wp-a11y', "/wp-includes/js/wp-a11y$suffix.js", array( 'jquery' ), false, 1 ); 87 87 88 $scripts->add( 'wp-hooks', "/wp-includes/js/wp-hooks$suffix.js", array(), false, 1 ); 89 88 90 $scripts->add( 'sack', "/wp-includes/js/tw-sack$suffix.js", array(), '1.6.1', 1 ); 89 91 90 92 $scripts->add( 'quicktags', "/wp-includes/js/quicktags$suffix.js", array(), false, 1 ); … … function wp_default_scripts( &$scripts ) { 336 338 'ajax' => array( 337 339 'url' => admin_url( 'admin-ajax.php', 'relative' ), 338 340 ), 341 'formatting' => array( 342 'trimWordsMore' => __( '…' ), 343 /* 344 * translators: If your word count is based on single characters (e.g. East Asian characters), 345 * enter 'characters_excluding_spaces' or 'characters_including_spaces'. Otherwise, enter 'words'. 346 * Do not translate into your own language. 347 */ 348 'trimWordsByCharacter' => strpos( _x( 'words', 'Word count type. Do not translate!' ), 'characters' ) === 0 && preg_match( '/^utf\-?8$/i', get_option( 'blog_charset' ) ), 349 ), 339 350 ) ); 340 351 341 352 $scripts->add( 'wp-backbone', "/wp-includes/js/wp-backbone$suffix.js", array('backbone', 'wp-util'), false, 1 ); … … function wp_default_scripts( &$scripts ) { 724 735 'current' => __( 'Current Color' ), 725 736 ) ); 726 737 727 $scripts->add( 'dashboard', "/wp-admin/js/dashboard$suffix.js", array( 'jquery', 'admin-comments', 'postbox' ), false, 1 ); 738 $scripts->add( 'dashboard', "/wp-admin/js/dashboard$suffix.js", array( 'jquery', 'admin-comments', 'postbox', 'wp-api', 'wp-backbone', 'wp-a11y', 'wp-util', 'wp-hooks' ), false, 1 ); 739 did_action( 'init' ) && $scripts->localize( 'dashboard', 'quickDraft', array( 740 'currentUserId' => get_current_user_id(), 741 'l10n' => array( 742 'error' => __( 'An error has occurred. Please reload the page and try again.' ), 743 'newDraftCreated' => __( 'Success. A new draft was created.' ), 744 'errorEmptyFields' => __( 'Error. All fields were empty.' ), 745 'noTitle' => __( '(no title)' ), 746 ), 747 'timezoneOffset' => ( get_option( 'gmt_offset' ) >= 0 ? '+' : '-' ) . date( 'H:i', abs( get_option( 'gmt_offset' ) * 3600 ) ), 748 ) ); 728 749 729 750 $scripts->add( 'list-revisions', "/wp-includes/js/wp-list-revisions$suffix.js" ); 730 751 -
tests/qunit/index.html
diff --git tests/qunit/index.html tests/qunit/index.html index 0c9d820..a707e13 100644
50 50 <script src="../../src/wp-includes/js/customize-base.js"></script> 51 51 <script src="../../src/wp-includes/js/customize-models.js"></script> 52 52 <script src="../../src/wp-includes/js/shortcode.js"></script> 53 <script src="../../src/wp-includes/js/wp-hooks.js"></script> 53 54 <script src="../../src/wp-admin/js/customize-controls.js"></script> 54 55 <script src="../../src/wp-includes/js/wp-api.js"></script> 55 56 … … 70 71 <script src="wp-admin/js/customize-base.js"></script> 71 72 <script src="wp-admin/js/customize-header.js"></script> 72 73 <script src="wp-includes/js/shortcode.js"></script> 74 <script src="wp-includes/js/wp-hooks.js"></script> 73 75 <script src="wp-includes/js/wp-api.js"></script> 74 76 <script src="wp-admin/js/customize-controls.js"></script> 75 77 <script src="wp-admin/js/customize-controls-utils.js"></script> … … 77 79 <script src="wp-admin/js/customize-widgets.js"></script> 78 80 <script src="wp-admin/js/word-count.js"></script> 79 81 <script src="wp-admin/js/nav-menu.js"></script> 82 <script src="wp-includes/js/wp-util.js"></script> 80 83 81 84 <!-- Customizer templates for sections --> 82 85 <script type="text/html" id="tmpl-customize-section-default"> -
new file tests/qunit/wp-includes/js/wp-hooks.js
diff --git tests/qunit/wp-includes/js/wp-hooks.js tests/qunit/wp-includes/js/wp-hooks.js new file mode 100644 index 0000000..8f894b2
- + 1 /* global wp */ 2 ( function( QUnit ) { 3 QUnit.module( 'wp-hooks' ); 4 5 function filter_a( str ) { 6 return str + 'a'; 7 } 8 function filter_b( str ) { 9 return str + 'b'; 10 } 11 function filter_c( str ) { 12 return str + 'c'; 13 } 14 function action_a() { 15 window.actionValue += 'a'; 16 } 17 function action_b() { 18 window.actionValue += 'b'; 19 } 20 function action_c() { 21 window.actionValue += 'c'; 22 } 23 window.actionValue = ''; 24 25 QUnit.test( 'add and remove a filter', function() { 26 expect( 1 ); 27 wp.hooks.addFilter( 'test.filter', filter_a ); 28 wp.hooks.removeFilter( 'test.filter' ); 29 equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'test' ); 30 } ); 31 32 QUnit.test( 'add a filter and run it', function() { 33 expect( 1 ); 34 wp.hooks.addFilter( 'test.filter', filter_a ); 35 equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'testa' ); 36 wp.hooks.removeFilter( 'test.filter' ); 37 } ); 38 39 QUnit.test( 'add 2 filters in a row and run them', function() { 40 expect( 1 ); 41 wp.hooks.addFilter( 'test.filter', filter_a ); 42 wp.hooks.addFilter( 'test.filter', filter_b ); 43 equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'testab' ); 44 wp.hooks.removeFilter( 'test.filter' ); 45 } ); 46 47 QUnit.test( 'add 3 filters with different priorities and run them', function() { 48 expect( 1 ); 49 wp.hooks.addFilter( 'test.filter', filter_a ); 50 wp.hooks.addFilter( 'test.filter', filter_b, 2 ); 51 wp.hooks.addFilter( 'test.filter', filter_c, 8 ); 52 equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'testbca' ); 53 wp.hooks.removeFilter( 'test.filter' ); 54 } ); 55 56 QUnit.test( 'add and remove an action', function() { 57 expect( 1 ); 58 window.actionValue = ''; 59 wp.hooks.addAction( 'test.action', action_a ); 60 wp.hooks.removeAction( 'test.action' ); 61 wp.hooks.doAction( 'test.action' ); 62 equal( window.actionValue, '' ); 63 } ); 64 65 QUnit.test( 'add an action and run it', function() { 66 expect( 1 ); 67 window.actionValue = ''; 68 wp.hooks.addAction( 'test.action', action_a ); 69 wp.hooks.doAction( 'test.action' ); 70 equal( window.actionValue, 'a' ); 71 wp.hooks.removeAction( 'test.action' ); 72 } ); 73 74 QUnit.test( 'add 2 actions in a row and then run them', function() { 75 expect( 1 ); 76 window.actionValue = ''; 77 wp.hooks.addAction( 'test.action', action_a ); 78 wp.hooks.addAction( 'test.action', action_b ); 79 wp.hooks.doAction( 'test.action' ); 80 equal( window.actionValue, 'ab' ); 81 wp.hooks.removeAction( 'test.action' ); 82 } ); 83 84 QUnit.test( 'add 3 actions with different priorities and run them', function() { 85 expect( 1 ); 86 window.actionValue = ''; 87 wp.hooks.addAction( 'test.action', action_a ); 88 wp.hooks.addAction( 'test.action', action_b, 2 ); 89 wp.hooks.addAction( 'test.action', action_c, 8 ); 90 wp.hooks.doAction( 'test.action' ); 91 equal( window.actionValue, 'bca' ); 92 wp.hooks.removeAction( 'test.action' ); 93 } ); 94 95 QUnit.test( 'pass in two arguments to an action', function() { 96 var arg1 = 10, 97 arg2 = 20; 98 99 expect( 4 ); 100 101 wp.hooks.addAction( 'test.action', function( a, b ) { 102 equal( arg1, a ); 103 equal( arg2, b ); 104 } ); 105 wp.hooks.doAction( 'test.action', arg1, arg2 ); 106 wp.hooks.removeAction( 'test.action' ); 107 108 equal( arg1, 10 ); 109 equal( arg2, 20 ); 110 } ); 111 112 QUnit.test( 'fire action multiple times', function() { 113 var func; 114 expect( 2 ); 115 116 func = function() { 117 ok( true ); 118 }; 119 120 wp.hooks.addAction( 'test.action', func ); 121 wp.hooks.doAction( 'test.action' ); 122 wp.hooks.doAction( 'test.action' ); 123 wp.hooks.removeAction( 'test.action' ); 124 } ); 125 126 QUnit.test( 'remove specific action callback', function() { 127 window.actionValue = ''; 128 wp.hooks.addAction( 'test.action', action_a ); 129 wp.hooks.addAction( 'test.action', action_b, 2 ); 130 wp.hooks.addAction( 'test.action', action_c, 8 ); 131 132 wp.hooks.removeAction( 'test.action', action_b ); 133 wp.hooks.doAction( 'test.action' ); 134 equal( window.actionValue, 'ca' ); 135 wp.hooks.removeAction( 'test.action' ); 136 } ); 137 138 QUnit.test( 'remove specific filter callback', function() { 139 wp.hooks.addFilter( 'test.filter', filter_a ); 140 wp.hooks.addFilter( 'test.filter', filter_b, 2 ); 141 wp.hooks.addFilter( 'test.filter', filter_c, 8 ); 142 143 wp.hooks.removeFilter( 'test.filter', filter_b ); 144 equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'testca' ); 145 wp.hooks.removeFilter( 'test.filter' ); 146 } ); 147 148 // Test doingAction, didAction, hasAction. 149 QUnit.test( 'Test doingAction, didAction and hasAction.', function() { 150 151 // Reset state for testing. 152 wp.hooks.removeAction( 'test.action' ); 153 wp.hooks.addAction( 'another.action', function(){} ); 154 wp.hooks.doAction( 'another.action' ); 155 156 // Verify no action is running yet. 157 ok( ! wp.hooks.doingAction( 'test.action' ), 'The test.action is not running.' ); 158 equal( wp.hooks.didAction( 'test.action' ), 0, 'The test.action has not run.' ); 159 ok( ! wp.hooks.hasAction( 'test.action' ), 'The test.action is not registered.' ); 160 161 wp.hooks.addAction( 'test.action', action_a ); 162 163 // Verify action added, not running yet. 164 ok( ! wp.hooks.doingAction( 'test.action' ), 'The test.action is not running.' ); 165 equal( wp.hooks.didAction( 'test.action' ), 0, 'The test.action has not run.' ); 166 ok( wp.hooks.hasAction( 'test.action' ), 'The test.action is registered.' ); 167 168 wp.hooks.doAction( 'test.action' ); 169 170 // Verify action added and running. 171 ok( wp.hooks.doingAction( 'test.action' ), 'The test.action is running.' ); 172 equal( wp.hooks.didAction( 'test.action' ), 1, 'The test.action has run once.' ); 173 ok( wp.hooks.hasAction( 'test.action' ), 'The test.action is registered.' ); 174 175 wp.hooks.doAction( 'test.action' ); 176 equal( wp.hooks.didAction( 'test.action' ), 2, 'The test.action has run twice.' ); 177 178 wp.hooks.removeAction( 'test.action' ); 179 180 // Verify state is reset appropriately. 181 ok( wp.hooks.doingAction( 'test.action' ), 'The test.action is running.' ); 182 equal( wp.hooks.didAction( 'test.action' ), 0, 'The test.action has not run.' ); 183 ok( ! wp.hooks.hasAction( 'test.action' ), 'The test.action is not registered.' ); 184 185 wp.hooks.doAction( 'another.action' ); 186 ok( ! wp.hooks.doingAction( 'test.action' ), 'The test.action is running.' ); 187 188 189 } ); 190 191 } )( window.QUnit ); -
new file tests/qunit/wp-includes/js/wp-util.js
diff --git tests/qunit/wp-includes/js/wp-util.js tests/qunit/wp-includes/js/wp-util.js new file mode 100644 index 0000000..6d42228
- + 1 /* global wp */ 2 ( function( QUnit ) { 3 wp.formatting.settings.trimWordsMore = '…'; 4 QUnit.module( 'wp-util' ); 5 var longText = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce varius lacinia vehicula. Etiam sapien risus, ultricies ac posuere eu, convallis sit amet augue. Pellentesque urna massa, lacinia vel iaculis eget, bibendum in mauris. Aenean eleifend pulvinar ligula, a convallis eros gravida non. Suspendisse potenti. Pellentesque et odio tortor. In vulputate pellentesque libero, sed dapibus velit mollis viverra. Pellentesque id urna euismod dolor cursus sagittis.'; 6 7 QUnit.test( 'wp.formatting.trimWords', function( assert ) { 8 _.each( [ 9 { 10 'trimmed': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce varius lacinia vehicula. Etiam sapien risus, ultricies ac posuere eu, convallis sit amet augue. Pellentesque urna massa, lacinia vel iaculis eget, bibendum in mauris. Aenean eleifend pulvinar ligula, a convallis eros gravida non. Suspendisse potenti. Pellentesque et odio tortor. In vulputate pellentesque libero, sed dapibus velit…', 11 'text': longText, 12 'description': 'Trims to 55 by default.' 13 }, 14 { 15 'trimmed': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce varius…', 16 'text': longText, 17 'length': 10, 18 'description': 'Trims to 10.' 19 }, 20 { 21 'trimmed': 'Lorem ipsum dolor sit amet,[...] Read on!', 22 'text': longText, 23 'description': 'Trims to 5 and uses custom more.', 24 'length': 5, 25 'more': '[...] Read on!' 26 }, 27 { 28 'trimmed': 'This is some short text.', 29 'text': 'This is some short text.', 30 'description': 'Doesn\'t strip short text.' 31 } 32 33 ], function( test ) { 34 assert.equal( 35 wp.formatting.trimWords( test.text, test.length, test.more ), 36 test.trimmed, 37 test.description 38 ); 39 } ); 40 } ); 41 } )( window.QUnit );