Ticket #27404: 27404-6-8-16.patch
File 27404-6-8-16.patch, 34.2 KB (added by , 8 years ago) |
---|
-
src/wp-includes/class-wp-customize-widgets.php
444 444 445 445 // Add section to contain controls. 446 446 $section_id = sprintf( 'sidebar-widgets-%s', $sidebar_id ); 447 if ( $is_active_sidebar ) {448 447 449 $section_args = array(450 'title' => $wp_registered_sidebars[ $sidebar_id ]['name'],451 'description' => $wp_registered_sidebars[ $sidebar_id ]['description'],452 'priority' => array_search( $sidebar_id, array_keys( $wp_registered_sidebars ) ),453 'panel' => 'widgets',454 'sidebar_id' => $sidebar_id,455 );448 if ( $is_inactive_widgets ) { 449 $title = 'Inactive Widgets'; 450 $description = $title; 451 } else { 452 $title = $wp_registered_sidebars[ $sidebar_id ]['name']; 453 $description = $wp_registered_sidebars[ $sidebar_id ]['description']; 454 } 456 455 457 /** 458 * Filters Customizer widget section arguments for a given sidebar. 459 * 460 * @since 3.9.0 461 * 462 * @param array $section_args Array of Customizer widget section arguments. 463 * @param string $section_id Customizer section ID. 464 * @param int|string $sidebar_id Sidebar ID. 465 */ 466 $section_args = apply_filters( 'customizer_widgets_section_args', $section_args, $section_id, $sidebar_id ); 456 $section_args = array( 457 'title' => $title, 458 'description' => $description, 459 'priority' => array_search( $sidebar_id, array_keys( $wp_registered_sidebars ) ), 460 'panel' => 'widgets', 461 'sidebar_id' => $sidebar_id, 462 ); 467 463 468 $section = new WP_Customize_Sidebar_Section( $this->manager, $section_id, $section_args ); 469 $this->manager->add_section( $section ); 464 /** 465 * Filters Customizer widget section arguments for a given sidebar. 466 * 467 * @since 3.9.0 468 * 469 * @param array $section_args Array of Customizer widget section arguments. 470 * @param string $section_id Customizer section ID. 471 * @param int|string $sidebar_id Sidebar ID. 472 */ 473 $section_args = apply_filters( 'customizer_widgets_section_args', $section_args, $section_id, $sidebar_id ); 470 474 471 $control = new WP_Widget_Area_Customize_Control( $this->manager, $setting_id, array( 472 'section' => $section_id, 473 'sidebar_id' => $sidebar_id, 474 'priority' => count( $sidebar_widget_ids ), // place 'Add Widget' and 'Reorder' buttons at end. 475 ) ); 476 $new_setting_ids[] = $setting_id; 475 $section = new WP_Customize_Sidebar_Section( $this->manager, $section_id, $section_args ); 476 $this->manager->add_section( $section ); 477 477 478 $this->manager->add_control( $control ); 479 } 478 $control = new WP_Widget_Area_Customize_Control( $this->manager, $setting_id, array( 479 'section' => $section_id, 480 'sidebar_id' => $sidebar_id, 481 'priority' => count( $sidebar_widget_ids ), // place 'Add Widget' and 'Reorder' buttons at end. 482 ) ); 483 $new_setting_ids[] = $setting_id; 484 485 $this->manager->add_control( $control ); 486 487 $section_args = array( 488 'title' => $title, 489 'description' => $description, 490 'priority' => array_search( $sidebar_id, array_keys( $wp_registered_sidebars ) ), 491 'panel' => 'widgets', 492 'sidebar_id' => $sidebar_id, 493 ); 494 495 /** 496 * Filter Customizer widget section arguments for a given sidebar. 497 * 498 * @since 3.9.0 499 * 500 * @param array $section_args Array of Customizer widget section arguments. 501 * @param string $section_id Customizer section ID. 502 * @param int|string $sidebar_id Sidebar ID. 503 */ 504 $section_args = apply_filters( 'customizer_widgets_section_args', $section_args, $section_id, $sidebar_id ); 505 506 $section = new WP_Customize_Sidebar_Section( $this->manager, $section_id, $section_args ); 507 $this->manager->add_section( $section ); 508 509 $control = new WP_Widget_Area_Customize_Control( $this->manager, $setting_id, array( 510 'section' => $section_id, 511 'sidebar_id' => $sidebar_id, 512 'priority' => count( $sidebar_widget_ids ), // place 'Add Widget' and 'Reorder' buttons at end. 513 ) ); 514 $new_setting_ids[] = $setting_id; 515 516 $this->manager->add_control( $control ); 480 517 } 481 518 482 519 // Add a control for each active widget (located in a sidebar). 483 520 foreach ( $sidebar_widget_ids as $i => $widget_id ) { 484 521 485 522 // Skip widgets that may have gone away due to a plugin being deactivated. 486 if ( ! $is_active_sidebar || !isset( $wp_registered_widgets[$widget_id] ) ) {523 if ( ! isset( $wp_registered_widgets[$widget_id] ) ) { 487 524 continue; 488 525 } 489 526 … … 502 539 'height' => $wp_registered_widget_controls[$widget_id]['height'], 503 540 'is_wide' => $this->is_wide_widget( $widget_id ), 504 541 ) ); 542 505 543 $this->manager->add_control( $control ); 506 544 } 507 545 } … … 717 755 </div>' 718 756 ); 719 757 758 $inactive_widget_tpl = str_replace( 759 array( '{delete}', '{add}' ), 760 array( 761 __( 'Delete' ), 762 __( 'Add' ) 763 ), 764 '<div id="saved-widget-<%- id %>" data-id-base="<%- idBase %>" data-widget-id="<%- id %>" class="widget-title saved-widget widget-tpl" tabindex="0"> 765 <h3><%- type %><span class="in-widget-title"></span></h3> 766 <div class="saved-widget-controls"> 767 <a href="#" class="add-saved-widget" data-is-saved-widget="1" data-widget-id="<%- id %>" 768 >{add}</a> | <a href="#" class="delete-widget-permanently">{delete}</a> 769 </div> 770 </div>' 771 ); 772 773 $inactive_sidebar = array( 774 'inactive-sidebar' => array( 775 'name' => __( 'Save For Later' ), 776 'id' => 'wp_inactive_widgets', 777 'description' => __( 'Save widgets to the Inactive Sidebar to use later.' ), 778 'class' => 'inactive-sidebar', 779 'before_widget' => '<aside id="%1$s" class="widget %2$s">', 780 'after_widget' => '</aside>', 781 'before_title' => '<h1 class="widget-title">', 782 'after_title' => '</h1>', 783 ), 784 ); 785 786 $all_sidebars = array_values( array_merge( $wp_registered_sidebars, $inactive_sidebar ) ); 787 720 788 $settings = array( 721 'registeredSidebars' => array_values( $wp_registered_sidebars ),789 'registeredSidebars' => $all_sidebars, 722 790 'registeredWidgets' => $wp_registered_widgets, 723 791 'availableWidgets' => $available_widgets, // @todo Merge this with registered_widgets 724 792 'l10n' => array( … … 726 794 'saveBtnTooltip' => __( 'Save and preview changes before publishing them.' ), 727 795 'removeBtnLabel' => __( 'Remove' ), 728 796 'removeBtnTooltip' => __( 'Trash widget by moving it to the inactive widgets sidebar.' ), 797 'moveBtnLabel' => __( 'Move' ), 798 'moveBtnTooltip' => __( 'Reorder the widgets in this sidebar.' ), 729 799 'error' => __( 'An error has occurred. Please reload the page and try again.' ), 730 800 'widgetMovedUp' => __( 'Widget moved up' ), 731 801 'widgetMovedDown' => __( 'Widget moved down' ), … … 734 804 'reorderModeOff' => __( 'Reorder mode closed' ), 735 805 'reorderLabelOn' => esc_attr__( 'Reorder widgets' ), 736 806 'reorderLabelOff' => esc_attr__( 'Close reorder mode' ), 807 'itemDeleted' => __( 'Widget deleted' ), 737 808 ), 738 809 'tpl' => array( 739 810 'widgetReorderNav' => $widget_reorder_nav_tpl, 740 811 'moveWidgetArea' => $move_widget_area_tpl, 812 'inactiveWidget' => $inactive_widget_tpl, 741 813 ), 742 814 'selectiveRefreshableWidgets' => $this->get_selective_refreshable_widgets(), 743 815 ); … … 781 853 </div> 782 854 <div id="available-widgets-list"> 783 855 <?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">856 <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 857 <?php echo $available_widget['control_tpl']; ?> 786 858 </div> 787 859 <?php endforeach; ?> … … 868 940 foreach ( $widget_ids as $widget_id ) { 869 941 $sanitized_widget_ids[] = preg_replace( '/[^a-z0-9_\-]/', '', $widget_id ); 870 942 } 943 871 944 return $sanitized_widget_ids; 872 945 } 873 946 874 947 /** 948 * Create an list of saved widgets that correspond to a widget type. 949 * 950 * @since 4.6.0 951 * @access protected 952 * 953 * @global array $wp_registered_widget_controls 954 * 955 * @return array List of saved widget by type. 956 */ 957 protected function get_sorted_saved_widgets() { 958 global $wp_registered_widget_controls; 959 960 $sidebars_widgets = wp_get_sidebars_widgets(); 961 962 $sorted_saved_widgets = array(); 963 foreach ( $sidebars_widgets as $sidebars_widget ) { 964 foreach( $sidebars_widget as $widget ) { 965 966 if ( isset ( $wp_registered_widget_controls[ $widget ]['id_base'] ) ) { 967 $current_base_id = $wp_registered_widget_controls[ $widget ]['id_base']; 968 $sorted_saved_widgets[ $current_base_id ][] = array( 969 'id' => $widget, 970 'type' => $wp_registered_widget_controls[ $widget ]['name'], 971 ); 972 } 973 974 } 975 } 976 977 return $sorted_saved_widgets; 978 } 979 980 /** 875 981 * Builds up an index of all available widgets for use in Backbone models. 876 982 * 877 983 * @since 3.9.0 … … 894 1000 global $wp_registered_widgets, $wp_registered_widget_controls; 895 1001 require_once ABSPATH . '/wp-admin/includes/widgets.php'; // for next_widget_id_number() 896 1002 1003 $sorted_saved_widgets = $this->get_sorted_saved_widgets(); 897 1004 $sort = $wp_registered_widgets; 898 1005 usort( $sort, array( $this, '_sort_name_callback' ) ); 899 1006 $done = array(); … … 950 1057 'width' => $wp_registered_widget_controls[$widget['id']]['width'], 951 1058 'height' => $wp_registered_widget_controls[$widget['id']]['height'], 952 1059 'is_wide' => $this->is_wide_widget( $widget['id'] ), 1060 'saved_widgets' => isset( $sorted_saved_widgets[ $id_base ] ) ? $sorted_saved_widgets[ $id_base ] : array(), 953 1061 ) ); 954 1062 955 1063 $available_widgets[] = $available_widget; … … 1257 1365 * @return array|void Sanitized widget instance. 1258 1366 */ 1259 1367 public function sanitize_widget_instance( $value ) { 1260 if ( $value === array() ) {1368 if ( $value === array() || false === $value ) { 1261 1369 return $value; 1262 1370 } 1263 1371 … … 1347 1455 global $wp_registered_widget_updates, $wp_registered_widget_controls; 1348 1456 1349 1457 $setting_id = $this->get_setting_id( $widget_id ); 1350 1351 1458 /* 1352 1459 * Make sure that other setting changes have previewed since this widget 1353 1460 * may depend on them (e.g. Menus being present for Custom Menu widget). … … 1396 1503 } 1397 1504 } 1398 1505 1506 // If deleting widget. 1507 $delete_widget_val = $this->get_post_value( 'delete_widget' ); 1508 $is_widget_delete = ! empty( $delete_widget_val ); 1509 1510 if ( $is_widget_delete ) { 1511 // Set post values needed in widget update_callback. 1512 $_POST[ 'sidebar' ] = 'wp_inactive_widgets'; 1513 $_POST[ 'widget-' . $parsed_id['id_base'] ] = array(); 1514 $_POST[ 'the-widget-id' ] = $widget_id; 1515 $_POST[ 'delete_widget' ] = '1'; 1516 1517 $post_values_added = array( 'sidebar', 'widget-' . $parsed_id[ 'id_base' ], 'the-widget-id', 'delete_widget' ); 1518 $added_input_vars = array_merge( $added_input_vars, $post_values_added ); 1519 1520 /** This action is documented in wp-admin/widgets.php */ 1521 do_action( 'delete_widget', $widget_id, $_POST[ 'sidebar' ], $parsed_id['id_base'] ); 1522 } 1523 1399 1524 // Invoke the widget update callback. 1400 1525 foreach ( (array) $wp_registered_widget_updates as $name => $control ) { 1401 1526 if ( $name === $parsed_id['id_base'] && is_callable( $control['callback'] ) ) { … … 1428 1553 1429 1554 // Obtain the widget instance. 1430 1555 $option = $this->get_captured_option( $option_name ); 1431 if ( null !== $parsed_id['number'] ) { 1556 if ( $is_widget_delete ) { 1557 $instance = array( 'multidimensional_delete' => true ); 1558 } else if ( null !== $parsed_id['number'] ) { 1432 1559 $instance = $option[ $parsed_id['number'] ]; 1433 1560 } else { 1434 1561 $instance = $option; … … 1445 1572 // Obtain the widget control with the updated instance in place. 1446 1573 ob_start(); 1447 1574 $form = $wp_registered_widget_controls[ $widget_id ]; 1448 if ( $form ) {1575 if ( $form && false === $is_widget_delete ) { 1449 1576 call_user_func_array( $form['callback'], $form['params'] ); 1450 1577 } 1451 1578 $form = ob_get_clean(); -
src/wp-includes/class-wp-customize-setting.php
845 845 846 846 $result = $this->multidimensional( $root, $keys, true ); 847 847 848 if ( isset( $result ) ) 849 $result['node'][ $result['key'] ] = $value; 848 if ( isset( $result ) ) { 850 849 850 if ( ! empty( $value['multidimensional_delete'] ) ) { 851 unset( $result['node'][ $result['key'] ] ); 852 } else { 853 $result['node'][ $result['key'] ] = $value; 854 } 855 856 } 857 851 858 return $root; 852 859 } 853 860 -
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; … … 478 689 if ( control.widgetControlEmbedded ) { 479 690 return; 480 691 } 692 481 693 control.widgetControlEmbedded = true; 482 694 483 695 widgetControl = $( control.params.widget_control ); … … 491 703 control._setupReorderUI(); 492 704 control._setupHighlightEffects(); 493 705 control._setupUpdateUI(); 706 control._setupMoveUI(); 494 707 control._setupRemoveUI(); 495 708 }, 496 709 … … 904 1117 }, 905 1118 906 1119 /** 907 * Set up event handlers for widget removal 1120 * Set up event handlers for widget move. 1121 * 1122 * @since 4.6.0 908 1123 */ 1124 _setupMoveUI: function() { 1125 var self = this, $moveBtn, $reorderToggle, replaceDeleteWithMove; 1126 1127 // Configure move button 1128 $moveBtn = this.container.find( 'a.widget-control-remove' ); 1129 1130 $moveBtn.on( 'click', function ( e ) { 1131 e.preventDefault(); 1132 1133 $reorderToggle = $( this ).closest( '.ui-sortable' ).find( '.reorder-toggle' ); 1134 1135 $reorderToggle.trigger( 'click' ); 1136 self.openWidgetMoveArea(); 1137 } ); 1138 1139 // Replace the Delete link with Move. 1140 replaceDeleteWithMove = function () { 1141 $moveBtn.text( l10n.moveBtnLabel ); // wp_widget_control() outputs the link as "Close" 1142 $moveBtn.attr( 'title', l10n.moveBtnTooltip ); 1143 }; 1144 1145 replaceDeleteWithMove(); 1146 }, 1147 1148 /** 1149 * Set up event handlers for widget removal. 1150 */ 909 1151 _setupRemoveUI: function() { 910 var self = this, $ removeBtn, replaceDeleteWithRemove;1152 var self = this, $closeBtn; 911 1153 912 1154 // Configure remove button 913 $ removeBtn = this.container.find( 'a.widget-control-remove' );914 $ removeBtn.on( 'click', function( e ) {1155 $closeBtn = this.container.find( 'a.widget-control-close' ); 1156 $closeBtn.on( 'click', function( e ) { 915 1157 e.preventDefault(); 916 1158 917 1159 // Find an adjacent element to add focus to when this widget goes away … … 926 1168 927 1169 self.container.slideUp( function() { 928 1170 var sidebarsWidgetsControl = api.Widgets.getSidebarWidgetControlContainingWidget( self.params.widget_id ), 929 sidebarWidgetIds, i ;1171 sidebarWidgetIds, i, inactiveWidgets, inactiveWidgetSetting; 930 1172 931 1173 if ( ! sidebarsWidgetsControl ) { 932 1174 return; … … 941 1183 sidebarWidgetIds.splice( i, 1 ); 942 1184 sidebarsWidgetsControl.setting( sidebarWidgetIds ); 943 1185 1186 // Add widget to inactive widgets. 1187 inactiveWidgetSetting = api( 'sidebars_widgets[wp_inactive_widgets]' ); 1188 inactiveWidgets = inactiveWidgetSetting(); 1189 inactiveWidgets.push( self.params.widget_id ); 1190 inactiveWidgetSetting.set( [] ).set( _( inactiveWidgets ).unique() ); 1191 944 1192 $adjacentFocusTarget.focus(); // keyboard accessibility 1193 1194 // Remove display none in case its added again. 1195 self.container.show(); 945 1196 } ); 946 1197 } ); 947 1198 948 replaceDeleteWithRemove = function() { 949 $removeBtn.text( l10n.removeBtnLabel ); // wp_widget_control() outputs the link as "Delete" 950 $removeBtn.attr( 'title', l10n.removeBtnTooltip ); 951 }; 952 953 if ( this.params.is_new ) { 954 api.bind( 'saved', replaceDeleteWithRemove ); 955 } else { 956 replaceDeleteWithRemove(); 957 } 1199 $closeBtn.text( l10n.removeBtnLabel ); // wp_widget_control() outputs the link as "Close" 1200 $closeBtn.attr( 'title', l10n.removeBtnTooltip ); 958 1201 }, 959 1202 960 1203 /** … … 1113 1356 params.nonce = api.settings.nonce['update-widget']; 1114 1357 params.theme = api.settings.theme.stylesheet; 1115 1358 params.customized = wp.customize.previewer.query().customized; 1359 params.delete_widget = ( false === self.setting() ) ? 1 : 0; 1116 1360 1117 1361 data = $.param( params ); 1118 1362 $inputs = this._getInputs( $widgetContent ); … … 1126 1370 1127 1371 if ( instanceOverride ) { 1128 1372 data += '&' + $.param( { 'sanitized_widget_setting': JSON.stringify( instanceOverride ) } ); 1129 } else {1373 } else if ( $inputs.length ) { 1130 1374 data += '&' + $inputs.serialize(); 1131 1375 } 1376 1132 1377 data += '&' + $widgetContent.find( '~ :input' ).serialize(); 1133 1378 1134 1379 if ( this._previousUpdateRequest ) { … … 1209 1454 * preview finishing loading. 1210 1455 */ 1211 1456 isChanged = ! isLiveUpdateAborted && ! _( self.setting() ).isEqual( r.data.instance ); 1212 if ( isChanged ) {1457 if ( isChanged || false === self.setting() ) { 1213 1458 self.isWidgetUpdating = true; // suppress triggering another updateWidget 1214 1459 self.setting( r.data.instance ); 1215 1460 self.isWidgetUpdating = false; … … 1326 1571 */ 1327 1572 onChangeExpanded: function ( expanded, args ) { 1328 1573 var self = this, $widget, $inside, complete, prevComplete; 1329 1330 1574 self.embedWidgetControl(); // Make sure the outer form is embedded so that the expanded state can be set in the UI. 1331 1575 if ( expanded ) { 1332 1576 self.embedWidgetContent(); … … 1485 1729 } 1486 1730 1487 1731 $moveWidgetArea.toggleClass( 'active', showOrHide ); 1732 self.toggleSaveForLater(); 1488 1733 }, 1489 1734 1490 1735 /** 1736 * Open widget move area if control is not active already. 1737 * 1738 * @since 4.6.0 1739 */ 1740 openWidgetMoveArea: function() { 1741 var self = this, $moveWidgetArea; 1742 1743 $moveWidgetArea = this.container.find( '.move-widget-area' ); 1744 1745 if( ! $moveWidgetArea.hasClass( 'active' ) ) { 1746 self.toggleWidgetMoveArea(); 1747 } 1748 }, 1749 1750 /** 1751 * Toggle visibility of the Save For Later/Inactive Widget Sidebar section. 1752 * 1753 * @since 4.6.0 1754 */ 1755 toggleSaveForLater: function() { 1756 var $moveWidgetArea, $saveForLater; 1757 1758 $moveWidgetArea = this.container.find( '.move-widget-area' ); 1759 $saveForLater = this.container.find( 'li[data-id="wp_inactive_widgets"]' ); 1760 1761 if( $moveWidgetArea.hasClass( 'active' ) ) { 1762 $saveForLater.show(); 1763 } else { 1764 $saveForLater.hide(); 1765 } 1766 }, 1767 1768 /** 1491 1769 * Highlight the widget control and section 1492 1770 */ 1493 1771 highlightSectionAndControl: function() { … … 1708 1986 return; 1709 1987 } 1710 1988 1711 removedControl = api.Widgets.getWidgetFormControlForWidget( removedWidgetId );1712 1713 // Detect if widget control was dragged to another sidebar1714 wasDraggedToAnotherSidebar = removedControl && $.contains( document, removedControl.container[0] ) && ! $.contains( self.$sectionContent[0], removedControl.container[0] );1715 1716 // Delete any widget form controls for removed widgets1717 if ( removedControl && ! wasDraggedToAnotherSidebar ) {1718 api.control.remove( removedControl.id );1719 removedControl.container.remove();1720 }1721 1722 1989 // Move widget to inactive widgets sidebar (move it to trash) if has been previously saved 1723 1990 // This prevents the inactive widgets sidebar from overflowing with throwaway widgets 1724 if ( api.Widgets.savedWidgetIds[removedWidgetId] ) {1991 if ( api.Widgets.savedWidgetIds[removedWidgetId] && 'sidebars_widgets[wp_inactive_widgets]' !== self.id ) { 1725 1992 inactiveWidgets = api.value( 'sidebars_widgets[wp_inactive_widgets]' )().slice(); 1726 1993 inactiveWidgets.push( removedWidgetId ); 1727 1994 api.value( 'sidebars_widgets[wp_inactive_widgets]' )( _( inactiveWidgets ).unique() ); … … 1990 2257 setting.set( {} ); // mark dirty, changing from '' to {} 1991 2258 } 1992 2259 1993 controlConstructor = api.controlConstructor[controlType]; 1994 widgetFormControl = new controlConstructor( settingId, { 1995 params: { 1996 settings: { 1997 'default': settingId 2260 widgetFormControl = api.control( settingId ); 2261 if ( ! widgetFormControl ) { 2262 2263 controlConstructor = api.controlConstructor[controlType]; 2264 widgetFormControl = new controlConstructor( settingId, { 2265 params: { 2266 settings: { 2267 'default': settingId 2268 }, 2269 content: controlContainer, 2270 sidebar_id: self.params.sidebar_id, 2271 widget_id: widgetId, 2272 widget_id_base: widget.get( 'id_base' ), 2273 type: controlType, 2274 is_new: ! isExistingWidget, 2275 width: widget.get( 'width' ), 2276 height: widget.get( 'height' ), 2277 is_wide: widget.get( 'is_wide' ), 2278 active: true 1998 2279 }, 1999 content: controlContainer, 2000 sidebar_id: self.params.sidebar_id, 2001 widget_id: widgetId, 2002 widget_id_base: widget.get( 'id_base' ), 2003 type: controlType, 2004 is_new: ! isExistingWidget, 2005 width: widget.get( 'width' ), 2006 height: widget.get( 'height' ), 2007 is_wide: widget.get( 'is_wide' ), 2008 active: true 2009 }, 2010 previewer: self.setting.previewer 2011 } ); 2012 api.control.add( settingId, widgetFormControl ); 2280 previewer: self.setting.previewer 2281 } ); 2282 api.control.add( settingId, widgetFormControl ); 2283 } 2013 2284 2014 2285 // Make sure widget is removed from the other sidebars 2015 2286 api.each( function( otherSetting ) { … … 2025 2296 i = _.indexOf( otherSidebarWidgets, widgetId ); 2026 2297 2027 2298 if ( -1 !== i ) { 2028 otherSidebarWidgets.splice( i );2299 otherSidebarWidgets.splice( i, 1 ); 2029 2300 otherSetting( otherSidebarWidgets ); 2030 2301 } 2031 2302 } ); -
src/wp-admin/css/customize-controls.css
1626 1626 #available-widgets-list { 1627 1627 top: 140px; 1628 1628 } 1629 1630 .wp-customizer #available-widgets .saved-widget-controls { 1631 visibility: visible; 1632 } 1629 1633 } -
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 }