wp/wp-admin/js/customize-nav-menus.js
changeset 16 a86126ab1dd4
parent 9 177826044cd9
child 18 be944660c56a
--- a/wp/wp-admin/js/customize-nav-menus.js	Tue Oct 22 16:11:46 2019 +0200
+++ b/wp/wp-admin/js/customize-nav-menus.js	Tue Dec 15 13:49:49 2020 +0100
@@ -95,7 +95,7 @@
 	 * @since 4.7.0
 	 * @alias wp.customize.Menus.insertAutoDraftPost
 	 *
-	 * @param {object} params - Parameters for the draft post to create.
+	 * @param {Object} params - Parameters for the draft post to create.
 	 * @param {string} params.post_type - Post type to add.
 	 * @param {string} params.post_title - Post title to use.
 	 * @return {jQuery.promise} Promise resolved with the added post.
@@ -203,9 +203,11 @@
 
 			_.bindAll( this, 'close' );
 
-			// If the available menu items panel is open and the customize controls are
-			// interacted with (other than an item being deleted), then close the
-			// available menu items panel. Also close on back button click.
+			/*
+			 * If the available menu items panel is open and the customize controls
+			 * are interacted with (other than an item being deleted), then close
+			 * the available menu items panel. Also close on back button click.
+			 */
 			$( '#customize-controls, .customize-section-back' ).on( 'click keydown', function( e ) {
 				var isDeleteBtn = $( e.target ).is( '.item-delete, .item-delete *' ),
 					isAddNewBtn = $( e.target ).is( '.add-new-menu-item, .add-new-menu-item *' );
@@ -214,9 +216,9 @@
 				}
 			} );
 
-			// Clear the search results and trigger a `keyup` event to fire a new search.
+			// Clear the search results and trigger an `input` event to fire a new search.
 			this.$clearResults.on( 'click', function() {
-				self.$search.val( '' ).focus().trigger( 'keyup' );
+				self.$search.val( '' ).focus().trigger( 'input' );
 			} );
 
 			this.$el.on( 'input', '#custom-menu-item-name.invalid, #custom-menu-item-url.invalid', function() {
@@ -252,7 +254,7 @@
 				}
 			});
 
-			// Close the panel if the URL in the preview changes
+			// Close the panel if the URL in the preview changes.
 			api.previewer.bind( 'url', this.close );
 
 			self.delegateEvents();
@@ -385,9 +387,9 @@
 		 * @since 4.7.0 Changed function signature to take list of item types instead of single type/object.
 		 * @access private
 		 *
-		 * @param {Array.<object>} itemTypes List of objects containing type and key.
+		 * @param {Array.<Object>} itemTypes List of objects containing type and key.
 		 * @param {string} deprecated Formerly the object parameter.
