Make WordPress Core

Ticket #35210: 35210_4.diff

File 35210_4.diff, 13.1 KB (added by delawski, 8 years ago)
  • src/wp-admin/css/customize-controls.css

    diff --git a/src/wp-admin/css/customize-controls.css b/src/wp-admin/css/customize-controls.css
    index f2fbc86..b04907e 100644
    a b body { 
    264264        box-sizing: border-box;
    265265}
    266266
     267#customize-notifications-area {
     268        position: absolute;
     269        top: 46px;
     270        width: 100%;
     271        max-height: 210px;
     272        overflow-x: hidden;
     273        overflow-y: auto;
     274        border-bottom: 1px solid #ddd;
     275}
     276
     277#customize-notifications-area > ul,
     278#customize-notifications-area .notice {
     279        margin: 0;
     280}
     281
     282#customize-notifications-area .notice + .notice {
     283        margin-top: 1px;
     284}
     285
    267286#customize-info,
    268287#customize-theme-controls .customize-pane-parent,
    269288#customize-theme-controls .customize-pane-child {
  • src/wp-admin/js/customize-controls.js

    diff --git a/src/wp-admin/js/customize-controls.js b/src/wp-admin/js/customize-controls.js
    index 56c061f..0032baf 100644
    a b  
    38883888        api.section = new api.Values({ defaultConstructor: api.Section });
    38893889        api.panel = new api.Values({ defaultConstructor: api.Panel });
    38903890
     3891        // Create the collection for global Notifications.
     3892        api.notifications = new api.Notifications();
     3893
    38913894        /**
    38923895         * An object that fetches a preview in the background of the document, which
    38933896         * allows for seamless replacement of an existing preview.
     
    53915394                                }
    53925395
    53935396                                var scrollTop = parentContainer.scrollTop(),
    5394                                         isScrollingUp = ( lastScrollTop ) ? scrollTop <= lastScrollTop : true;
     5397                                        scrollDirection;
    53955398
     5399                                if ( ! lastScrollTop ) {
     5400                                        scrollDirection = 1;
     5401                                } else {
     5402                                        if ( scrollTop === lastScrollTop ) {
     5403                                                scrollDirection = 0;
     5404                                        } else if ( scrollTop > lastScrollTop ) {
     5405                                                scrollDirection = 1;
     5406                                        } else {
     5407                                                scrollDirection = -1;
     5408                                        }
     5409                                }
    53965410                                lastScrollTop = scrollTop;
    5397                                 positionStickyHeader( activeHeader, scrollTop, isScrollingUp );
     5411                                positionStickyHeader( activeHeader, scrollTop, scrollDirection );
    53985412                        }, 8 ) );
    53995413
     5414                        // Update header position on sidebar layout change.
     5415                        parentContainer.on( 'customize:sidebar:updateLayout', function() {
     5416                                if ( activeHeader && activeHeader.element.hasClass( 'is-sticky' ) ) {
     5417                                        activeHeader.element.css( 'top', parentContainer.css( 'top' ) );
     5418                                }
     5419                        } );
     5420
    54005421                        // Release header element if it is sticky.
    54015422                        releaseStickyHeader = function( headerElement ) {
    54025423                                if ( ! headerElement.hasClass( 'is-sticky' ) ) {
     
    54105431
    54115432                        // Reset position of the sticky header.
    54125433                        resetStickyHeader = function( headerElement, headerParent ) {
    5413                                 headerElement
    5414                                         .removeClass( 'maybe-sticky is-in-view' )
    5415                                         .css( {
    5416                                                 width: '',
    5417                                                 top: ''
    5418                                         } );
    5419                                 headerParent.css( 'padding-top', '' );
     5434                                if ( headerElement.hasClass( 'is-in-view' ) ) {
     5435                                        headerElement
     5436                                                .removeClass( 'maybe-sticky is-in-view' )
     5437                                                .css( {
     5438                                                        width: '',
     5439                                                        top:   ''
     5440                                                } );
     5441                                        headerParent.css( 'padding-top', '' );
     5442                                }
    54205443                        };
    54215444
    54225445                        // Get header height.
     
    54305453                        };
    54315454
    54325455                        // Reposition header on throttled `scroll` event.
    5433                         positionStickyHeader = function( header, scrollTop, isScrollingUp ) {
     5456                        positionStickyHeader = function( header, scrollTop, scrollDirection ) {
     5457                                if ( 0 === scrollDirection ) {
     5458                                        return;
     5459                                }
     5460
    54345461                                var headerElement = header.element,
    54355462                                        headerParent = header.parent,
    54365463                                        headerHeight = header.height,
    54375464                                        headerTop = parseInt( headerElement.css( 'top' ), 10 ),
    54385465                                        maybeSticky = headerElement.hasClass( 'maybe-sticky' ),
    54395466                                        isSticky = headerElement.hasClass( 'is-sticky' ),
    5440                                         isInView = headerElement.hasClass( 'is-in-view' );
     5467                                        isInView = headerElement.hasClass( 'is-in-view' ),
     5468                                        isScrollingUp = ( -1 === scrollDirection );
     5469
    54415470
    54425471                                // When scrolling down, gradually hide sticky header.
    54435472                                if ( ! isScrollingUp ) {
     
    54805509                                                headerElement
    54815510                                                        .addClass( 'is-sticky' )
    54825511                                                        .css( {
    5483                                                                 top:   '',
     5512                                                                top:   parentContainer.css( 'top' ),
    54845513                                                                width: headerParent.outerWidth() + 'px'
    54855514                                                        } );
    54865515                                        }
  • src/wp-includes/class-wp-customize-manager.php

    diff --git a/src/wp-includes/class-wp-customize-manager.php b/src/wp-includes/class-wp-customize-manager.php
    index f96a220..1a6663e 100644
    a b public function __construct( $args = array() ) { 
    358358                add_action( 'customize_controls_init',            array( $this, 'prepare_controls' ) );
    359359                add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_control_scripts' ) );
    360360
    361                 // Render Panel, Section, and Control templates.
     361                // Render Common, Panel, Section, and Control templates.
     362                add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_common_templates' ), 1 );
    362363                add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_panel_templates' ), 1 );
    363364                add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_section_templates' ), 1 );
    364365                add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_control_templates' ), 1 );
    public function refresh_nonces() { 
    22712272        }
    22722273
    22732274        /**
     2275         * Render JS templates for all common elements.
     2276         *
     2277         * @since 4.7.0
     2278         * @access public
     2279         */
     2280        public function render_common_templates() {
     2281                ?>
     2282                <script type="text/html" id="tmpl-customize-notifications">
     2283                        <ul>
     2284                                <# _.each( data.notifications, function( notification ) { #>
     2285                                        <li class="notice notice-{{ notification.type || 'info' }} {{ data.altNotice ? 'notice-alt' : '' }} {{ notification.isDismissible ? 'is-dismissible' : '' }}" data-code="{{ notification.code }}" data-type="{{ notification.type }}">
     2286                                                <p>{{ notification.message || notification.code }}</p>
     2287                                                <# if ( notification.isDismissible ) { #>
     2288                                                        <button type="button" class="notice-dismiss"><span class="screen-reader-text">Dismiss</span></button>
     2289                                                <# } #>
     2290                                        </li>
     2291                                <# } ); #>
     2292                        </ul>
     2293                </script>
     2294                <?php
     2295        }
     2296
     2297        /**
    22742298         * Add a customize setting.
    22752299         *
    22762300         * @since 3.4.0
  • src/wp-includes/js/customize-base.js

    diff --git a/src/wp-includes/js/customize-base.js b/src/wp-includes/js/customize-base.js
    index a1528de..8659ef5 100644
    a b window.wp = window.wp || {}; 
    792792                }
    793793        });
    794794
     795        /**
     796         * A collection of observable notifications.
     797         *
     798         * @since 4.8.0
     799         * @class
     800         * @augments wp.customize.Values
     801         */
     802        api.Notifications = api.Values.extend({
     803
     804                /**
     805                 * The default constructor for items of the collection.
     806                 *
     807                 * @since 4.8.0
     808                 * @type {object}
     809                 */
     810                defaultConstructor: api.Notification,
     811
     812                /**
     813                 * The default template name for the notifications area.
     814                 *
     815                 * @since 4.8.0
     816                 * @type {string}
     817                 */
     818                defaultTemplateName: 'customize-notifications',
     819
     820                /**
     821                 * The notifications area parent container selector.
     822                 *
     823                 * @since 4.8.0
     824                 * @type {string}
     825                 */
     826                parentSelector: '#widgets-right',
     827
     828                /**
     829                 * Initialize notifications area.
     830                 *
     831                 * @since 4.8.0
     832                 * @constructor
     833                 * @param options
     834                 * @this {wp.customize.notifications}
     835                 */
     836                initialize: function( options ) {
     837                        var self = this,
     838                                debouncedRenderNotifications;
     839
     840                        $.extend( this, options || {} );
     841                        api.Values.prototype.initialize.call( self, null, options );
     842
     843                        _.bindAll( self, 'dismissNotification' );
     844
     845                        /*
     846                         * Note that this debounced/deferred rendering is needed because the 'remove' event
     847                         * is triggered just _before_ the notification is actually removed.
     848                         * Refer to: https://core.trac.wordpress.org/ticket/37269
     849                         */
     850                        debouncedRenderNotifications = _.debounce( function() {
     851                                self.render();
     852                        } );
     853                        self.bind( 'add', function( notification ) {
     854                                wp.a11y.speak( notification.message, 'assertive' );
     855                                debouncedRenderNotifications();
     856                        } );
     857                        self.bind( 'remove', debouncedRenderNotifications );
     858                },
     859
     860                /**
     861                 * Find and return notifications area container element.
     862                 *
     863                 * @since 4.8.0
     864                 * @returns {jQuery} Container jQuery element or an empty DIV.
     865                 * @this {wp.customize.notifications}
     866                 */
     867                getContainer: function() {
     868                        var self = this,
     869                                parent;
     870
     871                        if ( ! self.container ) {
     872                                parent = $( self.parentSelector );
     873
     874                                if ( ! parent || ! parent.length ) {
     875                                        parent = $( '<div></div>' );
     876                                }
     877                                self.container = $( '<div></div>', {
     878                                        id: 'customize-notifications-area'
     879                                } );
     880                                parent.prepend( self.container );
     881
     882                                self.container.on( 'click', '.notice-dismiss', self.dismissNotification );
     883                        }
     884
     885                        return self.container;
     886                },
     887
     888                /**
     889                 * Remove notifications area from the DOM.
     890                 *
     891                 * @since 4.8.0
     892                 * @this {wp.customize.notifications}
     893                 */
     894                destroy: function() {
     895                        this.container.next()
     896                                .css( 'top', '' )
     897                                .trigger( 'customize:sidebar:updateLayout' );
     898                        this.container.remove();
     899                        this.container = null;
     900                },
     901
     902                /**
     903                 * Render notifications area.
     904                 *
     905                 * @since 4.8.0
     906                 * @this {wp.customize.notifications}
     907                 */
     908                render: function() {
     909                        var self = this,
     910                                container = self.getContainer(),
     911                                sidebar = container.next(),
     912                                template = wp.template( self.defaultTemplateName ),
     913                                notifications, containerHeight, containerInitialTop;
     914
     915                        notifications = [];
     916                        self.each( function( notification ) {
     917                                notifications.unshift( notification );
     918                        } );
     919
     920                        if ( _.isEmpty( notifications ) ) {
     921                                self.destroy();
     922                        } else {
     923                                container.empty().append( $.trim(
     924                                        template( { notifications: notifications } )
     925                                ) );
     926                                sidebar.css( 'top', '' );
     927                                containerHeight = container.outerHeight() + 1;
     928                                containerInitialTop = parseInt( sidebar.css( 'top' ), 10 );
     929                                sidebar
     930                                        .css( 'top', containerInitialTop + containerHeight + 'px' )
     931                                        .trigger( 'customize:sidebar:updateLayout' );
     932                        }
     933                },
     934
     935                /**
     936                 * Remove notification from the collection on user request.
     937                 *
     938                 * @since 4.8.0
     939                 * @param {Event} e
     940                 * @this {wp.customize.notifications}
     941                 */
     942                dismissNotification: function( e ) {
     943                        var self = this,
     944                                code = $( e.currentTarget ).closest( '[data-code]' ).data( 'code' );
     945
     946                        if ( code && self.has( code ) ) {
     947                                self.remove( code );
     948                        }
     949                }
     950        });
     951
    795952        // The main API object is also a collection of all customizer settings.
    796953        api = $.extend( new api.Values(), api );
    797954
  • tests/qunit/index.html

    diff --git a/tests/qunit/index.html b/tests/qunit/index.html
    index 9a17ec2..4d4393b 100644
    a b <h3 class="accordion-section-title" tabindex="0"> 
    134134                                <# } #>
    135135                        </li>
    136136                </script>
     137                <script type="text/html" id="tmpl-customize-notifications">
     138                        <ul class="{{ ( data.listClass ) ? data.listClass : '' }}">
     139                                <# _.each( data.notifications, function( notification ) { #>
     140                                        <li data-code="{{ notification.code }}" data-type="{{ notification.type }}">
     141                                                <p>{{ notification.message || notification.code }}</p>
     142                                                <# if ( notification.isDismissible ) { #>
     143                                                        <button type="button" class="notice-dismiss"><span class="screen-reader-text">Dismiss</span></button>
     144                                                <# } #>
     145                                        </li>
     146                                <# } ); #>
     147                        </ul>
     148                </script>
    137149                <script type="text/html" id="tmpl-customize-control-notifications">
    138150                        <ul>
    139151                                <# _.each( data.notifications, function( notification ) { #>
  • tests/qunit/wp-admin/js/customize-base.js

    diff --git a/tests/qunit/wp-admin/js/customize-base.js b/tests/qunit/wp-admin/js/customize-base.js
    index 4f726bf..fb0b5d3 100644
    a b jQuery( function( $ ) { 
    203203                queryParams = wp.customize.utils.parseQueryString( 'a=1&b=' );
    204204                assert.ok( _.isEqual( queryParams, { 'a': '1', b: '' } ) );
    205205        } );
     206
     207        module( 'Customize Base: notifications collection' );
     208        test( 'Notifications collection exists', function() {
     209                ok( wp.customize.notifications );
     210                equal( wp.customize.notifications.defaultConstructor, wp.customize.Notification );
     211        } );
     212
     213        test( 'Notification objects are rendered as part of notifications collection', function () {
     214                var container = wp.customize.notifications.getContainer(),
     215                        items;
     216
     217                wp.customize.notifications.add( 'mycode-1', new wp.customize.Notification( 'mycode-1' ) );
     218                wp.customize.notifications.render();
     219                items = container.find( 'li' );
     220                equal( items.length, 1 );
     221                equal( items.first().data( 'code' ), 'mycode-1' );
     222
     223                wp.customize.notifications.add( 'mycode-2', new wp.customize.Notification( 'mycode-2', {
     224                        isDismissible: true
     225                } ) );
     226                wp.customize.notifications.render();
     227                items = container.find( 'li' );
     228                equal( items.length, 2 );
     229                equal( items.first().data( 'code' ), 'mycode-2' );
     230                equal( items.last().data( 'code' ), 'mycode-1' );
     231
     232                equal( items.first().find( '.notice-dismiss' ).length, 1 );
     233                equal( items.last().find( '.notice-dismiss' ).length, 0 );
     234
     235                wp.customize.notifications.remove( 'mycode-2' );
     236                wp.customize.notifications.render();
     237                items = container.find( 'li' );
     238                equal( items.length, 1 );
     239                equal( items.first().data( 'code' ), 'mycode-1' );
     240
     241                wp.customize.notifications.remove( 'mycode-1' );
     242                wp.customize.notifications.render();
     243                equal( container.parent().length, 0, 'Notifications area is removed from DOM if the collection is empty.' );
     244        } );
    206245});