Ticket #27404: 27404-3-24-16.patch
File 27404-3-24-16.patch, 33.1 KB (added by , 9 years ago) |
---|
-
src/wp-admin/css/customize-controls.css
1664 1664 #available-widgets-list { 1665 1665 top: 140px; 1666 1666 } 1667 1668 .wp-customizer #available-widgets .saved-widget-controls { 1669 visibility: visible; 1670 } 1667 1671 } -
src/wp-admin/css/customize-widgets.css
212 212 display: block; 213 213 } 214 214 215 #customize-theme-controls .widget-control-remove:hover { 216 color: #0096dd; 217 } 218 #customize-theme-controls .widget-control-close { 219 color: #a00; 220 } 221 #customize-theme-controls .widget-control-close:hover { 222 color: #f00; 223 } 224 215 225 /** 216 226 * Styles for new widget addition panel 217 227 */ … … 265 275 opacity: 0.4; 266 276 } 267 277 278 #available-widgets .widget-tpl { 279 padding-right: 35px; 280 } 268 281 282 #available-widgets .saved-widget { 283 margin-left: 48px; 284 border-left: 1px #e4e4e4 solid; 285 padding: 10px 20px 10px; 286 background-color: white; 287 display: none; 288 cursor: default; 289 } 290 291 #available-widgets .saved-widget:last-of-type { 292 margin-bottom: 40px; 293 } 294 295 #available-widgets .saved-widget h3 { 296 font-size: 1em; 297 margin: 0; 298 } 299 300 #available-widgets .saved-widget a { 301 text-decoration: none; 302 } 303 304 #available-widgets .saved-widget-controls { 305 visibility: hidden; 306 } 307 308 #available-widgets .saved-widget.selected .saved-widget-controls, 309 #available-widgets .saved-widget:hover .saved-widget-controls { 310 visibility: visible; 311 } 312 313 #available-widgets .delete-widget-permanently { 314 color: #a00; 315 } 316 317 #available-widgets .delete-widget-permanently:hover { 318 color: #f00; 319 } 320 321 #available-widgets .widget-tpl.expanded a.widget-action:after { 322 content: "\f142"; 323 } 324 325 #available-widgets .has-inactive-widgets .widget-action { 326 display: block; 327 position: relative; 328 top: 50%; 329 width: 20px; 330 margin: 0 auto; 331 -webkit-transform: translateY(-50%); 332 -ms-transform: translateY(-50%); 333 transform: translateY(-50%); 334 } 335 336 #available-widgets .has-inactive-widgets .widget-title-action { 337 display: block; 338 position: absolute; 339 right: 0; 340 top: 0; 341 height:100%; 342 width: 30px; 343 background-color: #eee; 344 } 345 346 #available-widgets .saved-widget.widget-title:before { 347 display: none; 348 } 349 350 #available-widgets .widget-tpl.expanded .widget-title-action, 351 #available-widgets .has-inactive-widgets .widget-title-action:hover { 352 border-left: 1px #e4e4e4 solid; 353 background-color: #FFFFFF; 354 } 355 356 #available-widgets .has-inactive-widgets .widget-title-action:hover .widget-action { 357 color: #555d66; 358 } 359 360 #available-widgets .has-inactive-widgets .widget-top a.widget-action:after { 361 margin: 0 auto; 362 } 363 269 364 /** 270 365 * Widget Icon styling 271 366 * No plurals in naming. … … 445 540 .widget-reorder-nav span:before { 446 541 line-height: 39px; 447 542 } 543 #available-widgets .widget-top { 544 position: static; 545 } 448 546 #customize-theme-controls .widget-area-select li { 449 547 padding: 9px 15px 11px 42px; 450 548 } -
src/wp-admin/js/customize-widgets.js
153 153 'search #widgets-search': 'search', 154 154 'focus .widget-tpl' : 'focus', 155 155 'click .widget-tpl' : '_submit', 156 'click .add-saved-widget' : '_submit', 157 'click .delete-widget-permanently' : '_delete', 158 'click .widget-tpl .widget-title-action' : '_toggleInactive', 156 159 'keypress .widget-tpl' : '_submit', 157 160 'keydown' : 'keyboardAccessible' 158 161 }, 159 162 163 // Inactive widget template to be used in available widgets. 164 inactiveWidgetTpl: null, 165 160 166 // Cache current selected widget 161 167 selected: null, 162 168 … … 175 181 176 182 this.updateList(); 177 183 184 this.inactiveWidgetTpl = _.template( api.Widgets.data.tpl.inactiveWidget ); 185 186 this._createInactiveWidgets(); 187 178 188 // If the available widgets panel is open and the customize controls are 179 189 // interacted with (i.e. available widgets panel is blurred) then close the 180 190 // available widgets panel. Also close on back button click. … … 214 224 this.select( firstVisible ); 215 225 } 216 226 } 227 228 // Collapse inactive widgets upon searching. 229 this.$el.find( '.saved-widget' ).hide(); 230 this.$el.find( '.widget-tpl.expanded' ).removeClass( 'expanded' ); 231 217 232 }, 218 233 219 234 // Changes visibility of available widgets … … 227 242 } ); 228 243 }, 229 244 245 /** 246 * Create the list of inactive widgets to be added to the available widgets sidebar onload. 247 * 248 * @since 4.6.0 249 */ 250 _createInactiveWidgets : function () { 251 var self = this; 252 253 _( api.Widgets.data.availableWidgets ).each( function ( availableWidget ) { 254 _( availableWidget.saved_widgets ).each( function ( savedWidget ) { 255 self.addInactiveWidget( { 256 id: savedWidget.id, 257 type: savedWidget.type, 258 idBase: availableWidget.id_base, 259 } ); 260 }); 261 }); 262 }, 263 264 /** 265 * Adds a single inactive widget to the available widgets sidebar. 266 * 267 * @since 4.6.0 268 */ 269 addInactiveWidget : function ( templateArgs ) { 270 var $inactiveWidget = $( this.inactiveWidgetTpl( templateArgs ) ); 271 272 if ( 0 === this.$el.find( '.saved-widget[data-widget-id="' + templateArgs.id + '"]' ).length ) { 273 this.$el.find( '[data-id-base="' + templateArgs.idBase + '"]' ).first().after( $inactiveWidget ); 274 } 275 276 return $inactiveWidget; 277 }, 278 279 /** 280 * Show inactive widgets and hide active widgets. 281 * 282 * @since 4.6.0 283 */ 284 updateVisibleSavedWidgets : function () { 285 var self = this; 286 287 // Remove state classes. 288 this.$el.find( '.widget-tpl.has-inactive-widgets, .widget-tpl.expanded' ) 289 .removeClass( 'has-inactive-widgets' ) 290 .removeClass( 'expanded' ); 291 292 // Hide all inactive widgets 293 this.$el.find( '.saved-widget' ) 294 .hide() 295 .removeClass( 'inactive-widget' ); 296 297 _( api( 'sidebars_widgets[wp_inactive_widgets]' )() ).each( function( widgetId ) { 298 var $savedWidget = self.$el.find( '#saved-widget-' + widgetId ), 299 parsedWidgetId = parseWidgetId( widgetId ), 300 widgetControlId = 'widget_' + parsedWidgetId.id_base + '[' + parsedWidgetId.number + ']', 301 widgetControl = api.control( widgetControlId ), 302 widgetSetting = api( widgetControlId ), 303 title = ( widgetSetting ) ? widgetSetting().title : null, 304 $inWidgetTitle, 305 availableWidget; 306 307 if ( ! widgetControl ) { 308 return; 309 } 310 311 // If inactive widget HTML does not exist, create it. 312 if ( 0 === $savedWidget.length ) { 313 availableWidget = api.Widgets.availableWidgets.findWhere( { id_base: parsedWidgetId.id_base } ); 314 $savedWidget = self.addInactiveWidget({ 315 id: widgetId, 316 type: availableWidget.attributes.name, 317 idBase: parsedWidgetId.id_base, 318 }); 319 } 320 321 $savedWidget.addClass( 'inactive-widget' ); 322 323 self.$el.find( '.widget-tpl[data-id-base="' + parsedWidgetId.id_base + '"]' ) 324 .not( '.saved-widget' ) 325 .addClass( 'has-inactive-widgets' ); 326 327 $inWidgetTitle = $savedWidget.find( '.in-widget-title' ); 328 if ( title ) { 329 $inWidgetTitle.text( ': ' + title ); 330 } else { 331 $inWidgetTitle.text( '' ); 332 } 333 } ); 334 }, 335 336 /** 337 * Show or hide the list of inactive widgets per widget id base. 338 * 339 * @since 4.6.0 340 */ 341 toggleInactive : function ( idBase ) { 342 var $widgetTpl = this.$el.find( '.has-inactive-widgets[data-id-base="' + idBase + '"]' ), 343 $inactiveWidgets = this.$el.find( '.saved-widget[data-id-base="' + idBase + '"].inactive-widget' ); 344 345 $widgetTpl.toggleClass( 'expanded' ); 346 $inactiveWidgets.stop().slideToggle( 'fast' ); 347 348 if ( $widgetTpl.hasClass('expanded') ) { 349 this.select( $inactiveWidgets.first().focus() ); 350 } else { 351 this.select( $widgetTpl.focus() ); 352 } 353 }, 354 355 /** 356 * Slides down a list of active widgets per widget type. 357 * 358 * @since 4.6.0 359 */ 360 _toggleInactive : function ( event ) { 361 event.stopPropagation(); 362 event.preventDefault(); 363 364 var $widgetTpl = $( event.currentTarget ).closest( '.widget-tpl' ); 365 this.toggleInactive( $widgetTpl.data('id-base') ); 366 }, 367 368 /** 369 * Permanently delete a widget. 370 * 371 * @since 4.6.0 372 */ 373 _delete : function ( event ) { 374 var self = this, 375 $currentTarget = $( event.currentTarget ), 376 $savedWidgets = $currentTarget.closest( '.saved-widget' ), 377 widgetId = $savedWidgets.data( 'widget-id' ), 378 idBase = $savedWidgets.data( 'id-base' ), 379 settingId = widgetIdToSettingId( widgetId ), 380 control = api.Widgets.getWidgetFormControlForWidget( widgetId ), 381 deleteCallback, 382 inactiveSidebarWidgets, 383 inactiveWidgets, 384 i; 385 386 // Remove from sidebar. 387 inactiveWidgets = api( 'sidebars_widgets[wp_inactive_widgets]' ); 388 389 inactiveSidebarWidgets = inactiveWidgets().slice(); 390 i = _.indexOf( inactiveSidebarWidgets, widgetId ); 391 if ( -1 !== i ) { 392 inactiveSidebarWidgets.splice( i, 1 ); 393 inactiveWidgets.set( inactiveSidebarWidgets ); 394 } 395 396 // Embed control to allow unembedded controls to trigger update calls. 397 if ( ! control.widgetControlEmdedded ) { 398 control.embedWidgetControl(); 399 } 400 api( settingId ).set( false ); 401 402 deleteCallback = function () { 403 var $remainingWidgets; 404 405 $( this ).remove(); 406 api.control.remove( control.id ); 407 control.container.remove(); 408 409 // Remove drop down if widgets no longer exist 410 $remainingWidgets = self.$el.find( '.saved-widget[data-id-base="' + idBase + '"].inactive-widget' ); 411 if ( ! $remainingWidgets.length ) { 412 self.$el.find( '.has-inactive-widgets[data-id-base="' + idBase + '"]' ) 413 .removeClass( 'has-inactive-widgets expanded' ); 414 } 415 416 wp.a11y.speak( l10n.itemDeleted ); 417 }; 418 419 $currentTarget.closest( '.saved-widget' ).slideUp( 'fast' , deleteCallback ); 420 }, 421 230 422 // Highlights a widget 231 423 select: function( widgetTpl ) { 232 424 this.selected = $( widgetTpl ); … … 251 443 252 444 // Adds a selected widget to the sidebar 253 445 submit: function( widgetTpl ) { 254 var widgetId, widget, widgetFormControl ;446 var widgetId, widget, widgetFormControl, isSavedWidget, addWidgetId; 255 447 256 448 if ( ! widgetTpl ) { 257 449 widgetTpl = this.selected; … … 263 455 264 456 this.select( widgetTpl ); 265 457 266 widgetId = $( this.selected ).data( 'widget-id' ); 458 widgetId = widgetTpl.data( 'widget-id' ); 459 isSavedWidget = widgetTpl.data( 'is-saved-widget' ); 460 267 461 widget = this.collection.findWhere( { id: widgetId } ); 268 if ( ! widget) {462 if ( ( ! widget && ! isSavedWidget ) || widgetTpl.hasClass( 'saved-widget' ) ) { 269 463 return; 270 464 } 271 465 272 widgetFormControl = this.currentSidebarControl.addWidget( widget.get( 'id_base' ) ); 466 addWidgetId = ( isSavedWidget ) ? widgetId : widget.get( 'id_base' ); 467 widgetFormControl = this.currentSidebarControl.addWidget( addWidgetId ); 468 273 469 if ( widgetFormControl ) { 274 470 widgetFormControl.focus(); 275 471 } … … 281 477 open: function( sidebarControl ) { 282 478 this.currentSidebarControl = sidebarControl; 283 479 480 // Create inactive widget controls that were moved to the inactive sidebar. 481 this.updateVisibleSavedWidgets(); 482 284 483 // Wide widget controls appear over the preview, and so they need to be collapsed when the panel opens 285 484 _( this.currentSidebarControl.getWidgetFormControls() ).each( function( control ) { 286 485 if ( control.params.is_wide ) { … … 316 515 this.$search.val( '' ); 317 516 }, 318 517 319 // Add keyboard accessib lity to the panel518 // Add keyboard accessibility to the panel 320 519 keyboardAccessible: function( event ) { 321 520 var isEnter = ( event.which === 13 ), 322 521 isEsc = ( event.which === 27 ), 323 522 isDown = ( event.which === 40 ), 324 523 isUp = ( event.which === 38 ), 524 isLeft = ( event.which === 37 ), 525 isRight = ( event.which === 39 ), 325 526 isTab = ( event.which === 9 ), 326 527 isShift = ( event.shiftKey ), 327 528 selected = null, 328 firstVisible = this.$el.find( ' >.widget-tpl:visible:first' ),329 lastVisible = this.$el.find( ' >.widget-tpl:visible:last' ),529 firstVisible = this.$el.find( '.widget-tpl:visible:first' ), 530 lastVisible = this.$el.find( '.widget-tpl:visible:last' ), 330 531 isSearchFocused = $( event.target ).is( this.$search ), 331 532 isLastWidgetFocused = $( event.target ).is( '.widget-tpl:visible:last' ); 332 533 … … 356 557 return; 357 558 } 358 559 560 // Toggle display when the user presses left or right on widget template with inactive widgets. 561 if ( isLeft || isRight ) { 562 if ( this.selected && (this.selected.hasClass( 'has-inactive-widgets' ) || this.selected.hasClass( 'saved-widget' ) ) ) { 563 var idBase = this.selected.data('id-base'); 564 this.toggleInactive( idBase ); 565 } 566 567 return; 568 } 569 359 570 // If enter pressed but nothing entered, don't do anything 360 571 if ( isEnter && ! this.$search.val() ) { 361 572 return; … … 477 688 if ( control.widgetControlEmbedded ) { 478 689 return; 479 690 } 691 480 692 control.widgetControlEmbedded = true; 481 693 482 694 widgetControl = $( control.params.widget_control ); … … 490 702 control._setupReorderUI(); 491 703 control._setupHighlightEffects(); 492 704 control._setupUpdateUI(); 705 control._setupMoveUI(); 493 706 control._setupRemoveUI(); 494 707 }, 495 708 … … 903 1116 }, 904 1117 905 1118 /** 906 * Set up event handlers for widget removal 1119 * Set up event handlers for widget move. 1120 * 1121 * @since 4.6.0 907 1122 */ 1123 _setupMoveUI: function() { 1124 var self = this, $moveBtn, $reorderToggle, replaceDeleteWithMove; 1125 1126 // Configure move button 1127 $moveBtn = this.container.find( 'a.widget-control-remove' ); 1128 1129 $moveBtn.on( 'click', function ( e ) { 1130 e.preventDefault(); 1131 1132 $reorderToggle = $( this ).closest( '.ui-sortable' ).find( '.reorder-toggle' ); 1133 1134 $reorderToggle.trigger( 'click' ); 1135 self.openWidgetMoveArea(); 1136 } ); 1137 1138 // Replace the Delete link with Move. 1139 replaceDeleteWithMove = function () { 1140 $moveBtn.text( l10n.moveBtnLabel ); // wp_widget_control() outputs the link as "Close" 1141 $moveBtn.attr( 'title', l10n.moveBtnTooltip ); 1142 }; 1143 1144 replaceDeleteWithMove(); 1145 }, 1146 1147 /** 1148 * Set up event handlers for widget removal. 1149 */ 908 1150 _setupRemoveUI: function() { 909 var self = this, $ removeBtn, replaceDeleteWithRemove;1151 var self = this, $closeBtn; 910 1152 911 1153 // Configure remove button 912 $ removeBtn = this.container.find( 'a.widget-control-remove' );913 $ removeBtn.on( 'click', function( e ) {1154 $closeBtn = this.container.find( 'a.widget-control-close' ); 1155 $closeBtn.on( 'click', function( e ) { 914 1156 e.preventDefault(); 915 1157 916 1158 // Find an adjacent element to add focus to when this widget goes away … … 925 1167 926 1168 self.container.slideUp( function() { 927 1169 var sidebarsWidgetsControl = api.Widgets.getSidebarWidgetControlContainingWidget( self.params.widget_id ), 928 sidebarWidgetIds, i ;1170 sidebarWidgetIds, i, inactiveWidgets, inactiveWidgetSetting; 929 1171 930 1172 if ( ! sidebarsWidgetsControl ) { 931 1173 return; … … 940 1182 sidebarWidgetIds.splice( i, 1 ); 941 1183 sidebarsWidgetsControl.setting( sidebarWidgetIds ); 942 1184 1185 // Add widget to inactive widgets. 1186 inactiveWidgetSetting = api( 'sidebars_widgets[wp_inactive_widgets]' ); 1187 inactiveWidgets = inactiveWidgetSetting(); 1188 inactiveWidgets.push( self.params.widget_id ); 1189 inactiveWidgetSetting.set( [] ).set( _( inactiveWidgets ).unique() ); 1190 943 1191 $adjacentFocusTarget.focus(); // keyboard accessibility 1192 1193 // Remove display none in case its added again. 1194 self.container.show(); 944 1195 } ); 945 1196 } ); 946 1197 947 replaceDeleteWithRemove = function() { 948 $removeBtn.text( l10n.removeBtnLabel ); // wp_widget_control() outputs the link as "Delete" 949 $removeBtn.attr( 'title', l10n.removeBtnTooltip ); 950 }; 951 952 if ( this.params.is_new ) { 953 api.bind( 'saved', replaceDeleteWithRemove ); 954 } else { 955 replaceDeleteWithRemove(); 956 } 1198 $closeBtn.text( l10n.removeBtnLabel ); // wp_widget_control() outputs the link as "Close" 1199 $closeBtn.attr( 'title', l10n.removeBtnTooltip ); 957 1200 }, 958 1201 959 1202 /** … … 1112 1355 params.nonce = api.settings.nonce['update-widget']; 1113 1356 params.theme = api.settings.theme.stylesheet; 1114 1357 params.customized = wp.customize.previewer.query().customized; 1358 params.delete_widget = ( false === self.setting() ) ? 1 : 0; 1115 1359 1116 1360 data = $.param( params ); 1117 1361 $inputs = this._getInputs( $widgetContent ); … … 1125 1369 1126 1370 if ( instanceOverride ) { 1127 1371 data += '&' + $.param( { 'sanitized_widget_setting': JSON.stringify( instanceOverride ) } ); 1128 } else {1372 } else if ( $inputs.length ) { 1129 1373 data += '&' + $inputs.serialize(); 1130 1374 } 1375 1131 1376 data += '&' + $widgetContent.find( '~ :input' ).serialize(); 1132 1377 1133 1378 if ( this._previousUpdateRequest ) { … … 1208 1453 * preview finishing loading. 1209 1454 */ 1210 1455 isChanged = ! isLiveUpdateAborted && ! _( self.setting() ).isEqual( r.data.instance ); 1211 if ( isChanged ) {1456 if ( isChanged || false === self.setting() ) { 1212 1457 self.isWidgetUpdating = true; // suppress triggering another updateWidget 1213 1458 self.setting( r.data.instance ); 1214 1459 self.isWidgetUpdating = false; … … 1325 1570 */ 1326 1571 onChangeExpanded: function ( expanded, args ) { 1327 1572 var self = this, $widget, $inside, complete, prevComplete; 1328 1329 1573 self.embedWidgetControl(); // Make sure the outer form is embedded so that the expanded state can be set in the UI. 1330 1574 if ( expanded ) { 1331 1575 self.embedWidgetContent(); … … 1484 1728 } 1485 1729 1486 1730 $moveWidgetArea.toggleClass( 'active', showOrHide ); 1731 self.toggleSaveForLater(); 1487 1732 }, 1488 1733 1489 1734 /** 1735 * Open widget move area if control is not active already. 1736 * 1737 * @since 4.6.0 1738 */ 1739 openWidgetMoveArea: function() { 1740 var self = this, $moveWidgetArea; 1741 1742 $moveWidgetArea = this.container.find( '.move-widget-area' ); 1743 1744 if( ! $moveWidgetArea.hasClass( 'active' ) ) { 1745 self.toggleWidgetMoveArea(); 1746 } 1747 }, 1748 1749 /** 1750 * Toggle visibility of the Save For Later/Inactive Widget Sidebar section. 1751 * 1752 * @since 4.6.0 1753 */ 1754 toggleSaveForLater: function() { 1755 var $moveWidgetArea, $saveForLater; 1756 1757 $moveWidgetArea = this.container.find( '.move-widget-area' ); 1758 $saveForLater = this.container.find( 'li[data-id="wp_inactive_widgets"]' ); 1759 1760 if( $moveWidgetArea.hasClass( 'active' ) ) { 1761 $saveForLater.show(); 1762 } else { 1763 $saveForLater.hide(); 1764 } 1765 }, 1766 1767 /** 1490 1768 * Highlight the widget control and section 1491 1769 */ 1492 1770 highlightSectionAndControl: function() { … … 1707 1985 return; 1708 1986 } 1709 1987 1710 removedControl = api.Widgets.getWidgetFormControlForWidget( removedWidgetId );1711 1712 // Detect if widget control was dragged to another sidebar1713 wasDraggedToAnotherSidebar = removedControl && $.contains( document, removedControl.container[0] ) && ! $.contains( self.$sectionContent[0], removedControl.container[0] );1714 1715 // Delete any widget form controls for removed widgets1716 if ( removedControl && ! wasDraggedToAnotherSidebar ) {1717 api.control.remove( removedControl.id );1718 removedControl.container.remove();1719 }1720 1721 1988 // Move widget to inactive widgets sidebar (move it to trash) if has been previously saved 1722 1989 // This prevents the inactive widgets sidebar from overflowing with throwaway widgets 1723 if ( api.Widgets.savedWidgetIds[removedWidgetId] ) {1990 if ( api.Widgets.savedWidgetIds[removedWidgetId] && 'sidebars_widgets[wp_inactive_widgets]' !== self.id ) { 1724 1991 inactiveWidgets = api.value( 'sidebars_widgets[wp_inactive_widgets]' )().slice(); 1725 1992 inactiveWidgets.push( removedWidgetId ); 1726 1993 api.value( 'sidebars_widgets[wp_inactive_widgets]' )( _( inactiveWidgets ).unique() ); … … 1989 2256 setting.set( {} ); // mark dirty, changing from '' to {} 1990 2257 } 1991 2258 1992 controlConstructor = api.controlConstructor[controlType]; 1993 widgetFormControl = new controlConstructor( settingId, { 1994 params: { 1995 settings: { 1996 'default': settingId 2259 widgetFormControl = api.control( settingId ); 2260 if ( ! widgetFormControl ) { 2261 2262 controlConstructor = api.controlConstructor[controlType]; 2263 widgetFormControl = new controlConstructor( settingId, { 2264 params: { 2265 settings: { 2266 'default': settingId 2267 }, 2268 content: controlContainer, 2269 sidebar_id: self.params.sidebar_id, 2270 widget_id: widgetId, 2271 widget_id_base: widget.get( 'id_base' ), 2272 type: controlType, 2273 is_new: ! isExistingWidget, 2274 width: widget.get( 'width' ), 2275 height: widget.get( 'height' ), 2276 is_wide: widget.get( 'is_wide' ), 2277 active: true 1997 2278 }, 1998 content: controlContainer, 1999 sidebar_id: self.params.sidebar_id, 2000 widget_id: widgetId, 2001 widget_id_base: widget.get( 'id_base' ), 2002 type: controlType, 2003 is_new: ! isExistingWidget, 2004 width: widget.get( 'width' ), 2005 height: widget.get( 'height' ), 2006 is_wide: widget.get( 'is_wide' ), 2007 active: true 2008 }, 2009 previewer: self.setting.previewer 2010 } ); 2011 api.control.add( settingId, widgetFormControl ); 2279 previewer: self.setting.previewer 2280 } ); 2281 api.control.add( settingId, widgetFormControl ); 2282 } 2012 2283 2013 2284 // Make sure widget is removed from the other sidebars 2014 2285 api.each( function( otherSetting ) { … … 2024 2295 i = _.indexOf( otherSidebarWidgets, widgetId ); 2025 2296 2026 2297 if ( -1 !== i ) { 2027 otherSidebarWidgets.splice( i );2298 otherSidebarWidgets.splice( i, 1 ); 2028 2299 otherSetting( otherSidebarWidgets ); 2029 2300 } 2030 2301 } ); -
src/wp-includes/class-wp-customize-setting.php
769 769 770 770 $result = $this->multidimensional( $root, $keys, true ); 771 771 772 if ( isset( $result ) ) 773 $result['node'][ $result['key'] ] = $value; 772 if ( isset( $result ) ) { 774 773 774 if ( ! empty( $value['multidimensional_delete'] ) ) { 775 unset( $result['node'][ $result['key'] ] ); 776 } else { 777 $result['node'][ $result['key'] ] = $value; 778 } 779 780 } 781 775 782 return $root; 776 783 } 777 784 -
src/wp-includes/class-wp-customize-widgets.php
442 442 443 443 // Add section to contain controls. 444 444 $section_id = sprintf( 'sidebar-widgets-%s', $sidebar_id ); 445 if ( $is_active_sidebar ) {446 445 447 $section_args = array(448 'title' => $wp_registered_sidebars[ $sidebar_id ]['name'],449 'description' => $wp_registered_sidebars[ $sidebar_id ]['description'],450 'priority' => array_search( $sidebar_id, array_keys( $wp_registered_sidebars ) ),451 'panel' => 'widgets',452 'sidebar_id' => $sidebar_id,453 );446 if ( $is_inactive_widgets ) { 447 $title = 'Inactive Widgets'; 448 $description = $title; 449 } else { 450 $title = $wp_registered_sidebars[ $sidebar_id ]['name']; 451 $description = $wp_registered_sidebars[ $sidebar_id ]['description']; 452 } 454 453 455 /** 456 * Filter Customizer widget section arguments for a given sidebar. 457 * 458 * @since 3.9.0 459 * 460 * @param array $section_args Array of Customizer widget section arguments. 461 * @param string $section_id Customizer section ID. 462 * @param int|string $sidebar_id Sidebar ID. 463 */ 464 $section_args = apply_filters( 'customizer_widgets_section_args', $section_args, $section_id, $sidebar_id ); 454 $section_args = array( 455 'title' => $title, 456 'description' => $description, 457 'priority' => array_search( $sidebar_id, array_keys( $wp_registered_sidebars ) ), 458 'panel' => 'widgets', 459 'sidebar_id' => $sidebar_id, 460 ); 465 461 466 $section = new WP_Customize_Sidebar_Section( $this->manager, $section_id, $section_args ); 467 $this->manager->add_section( $section ); 462 /** 463 * Filter Customizer widget section arguments for a given sidebar. 464 * 465 * @since 3.9.0 466 * 467 * @param array $section_args Array of Customizer widget section arguments. 468 * @param string $section_id Customizer section ID. 469 * @param int|string $sidebar_id Sidebar ID. 470 */ 471 $section_args = apply_filters( 'customizer_widgets_section_args', $section_args, $section_id, $sidebar_id ); 468 472 469 $control = new WP_Widget_Area_Customize_Control( $this->manager, $setting_id, array( 470 'section' => $section_id, 471 'sidebar_id' => $sidebar_id, 472 'priority' => count( $sidebar_widget_ids ), // place 'Add Widget' and 'Reorder' buttons at end. 473 ) ); 474 $new_setting_ids[] = $setting_id; 473 $section = new WP_Customize_Sidebar_Section( $this->manager, $section_id, $section_args ); 474 $this->manager->add_section( $section ); 475 475 476 $this->manager->add_control( $control ); 477 } 476 $control = new WP_Widget_Area_Customize_Control( $this->manager, $setting_id, array( 477 'section' => $section_id, 478 'sidebar_id' => $sidebar_id, 479 'priority' => count( $sidebar_widget_ids ), // place 'Add Widget' and 'Reorder' buttons at end. 480 ) ); 481 $new_setting_ids[] = $setting_id; 482 483 $this->manager->add_control( $control ); 478 484 } 479 485 480 486 // Add a control for each active widget (located in a sidebar). … … 481 487 foreach ( $sidebar_widget_ids as $i => $widget_id ) { 482 488 483 489 // Skip widgets that may have gone away due to a plugin being deactivated. 484 if ( ! $is_active_sidebar || !isset( $wp_registered_widgets[$widget_id] ) ) {490 if ( ! isset( $wp_registered_widgets[$widget_id] ) ) { 485 491 continue; 486 492 } 487 493 … … 500 506 'height' => $wp_registered_widget_controls[$widget_id]['height'], 501 507 'is_wide' => $this->is_wide_widget( $widget_id ), 502 508 ) ); 509 503 510 $this->manager->add_control( $control ); 504 511 } 505 512 } … … 717 724 </div>' 718 725 ); 719 726 727 $inactive_widget_tpl = str_replace( 728 array( '{delete}', '{add}' ), 729 array( 730 __( 'Delete' ), 731 __( 'Add' ) 732 ), 733 '<div id="saved-widget-<%- id %>" data-id-base="<%- idBase %>" data-widget-id="<%- id %>" class="widget-title saved-widget widget-tpl" tabindex="0"> 734 <h3><%- type %><span class="in-widget-title"></span></h3> 735 <div class="saved-widget-controls"> 736 <a href="#" class="add-saved-widget" data-is-saved-widget="1" data-widget-id="<%- id %>" 737 >{add}</a> | <a href="#" class="delete-widget-permanently">{delete}</a> 738 </div> 739 </div>' 740 ); 741 742 $inactive_sidebar = array( 743 'inactive-sidebar' => array( 744 'name' => __( 'Save For Later' ), 745 'id' => 'wp_inactive_widgets', 746 'description' => __( 'Save widgets to the Inactive Sidebar to use later.' ), 747 'class' => 'inactive-sidebar', 748 'before_widget' => '<aside id="%1$s" class="widget %2$s">', 749 'after_widget' => '</aside>', 750 'before_title' => '<h1 class="widget-title">', 751 'after_title' => '</h1>', 752 ), 753 ); 754 755 $all_sidebars = array_values( array_merge( $wp_registered_sidebars, $inactive_sidebar ) ); 756 720 757 $settings = array( 721 'registeredSidebars' => array_values( $wp_registered_sidebars ),758 'registeredSidebars' => $all_sidebars, 722 759 'registeredWidgets' => $wp_registered_widgets, 723 760 'availableWidgets' => $available_widgets, // @todo Merge this with registered_widgets 724 761 'l10n' => array( … … 726 763 'saveBtnTooltip' => __( 'Save and preview changes before publishing them.' ), 727 764 'removeBtnLabel' => __( 'Remove' ), 728 765 'removeBtnTooltip' => __( 'Trash widget by moving it to the inactive widgets sidebar.' ), 766 'moveBtnLabel' => __( 'Move' ), 767 'moveBtnTooltip' => __( 'Reorder the widgets in this sidebar.' ), 729 768 'error' => __( 'An error has occurred. Please reload the page and try again.' ), 730 769 'widgetMovedUp' => __( 'Widget moved up' ), 731 770 'widgetMovedDown' => __( 'Widget moved down' ), … … 734 773 'reorderModeOff' => __( 'Reorder mode closed' ), 735 774 'reorderLabelOn' => esc_attr__( 'Reorder widgets' ), 736 775 'reorderLabelOff' => esc_attr__( 'Close reorder mode' ), 776 'itemDeleted' => __( 'Widget deleted' ), 737 777 ), 738 778 'tpl' => array( 739 779 'widgetReorderNav' => $widget_reorder_nav_tpl, 740 780 'moveWidgetArea' => $move_widget_area_tpl, 781 'inactiveWidget' => $inactive_widget_tpl, 741 782 ), 742 783 'selectiveRefreshableWidgets' => $this->get_selective_refreshable_widgets(), 743 784 ); … … 781 822 </div> 782 823 <div id="available-widgets-list"> 783 824 <?php foreach ( $this->get_available_widgets() as $available_widget ): ?> 784 <div id="widget-tpl-<?php echo esc_attr( $available_widget['id'] ) ?>" data- widget-id="<?php echo esc_attr( $available_widget['id'] ) ?>" class="widget-tpl <?php echo esc_attr( $available_widget['id'] ) ?>" tabindex="0">825 <div id="widget-tpl-<?php echo esc_attr( $available_widget['id'] ) ?>" data-id-base="<?php echo esc_attr( $available_widget['id_base'] ) ?>" data-widget-id="<?php echo esc_attr( $available_widget['id'] ) ?>" class="widget-tpl <?php echo esc_attr( $available_widget['id'] ) ?>" tabindex="0"> 785 826 <?php echo $available_widget['control_tpl']; ?> 786 827 </div> 787 828 <?php endforeach; ?> … … 865 906 foreach ( $widget_ids as $widget_id ) { 866 907 $sanitized_widget_ids[] = preg_replace( '/[^a-z0-9_\-]/', '', $widget_id ); 867 908 } 909 868 910 return $sanitized_widget_ids; 869 911 } 870 912 871 913 /** 914 * Create an list of saved widgets that correspond to a widget type. 915 * 916 * @since 4.6.0 917 * @access protected 918 * 919 * @global array $wp_registered_widget_controls 920 * 921 * @return array List of saved widget by type. 922 */ 923 protected function get_sorted_saved_widgets() { 924 global $wp_registered_widget_controls; 925 926 $sidebars_widgets = wp_get_sidebars_widgets(); 927 928 $sorted_saved_widgets = array(); 929 foreach ( $sidebars_widgets as $sidebars_widget ) { 930 foreach( $sidebars_widget as $widget ) { 931 932 if ( isset ( $wp_registered_widget_controls[ $widget ]['id_base'] ) ) { 933 $current_base_id = $wp_registered_widget_controls[ $widget ]['id_base']; 934 $sorted_saved_widgets[ $current_base_id ][] = array( 935 'id' => $widget, 936 'type' => $wp_registered_widget_controls[ $widget ]['name'], 937 ); 938 } 939 940 } 941 } 942 943 return $sorted_saved_widgets; 944 } 945 946 /** 872 947 * Builds up an index of all available widgets for use in Backbone models. 873 948 * 874 949 * @since 3.9.0 … … 891 966 global $wp_registered_widgets, $wp_registered_widget_controls; 892 967 require_once ABSPATH . '/wp-admin/includes/widgets.php'; // for next_widget_id_number() 893 968 969 $sorted_saved_widgets = $this->get_sorted_saved_widgets(); 894 970 $sort = $wp_registered_widgets; 895 971 usort( $sort, array( $this, '_sort_name_callback' ) ); 896 972 $done = array(); … … 947 1023 'width' => $wp_registered_widget_controls[$widget['id']]['width'], 948 1024 'height' => $wp_registered_widget_controls[$widget['id']]['height'], 949 1025 'is_wide' => $this->is_wide_widget( $widget['id'] ), 1026 'saved_widgets' => isset( $sorted_saved_widgets[ $id_base ] ) ? $sorted_saved_widgets[ $id_base ] : array(), 950 1027 ) ); 951 1028 952 1029 $available_widgets[] = $available_widget; … … 1254 1331 * @return array|void Sanitized widget instance. 1255 1332 */ 1256 1333 public function sanitize_widget_instance( $value ) { 1257 if ( $value === array() ) {1334 if ( $value === array() || false === $value ) { 1258 1335 return $value; 1259 1336 } 1260 1337 … … 1344 1421 global $wp_registered_widget_updates, $wp_registered_widget_controls; 1345 1422 1346 1423 $setting_id = $this->get_setting_id( $widget_id ); 1347 1348 1424 /* 1349 1425 * Make sure that other setting changes have previewed since this widget 1350 1426 * may depend on them (e.g. Menus being present for Custom Menu widget). … … 1393 1469 } 1394 1470 } 1395 1471 1472 // If deleting widget. 1473 $is_widget_delete = ! empty( $this->get_post_value( 'delete_widget' ) ); 1474 1475 if ( $is_widget_delete ) { 1476 // Set post values needed in widget update_callback. 1477 $_POST[ 'sidebar' ] = 'wp_inactive_widgets'; 1478 $_POST[ 'widget-' . $parsed_id['id_base'] ] = array(); 1479 $_POST[ 'the-widget-id' ] = $widget_id; 1480 $_POST[ 'delete_widget' ] = '1'; 1481 1482 $post_values_added = array( 'sidebar', 'widget-' . $parsed_id[ 'id_base' ], 'the-widget-id', 'delete_widget' ); 1483 $added_input_vars = array_merge( $added_input_vars, $post_values_added ); 1484 1485 /** This action is documented in wp-admin/widgets.php */ 1486 do_action( 'delete_widget', $widget_id, $_POST[ 'sidebar' ], $parsed_id['id_base'] ); 1487 } 1488 1396 1489 // Invoke the widget update callback. 1397 1490 foreach ( (array) $wp_registered_widget_updates as $name => $control ) { 1398 1491 if ( $name === $parsed_id['id_base'] && is_callable( $control['callback'] ) ) { … … 1425 1518 1426 1519 // Obtain the widget instance. 1427 1520 $option = $this->get_captured_option( $option_name ); 1428 if ( null !== $parsed_id['number'] ) { 1521 if ( $is_widget_delete ) { 1522 $instance = array( 'multidimensional_delete' => true ); 1523 } else if ( null !== $parsed_id['number'] ) { 1429 1524 $instance = $option[ $parsed_id['number'] ]; 1430 1525 } else { 1431 1526 $instance = $option; … … 1442 1537 // Obtain the widget control with the updated instance in place. 1443 1538 ob_start(); 1444 1539 $form = $wp_registered_widget_controls[ $widget_id ]; 1445 if ( $form ) {1540 if ( $form && false === $is_widget_delete ) { 1446 1541 call_user_func_array( $form['callback'], $form['params'] ); 1447 1542 } 1448 1543 $form = ob_get_clean();