-		 * @returns {void}
+		 * @return {void}
 		 */
 		loadItems: function( itemTypes, deprecated ) {
 			var self = this, _itemTypes, requestItemTypes = [], params, request, itemTemplate, availableMenuItemContainers = {};
@@ -496,7 +498,7 @@
 
 		// Submit handler for keypress and click on menu item.
 		_submit: function( event ) {
-			// Only proceed with keypress if it is Enter or Spacebar
+			// Only proceed with keypress if it is Enter or Spacebar.
 			if ( 'keypress' === event.type && ( 13 !== event.which && 32 !== event.which ) ) {
 				return;
 			}
@@ -584,7 +586,7 @@
 			this.currentMenuControl.addItemToMenu( menuItem );
 
 			// Reset the custom link form.
-			itemUrl.val( 'http://' );
+			itemUrl.val( '' ).attr( 'placeholder', 'https://' );
 			itemName.val( '' );
 		},
 
@@ -595,7 +597,7 @@
 		 * @private
 		 *
 		 * @param {jQuery.Event} event Event.
-		 * @returns {void}
+		 * @return {void}
 		 */
 		_submitNew: function( event ) {
 			var container;
@@ -621,7 +623,7 @@
 		 * @private
 		 *
 		 * @param {jQuery} container
-		 * @returns {void}
+		 * @return {void}
 		 */
 		submitNew: function( container ) {
 			var panel = this,
@@ -717,7 +719,7 @@
 			this.$search.focus();
 		},
 
-		// Closes the panel
+		// Closes the panel.
 		close: function( options ) {
 			options = options || {};
 
@@ -731,7 +733,7 @@
 			$( 'body' ).removeClass( 'adding-menu-items' );
 			$( '#available-menu-items .menu-item-handle.item-added' ).removeClass( 'item-added' );
 
-			this.$search.val( '' ).trigger( 'keyup' );
+			this.$search.val( '' ).trigger( 'input' );
 		},
 
 		// Add a few keyboard enhancements to the panel.
@@ -741,7 +743,7 @@
 				isBackTab = ( 9 === event.which && event.shiftKey ),
 				isSearchFocused = $( event.target ).is( this.$search );
 
-			// If enter pressed but nothing entered, don't do anything
+			// If enter pressed but nothing entered, don't do anything.
 			if ( isEnter && ! this.$search.val() ) {
 				return;
 			}
@@ -781,7 +783,7 @@
 				}
 				event.preventDefault();
 
-				// Hide description
+				// Hide description.
 				if ( content.not( ':hidden' ) ) {
 					content.slideUp( 'fast' );
 					help.attr( 'aria-expanded', 'false' );
@@ -802,7 +804,7 @@
 				return false;
 			} );
 
