From ce69e660bd038e80aec3bea6a10ab39bbdc0c04b Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Tue, 19 Sep 2017 05:39:37 +0000 Subject: [PATCH] Customize: Add notifications API to sections and panels. * Adds a `notifications` property to instances of `wp.customize.Panel` and `wp.customize.Section`. * Adds a `setupNotifications()` method to `Panel`, `Section`, and `Control`. * Adds a `getNotificationsContainerElement()` method to the `Panel` and `Section` classes, like `Control` has. * Replace hard-coded notification in header media section with a notification. * Limit rendering notifications to panels and sections that are expanded, and to controls that have an expanded section. See #34893, #35210, #38778. Fixes #38794. git-svn-id: https://develop.svn.wordpress.org/trunk@41390 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-admin/css/customize-controls.css | 41 +++++-- src/wp-admin/js/customize-controls.js | 101 +++++++++++++++--- .../class-wp-customize-manager.php | 4 - src/wp-includes/class-wp-customize-panel.php | 2 + .../class-wp-customize-section.php | 2 + src/wp-includes/script-loader.php | 1 + 6 files changed, 122 insertions(+), 29 deletions(-) diff --git a/src/wp-admin/css/customize-controls.css b/src/wp-admin/css/customize-controls.css index c4df304ad5..c1aa128a34 100644 --- a/src/wp-admin/css/customize-controls.css +++ b/src/wp-admin/css/customize-controls.css @@ -789,10 +789,14 @@ p.customize-section-description { transition: .15s box-shadow linear; } -.customize-control-notifications-container li.notice { +#customize-controls .customize-control-notifications-container li.notice { list-style: none; margin: 0 0 6px 0; - padding: 4px 8px; + padding: 9px 14px; + overflow: hidden; +} +#customize-controls .customize-control-notifications-container .notice.is-dismissible { + padding-right: 38px; } .customize-control-notifications-container li.notice:last-child { @@ -815,26 +819,45 @@ p.customize-section-description { position: absolute; top: 46px; width: 100%; - max-height: 210px; - overflow-x: hidden; - overflow-y: auto; border-bottom: 1px solid #ddd; display: block; padding: 0; margin: 0; } +#customize-controls #customize-notifications-area, +#customize-controls .customize-section-title > .customize-control-notifications-container, +#customize-controls .panel-meta > .customize-control-notifications-container { + max-height: 210px; + overflow-x: hidden; + overflow-y: auto; +} + #customize-controls #customize-notifications-area > ul, -#customize-controls #customize-notifications-area .notice { +#customize-controls #customize-notifications-area .notice, +#customize-controls .panel-meta > .customize-control-notifications-container, +#customize-controls .panel-meta > .customize-control-notifications-container .notice, +#customize-controls .customize-section-title > .customize-control-notifications-container, +#customize-controls .customize-section-title > .customize-control-notifications-container .notice { margin: 0; } -#customize-controls #customize-notifications-area .notice { +#customize-controls .panel-meta > .customize-control-notifications-container, +#customize-controls .customize-section-title > .customize-control-notifications-container { + border-top: 1px solid #ddd; +} +#customize-controls #customize-notifications-area .notice, +#customize-controls .panel-meta > .customize-control-notifications-container .notice, +#customize-controls .customize-section-title > .customize-control-notifications-container .notice { padding: 9px 14px; } -#customize-controls #customize-notifications-area .notice.is-dismissible { +#customize-controls #customize-notifications-area .notice.is-dismissible, +#customize-controls .panel-meta > .customize-control-notifications-container .notice.is-dismissible, +#customize-controls .customize-section-title > .customize-control-notifications-container .notice.is-dismissible { padding-right: 38px; } -#customize-controls #customize-notifications-area .notice + .notice { +#customize-controls #customize-notifications-area .notice + .notice, +#customize-controls .panel-meta > .customize-control-notifications-container .notice + .notice, +#customize-controls .customize-section-title > .customize-control-notifications-container .notice + .notice { margin-top: 1px; } diff --git a/src/wp-admin/js/customize-controls.js b/src/wp-admin/js/customize-controls.js index 4463e47b40..b483aea335 100644 --- a/src/wp-admin/js/customize-controls.js +++ b/src/wp-admin/js/customize-controls.js @@ -158,7 +158,7 @@ collection.container.toggle( 0 !== notifications.length ); // Short-circuit if there are no changes to the notifications. - if ( _.isEqual( notifications, collection.previousNotifications ) ) { + if ( collection.container.is( collection.previousContainer ) && _.isEqual( notifications, collection.previousNotifications ) ) { return; } @@ -185,6 +185,7 @@ }); collection.previousNotifications = notifications; + collection.previousContainer = collection.container; collection.trigger( 'rendered' ); } }); @@ -626,6 +627,7 @@ ); $.extend( container, options ); + container.notifications = new api.Notifications(); container.templateSelector = 'customize-' + container.containerType + '-' + container.params.type; container.container = $( container.params.content ); if ( 0 === container.container.length ) { @@ -657,6 +659,7 @@ }); container.deferred.embedded.done( function () { + container.setupNotifications(); container.attachEvents(); }); @@ -667,6 +670,39 @@ container.expanded.set( false ); }, + /** + * Get the element that will contain the notifications. + * + * @since 4.9.0 + * @returns {jQuery} Notification container element. + * @this {wp.customize.Control} + */ + getNotificationsContainerElement: function() { + var container = this; + return container.contentContainer.find( '.customize-control-notifications-container:first' ); + }, + + /** + * Set up notifications. + * + * @since 4.9.0 + * @returns {void} + */ + setupNotifications: function() { + var container = this, renderNotifications; + container.notifications.container = container.getNotificationsContainerElement(); + + // Render notifications when they change and when the construct is expanded. + renderNotifications = function() { + if ( container.expanded.get() ) { + container.notifications.render(); + } + }; + container.expanded.bind( renderNotifications ); + renderNotifications(); + container.notifications.bind( 'change', _.debounce( renderNotifications ) ); + }, + /** * @since 4.1.0 * @@ -2162,17 +2198,7 @@ // After the control is embedded on the page, invoke the "ready" method. control.deferred.embedded.done( function () { - var renderNotifications = function() { - control.notifications.render(); - }; - control.notifications.container = control.getNotificationsContainerElement(); - control.notifications.bind( 'rendered', function() { - var notifications = control.notifications.get(); - control.container.toggleClass( 'has-notifications', 0 !== notifications.length ); - control.container.toggleClass( 'has-error', 0 !== _.where( notifications, { type: 'error' } ).length ); - } ); - renderNotifications(); - control.notifications.bind( 'change', _.debounce( renderNotifications ) ); + control.setupNotifications(); control.ready(); }); }, @@ -2269,6 +2295,47 @@ return notificationsContainer; }, + /** + * Set up notifications. + * + * @since 4.9.0 + * @returns {void} + */ + setupNotifications: function() { + var control = this, renderNotificationsIfVisible, onSectionAssigned; + + control.notifications.container = control.getNotificationsContainerElement(); + + renderNotificationsIfVisible = function() { + var sectionId = control.section(); + if ( ! sectionId || ( api.section.has( sectionId ) && api.section( sectionId ).expanded() ) ) { + control.notifications.render(); + } + }; + + control.notifications.bind( 'rendered', function() { + var notifications = control.notifications.get(); + control.container.toggleClass( 'has-notifications', 0 !== notifications.length ); + control.container.toggleClass( 'has-error', 0 !== _.where( notifications, { type: 'error' } ).length ); + } ); + + onSectionAssigned = function( newSectionId, oldSectionId ) { + if ( oldSectionId && api.section.has( oldSectionId ) ) { + api.section( oldSectionId ).expanded.unbind( renderNotificationsIfVisible ); + } + if ( newSectionId ) { + api.section( newSectionId, function( section ) { + section.expanded.bind( renderNotificationsIfVisible ); + renderNotificationsIfVisible(); + }); + } + }; + + control.section.bind( onSectionAssigned ); + onSectionAssigned( control.section.get() ); + control.notifications.bind( 'change', _.debounce( renderNotificationsIfVisible ) ); + }, + /** * Render notifications. * @@ -5854,15 +5921,17 @@ api.control( 'header_video', function( headerVideoControl ) { headerVideoControl.deferred.embedded.done( function() { var toggleNotice = function() { - var section = api.section( headerVideoControl.section() ), notice; + var section = api.section( headerVideoControl.section() ), noticeCode = 'video_header_not_available'; if ( ! section ) { return; } - notice = section.container.find( '.header-video-not-currently-previewable:first' ); if ( headerVideoControl.active.get() ) { - notice.stop().slideUp( 'fast' ); + section.notifications.remove( noticeCode ); } else { - notice.stop().slideDown( 'fast' ); + section.notifications.add( noticeCode, new api.Notification( noticeCode, { + type: 'info', + message: api.l10n.videoHeaderNotice + } ) ); } }; toggleNotice(); diff --git a/src/wp-includes/class-wp-customize-manager.php b/src/wp-includes/class-wp-customize-manager.php index 7bd1489543..fa97e3f680 100644 --- a/src/wp-includes/class-wp-customize-manager.php +++ b/src/wp-includes/class-wp-customize-manager.php @@ -3928,10 +3928,6 @@ final class WP_Customize_Manager { $title = __( 'Header Media' ); $description = '

