diff --git a/src/wp-admin/css/customize-nav-menus.css b/src/wp-admin/css/customize-nav-menus.css index aa0deed41a..fc14598fd7 100644 --- a/src/wp-admin/css/customize-nav-menus.css +++ b/src/wp-admin/css/customize-nav-menus.css @@ -654,7 +654,9 @@ button.not-a-button { } #custom-menu-item-name.invalid, -#custom-menu-item-url.invalid { +#custom-menu-item-url.invalid, +.menu-name-field.invalid, +.menu-name-field.invalid:focus { border: 1px solid #f00; } diff --git a/src/wp-admin/js/customize-nav-menus.js b/src/wp-admin/js/customize-nav-menus.js index 278951df4b..42e362c46b 100644 --- a/src/wp-admin/js/customize-nav-menus.js +++ b/src/wp-admin/js/customize-nav-menus.js @@ -833,6 +833,7 @@ button.removeClass( 'open' ); button.attr( 'aria-expanded', 'false' ); content.slideUp( 'fast' ); + content.find( '.menu-name-field' ).removeClass( 'invalid' ); } } }); @@ -869,7 +870,7 @@ return; } menuId = matches[1]; - option = new Option( setting().name, menuId ); + option = new Option( displayNavMenuName( setting().name ), menuId ); control.container.find( 'select' ).append( option ); }); api.bind( 'remove', function( setting ) { @@ -895,7 +896,7 @@ } control.container.find( 'option[value=' + menuId + ']' ).remove(); } else { - control.container.find( 'option[value=' + menuId + ']' ).text( setting().name ); + control.container.find( 'option[value=' + menuId + ']' ).text( displayNavMenuName( setting().name ) ); } }); } @@ -1605,7 +1606,9 @@ */ ready: function() { var control = this, - menuId = control.params.menu_id; + menuId = control.params.menu_id, + menu = control.setting(), + name; if ( 'undefined' === typeof this.params.menu_id ) { throw new Error( 'params.menu_id was not defined' ); @@ -1636,17 +1639,19 @@ this._setupTitle(); // Add menu to Custom Menu widgets. - if ( control.setting() ) { + if ( menu ) { + name = displayNavMenuName( menu.name ); + api.control.each( function( widgetControl ) { if ( ! widgetControl.extended( api.controlConstructor.widget_form ) || 'nav_menu' !== widgetControl.params.widget_id_base ) { return; } var select = widgetControl.container.find( 'select' ); if ( select.find( 'option[value=' + String( menuId ) + ']' ).length === 0 ) { - select.append( new Option( control.setting().name, menuId ) ); + select.append( new Option( name, menuId ) ); } } ); - $( '#available-widgets-list .widget-inside:has(input.id_base[value=nav_menu]) select:first' ).append( new Option( control.setting().name, menuId ) ); + $( '#available-widgets-list .widget-inside:has(input.id_base[value=nav_menu]) select:first' ).append( new Option( name, menuId ) ); } }, @@ -1677,18 +1682,20 @@ }); control.setting.bind( function( to ) { + var name; if ( false === to ) { control._handleDeletion(); } else { // Update names in the Custom Menu widgets. + name = displayNavMenuName( to.name ); api.control.each( function( widgetControl ) { if ( ! widgetControl.extended( api.controlConstructor.widget_form ) || 'nav_menu' !== widgetControl.params.widget_id_base ) { return; } var select = widgetControl.container.find( 'select' ); - select.find( 'option[value=' + String( menuId ) + ']' ).text( to.name ); + select.find( 'option[value=' + String( menuId ) + ']' ).text( name ); }); - $( '#available-widgets-list .widget-inside:has(input.id_base[value=nav_menu]) select:first option[value=' + String( menuId ) + ']' ).text( to.name ); + $( '#available-widgets-list .widget-inside:has(input.id_base[value=nav_menu]) select:first option[value=' + String( menuId ) + ']' ).text( name ); } } ); @@ -1847,7 +1854,7 @@ if ( ! selectedMenuId || ! menuSetting || ! menuSetting() ) { container.find( '.theme-location-set' ).hide(); } else { - container.find( '.theme-location-set' ).show().find( 'span' ).text( menuSetting().name ); + container.find( '.theme-location-set' ).show().find( 'span' ).text( displayNavMenuName( menuSetting().name ) ); } }; @@ -1879,41 +1886,39 @@ return; } - // Empty names are not allowed (will not be saved), don't update to one. - if ( menu.name ) { - var section = control.container.closest( '.accordion-section' ), - menuId = control.params.menu_id, - controlTitle = section.find( '.accordion-section-title' ), - sectionTitle = section.find( '.customize-section-title h3' ), - location = section.find( '.menu-in-location' ), - action = sectionTitle.find( '.customize-action' ); + var section = control.container.closest( '.accordion-section' ), + menuId = control.params.menu_id, + controlTitle = section.find( '.accordion-section-title' ), + sectionTitle = section.find( '.customize-section-title h3' ), + location = section.find( '.menu-in-location' ), + action = sectionTitle.find( '.customize-action' ), + name = displayNavMenuName( menu.name ); - // Update the control title - controlTitle.text( menu.name ); - if ( location.length ) { - location.appendTo( controlTitle ); - } - - // Update the section title - sectionTitle.text( menu.name ); - if ( action.length ) { - action.prependTo( sectionTitle ); - } - - // Update the nav menu name in location selects. - api.control.each( function( control ) { - if ( /^nav_menu_locations\[/.test( control.id ) ) { - control.container.find( 'option[value=' + menuId + ']' ).text( menu.name ); - } - } ); - - // Update the nav menu name in all location checkboxes. - section.find( '.customize-control-checkbox input' ).each( function() { - if ( $( this ).prop( 'checked' ) ) { - $( '.current-menu-location-name-' + $( this ).data( 'location-id' ) ).text( menu.name ); - } - } ); + // Update the control title + controlTitle.text( name ); + if ( location.length ) { + location.appendTo( controlTitle ); } + + // Update the section title + sectionTitle.text( name ); + if ( action.length ) { + action.prependTo( sectionTitle ); + } + + // Update the nav menu name in location selects. + api.control.each( function( control ) { + if ( /^nav_menu_locations\[/.test( control.id ) ) { + control.container.find( 'option[value=' + menuId + ']' ).text( name ); + } + } ); + + // Update the nav menu name in all location checkboxes. + section.find( '.customize-control-checkbox input' ).each( function() { + if ( $( this ).prop( 'checked' ) ) { + $( '.current-menu-location-name-' + $( this ).data( 'location-id' ) ).text( name ); + } + } ); } ); }, @@ -2151,8 +2156,6 @@ /** * Create the new menu with the name supplied. - * - * @returns {boolean} */ submit: function() { @@ -2164,6 +2167,12 @@ customizeId, placeholderId = api.Menus.generatePlaceholderAutoIncrementId(); + if ( ! name ) { + nameInput.addClass( 'invalid' ); + nameInput.focus(); + return; + } + customizeId = 'nav_menu[' + String( placeholderId ) + ']'; // Register the menu control setting. @@ -2189,7 +2198,7 @@ params: { id: customizeId, panel: 'nav_menus', - title: name, + title: displayNavMenuName( name ), customizeAction: api.Menus.data.l10n.customizingMenus, type: 'nav_menu', priority: 10, @@ -2200,6 +2209,7 @@ // Clear name field. nameInput.val( '' ); + nameInput.removeClass( 'invalid' ); wp.a11y.speak( api.Menus.data.l10n.menuAdded ); @@ -2269,7 +2279,7 @@ var insertedMenuIdMapping = {}; _( data.nav_menu_updates ).each(function( update ) { - var oldCustomizeId, newCustomizeId, oldSetting, newSetting, settingValue, oldSection, newSection; + var oldCustomizeId, newCustomizeId, customizeId, oldSetting, newSetting, setting, settingValue, oldSection, newSection, wasSaved; if ( 'inserted' === update.status ) { if ( ! update.previous_term_id ) { throw new Error( 'Expected previous_term_id' ); @@ -2291,7 +2301,7 @@ if ( ! settingValue ) { throw new Error( 'Did not expect setting to be empty (deleted).' ); } - settingValue = _.clone( settingValue ); + settingValue = $.extend( _.clone( settingValue ), update.saved_value ); insertedMenuIdMapping[ update.previous_term_id ] = update.term_id; newCustomizeId = 'nav_menu[' + String( update.term_id ) + ']'; @@ -2349,6 +2359,20 @@ } // @todo Update the Custom Menu selects, ensuring the newly-inserted IDs are used for any that have selected a placeholder menu. + } else if ( 'updated' === update.status ) { + customizeId = 'nav_menu[' + String( update.term_id ) + ']'; + if ( ! api.has( customizeId ) ) { + throw new Error( 'Expected setting to exist: ' + customizeId ); + } + + // Make sure the setting gets updated with its sanitized server value (specifically the conflict-resolved name). + setting = api( customizeId ); + if ( ! _.isEqual( update.saved_value, setting.get() ) ) { + wasSaved = api.state( 'saved' ).get(); + setting.set( update.saved_value ); + setting._dirty = false; + api.state( 'saved' ).set( wasSaved ); + } } } ); @@ -2496,4 +2520,17 @@ return 'nav_menu_item[' + menuItemId + ']'; } + /** + * Apply sanitize_text_field()-like logic to the supplied name, returning a + * "unnammed" fallback string if the name is then empty. + * + * @param {string} name + * @returns {string} + */ + function displayNavMenuName( name ) { + name = $( '