-			// Help toggle
+			// Help toggle.
 			help.on( 'click keydown', function( event ) {
 				if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
 					return;
@@ -843,7 +845,7 @@
 		 * @since 4.3.0
 		 * @private
 		 *
-		 * @returns {void}
+		 * @return {void}
 		 */
 		saveManageColumnsState: _.debounce( function() {
 			var panel = this;
@@ -877,7 +879,7 @@
 		 * @since 4.3.0
 		 * @private
 		 *
-		 * @returns {Array} Fields (columns) that are hidden.
+		 * @return {Array} Fields (columns) that are hidden.
 		 */
 		hidden: function() {
 			return $( '.hide-column-tog' ).not( ':checked' ).map( function() {
@@ -903,7 +905,7 @@
 		 *
 		 * @since 4.3.0
 		 *
-		 * @param {String} id
+		 * @param {string} id
 		 * @param {Object} options
 		 */
 		initialize: function( id, options ) {
@@ -973,7 +975,7 @@
 			 * Update the active field class for the content container for a given checkbox toggle.
 			 *
 			 * @this {jQuery}
-			 * @returns {void}
+			 * @return {void}
 			 */
 			handleFieldActiveToggle = function() {
 				var className = 'field-' + $( this ).val() + '-active';
@@ -1062,7 +1064,7 @@
 				menuAutoAddControl.active.set( true );
 			}
 
-			// Add the control for deleting the menu
+			// Add the control for deleting the menu.
 			menuDeleteControlId = section.id + '[delete]';
 			menuDeleteControl = api.control( menuDeleteControlId );
 			if ( ! menuDeleteControl ) {
@@ -1126,7 +1128,7 @@
 				wpNavMenu.menuList = section.contentContainer;
 				wpNavMenu.targetList = wpNavMenu.menuList;
 
-				// Add attributes needed by wpNavMenu
+				// Add attributes needed by wpNavMenu.
 				$( '#menu-to-edit' ).removeAttr( 'id' );
 				wpNavMenu.menuList.attr( 'id', 'menu-to-edit' ).addClass( 'menu' );
 
@@ -1145,7 +1147,8 @@
 						wpNavMenu.initSortables(); // Depends on menu-to-edit ID being set above.
 						section.deferred.initSortables.resolve( wpNavMenu.menuList ); // Now MenuControl can extend the sortable.
 
-						// @todo Note that wp.customize.reflowPaneContents() is debounced, so this immediate change will show a slight flicker while priorities get updated.
+						// @todo Note that wp.customize.reflowPaneContents() is debounced,
+						// so this immediate change will show a slight flicker while priorities get updated.
 						api.control( 'nav_menu[' + String( section.params.menu_id ) + ']' ).reflowMenuItems();
 					}
 					if ( _.isFunction( completeCallback ) ) {
@@ -1166,7 +1169,7 @@
 		 *
 		 * @since 4.9.0
 		 *
-		 * @returns {void}
+		 * @return {void}
 		 */
 		highlightNewItemButton: function() {
 			api.utils.highlightButton( this.contentContainer.find( '.add-new-menu-item' ), { delay: 2000 } );
@@ -1179,7 +1182,7 @@
 	 * @since 4.9.0
 	 *
 	 * @param {string} [name=''] Nav menu name.
-	 * @returns {wp.customize.Menus.MenuSection} Added nav menu.
+	 * @return {wp.customize.Menus.MenuSection} Added nav menu.
 	 */
 	api.Menus.createNavMenu = function createNavMenu( name ) {
 		var customizeId, placeholderId, setting;
@@ -1263,7 +1266,7 @@
 			 * Get number of non-deleted nav menus.
 			 *
 			 * @since 4.9.0
-			 * @returns {number} Count.
+			 * @return {number} Count.
 			 */
 			function getNavMenuCount() {
 				var count = 0;
@@ -1279,7 +1282,7 @@
 			 * Update visibility of notice to prompt users to create menus.
 			 *
 			 * @since 4.9.0
-			 * @returns {void}
+			 * @return {void}
 			 */
 			function updateNoticeVisibility() {
 				container.find( '.add-new-menu-notice' ).prop( 'hidden', getNavMenuCount() > 0 );
@@ -1290,7 +1293,7 @@
 			 *
 			 * @since 4.9.0
 			 * @param {wp.customize.Setting} setting - Added setting.
-			 * @returns {void}
+			 * @return {void}
 			 */
 			function addChangeEventListener( setting ) {
 				if ( navMenuSettingPattern.test( setting.id ) ) {
@@ -1304,7 +1307,7 @@
 			 *
 			 * @since 4.9.0
 			 * @param {wp.customize.Setting} setting - Removed setting.
-			 * @returns {void}
+			 * @return {void}
 			 */
 			function removeChangeEventListener( setting ) {
 				if ( navMenuSettingPattern.test( setting.id ) ) {
@@ -1415,7 +1418,7 @@
 					navMenuLocationSetting = api( 'nav_menu_locations[' + checkbox.data( 'location-id' ) + ']' );
 					navMenuLocationSetting.set( menuSection.params.menu_id );
 
-					// Reset state for next new menu
+					// Reset state for next new menu.
 					checkbox.prop( 'checked', false );
 				}
 			} );
@@ -1439,7 +1442,7 @@
 		 * @since 4.9.0
 		 *
 		 * @param {string|null} locationId - The ID of the location to select. `null` clears all selections.
-		 * @returns {void}
+		 * @return {void}
 		 */
 		selectDefaultLocation: function( locationId ) {
 			var locationControl = api.control( this.id + '[locations]' ),
@@ -1698,7 +1701,8 @@
 			control.elements.classes = new api.Element( control.container.find( '.edit-menu-item-classes' ) );
 			control.elements.xfn = new api.Element( control.container.find( '.edit-menu-item-xfn' ) );
 			control.elements.description = new api.Element( control.container.find( '.edit-menu-item-description' ) );
-			// @todo allow other elements, added by plugins, to be automatically picked up here; allow additional values to be added to setting array.
+			// @todo Allow other elements, added by plugins, to be automatically picked up here;
+			// allow additional values to be added to setting array.
 
 			_.each( control.elements, function( element, property ) {
 				element.bind(function( value ) {
@@ -1790,8 +1794,12 @@
 			$removeBtn = control.container.find( '.item-delete' );
 
 			$removeBtn.on( 'click', function() {
-				// Find an adjacent element to add focus to when this menu item goes away
-				var addingItems = true, $adjacentFocusTarget, $next, $prev;
+				// Find an adjacent element to add focus to when this menu item goes away.
+				var addingItems = true, $adjacentFocusTarget, $next, $prev,
+					instanceCounter = 0, // Instance count of the menu item deleted.
+					deleteItemOriginalItemId = control.params.original_item_id,
+					addedItems = control.getMenuControl().$sectionContent.find( '.menu-item' ),
+					availableMenuItem;
 
 				if ( ! $( 'body' ).hasClass( 'adding-menu-items' ) ) {
 					addingItems = false;
@@ -1808,10 +1816,43 @@
 					$adjacentFocusTarget = control.container.nextAll( '.customize-control-nav_menu' ).find( '.add-new-menu-item' ).first();
 				}
 
+				/*
+				 * If the menu item deleted is the only of its instance left,
+				 * remove the check icon of this menu item in the right panel.
+				 */
+				_.each( addedItems, function( addedItem ) {
+					var menuItemId, menuItemControl, matches;
+
+					// This is because menu item that's deleted is just hidden.
+					if ( ! $( addedItem ).is( ':visible' ) ) {
+						return;
+					}
+
+					matches = addedItem.getAttribute( 'id' ).match( /^customize-control-nav_menu_item-(-?\d+)$/, '' );
+					if ( ! matches ) {
+						return;
+					}
+
+					menuItemId      = parseInt( matches[1], 10 );
+					menuItemControl = api.control( 'nav_menu_item[' + String( menuItemId ) + ']' );
+
+					// Check for duplicate menu items.
+					if ( menuItemControl && deleteItemOriginalItemId == menuItemControl.params.original_item_id ) {
+						instanceCounter++;
+					}
+				} );
+
+				if ( instanceCounter <= 1 ) {
+					// Revert the check icon to add icon.
+					availableMenuItem = $( '#menu-item-tpl-' + control.params.original_item_id );
+					availableMenuItem.removeClass( 'selected' );
+					availableMenuItem.find( '.menu-item-handle' ).removeClass( 'item-added' );
+				}
+
 				control.container.slideUp( function() {
 					control.setting.set( false );
 					wp.a11y.speak( api.Menus.data.l10n.itemDeleted );
-					$adjacentFocusTarget.focus(); // keyboard accessibility
+					$adjacentFocusTarget.focus(); // Keyboard accessibility.
 				} );
 
 				control.setting.set( false );
@@ -1870,7 +1911,7 @@
 
 		/**
 		 *
-		 * @returns {number}
+		 * @return {number}
 		 */
 		getDepth: function() {
 			var control = this, setting = control.setting(), depth = 0;
@@ -1921,7 +1962,6 @@
 			control.params.target = settingValue.target;
 			control.params.attr_title = settingValue.attr_title;
 			control.params.classes = _.isArray( settingValue.classes ) ? settingValue.classes.join( ' ' ) : settingValue.classes;
-			control.params.attr_title = settingValue.attr_title;
 			control.params.xfn = settingValue.xfn;
 			control.params.description = settingValue.description;
 			control.params.parent = settingValue.menu_item_parent;
@@ -1963,7 +2003,7 @@
 		 *
 		 * @param {Boolean} expanded
 		 * @param {Object} [params]
-		 * @returns {Boolean} false if state already applied
+		 * @return {Boolean} False if state already applied.
 		 */
 		_toggleExpanded: api.Section.prototype._toggleExpanded,
 
@@ -1971,7 +2011,7 @@
 		 * @since 4.6.0
 		 *
 		 * @param {Object} [params]
-		 * @returns {Boolean} false if already expanded
+		 * @return {Boolean} False if already expanded.
 		 */
 		expand: api.Section.prototype.expand,
 
@@ -1991,7 +2031,7 @@
 		 * @since 4.6.0
 		 *
 		 * @param {Object} [params]
-		 * @returns {Boolean} false if already collapsed
+		 * @return {Boolean} False if already collapsed.
 		 */
 		collapse: api.Section.prototype.collapse,
 
@@ -2172,7 +2212,7 @@
 		 *
 		 * @private
 		 *
-		 * @param {Number} offset 1|-1
+		 * @param {number} offset 1|-1
 		 */
 		_changePosition: function( offset ) {
 			var control = this,
@@ -2232,7 +2272,7 @@
 		 *
 		 * @private
 		 *
-		 * @param {Number} offset 1|-1
+		 * @param {number} offset 1|-1
 		 */
 		_changeDepth: function( offset ) {
 			if ( 1 !== offset && -1 !== offset ) {
@@ -2429,7 +2469,7 @@
 		 * @since 4.9.0
 		 *
 		 * @param {Object.<string,boolean>} selections - A map of location selections.
-		 * @returns {void}
+		 * @return {void}
 		 */
 		setSelections: function( selections ) {
 			this.container.find( '.menu-location' ).each( function( i, checkboxNode ) {
@@ -2615,7 +2655,7 @@
 		 * Notice that the UI aspects here are handled by wpNavMenu.initSortables()
 		 * which is called in MenuSection.onChangeExpanded()
 		 *
-		 * @param {object} menuList - The element that has sortable().
+		 * @param {Object} menuList - The element that has sortable().
 		 */
 		_setupSortable: function( menuList ) {
 			var control = this;
@@ -2779,13 +2819,13 @@
 					action = sectionTitle.find( '.customize-action' ),
 					name = displayNavMenuName( menu.name );
 
-				// Update the control title
+				// Update the control title.
 				controlTitle.text( name );
 				if ( location.length ) {
 					location.appendTo( controlTitle );
 				}
 
-				// Update the section title
+				// Update the section title.
 				sectionTitle.text( name );
 				if ( action.length ) {
 					action.prependTo( sectionTitle );
@@ -2814,7 +2854,7 @@
 		/**
 		 * Enable/disable the reordering UI
 		 *
-		 * @param {Boolean} showOrHide to enable/disable reordering
+		 * @param {boolean} showOrHide to enable/disable reordering
 		 */
 		toggleReordering: function( showOrHide ) {
 			var addNewItemBtn = this.container.find( '.add-new-menu-item' ),
@@ -2954,11 +2994,12 @@
 		/**
 		 * Add a new item to this menu.
 		 *
-		 * @param {object} item - Value for the nav_menu_item setting to be created.
-		 * @returns {wp.customize.Menus.controlConstructor.nav_menu_item} The newly-created nav_menu_item control instance.
+		 * @param {Object} item - Value for the nav_menu_item setting to be created.
+		 * @return {wp.customize.Menus.controlConstructor.nav_menu_item} The newly-created nav_menu_item control instance.
 		 */
 		addItemToMenu: function( item ) {
-			var menuControl = this, customizeId, settingArgs, setting, menuItemControl, placeholderId, position = 0, priority = 10;
+			var menuControl = this, customizeId, settingArgs, setting, menuItemControl, placeholderId, position = 0, priority = 10,
+				originalItemId = item.id || '';
 
 			_.each( menuControl.getMenuItemControls(), function( control ) {
 				if ( false === control.setting() ) {
@@ -2982,7 +3023,7 @@
 					position: position
 				}
 			);
-			delete item.id; // only used by Backbone
+			delete item.id; // Only used by Backbone.
 
 			placeholderId = api.Menus.generatePlaceholderAutoIncrementId();
 			customizeId = 'nav_menu_item[' + String( placeholderId ) + ']';
@@ -3002,7 +3043,8 @@
 				settings: {
 					'default': customizeId
 				},
-				menu_item_id: placeholderId
+				menu_item_id: placeholderId,
+				original_item_id: originalItemId
 			} );
 
 			api.control.add( menuItemControl );
@@ -3028,83 +3070,6 @@
 		}
 	} );
 
-	api.Menus.NewMenuControl = api.Control.extend(/** @lends wp.customize.Menus.NewMenuControl.prototype */{
-
-		/**
-		 * wp.customize.Menus.NewMenuControl
-		 *
-		 * Customizer control for creating new menus and handling deletion of existing menus.
-		 * Note that 'new_menu' must match the WP_Customize_New_Menu_Control::$type.
-		 *
-		 * @constructs wp.customize.Menus.NewMenuControl
-		 * @augments   wp.customize.Control
-		 *
-		 * @deprecated 4.9.0 This class is no longer used due to new menu creation UX.
-		 */
-		initialize: function() {
-			if ( 'undefined' !== typeof console && console.warn ) {
-				console.warn( '[DEPRECATED] wp.customize.NewMenuControl will be removed. Please use wp.customize.Menus.createNavMenu() instead.' );
-			}
-			api.Control.prototype.initialize.apply( this, arguments );
-		},
-
-		/**
-		 * Set up the control.
-		 *
-		 * @deprecated 4.9.0
-		 */
-		ready: function() {
-			this._bindHandlers();
-		},
-
-		_bindHandlers: function() {
-			var self = this,
-				name = $( '#customize-control-new_menu_name input' ),
-				submit = $( '#create-new-menu-submit' );
-			name.on( 'keydown', function( event ) {
-				if ( 13 === event.which ) { // Enter.
-					self.submit();
-				}
-			} );
-			submit.on( 'click', function( event ) {
-				self.submit();
-				event.stopPropagation();
-				event.preventDefault();
-			} );
-		},
-
-		/**
-		 * Create the new menu with the name supplied.
-		 *
-		 * @deprecated 4.9.0
-		 */
-		submit: function() {
-
-			var control = this,
-				container = control.container.closest( '.accordion-section-new-menu' ),
-				nameInput = container.find( '.menu-name-field' ).first(),
-				name = nameInput.val(),
-				menuSection;
-
-			if ( ! name ) {
-				nameInput.addClass( 'invalid' );
-				nameInput.focus();
-				return;
-			}
-
-			menuSection = api.Menus.createNavMenu( name );
-
-			// Clear name field.
-			nameInput.val( '' );
-			nameInput.removeClass( 'invalid' );
-
-			wp.a11y.speak( api.Menus.data.l10n.menuAdded );
-
-			// Focus on the new menu section.
-			menuSection.focus();
-		}
-	});
-
 	/**
 	 * Extends wp.customize.controlConstructor with control constructor for
 	 * menu_location, menu_item, nav_menu, and new_menu.
@@ -3114,7 +3079,6 @@
 		nav_menu_item: api.Menus.MenuItemControl,
 		nav_menu: api.Menus.MenuControl,
 		nav_menu_name: api.Menus.MenuNameControl,
-		new_menu: api.Menus.NewMenuControl, // @todo Remove in a future release. See #42364.
 		nav_menu_locations: api.Menus.MenuLocationsControl,
 		nav_menu_auto_add: api.Menus.MenuAutoAddControl
 	});
@@ -3171,9 +3135,9 @@
 	 *
 	 * @alias wp.customize.Menus.applySavedData
 	 *
-	 * @param {object} data
-	 * @param {array} data.nav_menu_updates
-	 * @param {array} data.nav_menu_item_updates
+	 * @param {Object} data
+	 * @param {Array} data.nav_menu_updates
+	 * @param {Array} data.nav_menu_item_updates
 	 */
 	api.Menus.applySavedData = function( data ) {
 
@@ -3430,7 +3394,7 @@
 	 * @alias wp.customize.Menus.getMenuItemControl
 	 *
 	 * @param {string} menuItemId
-	 * @return {object|null}
+	 * @return {Object|null}
 	 */
 	api.Menus.getMenuItemControl = function( menuItemId ) {
 		return api.control( menuItemIdToSettingId( menuItemId ) );
@@ -3439,7 +3403,7 @@
 	/**
 	 * @alias wp.customize.Menus~menuItemIdToSettingId
 	 *
-	 * @param {String} menuItemId
+	 * @param {string} menuItemId
 	 */
 	function menuItemIdToSettingId( menuItemId ) {
 		return 'nav_menu_item[' + menuItemId + ']';
@@ -3452,7 +3416,7 @@
 	 * @alias wp.customize.Menus~displayNavMenuName
 	 *
 	 * @param {string} name
-	 * @returns {string}
+	 * @return {string}
 	 */
 	function displayNavMenuName( name ) {
 		name = name || '';