Ticket #27404: 27404-1-30-17.patch
File 27404-1-30-17.patch, 34.2 KB (added by , 8 years ago) |
---|
-
src/wp-includes/class-wp-customize-widgets.php
447 447 448 448 // Add section to contain controls. 449 449 $section_id = sprintf( 'sidebar-widgets-%s', $sidebar_id ); 450 if ( $is_active_sidebar ) {451 450 452 $section_args = array(453 'title' => $wp_registered_sidebars[ $sidebar_id ]['name'],454 'description' => $wp_registered_sidebars[ $sidebar_id ]['description'],455 'priority' => array_search( $sidebar_id, array_keys( $wp_registered_sidebars ) ),456 'panel' => 'widgets',457 'sidebar_id' => $sidebar_id,458 );451 if ( $is_inactive_widgets ) { 452 $title = 'Inactive Widgets'; 453 $description = $title; 454 } else { 455 $title = $wp_registered_sidebars[ $sidebar_id ]['name']; 456 $description = $wp_registered_sidebars[ $sidebar_id ]['description']; 457 } 459 458 460 /** 461 * Filters Customizer widget section arguments for a given sidebar. 462 * 463 * @since 3.9.0 464 * 465 * @param array $section_args Array of Customizer widget section arguments. 466 * @param string $section_id Customizer section ID. 467 * @param int|string $sidebar_id Sidebar ID. 468 */ 469 $section_args = apply_filters( 'customizer_widgets_section_args', $section_args, $section_id, $sidebar_id ); 459 $section_args = array( 460 'title' => $title, 461 'description' => $description, 462 'priority' => array_search( $sidebar_id, array_keys( $wp_registered_sidebars ) ), 463 'panel' => 'widgets', 464 'sidebar_id' => $sidebar_id, 465 ); 470 466 471 $section = new WP_Customize_Sidebar_Section( $this->manager, $section_id, $section_args ); 472 $this->manager->add_section( $section ); 467 /** 468 * Filters Customizer widget section arguments for a given sidebar. 469 * 470 * @since 3.9.0 471 * 472 * @param array $section_args Array of Customizer widget section arguments. 473 * @param string $section_id Customizer section ID. 474 * @param int|string $sidebar_id Sidebar ID. 475 */ 476 $section_args = apply_filters( 'customizer_widgets_section_args', $section_args, $section_id, $sidebar_id ); 473 477 474 $control = new WP_Widget_Area_Customize_Control( $this->manager, $setting_id, array( 475 'section' => $section_id, 476 'sidebar_id' => $sidebar_id, 477 'priority' => count( $sidebar_widget_ids ), // place 'Add Widget' and 'Reorder' buttons at end. 478 ) ); 479 $new_setting_ids[] = $setting_id; 478 $section = new WP_Customize_Sidebar_Section( $this->manager, $section_id, $section_args ); 479 $this->manager->add_section( $section ); 480 480 481 $this->manager->add_control( $control ); 482 } 481 $control = new WP_Widget_Area_Customize_Control( $this->manager, $setting_id, array( 482 'section' => $section_id, 483 'sidebar_id' => $sidebar_id, 484 'priority' => count( $sidebar_widget_ids ), // place 'Add Widget' and 'Reorder' buttons at end. 485 ) ); 486 $new_setting_ids[] = $setting_id; 487 488 $this->manager->add_control( $control ); 489 490 $section_args = array( 491 'title' => $title, 492 'description' => $description, 493 'priority' => array_search( $sidebar_id, array_keys( $wp_registered_sidebars ) ), 494 'panel' => 'widgets', 495 'sidebar_id' => $sidebar_id, 496 ); 497 498 /** 499 * Filter Customizer widget section arguments for a given sidebar. 500 * 501 * @since 3.9.0 502 * 503 * @param array $section_args Array of Customizer widget section arguments. 504 * @param string $section_id Customizer section ID. 505 * @param int|string $sidebar_id Sidebar ID. 506 */ 507 $section_args = apply_filters( 'customizer_widgets_section_args', $section_args, $section_id, $sidebar_id ); 508 509 $section = new WP_Customize_Sidebar_Section( $this->manager, $section_id, $section_args ); 510 $this->manager->add_section( $section ); 511 512 $control = new WP_Widget_Area_Customize_Control( $this->manager, $setting_id, array( 513 'section' => $section_id, 514 'sidebar_id' => $sidebar_id, 515 'priority' => count( $sidebar_widget_ids ), // place 'Add Widget' and 'Reorder' buttons at end. 516 ) ); 517 $new_setting_ids[] = $setting_id; 518 519 $this->manager->add_control( $control ); 483 520 } 484 521 485 522 // Add a control for each active widget (located in a sidebar). 486 523 foreach ( $sidebar_widget_ids as $i => $widget_id ) { 487 524 488 525 // Skip widgets that may have gone away due to a plugin being deactivated. 489 if ( ! $is_active_sidebar || !isset( $wp_registered_widgets[$widget_id] ) ) {526 if ( ! isset( $wp_registered_widgets[$widget_id] ) ) { 490 527 continue; 491 528 } 492 529 … … 505 542 'height' => $wp_registered_widget_controls[$widget_id]['height'], 506 543 'is_wide' => $this->is_wide_widget( $widget_id ), 507 544 ) ); 545 508 546 $this->manager->add_control( $control ); 509 547 } 510 548 } … … 720 758 </div>' 721 759 ); 722 760 761 $inactive_widget_tpl = str_replace( 762 array( '{delete}', '{add}' ), 763 array( 764 __( 'Delete' ), 765 __( 'Add' ) 766 ), 767 '<div id="saved-widget-<%- id %>" data-id-base="<%- idBase %>" data-widget-id="<%- id %>" class="widget-title saved-widget widget-tpl" tabindex="0"> 768 <h3><%- type %><span class="in-widget-title"></span></h3> 769 <div class="saved-widget-controls"> 770 <a href="#" class="add-saved-widget" data-is-saved-widget="1" data-widget-id="<%- id %>" 771 >{add}</a> | <a href="#" class="delete-widget-permanently">{delete}</a> 772 </div> 773 </div>' 774 ); 775 776 $inactive_sidebar = array( 777 'inactive-sidebar' => array( 778 'name' => __( 'Save For Later' ), 779 'id' => 'wp_inactive_widgets', 780 'description' => __( 'Save widgets to the Inactive Sidebar to use later.' ), 781 'class' => 'inactive-sidebar', 782 'before_widget' => '<aside id="%1$s" class="widget %2$s">', 783 'after_widget' => '</aside>', 784 'before_title' => '<h1 class="widget-title">', 785 'after_title' => '</h1>', 786 ), 787 ); 788 789 $all_sidebars = array_values( array_merge( $wp_registered_sidebars, $inactive_sidebar ) ); 790 723 791 $settings = array( 724 'registeredSidebars' => array_values( $wp_registered_sidebars ),792 'registeredSidebars' => $all_sidebars, 725 793 'registeredWidgets' => $wp_registered_widgets, 726 794 'availableWidgets' => $available_widgets, // @todo Merge this with registered_widgets 727 795 'l10n' => array( … … 729 797 'saveBtnTooltip' => __( 'Save and preview changes before publishing them.' ), 730 798 'removeBtnLabel' => __( 'Remove' ), 731 799 'removeBtnTooltip' => __( 'Trash widget by moving it to the inactive widgets sidebar.' ), 800 'moveBtnLabel' => __( 'Move' ), 801 'moveBtnTooltip' => __( 'Reorder the widgets in this sidebar.' ), 732 802 'error' => __( 'An error has occurred. Please reload the page and try again.' ), 733 803 'widgetMovedUp' => __( 'Widget moved up' ), 734 804 'widgetMovedDown' => __( 'Widget moved down' ), … … 736 806 'reorderModeOn' => __( 'Reorder mode enabled' ), 737 807 'reorderModeOff' => __( 'Reorder mode closed' ), 738 808 'reorderLabelOn' => esc_attr__( 'Reorder widgets' ), 809 'reorderLabelOff' => esc_attr__( 'Close reorder mode' ), 810 'itemDeleted' => __( 'Widget deleted' ), 739 811 'widgetsFound' => __( 'Number of widgets found: %d' ), 740 812 'noWidgetsFound' => __( 'No widgets found.' ), 741 813 ), 742 814 'tpl' => array( 743 815 'widgetReorderNav' => $widget_reorder_nav_tpl, 744 816 'moveWidgetArea' => $move_widget_area_tpl, 817 'inactiveWidget' => $inactive_widget_tpl, 745 818 ), 746 819 'selectiveRefreshableWidgets' => $this->get_selective_refreshable_widgets(), 747 820 ); … … 788 861 </div> 789 862 <div id="available-widgets-list"> 790 863 <?php foreach ( $this->get_available_widgets() as $available_widget ): ?> 791 <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">864 <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"> 792 865 <?php echo $available_widget['control_tpl']; ?> 793 866 </div> 794 867 <?php endforeach; ?> … … 876 949 foreach ( $widget_ids as $widget_id ) { 877 950 $sanitized_widget_ids[] = preg_replace( '/[^a-z0-9_\-]/', '', $widget_id ); 878 951 } 952 879 953 return $sanitized_widget_ids; 880 954 } 881 955 882 956 /** 957 * Create an list of saved widgets that correspond to a widget type. 958 * 959 * @since 4.8.0 960 * @access protected 961 * 962 * @global array $wp_registered_widget_controls 963 * 964 * @return array List of saved widget by type. 965 */ 966 protected function get_sorted_saved_widgets() { 967 global $wp_registered_widget_controls; 968 969 $sidebars_widgets = wp_get_sidebars_widgets(); 970 971 $sorted_saved_widgets = array(); 972 foreach ( $sidebars_widgets as $sidebars_widget ) { 973 foreach( $sidebars_widget as $widget ) { 974 975 if ( isset ( $wp_registered_widget_controls[ $widget ]['id_base'] ) ) { 976 $current_base_id = $wp_registered_widget_controls[ $widget ]['id_base']; 977 $sorted_saved_widgets[ $current_base_id ][] = array( 978 'id' => $widget, 979 'type' => $wp_registered_widget_controls[ $widget ]['name'], 980 ); 981 } 982 983 } 984 } 985 986 return $sorted_saved_widgets; 987 } 988 989 /** 883 990 * Builds up an index of all available widgets for use in Backbone models. 884 991 * 885 992 * @since 3.9.0 … … 902 1009 global $wp_registered_widgets, $wp_registered_widget_controls; 903 1010 require_once ABSPATH . '/wp-admin/includes/widgets.php'; // for next_widget_id_number() 904 1011 1012 $sorted_saved_widgets = $this->get_sorted_saved_widgets(); 905 1013 $sort = $wp_registered_widgets; 906 1014 usort( $sort, array( $this, '_sort_name_callback' ) ); 907 1015 $done = array(); … … 958 1066 'width' => $wp_registered_widget_controls[$widget['id']]['width'], 959 1067 'height' => $wp_registered_widget_controls[$widget['id']]['height'], 960 1068 'is_wide' => $this->is_wide_widget( $widget['id'] ), 1069 'saved_widgets' => isset( $sorted_saved_widgets[ $id_base ] ) ? $sorted_saved_widgets[ $id_base ] : array(), 961 1070 ) ); 962 1071 963 1072 $available_widgets[] = $available_widget; … … 1270 1379 * @return array|void Sanitized widget instance. 1271 1380 */ 1272 1381 public function sanitize_widget_instance( $value ) { 1273 if ( $value === array() ) {1382 if ( $value === array() || false === $value ) { 1274 1383 return $value; 1275 1384 } 1276 1385 … … 1360 1469 global $wp_registered_widget_updates, $wp_registered_widget_controls; 1361 1470 1362 1471 $setting_id = $this->get_setting_id( $widget_id ); 1363 1364 1472 /* 1365 1473 * Make sure that other setting changes have previewed since this widget 1366 1474 * may depend on them (e.g. Menus being present for Custom Menu widget). … … 1409 1517 } 1410 1518 } 1411 1519 1520 // If deleting widget. 1521 $delete_widget_val = $this->get_post_value( 'delete_widget' ); 1522 $is_widget_delete = ! empty( $delete_widget_val ); 1523 1524 if ( $is_widget_delete ) { 1525 // Set post values needed in widget update_callback. 1526 $_POST[ 'sidebar' ] = 'wp_inactive_widgets'; 1527 $_POST[ 'widget-' . $parsed_id['id_base'] ] = array(); 1528 $_POST[ 'the-widget-id' ] = $widget_id; 1529 $_POST[ 'delete_widget' ] = '1'; 1530 1531 $post_values_added = array( 'sidebar', 'widget-' . $parsed_id[ 'id_base' ], 'the-widget-id', 'delete_widget' ); 1532 $added_input_vars = array_merge( $added_input_vars, $post_values_added ); 1533 1534 /** This action is documented in wp-admin/widgets.php */ 1535 do_action( 'delete_widget', $widget_id, $_POST[ 'sidebar' ], $parsed_id['id_base'] ); 1536 } 1537 1412 1538 // Invoke the widget update callback. 1413 1539 foreach ( (array) $wp_registered_widget_updates as $name => $control ) { 1414 1540 if ( $name === $parsed_id['id_base'] && is_callable( $control['callback'] ) ) { … … 1441 1567 1442 1568 // Obtain the widget instance. 1443 1569 $option = $this->get_captured_option( $option_name ); 1444 if ( null !== $parsed_id['number'] ) { 1570 if ( $is_widget_delete ) { 1571 $instance = array( 'multidimensional_delete' => true ); 1572 } else if ( null !== $parsed_id['number'] ) { 1445 1573 $instance = $option[ $parsed_id['number'] ]; 1446 1574 } else { 1447 1575 $instance = $option; … … 1458 1586 // Obtain the widget control with the updated instance in place. 1459 1587 ob_start(); 1460 1588 $form = $wp_registered_widget_controls[ $widget_id ]; 1461 if ( $form ) {1589 if ( $form && false === $is_widget_delete ) { 1462 1590 call_user_func_array( $form['callback'], $form['params'] ); 1463 1591 } 1464 1592 $form = ob_get_clean(); -
src/wp-includes/class-wp-customize-setting.php
868 868 869 869 $result = $this->multidimensional( $root, $keys, true ); 870 870 871 if ( isset( $result ) ) 872 $result['node'][ $result['key'] ] = $value; 871 if ( isset( $result ) ) { 873 872 873 if ( ! empty( $value['multidimensional_delete'] ) ) { 874 unset( $result['node'][ $result['key'] ] ); 875 } else { 876 $result['node'][ $result['key'] ] = $value; 877 } 878 879 } 880 874 881 return $root; 875 882 } 876 883 -
src/wp-admin/js/customize-widgets.js
150 150 'keyup #widgets-search': 'search', 151 151 'focus .widget-tpl' : 'focus', 152 152 'click .widget-tpl' : '_submit', 153 'click .add-saved-widget' : '_submit', 154 'click .delete-widget-permanently' : '_delete', 155 'click .widget-tpl .widget-title-action' : '_toggleInactive', 153 156 'keypress .widget-tpl' : '_submit', 154 157 'keydown' : 'keyboardAccessible' 155 158 }, 156 159 160 // Inactive widget template to be used in available widgets. 161 inactiveWidgetTpl: null, 162 157 163 // Cache current selected widget 158 164 selected: null, 159 165 … … 176 182 177 183 this.updateList(); 178 184 185 this.inactiveWidgetTpl = _.template( api.Widgets.data.tpl.inactiveWidget ); 186 187 this._createInactiveWidgets(); 188 179 189 // Set the initial search count to the number of available widgets. 180 190 this.searchMatchesCount = this.collection.length; 181 191 … … 228 238 } 229 239 } 230 240 241 // Collapse inactive widgets upon searching. 242 this.$el.find( '.saved-widget' ).hide(); 243 this.$el.find( '.widget-tpl.expanded' ).removeClass( 'expanded' ); 244 231 245 // Toggle the clear search results button. 232 246 if ( '' !== event.target.value ) { 233 247 this.$clearResults.addClass( 'is-visible' ); … … 270 284 } ); 271 285 }, 272 286 287 /** 288 * Create the list of inactive widgets to be added to the available widgets sidebar onload. 289 * 290 * @since 4.8.0 291 */ 292 _createInactiveWidgets : function () { 293 var self = this; 294 295 _( api.Widgets.data.availableWidgets ).each( function ( availableWidget ) { 296 _( availableWidget.saved_widgets ).each( function ( savedWidget ) { 297 self.addInactiveWidget( { 298 id: savedWidget.id, 299 type: savedWidget.type, 300 idBase: availableWidget.id_base, 301 } ); 302 }); 303 }); 304 }, 305 306 /** 307 * Adds a single inactive widget to the available widgets sidebar. 308 * 309 * @since 4.8.0 310 */ 311 addInactiveWidget : function ( templateArgs ) { 312 var $inactiveWidget = $( this.inactiveWidgetTpl( templateArgs ) ); 313 314 if ( 0 === this.$el.find( '.saved-widget[data-widget-id="' + templateArgs.id + '"]' ).length ) { 315 this.$el.find( '[data-id-base="' + templateArgs.idBase + '"]' ).first().after( $inactiveWidget ); 316 } 317 318 return $inactiveWidget; 319 }, 320 321 /** 322 * Show inactive widgets and hide active widgets. 323 * 324 * @since 4.8.0 325 */ 326 updateVisibleSavedWidgets : function () { 327 var self = this; 328 329 // Remove state classes. 330 this.$el.find( '.widget-tpl.has-inactive-widgets, .widget-tpl.expanded' ) 331 .removeClass( 'has-inactive-widgets' ) 332 .removeClass( 'expanded' ); 333 334 // Hide all inactive widgets 335 this.$el.find( '.saved-widget' ) 336 .hide() 337 .removeClass( 'inactive-widget' ); 338 339 _( api( 'sidebars_widgets[wp_inactive_widgets]' )() ).each( function( widgetId ) { 340 var $savedWidget = self.$el.find( '#saved-widget-' + widgetId ), 341 parsedWidgetId = parseWidgetId( widgetId ), 342 widgetControlId = 'widget_' + parsedWidgetId.id_base + '[' + parsedWidgetId.number + ']', 343 widgetControl = api.control( widgetControlId ), 344 widgetSetting = api( widgetControlId ), 345 title = ( widgetSetting ) ? widgetSetting().title : null, 346 $inWidgetTitle, 347 availableWidget; 348 349 if ( ! widgetControl ) { 350 return; 351 } 352 353 // If inactive widget HTML does not exist, create it. 354 if ( 0 === $savedWidget.length ) { 355 availableWidget = api.Widgets.availableWidgets.findWhere( { id_base: parsedWidgetId.id_base } ); 356 $savedWidget = self.addInactiveWidget({ 357 id: widgetId, 358 type: availableWidget.attributes.name, 359 idBase: parsedWidgetId.id_base, 360 }); 361 } 362 363 $savedWidget.addClass( 'inactive-widget' ); 364 365 self.$el.find( '.widget-tpl[data-id-base="' + parsedWidgetId.id_base + '"]' ) 366 .not( '.saved-widget' ) 367 .addClass( 'has-inactive-widgets' ); 368 369 $inWidgetTitle = $savedWidget.find( '.in-widget-title' ); 370 if ( title ) { 371 $inWidgetTitle.text( ': ' + title ); 372 } else { 373 $inWidgetTitle.text( '' ); 374 } 375 } ); 376 }, 377 378 /** 379 * Show or hide the list of inactive widgets per widget id base. 380 * 381 * @since 4.8.0 382 */ 383 toggleInactive : function ( idBase ) { 384 var $widgetTpl = this.$el.find( '.has-inactive-widgets[data-id-base="' + idBase + '"]' ), 385 $inactiveWidgets = this.$el.find( '.saved-widget[data-id-base="' + idBase + '"].inactive-widget' ); 386 387 $widgetTpl.toggleClass( 'expanded' ); 388 $inactiveWidgets.stop().slideToggle( 'fast' ); 389 390 if ( $widgetTpl.hasClass('expanded') ) { 391 this.select( $inactiveWidgets.first().focus() ); 392 } else { 393 this.select( $widgetTpl.focus() ); 394 } 395 }, 396 397 /** 398 * Slides down a list of active widgets per widget type. 399 * 400 * @since 4.8.0 401 */ 402 _toggleInactive : function ( event ) { 403 event.stopPropagation(); 404 event.preventDefault(); 405 406 var $widgetTpl = $( event.currentTarget ).closest( '.widget-tpl' ); 407 this.toggleInactive( $widgetTpl.data('id-base') ); 408 }, 409 410 /** 411 * Permanently delete a widget. 412 * 413 * @since 4.8.0 414 */ 415 _delete : function ( event ) { 416 var self = this, 417 $currentTarget = $( event.currentTarget ), 418 $savedWidgets = $currentTarget.closest( '.saved-widget' ), 419 widgetId = $savedWidgets.data( 'widget-id' ), 420 idBase = $savedWidgets.data( 'id-base' ), 421 settingId = widgetIdToSettingId( widgetId ), 422 control = api.Widgets.getWidgetFormControlForWidget( widgetId ), 423 deleteCallback, 424 inactiveSidebarWidgets, 425 inactiveWidgets, 426 i; 427 428 // Remove from sidebar. 429 inactiveWidgets = api( 'sidebars_widgets[wp_inactive_widgets]' ); 430 431 inactiveSidebarWidgets = inactiveWidgets().slice(); 432 i = _.indexOf( inactiveSidebarWidgets, widgetId ); 433 if ( -1 !== i ) { 434 inactiveSidebarWidgets.splice( i, 1 ); 435 inactiveWidgets.set( inactiveSidebarWidgets ); 436 } 437 438 // Embed control to allow unembedded controls to trigger update calls. 439 if ( ! control.widgetControlEmdedded ) { 440 control.embedWidgetControl(); 441 } 442 api( settingId ).set( false ); 443 444 deleteCallback = function () { 445 var $remainingWidgets; 446 447 $( this ).remove(); 448 api.control.remove( control.id ); 449 control.container.remove(); 450 451 // Remove drop down if widgets no longer exist 452 $remainingWidgets = self.$el.find( '.saved-widget[data-id-base="' + idBase + '"].inactive-widget' ); 453 if ( ! $remainingWidgets.length ) { 454 self.$el.find( '.has-inactive-widgets[data-id-base="' + idBase + '"]' ) 455 .removeClass( 'has-inactive-widgets expanded' ); 456 } 457 458 wp.a11y.speak( l10n.itemDeleted ); 459 }; 460 461 $currentTarget.closest( '.saved-widget' ).slideUp( 'fast' , deleteCallback ); 462 }, 463 273 464 // Highlights a widget 274 465 select: function( widgetTpl ) { 275 466 this.selected = $( widgetTpl ); … … 294 485 295 486 // Adds a selected widget to the sidebar 296 487 submit: function( widgetTpl ) { 297 var widgetId, widget, widgetFormControl ;488 var widgetId, widget, widgetFormControl, isSavedWidget, addWidgetId; 298 489 299 490 if ( ! widgetTpl ) { 300 491 widgetTpl = this.selected; … … 306 497 307 498 this.select( widgetTpl ); 308 499 309 widgetId = $( this.selected ).data( 'widget-id' ); 500 widgetId = widgetTpl.data( 'widget-id' ); 501 isSavedWidget = widgetTpl.data( 'is-saved-widget' ); 502 310 503 widget = this.collection.findWhere( { id: widgetId } ); 311 if ( ! widget) {504 if ( ( ! widget && ! isSavedWidget ) || widgetTpl.hasClass( 'saved-widget' ) ) { 312 505 return; 313 506 } 314 507 315 widgetFormControl = this.currentSidebarControl.addWidget( widget.get( 'id_base' ) ); 508 addWidgetId = ( isSavedWidget ) ? widgetId : widget.get( 'id_base' ); 509 widgetFormControl = this.currentSidebarControl.addWidget( addWidgetId ); 510 316 511 if ( widgetFormControl ) { 317 512 widgetFormControl.focus(); 318 513 } … … 324 519 open: function( sidebarControl ) { 325 520 this.currentSidebarControl = sidebarControl; 326 521 522 // Create inactive widget controls that were moved to the inactive sidebar. 523 this.updateVisibleSavedWidgets(); 524 327 525 // Wide widget controls appear over the preview, and so they need to be collapsed when the panel opens 328 526 _( this.currentSidebarControl.getWidgetFormControls() ).each( function( control ) { 329 527 if ( control.params.is_wide ) { … … 359 557 this.$search.val( '' ); 360 558 }, 361 559 362 // Add keyboard accessib lity to the panel560 // Add keyboard accessibility to the panel 363 561 keyboardAccessible: function( event ) { 364 562 var isEnter = ( event.which === 13 ), 365 563 isEsc = ( event.which === 27 ), 366 564 isDown = ( event.which === 40 ), 367 565 isUp = ( event.which === 38 ), 566 isLeft = ( event.which === 37 ), 567 isRight = ( event.which === 39 ), 368 568 isTab = ( event.which === 9 ), 369 569 isShift = ( event.shiftKey ), 370 570 selected = null, 371 firstVisible = this.$el.find( ' >.widget-tpl:visible:first' ),372 lastVisible = this.$el.find( ' >.widget-tpl:visible:last' ),571 firstVisible = this.$el.find( '.widget-tpl:visible:first' ), 572 lastVisible = this.$el.find( '.widget-tpl:visible:last' ), 373 573 isSearchFocused = $( event.target ).is( this.$search ), 374 574 isLastWidgetFocused = $( event.target ).is( '.widget-tpl:visible:last' ); 375 575 … … 399 599 return; 400 600 } 401 601 602 // Toggle display when the user presses left or right on widget template with inactive widgets. 603 if ( isLeft || isRight ) { 604 if ( this.selected && (this.selected.hasClass( 'has-inactive-widgets' ) || this.selected.hasClass( 'saved-widget' ) ) ) { 605 var idBase = this.selected.data('id-base'); 606 this.toggleInactive( idBase ); 607 } 608 609 return; 610 } 611 402 612 // If enter pressed but nothing entered, don't do anything 403 613 if ( isEnter && ! this.$search.val() ) { 404 614 return; … … 521 731 if ( control.widgetControlEmbedded ) { 522 732 return; 523 733 } 734 524 735 control.widgetControlEmbedded = true; 525 736 526 737 widgetControl = $( control.params.widget_control ); … … 534 745 control._setupReorderUI(); 535 746 control._setupHighlightEffects(); 536 747 control._setupUpdateUI(); 748 control._setupMoveUI(); 537 749 control._setupRemoveUI(); 538 750 }, 539 751 … … 947 1159 }, 948 1160 949 1161 /** 950 * Set up event handlers for widget removal 1162 * Set up event handlers for widget move. 1163 * 1164 * @since 4.8.0 951 1165 */ 1166 _setupMoveUI: function() { 1167 var self = this, $moveBtn, $reorderToggle, replaceDeleteWithMove; 1168 1169 // Configure move button 1170 $moveBtn = this.container.find( 'a.widget-control-remove' ); 1171 1172 $moveBtn.on( 'click', function ( e ) { 1173 e.preventDefault(); 1174 1175 $reorderToggle = $( this ).closest( '.ui-sortable' ).find( '.reorder-toggle' ); 1176 1177 $reorderToggle.trigger( 'click' ); 1178 self.openWidgetMoveArea(); 1179 } ); 1180 1181 // Replace the Delete link with Move. 1182 replaceDeleteWithMove = function () { 1183 $moveBtn.text( l10n.moveBtnLabel ); // wp_widget_control() outputs the link as "Close" 1184 $moveBtn.attr( 'title', l10n.moveBtnTooltip ); 1185 }; 1186 1187 replaceDeleteWithMove(); 1188 }, 1189 1190 /** 1191 * Set up event handlers for widget removal. 1192 */ 952 1193 _setupRemoveUI: function() { 953 var self = this, $ removeBtn, replaceDeleteWithRemove;1194 var self = this, $closeBtn; 954 1195 955 1196 // Configure remove button 956 $ removeBtn = this.container.find( 'a.widget-control-remove' );957 $ removeBtn.on( 'click', function( e ) {1197 $closeBtn = this.container.find( 'a.widget-control-close' ); 1198 $closeBtn.on( 'click', function( e ) { 958 1199 e.preventDefault(); 959 1200 960 1201 // Find an adjacent element to add focus to when this widget goes away … … 969 1210 970 1211 self.container.slideUp( function() { 971 1212 var sidebarsWidgetsControl = api.Widgets.getSidebarWidgetControlContainingWidget( self.params.widget_id ), 972 sidebarWidgetIds, i ;1213 sidebarWidgetIds, i, inactiveWidgets, inactiveWidgetSetting; 973 1214 974 1215 if ( ! sidebarsWidgetsControl ) { 975 1216 return; … … 984 1225 sidebarWidgetIds.splice( i, 1 ); 985 1226 sidebarsWidgetsControl.setting( sidebarWidgetIds ); 986 1227 1228 // Add widget to inactive widgets. 1229 inactiveWidgetSetting = api( 'sidebars_widgets[wp_inactive_widgets]' ); 1230 inactiveWidgets = inactiveWidgetSetting(); 1231 inactiveWidgets.push( self.params.widget_id ); 1232 inactiveWidgetSetting.set( [] ).set( _( inactiveWidgets ).unique() ); 1233 987 1234 $adjacentFocusTarget.focus(); // keyboard accessibility 1235 1236 // Remove display none in case its added again. 1237 self.container.show(); 988 1238 } ); 989 1239 } ); 990 1240 991 replaceDeleteWithRemove = function() { 992 $removeBtn.text( l10n.removeBtnLabel ); // wp_widget_control() outputs the link as "Delete" 993 $removeBtn.attr( 'title', l10n.removeBtnTooltip ); 994 }; 995 996 if ( this.params.is_new ) { 997 api.bind( 'saved', replaceDeleteWithRemove ); 998 } else { 999 replaceDeleteWithRemove(); 1000 } 1241 $closeBtn.text( l10n.removeBtnLabel ); // wp_widget_control() outputs the link as "Close" 1242 $closeBtn.attr( 'title', l10n.removeBtnTooltip ); 1001 1243 }, 1002 1244 1003 1245 /** … … 1156 1398 params.nonce = api.settings.nonce['update-widget']; 1157 1399 params.customize_theme = api.settings.theme.stylesheet; 1158 1400 params.customized = wp.customize.previewer.query().customized; 1401 params.delete_widget = ( false === self.setting() ) ? 1 : 0; 1159 1402 1160 1403 data = $.param( params ); 1161 1404 $inputs = this._getInputs( $widgetContent ); … … 1169 1412 1170 1413 if ( instanceOverride ) { 1171 1414 data += '&' + $.param( { 'sanitized_widget_setting': JSON.stringify( instanceOverride ) } ); 1172 } else {1415 } else if ( $inputs.length ) { 1173 1416 data += '&' + $inputs.serialize(); 1174 1417 } 1418 1175 1419 data += '&' + $widgetContent.find( '~ :input' ).serialize(); 1176 1420 1177 1421 if ( this._previousUpdateRequest ) { … … 1252 1496 * preview finishing loading. 1253 1497 */ 1254 1498 isChanged = ! isLiveUpdateAborted && ! _( self.setting() ).isEqual( r.data.instance ); 1255 if ( isChanged ) {1499 if ( isChanged || false === self.setting() ) { 1256 1500 self.isWidgetUpdating = true; // suppress triggering another updateWidget 1257 1501 self.setting( r.data.instance ); 1258 1502 self.isWidgetUpdating = false; … … 1534 1778 } 1535 1779 1536 1780 $moveWidgetArea.toggleClass( 'active', showOrHide ); 1781 self.toggleSaveForLater(); 1537 1782 }, 1538 1783 1539 1784 /** 1785 * Open widget move area if control is not active already. 1786 * 1787 * @since 4.8.0 1788 */ 1789 openWidgetMoveArea: function() { 1790 var self = this, $moveWidgetArea; 1791 1792 $moveWidgetArea = this.container.find( '.move-widget-area' ); 1793 1794 if( ! $moveWidgetArea.hasClass( 'active' ) ) { 1795 self.toggleWidgetMoveArea(); 1796 } 1797 }, 1798 1799 /** 1800 * Toggle visibility of the Save For Later/Inactive Widget Sidebar section. 1801 * 1802 * @since 4.8.0 1803 */ 1804 toggleSaveForLater: function() { 1805 var $moveWidgetArea, $saveForLater; 1806 1807 $moveWidgetArea = this.container.find( '.move-widget-area' ); 1808 $saveForLater = this.container.find( 'li[data-id="wp_inactive_widgets"]' ); 1809 1810 if( $moveWidgetArea.hasClass( 'active' ) ) { 1811 $saveForLater.show(); 1812 } else { 1813 $saveForLater.hide(); 1814 } 1815 }, 1816 1817 /** 1540 1818 * Highlight the widget control and section 1541 1819 */ 1542 1820 highlightSectionAndControl: function() { … … 1757 2035 return; 1758 2036 } 1759 2037 1760 removedControl = api.Widgets.getWidgetFormControlForWidget( removedWidgetId );1761 1762 // Detect if widget control was dragged to another sidebar1763 wasDraggedToAnotherSidebar = removedControl && $.contains( document, removedControl.container[0] ) && ! $.contains( self.$sectionContent[0], removedControl.container[0] );1764 1765 // Delete any widget form controls for removed widgets1766 if ( removedControl && ! wasDraggedToAnotherSidebar ) {1767 api.control.remove( removedControl.id );1768 removedControl.container.remove();1769 }1770 1771 2038 // Move widget to inactive widgets sidebar (move it to trash) if has been previously saved 1772 2039 // This prevents the inactive widgets sidebar from overflowing with throwaway widgets 1773 if ( api.Widgets.savedWidgetIds[removedWidgetId] ) {2040 if ( api.Widgets.savedWidgetIds[removedWidgetId] && 'sidebars_widgets[wp_inactive_widgets]' !== self.id ) { 1774 2041 inactiveWidgets = api.value( 'sidebars_widgets[wp_inactive_widgets]' )().slice(); 1775 2042 inactiveWidgets.push( removedWidgetId ); 1776 2043 api.value( 'sidebars_widgets[wp_inactive_widgets]' )( _( inactiveWidgets ).unique() ); … … 2039 2306 setting.set( {} ); // mark dirty, changing from '' to {} 2040 2307 } 2041 2308 2042 controlConstructor = api.controlConstructor[controlType]; 2043 widgetFormControl = new controlConstructor( settingId, { 2044 params: { 2045 settings: { 2046 'default': settingId 2309 widgetFormControl = api.control( settingId ); 2310 if ( ! widgetFormControl ) { 2311 2312 controlConstructor = api.controlConstructor[controlType]; 2313 widgetFormControl = new controlConstructor( settingId, { 2314 params: { 2315 settings: { 2316 'default': settingId 2317 }, 2318 content: controlContainer, 2319 sidebar_id: self.params.sidebar_id, 2320 widget_id: widgetId, 2321 widget_id_base: widget.get( 'id_base' ), 2322 type: controlType, 2323 is_new: ! isExistingWidget, 2324 width: widget.get( 'width' ), 2325 height: widget.get( 'height' ), 2326 is_wide: widget.get( 'is_wide' ), 2327 active: true 2047 2328 }, 2048 content: controlContainer, 2049 sidebar_id: self.params.sidebar_id, 2050 widget_id: widgetId, 2051 widget_id_base: widget.get( 'id_base' ), 2052 type: controlType, 2053 is_new: ! isExistingWidget, 2054 width: widget.get( 'width' ), 2055 height: widget.get( 'height' ), 2056 is_wide: widget.get( 'is_wide' ), 2057 active: true 2058 }, 2059 previewer: self.setting.previewer 2060 } ); 2061 api.control.add( settingId, widgetFormControl ); 2329 previewer: self.setting.previewer 2330 } ); 2331 api.control.add( settingId, widgetFormControl ); 2332 } 2062 2333 2063 2334 // Make sure widget is removed from the other sidebars 2064 2335 api.each( function( otherSetting ) { … … 2074 2345 i = _.indexOf( otherSidebarWidgets, widgetId ); 2075 2346 2076 2347 if ( -1 !== i ) { 2077 otherSidebarWidgets.splice( i );2348 otherSidebarWidgets.splice( i, 1 ); 2078 2349 otherSetting( otherSidebarWidgets ); 2079 2350 } 2080 2351 } ); -
src/wp-admin/css/customize-controls.css
1971 1971 #available-menu-items-search .search-icon { 1972 1972 top: 85px; /* 70 section title height + 13 container padding +1 input margin +1 input border */ 1973 1973 } 1974 1975 .wp-customizer #available-widgets .saved-widget-controls { 1976 visibility: visible; 1977 } 1974 1978 } -
src/wp-admin/css/customize-widgets.css
212 212 display: block; 213 213 } 214 214 215 #customize-theme-controls .widget-control-remove { 216 color: #0073aa; 217 } 218 #customize-theme-controls .widget-control-remove:hover { 219 color: #0096dd; 220 } 221 #customize-theme-controls .widget-control-close { 222 color: #a00; 223 } 224 #customize-theme-controls .widget-control-close:hover { 225 color: #f00; 226 } 227 215 228 /** 216 229 * Styles for new widget addition panel 217 230 */ … … 267 280 opacity: 0.4; 268 281 } 269 282 283 #available-widgets .widget-tpl { 284 padding-right: 35px; 285 } 270 286 287 #available-widgets .saved-widget { 288 margin-left: 48px; 289 border-left: 1px #e4e4e4 solid; 290 padding: 10px 20px 10px; 291 background-color: white; 292 display: none; 293 cursor: default; 294 } 295 296 #available-widgets .saved-widget:last-of-type { 297 margin-bottom: 40px; 298 } 299 300 #available-widgets .saved-widget h3 { 301 font-size: 1em; 302 margin: 0; 303 } 304 305 #available-widgets .saved-widget a { 306 text-decoration: none; 307 } 308 309 #available-widgets .saved-widget-controls { 310 visibility: hidden; 311 } 312 313 #available-widgets .saved-widget.selected .saved-widget-controls, 314 #available-widgets .saved-widget:hover .saved-widget-controls { 315 visibility: visible; 316 } 317 318 #available-widgets .delete-widget-permanently { 319 color: #a00; 320 } 321 322 #available-widgets .delete-widget-permanently:hover { 323 color: #f00; 324 } 325 326 #available-widgets .widget-tpl.expanded a.widget-action:after { 327 content: "\f142"; 328 } 329 330 #available-widgets .has-inactive-widgets .widget-action { 331 display: block; 332 position: relative; 333 top: 50%; 334 width: 20px; 335 margin: 0 auto; 336 -webkit-transform: translateY(-50%); 337 -ms-transform: translateY(-50%); 338 transform: translateY(-50%); 339 } 340 341 #available-widgets .has-inactive-widgets .widget-title-action { 342 display: block; 343 position: absolute; 344 right: 0; 345 top: 0; 346 height:100%; 347 width: 30px; 348 background-color: #eee; 349 } 350 351 #available-widgets .saved-widget.widget-title:before { 352 display: none; 353 } 354 355 #available-widgets .widget-tpl.expanded .widget-title-action, 356 #available-widgets .has-inactive-widgets .widget-title-action:hover { 357 border-left: 1px #e4e4e4 solid; 358 background-color: #FFFFFF; 359 } 360 361 #available-widgets .has-inactive-widgets .widget-title-action:hover .widget-action { 362 color: #555d66; 363 } 364 365 #available-widgets .has-inactive-widgets .widget-top a.widget-action:after { 366 margin: 0 auto; 367 } 368 271 369 /** 272 370 * Widget Icon styling 273 371 * No plurals in naming. … … 447 545 .widget-reorder-nav span:before { 448 546 line-height: 39px; 449 547 } 548 #available-widgets .widget-top { 549 position: static; 550 } 450 551 #customize-theme-controls .widget-area-select li { 451 552 padding: 9px 15px 11px 42px; 452 553 }