WordPress.org

Make WordPress Core

Ticket #38342: 38342.23.diff

File 38342.23.diff, 28.8 KB (added by aduth, 4 years ago)
  • src/wp-admin/css/dashboard.css

    diff --git src/wp-admin/css/dashboard.css src/wp-admin/css/dashboard.css
    index 72b0230..3c7b618 100644
    form.initial-form.quickpress-open input#title { 
    518518        resize: none;
    519519}
    520520
     521#quick-press.is-saving .spinner {
     522        visibility: inherit;
     523}
     524
    521525/* Dashboard Quick Draft - Drafts list */
    522526
    523527.js #dashboard_quick_press .drafts {
    form.initial-form.quickpress-open input#title { 
    541545        margin: 0 12px;
    542546}
    543547
     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
    544602#dashboard_quick_press .drafts li {
    545603        margin-bottom: 1em;
    546604}
     605
     606#dashboard_quick_press .drafts li.is-new {
     607        background-color: #fffbe5;
     608}
     609
    547610#dashboard_quick_press .drafts li time {
    548611        color: #72777c;
    549612}
  • src/wp-admin/includes/dashboard.php

    diff --git src/wp-admin/includes/dashboard.php src/wp-admin/includes/dashboard.php
    index 0ecf0a2..7107f3a 100644
    function wp_dashboard_quick_press( $error_msg = false ) { 
    494494        $post_ID = (int) $post->ID;
    495495?>
    496496
    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>
    503499                <div class="input-text-wrap" id="title-wrap">
    504                         <label class="screen-reader-text prompt" for="title" id="title-prompt-text">
     500                        <label class="prompt" for="title" id="title-prompt-text">
    505501
    506502                                <?php
    507503                                /** This filter is documented in wp-admin/edit-form-advanced.php */
    508504                                echo apply_filters( 'enter_title_here', __( 'Title' ), $post );
    509505                                ?>
    510506                        </label>
    511                         <input type="text" name="post_title" id="title" autocomplete="off" />
     507                        <input type="text" name="title" id="title" autocomplete="off" />
    512508                </div>
    513509
    514510                <div class="textarea-wrap" id="description-wrap">
    515                         <label class="screen-reader-text prompt" for="content" id="content-prompt-text"><?php _e( 'What&#8217;s on your mind?' ); ?></label>
     511                        <label class="prompt" for="content" id="content-prompt-text"><?php _e( 'What&#8217;s on your mind?' ); ?></label>
    516512                        <textarea name="content" id="content" class="mceEditor" rows="3" cols="15" autocomplete="off"></textarea>
    517513                </div>
    518 
    519514                <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>
    524516                        <?php submit_button( __( 'Save Draft' ), 'primary', 'save', false, array( 'id' => 'save-post' ) ); ?>
    525517                        <br class="clear" />
    526518                </p>
    527519
    528520        </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&hellip;' ) ?></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>
    529537        <?php
    530         wp_dashboard_recent_drafts();
    531538}
    532539
    533540/**
    534541 * Show recent drafts of the user on the dashboard.
    535542 *
    536543 * @since 2.7.0
     544 * @deprecated 4.8.0
    537545 *
    538546 * @param array $drafts
    539547 */
    function wp_dashboard_recent_drafts( $drafts = false ) { 
    548556                        'order'          => 'DESC'
    549557                );
    550558
    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 */
    558560                $query_args = apply_filters( 'dashboard_recent_drafts_query_args', $query_args );
    559561
    560562                $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..53a2a94 100644
     
    1 /* global pagenow, ajaxurl, postboxes, wpActiveEditor:true */
    2 var ajaxWidgets, ajaxPopulateWidgets, quickPressLoad;
     1/* global _, wp, quickDraft, pagenow, ajaxurl, postboxes, wpActiveEditor:true */
     2var ajaxWidgets, ajaxPopulateWidgets, QuickDraft = {};
    33
    44jQuery(document).ready( function($) {
    55        var welcomePanel = $( '#welcome-panel' ),
    jQuery(document).ready( function($) { 
    5959        };
    6060        ajaxPopulateWidgets();
    6161
    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                         });
    106 
    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                 });
     62        postboxes.add_postbox_toggles(pagenow, { pbshow: function( id ) {
     63                ajaxPopulateWidgets();
    11764
    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        } } );
    12569
    12670        $( '.meta-box-sortables' ).sortable( 'option', 'containment', '#wpwrap' );
    12771
    jQuery(document).ready( function($) { 
    186130                });
    187131        }
    188132
     133        autoResizeTextarea();
     134
     135        if ( jQuery( '#dashboard_quick_press' ).is( ':visible' ) ) {
     136                QuickDraft.init();
     137        }
     138});
     139
     140// Set up the QuickDraft views.
     141QuickDraft.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 */
     150QuickDraft.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                jQuery( element ).siblings( '.prompt' ).toggleClass( 'screen-reader-text', ! visible || hasContent );
     191        },
     192
     193        // Show all of the field promts.
     194        showAllPrompts: function() {
     195                this.$el.find( ':input' ).each( _.bind( function( i, input ) {
     196
     197                        // Prompt toggling must be deferred because the reset event is
     198                        // fired before the input values have been cleared
     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                                        // Refocus in the title field, hiding its prompt.
     296                                        _.delay( function() { jQuery( '#quick-press input#title' ).focus(); }, 250 );
     297
     298                                        // Submission complete
     299                                        quickDraft.state.set( 'submitting', false );
     300
     301                                }, this )
     302                        )
     303
     304                        // Handle save success.
     305                        .success(
     306                                _.bind( function() {
     307                                        // Success! Clear any previous error state.
     308                                         this.render();
     309
     310                                        // Add the post model to the head of our collection.
     311                                        this.collection.add( this.model, { at: 0 } );
     312
     313                                        // Create a new post model to contain the form data and reset the form.
     314                                        this.model = new wp.api.models.Post();
     315                                        this.model.on( 'change:errorState', _.bind( this.render, this ) );
     316
     317                                        this.el.reset();
     318                                }, this )
     319                        )
     320
     321                        // Handle save failure.
     322                        .error(
     323                                _.bind( function( model, error ) {
     324                                        var message = '';
     325
     326                                        // Try to parse and use the response message.
     327                                        try {
     328                                                message = JSON.parse( error.responseText ).message;
     329                                        } catch( e ) {
     330
     331                                                // Fall back to a default error string if the parse fails.
     332                                                message = quickDraft.l10n.error;
     333                                        }
     334
     335                                        // Set the app error condition.
     336                                        this.setErrorState( message );
     337                                }, this )
     338                        );
     339        },
     340
     341        // Render the form view.
     342        render: function() {
     343                var $error    = this.$el.find( '.notice-alt' ),
     344                        errorText = quickDraft.state.get( 'errorState' );
     345
     346                // Error notice is only visible if error text is set.
     347                $error.toggleClass( 'hidden', ! errorText );
     348                if ( errorText ) {
     349
     350                        // Note: The inner text transform prevents XSS via html().
     351                        $error.html( jQuery( '<p />', { text: errorText } ) );
     352                }
     353        }
     354} );
     355
     356/**
     357 * Set up a view object for the Quick Draft list of drafts.
     358 *
     359 * @since 4.8.0
     360 *
     361 * @augments wp.Backbone.View
     362 */
     363QuickDraft.Views.DraftList = wp.Backbone.View.extend( {
     364
     365        // Initialize the draft list view.
     366        initialize: function() {
     367
     368                // Render the view once the drafts have loaded.
     369                this.listenTo( this.collection, 'sync', this.onDraftsLoaded );
     370        },
     371
     372        // Once the drafts have loaded, complete the setup.
     373        onDraftsLoaded: function() {
     374
     375                // Add a listener for new items added to the underlying (draft) post collection.
     376                this.listenTo( this.collection, 'add', this.renderNew );
     377
     378                // Render the view!
     379                this.render();
     380        },
     381
     382        // Handle a new item being added to the collection.
     383        renderNew: function() {
     384
     385                // Display highlight effect to first (added) item for one second.
     386                var $newEl = this.render().$el.find( 'li:first' ).addClass( 'is-new' );
     387                setTimeout( function() {
     388                        $newEl.removeClass( 'is-new' );
     389                }, 1000 );
     390
     391                // Alert screen readers that a new draft has been added.
     392                wp.a11y.speak( quickDraft.l10n.newDraftCreated, 'assertive' );
     393        },
     394
     395        // Render the draft post list view.
     396        render: function() {
     397
     398                // Hide drafts list entirely if no drafts exist.
     399                this.$el.toggle( this.collection.length > 0 );
     400
     401                // Display a 'View All' link if there are more drafts available.
     402                this.$el.find( '.view-all' ).toggle( this.collection.hasMore() );
     403
     404                // Remove the placeholder class and render the models.
     405                this.$el.find( '.drafts-list' )
     406                        .removeClass( 'is-placeholder' )
     407                        .html(
     408                                _.map( this.collection.models, function( draft ) {
     409                                        return new QuickDraft.Views.DraftListItem( {
     410                                                model: draft
     411                                        } ).render().el;
     412                                } )
     413                        );
     414
     415                return this;
     416        }
    189417} );
     418
     419/**
     420 * Set up a view object an individual draft in the draft list.
     421 *
     422 * @since 4.8.0
     423 *
     424 * @augments wp.Backbone.View
     425 */
     426QuickDraft.Views.DraftListItem = wp.Backbone.View.extend( {
     427        tagName: 'li',
     428
     429        // Render beased on the passed template.
     430        template: wp.template( 'item-quick-press-draft' ),
     431
     432        // Render a single draft list item.
     433        render: function() {
     434                var attributes, date;
     435
     436                // Clone the original model attributes, so we can leave the model untouched.
     437                attributes = _.clone( this.model.attributes );
     438
     439                // Trim the content to 10 words.
     440                attributes.formattedContent = wp.formatting.trimWords( attributes.content.rendered, 10 );
     441
     442                // If the title is missing entirely, add a no title placeholder.
     443                attributes.formattedTitle = attributes.title.rendered.length > 0 ? attributes.title.rendered : quickDraft.l10n.noTitle;
     444
     445                // Format the date
     446                attributes.formattedDate = wp.formatting.date( wp.api.utils.parseISO8601( attributes.date ) );
     447
     448                // Output the rendered template.
     449                this.$el.html( this.template( attributes ) );
     450
     451                // Continue the rendering chain.
     452                return this;
     453        }
     454} );
     455
     456
     457/**
     458 * Initialize the Quick Draft feature.
     459 *
     460 * @since 4.8.0
     461 *
     462 */
     463QuickDraft.init = function() {
     464
     465        // Set up a state model to track the application state.
     466        quickDraft.state = new Backbone.Model({
     467                'errorState': false
     468        });
     469
     470        // Wait for the wp-api client to initialize.
     471        wp.api.loadPromise.done( function() {
     472
     473                // Fetch up to 4 of the current user's recent drafts by extending wp.api.collections.Posts.
     474                var draftsCollection = new wp.api.collections.Posts();
     475                draftsCollection.fetch( {
     476                        data: {
     477                                status: 'draft',
     478                                author: quickDraft.currentUserId,
     479                                per_page: 4,
     480                                order_by: 'date',
     481                                'quick-draft-post-list': true /* flag passed for back end filters */
     482                        }
     483                } );
     484
     485                // Drafts list is initialized but not rendered until drafts load.
     486                new QuickDraft.Views.DraftList( {
     487                        el: '#quick-press-drafts',
     488                        collection: draftsCollection
     489                } );
     490
     491                new QuickDraft.Views.Form( {
     492                        el: '#quick-press',
     493                        model: new wp.api.models.Post(),
     494                        collection: draftsCollection
     495                } ).render();
     496        });
     497};
  • 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..abf796f 100644
    window.wp = window.wp || {}; 
    121121                }
    122122        };
    123123
     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                /**
     170                 * Given optional date and format string, returns a localized date
     171                 * string approximating specified format. Uses browser i18n features
     172                 * when available, with fallback. Undefined arguments default to now
     173                 * and site configured date format respectively.
     174                 *
     175                 * @see http://php.net/manual/en/function.date.php
     176                 *
     177                 * @param  {?*}      date   Optional date object or timestamp, defaults
     178                 *                          to now
     179                 * @param  {?String} format Optional PHP date format string, defaults
     180                 *                          to site date format option
     181                 * @return {String}         Localized formatted date string
     182                 */
     183                date: function( date, format ) {
     184                        var options, matches, m, ml, character, locales;
     185
     186                        // Cast date parameter to date object. This accommodates timestamp,
     187                        // Date object, ISO8601 string, and undefined (defaulting to now).
     188                        date = new Date( date );
     189
     190                        // Adjust JavaScript date from local browser timezone to GMT+0
     191                        date.setTime( date.getTime() + ( date.getTimezoneOffset() * 60000 ) );
     192
     193                        // If no browser support for Intl, return fallback
     194                        if ( 'undefined' === typeof Intl || ! Intl.DateTimeFormat ) {
     195                                return date.toLocaleDateString();
     196                        }
     197
     198                        // Default format to site date option
     199                        if ( 'undefined' === typeof format ) {
     200                                format = wp.formatting.settings.dateFormat;
     201                        }
     202
     203                        matches = format.match( /\\?[a-zA-Z]/g );
     204                        if ( ! matches ) {
     205                                return format;
     206                        }
     207
     208                        options = {};
     209                        for ( m = 0, ml = matches.length; m < ml; m++ ) {
     210                                character = matches[ m ];
     211
     212                                // Ignore characters prefixed with backslash
     213                                if ( 0 === character.indexOf( '\\' ) ) {
     214                                        continue;
     215                                }
     216
     217                                switch ( character ) {
     218                                        // Supported:
     219                                        case 'd': options.day = '2-digit'; break;
     220                                        case 'D': options.weekday = 'short'; break;
     221                                        case 'j': options.day = 'numeric'; break;
     222                                        case 'l': options.weekday = 'long'; break;
     223                                        case 'F': options.month = 'long'; break;
     224                                        case 'm': options.month = '2-digit'; break;
     225                                        case 'M': options.month = 'short'; break;
     226                                        case 'n': options.month = 'numeric'; break;
     227                                        case 'Y': options.year = 'numeric'; break;
     228                                        case 'y': options.year = '2-digit'; break;
     229                                        case 'g': options.hour = 'numeric'; options.hour12 = true; break;
     230                                        case 'G': options.hour = 'numeric'; options.hour12 = false; break;
     231                                        case 'h': options.hour = '2-digit'; options.hour12 = true; break;
     232                                        case 'H': options.hour = '2-digit'; options.hour12 = false; break;
     233                                        case 'i': options.minute = '2-digit'; break;
     234                                        case 's': options.second = '2-digit'; break;
     235                                        case 'e': options.timeZoneName = 'long'; break;
     236                                        case 'T': options.timeZoneName = 'short'; break;
     237                                        case 'c': return date.toISOString();
     238                                        case 'U': return Number( date );
     239
     240                                        // Unsupported with fallback:
     241                                        case 'N': options.weekday = 'narrow'; break;
     242                                        case 'w': options.weekday = 'narrow'; break;
     243                                        case 'o': options.year = 'numeric'; break;
     244                                        case 'O': options.timeZoneName = 'short'; break;
     245                                        case 'P': options.timeZoneName = 'short'; break;
     246                                        case 'Z': options.timeZoneName = 'short'; break;
     247                                        case 'r': return String( time );
     248
     249                                        // Unsupported: 'S', 'z', 'W', 't', 'L', 'a', 'A', 'B', 'u', 'v', 'I'
     250                                }
     251                        }
     252
     253                        locales = wp.formatting.settings.userLocale.replace( '_', '-' );
     254                        return new Intl.DateTimeFormat( locales, options ).format( date );
     255                }
     256        };
     257
    124258}(jQuery));
  • src/wp-includes/rest-api.php

    diff --git src/wp-includes/rest-api.php src/wp-includes/rest-api.php
    index 3d0b7ee..3711e32 100644
    function rest_api_default_filters() { 
    164164        add_filter( 'rest_post_dispatch', 'rest_send_allow_header', 10, 3 );
    165165
    166166        add_filter( 'rest_pre_dispatch', 'rest_handle_options_request', 10, 3 );
     167
     168        // Legacy filter for Quick Draft recent posts.
     169        add_filter( 'rest_post_query', 'rest_filter_quick_draft_query', 10, 2 );
     170}
     171
     172/**
     173 * Filter query args used by he Quick Draft recent posts list.
     174 *
     175 * @param array           $args    Key value array of query var to query value.
     176 * @param WP_REST_Request $request The request used.
     177 */
     178function rest_filter_quick_draft_query( $query_args, $request ) {
     179
     180        // Only modify Quick Draft queries.
     181        $params = $request->get_query_params();
     182
     183        if ( ! isset( $params['quick-draft-post-list'] ) ) {
     184                return $query_args;
     185        }
     186
     187        /**
     188         * Filters the post query arguments for the 'Recent Drafts' dashboard widget.
     189         *
     190         * @since 4.4.0
     191         *
     192         * @param array $query_args The query arguments for the 'Recent Drafts' dashboard widget.
     193         */
     194        $query_args = apply_filters( 'dashboard_recent_drafts_query_args', $query_args );
     195
     196        return $query_args;
     197
    167198}
    168199
    169200/**
  • src/wp-includes/script-loader.php

    diff --git src/wp-includes/script-loader.php src/wp-includes/script-loader.php
    index be32073..02f665b 100644
    function wp_default_scripts( &$scripts ) { 
    336336                'ajax' => array(
    337337                        'url' => admin_url( 'admin-ajax.php', 'relative' ),
    338338                ),
     339                'formatting' => array(
     340                        'trimWordsMore'  => __( '&hellip;' ),
     341                        /*
     342                         * translators: If your word count is based on single characters (e.g. East Asian characters),
     343                         * enter 'characters_excluding_spaces' or 'characters_including_spaces'. Otherwise, enter 'words'.
     344                         * Do not translate into your own language.
     345                         */
     346                        'trimWordsByCharacter' => strpos( _x( 'words', 'Word count type. Do not translate!' ), 'characters' ) === 0 && preg_match( '/^utf\-?8$/i', get_option( 'blog_charset' ) ),
     347                        'userLocale' => get_user_locale(),
     348                        'dateFormat' => get_option( 'date_format' ),
     349                ),
    339350        ) );
    340351
    341352        $scripts->add( 'wp-backbone', "/wp-includes/js/wp-backbone$suffix.js", array('backbone', 'wp-util'), false, 1 );
    function wp_default_scripts( &$scripts ) { 
    721732                        'current' => __( 'Current Color' ),
    722733                ) );
    723734
    724                 $scripts->add( 'dashboard', "/wp-admin/js/dashboard$suffix.js", array( 'jquery', 'admin-comments', 'postbox' ), false, 1 );
     735                $scripts->add( 'dashboard', "/wp-admin/js/dashboard$suffix.js", array( 'jquery', 'admin-comments', 'postbox', 'wp-api', 'wp-backbone', 'wp-a11y', 'wp-util' ), false, 1 );
     736                did_action( 'init' ) && $scripts->localize( 'dashboard', 'quickDraft', array(
     737                        'currentUserId'  => get_current_user_id(),
     738                        'l10n' => array(
     739                                'error'            => __( 'An error has occurred. Please reload the page and try again.' ),
     740                                'newDraftCreated'  => __( 'Success. A new draft was created.' ),
     741                                'errorEmptyFields' => __( 'Error. All fields were empty.' ),
     742                                'noTitle'          => __( '(no title)' ),
     743                        )
     744                ) );
    725745
    726746                $scripts->add( 'list-revisions', "/wp-includes/js/wp-list-revisions$suffix.js" );
    727747
  • tests/qunit/index.html

    diff --git tests/qunit/index.html tests/qunit/index.html
    index 9a17ec2..baf0f46 100644
     
    6161                <script src="wp-admin/js/customize-base.js"></script>
    6262                <script src="wp-admin/js/customize-header.js"></script>
    6363                <script src="wp-includes/js/shortcode.js"></script>
     64                <script src="wp-includes/js/wp-util.js"></script>
    6465                <script src="wp-admin/js/customize-controls.js"></script>
    6566                <script src="wp-admin/js/customize-controls-utils.js"></script>
    6667                <script src="wp-admin/js/customize-nav-menus.js"></script>
  • 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..b24ec49
    - +  
     1/* global wp, jQuery, _ */
     2jQuery(function() {
     3        var originalSettings, sampleText;
     4
     5        module( 'wp.formatting', {
     6                beforeEach: function() {
     7                        originalSettings = _.clone( wp.formatting.settings );
     8                        wp.formatting.settings.trimWordsMore = '…';
     9                },
     10                afterEach: function() {
     11                        wp.formatting.settings = originalSettings;
     12                }
     13        });
     14
     15        sampleText = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec velit' +
     16                ' tellus, porta ac nisl vitae, gravida rutrum risus. Integer porttitor rhoncus' +
     17                ' convallis. Vestibulum et rutrum nulla. Aliquam efficitur vehicula tempor. Aliquam' +
     18                ' sodales ligula vitae lorem vulputate, vitae sagittis turpis volutpat. Donec sapien' +
     19                ' justo, facilisis eget dignissim vel, ultrices nec est. Vestibulum tempor purus dolor,' +
     20                ' tincidunt suscipit diam consectetur eu.';
     21
     22        test( 'trimWords() should default numWords to 55', function() {
     23                var expected, result;
     24
     25                result = wp.formatting.trimWords( sampleText );
     26                expected = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec velit tellus,' +
     27                        ' porta ac nisl vitae, gravida rutrum risus. Integer porttitor rhoncus convallis.' +
     28                        ' Vestibulum et rutrum nulla. Aliquam efficitur vehicula tempor. Aliquam sodales' +
     29                        ' ligula vitae lorem vulputate, vitae sagittis turpis volutpat. Donec sapien justo,' +
     30                        ' facilisis eget dignissim vel, ultrices nec est. Vestibulum tempor purus dolor,' +
     31                        ' tincidunt…';
     32
     33                equal( result, expected );
     34        });
     35
     36        test( 'trimWords() accepts custom numWords', function() {
     37                var expected, result;
     38
     39                result = wp.formatting.trimWords( sampleText, 4 );
     40                expected = 'Lorem ipsum dolor sit…';
     41
     42                equal( result, expected );
     43        });
     44
     45        test( 'trimWords() accepts custom truncation', function() {
     46                var expected, result;
     47
     48                result = wp.formatting.trimWords( sampleText, 4, '...' );
     49                expected = 'Lorem ipsum dolor sit...';
     50
     51                equal( result, expected );
     52        });
     53
     54        test( 'trimWords() separated by character if defined by settings', function() {
     55                var expected, result;
     56
     57                wp.formatting.settings.trimWordsByCharacter = true;
     58
     59                result = wp.formatting.trimWords( sampleText, 4 );
     60                expected = 'Lore…';
     61
     62                equal( result, expected );
     63        });
     64
     65        test( 'trimWords() separates by any whitespace', function() {
     66                var modifiedSampleText, expected, result;
     67
     68                modifiedSampleText = "Lorem\nipsum\tdolor sit\n\ramet";
     69                result = wp.formatting.trimWords( modifiedSampleText, 4 );
     70                expected = 'Lorem ipsum dolor sit…';
     71
     72                equal( result, expected );
     73        });
     74
     75        test( 'trimWords() does not truncate if fewer words than numWords', function() {
     76                var expected, result;
     77
     78                result = wp.formatting.trimWords( sampleText, Infinity );
     79
     80                equal( result, sampleText );
     81        });
     82});