' . __( 'If you add a video, the image will be used as a fallback while the video loads.' ) . '

'; - // @todo Customizer sections should support having notifications just like controls do. See . - $description .= ''; $width = absint( get_theme_support( 'custom-header', 'width' ) ); $height = absint( get_theme_support( 'custom-header', 'height' ) ); if ( $width && $height ) { diff --git a/src/wp-includes/class-wp-customize-panel.php b/src/wp-includes/class-wp-customize-panel.php index de839f5ce0..53b90bde65 100644 --- a/src/wp-includes/class-wp-customize-panel.php +++ b/src/wp-includes/class-wp-customize-panel.php @@ -371,6 +371,8 @@ class WP_Customize_Panel { {{{ data.description }}} <# } #> + +
<# } #> + +
<# if ( data.description && ! data.description_hidden ) { #> diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 15fb88901a..abfc40a607 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -563,6 +563,7 @@ function wp_default_scripts( &$scripts ) { 'expandSidebar' => _x( 'Show Controls', 'label for hide controls button without length constraints' ), 'untitledBlogName' => __( '(Untitled)' ), 'serverSaveError' => __( 'Failed connecting to the server. Please try saving again.' ), + 'videoHeaderNotice' => __( 'This theme doesn\'t support video headers on this page. Navigate to the front page or another page that supports video headers.' ), // Used for overriding the file types allowed in plupload. 'allowedFiles' => __( 'Allowed Files' ), 'customCssError' => wp_array_slice_assoc(