Ticket #34923: 34923.4.diff
File 34923.4.diff, 21.2 KB (added by , 8 years ago) |
---|
-
src/wp-admin/css/customize-nav-menus.css
479 479 color: #23282d; 480 480 } 481 481 482 #available-menu-items .a ccordion-section-content {482 #available-menu-items .available-menu-items-list { 483 483 overflow-y: auto; 484 484 max-height: 200px; /* This gets set in JS to fit the screen size, and based on # of sections. */ 485 485 background: transparent; … … 516 516 } 517 517 518 518 #available-menu-items .accordion-section-content { 519 padding: 1px 15px 15px 15px;519 max-height: 290px; 520 520 margin: 0; 521 max-height: 290px; 521 padding: 0; 522 position: relative; 523 background: transparent; 522 524 } 523 525 526 #available-menu-items .accordion-section-content .available-menu-items-list { 527 margin: 0 0 45px 0; 528 padding: 1px 15px 15px 15px; 529 } 530 531 #available-menu-items .accordion-section-content .available-menu-items-list:only-child { /* Types that do not support new items for the current user */ 532 margin-bottom: 0; 533 } 534 535 #new-custom-menu-item .accordion-section-content { 536 padding: 0 15px 15px 15px; 537 } 538 539 #available-menu-items .accordion-section-content .new-content-item { 540 width: calc(100% - 30px); 541 padding: 8px 15px; 542 position: absolute; 543 bottom: 0; 544 z-index: 10; 545 background: #eee; 546 } 547 548 #available-menu-items .new-content-item .add-content { 549 float: right; 550 padding-left: 6px; 551 } 552 553 #available-menu-items .new-content-item .add-content:before { 554 content: "\f543"; 555 font: 20px/16px dashicons; 556 position: relative; 557 display: inline-block; 558 top: 5px; 559 left: -2px; 560 } 561 524 562 #available-menu-items .menu-item-tpl { 525 563 margin: 0; 526 564 } -
src/wp-admin/js/customize-nav-menus.js
100 100 'click .menu-item-tpl': '_submit', 101 101 'click #custom-menu-item-submit': '_submitLink', 102 102 'keypress #custom-menu-item-name': '_submitLink', 103 'click .new-content-item .add-content': '_submitNew', 104 'keypress .create-item-input': '_submitNew', 103 105 'keydown': 'keyboardAccessible' 104 106 }, 105 107 … … 115 117 pages: {}, 116 118 sectionContent: '', 117 119 loading: false, 120 addingNew: false, 118 121 119 122 initialize: function() { 120 123 var self = this; … … 124 127 } 125 128 126 129 this.$search = $( '#menu-items-search' ); 127 this.sectionContent = this.$el.find( '.a ccordion-section-content' );130 this.sectionContent = this.$el.find( '.available-menu-items-list' ); 128 131 129 132 this.debounceSearch = _.debounce( self.search, 500 ); 130 133 … … 160 163 161 164 // Load more items. 162 165 this.sectionContent.scroll( function() { 163 var totalHeight = self.$el.find( '.accordion-section.open .a ccordion-section-content' ).prop( 'scrollHeight' ),166 var totalHeight = self.$el.find( '.accordion-section.open .available-menu-items-list' ).prop( 'scrollHeight' ), 164 167 visibleHeight = self.$el.find( '.accordion-section.open' ).height(); 165 168 166 169 if ( ! self.loading && $( this ).scrollTop() > 3 / 4 * totalHeight - visibleHeight ) { … … 337 340 } 338 341 items = new api.Menus.AvailableItemCollection( items ); // @todo Why is this collection created and then thrown away? 339 342 self.collection.add( items.models ); 340 typeInner = availableMenuItemContainer.find( '.a ccordion-section-content' );343 typeInner = availableMenuItemContainer.find( '.available-menu-items-list' ); 341 344 items.each(function( menuItem ) { 342 345 typeInner.append( itemTemplate( menuItem.attributes ) ); 343 346 }); … … 356 359 357 360 // Adjust the height of each section of items to fit the screen. 358 361 itemSectionHeight: function() { 359 var sections, totalHeight, accordionHeight, diff;362 var sections, lists, totalHeight, accordionHeight, diff, totalWidth, button, buttonWidth; 360 363 totalHeight = window.innerHeight; 361 364 sections = this.$el.find( '.accordion-section:not( #available-menu-items-search ) .accordion-section-content' ); 362 accordionHeight = 46 * ( 2 + sections.length ) - 13; // Magic numbers. 365 lists = this.$el.find( '.accordion-section:not( #available-menu-items-search ) .available-menu-items-list:not(":only-child")' ); 366 accordionHeight = 46 * ( 1 + sections.length ) + 14; // Magic numbers. 363 367 diff = totalHeight - accordionHeight; 364 368 if ( 120 < diff && 290 > diff ) { 365 369 sections.css( 'max-height', diff ); 370 lists.css( 'max-height', ( diff - 60 ) ); 366 371 } 372 // Fit the new-content input and button in the available space. 373 totalWidth = this.$el.width(); 374 // Clone button to get width of invisible element. 375 button = this.$el.find( '.accordion-section .new-content-item .add-content' ).first().clone().appendTo( 'body' ).css({ 'display': 'block', 'visibility': 'hidden' }); 376 buttonWidth = button.outerWidth(); 377 button.remove(); 378 this.$el.find( '.accordion-section .new-content-item .create-item-input' ).width( ( totalWidth - buttonWidth - 70 ) ); // 70 = additional margins and padding. 367 379 }, 368 380 369 381 // Highlights a menu item. … … 456 468 itemName.val( '' ); 457 469 }, 458 470 471 // Submit handler for keypress (enter) on field and click on button. 472 _submitNew: function( event ) { 473 // Only proceed with keypress if it is Enter. 474 if ( 'keypress' === event.type && 13 !== event.which ) { 475 return; 476 } 477 478 if ( this.addingNew ) { 479 return; 480 } 481 482 var container = $( event.target ).closest( '.accordion-section' ); 483 484 this.submitNew( container ); 485 }, 486 487 // Creates a new object and adds an associated menu item to the menu. 488 submitNew: function( container ) { 489 var panel = this, 490 itemName = container.find( '.create-item-input' ), 491 title = itemName.val(), 492 dataContainer = container.find( '.available-menu-items-list' ), 493 itemType = dataContainer.data( 'type' ), 494 itemObject = dataContainer.data( 'object' ), 495 itemTypeLabel = dataContainer.data( 'type_label' ), 496 promise; 497 498 if ( ! this.currentMenuControl ) { 499 return; 500 } 501 502 if ( '' === itemName.val() ) { 503 itemName.addClass( 'invalid' ); 504 return; 505 } else { 506 container.find( '.accordion-section-title' ).addClass( 'loading' ); 507 } 508 509 // Only posts are supported currently. 510 if ( 'post_type' !== itemType ) { 511 return; 512 } 513 514 panel.addingNew = true; 515 itemName.attr( 'disabled', 'disabled' ); 516 promise = wp.customize.Posts.insertAutoDraftPost( { 517 post_title: title, 518 post_type: itemObject, 519 post_status: 'publish' 520 } ); 521 promise.done( function( data ) { 522 var menuItem = { 523 'title': itemName.val(), 524 'type': itemType, 525 'type_label': itemTypeLabel, 526 'object': itemObject, 527 'object_id': data.postId, 528 'url': data.url 529 }, availableItems, $content, itemTemplate; 530 531 // Add new item to menu. 532 panel.currentMenuControl.addItemToMenu( menuItem ); 533 534 // Add the new item to the list of available items. 535 menuItem['id'] = 'post-' + data.postId; // `id` is used for available menu item Backbone models. 536 availableItems = new api.Menus.AvailableItemCollection( [ menuItem ] ); 537 api.Menus.availableMenuItemsPanel.collection.add( availableItems.models ); 538 $content = container.find( '.available-menu-items-list' ), 539 itemTemplate = wp.template( 'available-menu-item' ); 540 $content.prepend( itemTemplate( menuItem ) ); 541 $content.scrollTop(); 542 543 // Reset the create content form. 544 itemName.val( '' ) 545 .removeAttr( 'disabled' ) 546 .focus(); 547 panel.addingNew = false; 548 container.find( '.accordion-section-title' ).removeClass( 'loading' ); 549 } ); 550 }, 551 459 552 // Opens the panel. 460 553 open: function( menuControl ) { 461 554 this.currentMenuControl = menuControl; -
src/wp-admin/js/customize-posts.js
1 /* global jQuery, wp, _, console */ 2 3 (function( api, $ ) { 4 'use strict'; 5 6 var component; 7 8 if ( ! api.Posts ) { 9 api.Posts = {}; 10 } 11 12 component = api.Posts; 13 14 component.autoDrafts = []; 15 16 /** 17 * Insert a new `auto-draft` post. 18 * 19 * @param {object} params - Parameters for the draft post to create. 20 * @param {string} params.post_type - Post type to add. 21 * @param {number} params.title - Post title to use. 22 * @return {jQuery.promise} Promise resolved with the added post. 23 */ 24 component.insertAutoDraftPost = function( params ) { 25 var request, deferred = $.Deferred(); 26 27 request = wp.ajax.post( 'customize-posts-insert-auto-draft', { 28 'customize-menus-nonce': api.settings.nonce['customize-menus'], 29 'wp_customize': 'on', 30 'params': params 31 } ); 32 33 request.done( function( response ) { 34 if ( response.postId ) { 35 deferred.resolve( response ); 36 component.autoDrafts.push( response.postId ); 37 api( 'nav_menus_created_posts' ).set( component.autoDrafts ); 38 } 39 } ); 40 41 request.fail( function( response ) { 42 var error = response || ''; 43 44 if ( 'undefined' !== typeof response.message ) { 45 error = response.message; 46 } 47 48 console.error( error ); 49 deferred.rejectWith( error ); 50 } ); 51 52 return deferred.promise(); 53 }; 54 55 api.bind( 'ready', function() { 56 57 api.bind( 'saved', function( data ) { 58 // @todo: show users links to edit newly-published posts. 59 60 // Reset auto-drafts. 61 component.autoDrafts = []; // Reset the array the next time an item is created. Don't update the setting yet as that would trigger the customizer's dirty state. 62 } ); 63 64 } ); 65 66 })( wp.customize, jQuery ); -
src/wp-includes/class-wp-customize-nav-menus.php
45 45 * @param object $manager An instance of the WP_Customize_Manager class. 46 46 */ 47 47 public function __construct( $manager ) { 48 $this->previewed_menus = array();49 $this->manager = $manager;48 $this->previewed_menus = array(); 49 $this->manager = $manager; 50 50 51 51 // Skip useless hooks when the user can't manage nav menus anyway. 52 52 if ( ! current_user_can( 'edit_theme_options' ) ) { … … 56 56 add_filter( 'customize_refresh_nonces', array( $this, 'filter_nonces' ) ); 57 57 add_action( 'wp_ajax_load-available-menu-items-customizer', array( $this, 'ajax_load_available_items' ) ); 58 58 add_action( 'wp_ajax_search-available-menu-items-customizer', array( $this, 'ajax_search_available_items' ) ); 59 add_action( 'wp_ajax_customize-posts-insert-auto-draft', array( $this, 'ajax_add_new_auto_draft_post' ) ); 59 60 add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); 60 61 // Needs to run after core Navigation section is set up.62 61 add_action( 'customize_register', array( $this, 'customize_register' ), 11 ); 63 64 62 add_filter( 'customize_dynamic_setting_args', array( $this, 'filter_dynamic_setting_args' ), 10, 2 ); 65 63 add_filter( 'customize_dynamic_setting_class', array( $this, 'filter_dynamic_setting_class' ), 10, 3 ); 66 64 add_action( 'customize_controls_print_footer_scripts', array( $this, 'print_templates' ) ); 67 65 add_action( 'customize_controls_print_footer_scripts', array( $this, 'available_items_template' ) ); 68 66 add_action( 'customize_preview_init', array( $this, 'customize_preview_init' ) ); 67 add_action( 'customize_preview_init', array( $this, 'make_auto_draft_status_previewable' ) ); 68 add_action( 'customize_save_nav_menus_created_posts', array( $this, 'publish_auto_draft_posts' ) ); 69 69 70 70 // Selective Refresh partials. 71 71 add_filter( 'customize_dynamic_partial_args', array( $this, 'customize_dynamic_partial_args' ), 10, 2 ); … … 356 356 public function enqueue_scripts() { 357 357 wp_enqueue_style( 'customize-nav-menus' ); 358 358 wp_enqueue_script( 'customize-nav-menus' ); 359 wp_enqueue_script( 'customize-posts' ); 359 360 360 361 $temp_nav_menu_setting = new WP_Customize_Nav_Menu_Setting( $this->manager, 'nav_menu[-1]' ); 361 362 $temp_nav_menu_item_setting = new WP_Customize_Nav_Menu_Item_Setting( $this->manager, 'nav_menu_item[-1]' ); … … 621 622 'section' => 'add_menu', 622 623 'settings' => array(), 623 624 ) ) ); 625 626 $this->manager->add_setting( new WP_Customize_Filter_Setting( $this->manager, 'nav_menus_created_posts', array( 627 'transport' => 'postMessage', 628 'default' => array(), 629 ) ) ); 624 630 } 625 631 626 632 /** … … 655 661 foreach ( $post_types as $slug => $post_type ) { 656 662 $item_types[] = array( 657 663 'title' => $post_type->labels->name, 664 'label' => $post_type->labels->singular_name, 658 665 'type' => 'post_type', 659 666 'object' => $post_type->name, 660 667 ); … … 669 676 } 670 677 $item_types[] = array( 671 678 'title' => $taxonomy->labels->name, 679 'label' => $taxonomy->labels->singular_name, 672 680 'type' => 'taxonomy', 673 681 'object' => $taxonomy->name, 674 682 ); … … 688 696 } 689 697 690 698 /** 699 * Add a new `auto-draft` post. 700 * 701 * @access public 702 * @since 4.6.0 703 * 704 * @param string $post_type The post type. 705 * @param string $title The post title. 706 * @return WP_Post|WP_Error 707 */ 708 public function insert_auto_draft_post( $post_type, $title ) { 709 710 $post_type_obj = get_post_type_object( $post_type ); 711 if ( ! $post_type_obj ) { 712 return new WP_Error( 'unknown_post_type', __( 'Unknown post type', 'customize-posts' ) ); 713 } 714 715 add_filter( 'wp_insert_post_empty_content', '__return_false' ); 716 $args = array( 717 'post_status' => 'auto-draft', 718 'post_type' => $post_type, 719 'post_title' => $title, 720 'post_name' => sanitize_title( $title ), // Auto-drafts are allowed to have empty post_names, so we need to explicitly set it. 721 ); 722 $r = wp_insert_post( wp_slash( $args ), true ); 723 remove_filter( 'wp_insert_post_empty_content', '__return_false' ); 724 725 if ( is_wp_error( $r ) ) { 726 return $r; 727 } else { 728 return get_post( $r ); 729 } 730 } 731 732 /** 733 * Ajax handler for adding a new auto-draft post. 734 * 735 * @action wp_ajax_customize-posts-insert-auto-draft 736 * @access public 737 * @since 4.6.0 738 */ 739 public function ajax_add_new_auto_draft_post() { 740 if ( ! check_ajax_referer( 'customize-menus', 'customize-menus-nonce' ) ) { 741 status_header( 400 ); 742 wp_send_json_error( 'bad_nonce' ); 743 } 744 745 if ( ! current_user_can( 'customize' ) ) { 746 status_header( 403 ); 747 wp_send_json_error( 'customize_not_allowed' ); 748 } 749 750 if ( empty( $_POST['params'] ) || ! is_array( $_POST['params'] ) ) { 751 status_header( 400 ); 752 wp_send_json_error( 'missing_params' ); 753 } 754 755 $params = wp_unslash( $_POST['params'] ); 756 757 if ( empty( $params['post_type'] ) ) { 758 status_header( 400 ); 759 wp_send_json_error( 'missing_post_type_param' ); 760 } 761 762 $post_type_object = get_post_type_object( $params['post_type'] ); 763 if ( ! $post_type_object || ! current_user_can( $post_type_object->cap->create_posts ) ) { 764 status_header( 403 ); 765 wp_send_json_error( 'insufficient_post_permissions' ); 766 } 767 768 if ( ! $params['title'] ) { 769 $params['title'] = ''; 770 } 771 772 $r = $this->insert_auto_draft_post( $post_type_object->name, $params['post_title'] ); 773 if ( is_wp_error( $r ) ) { 774 $error = $r; 775 if ( ! empty( $post_type_object->labels->singular_name ) ) { 776 $singular_name = $post_type_object->labels->singular_name; 777 } else { 778 $singular_name = __( 'Post' ); 779 } 780 781 $data = array( 782 /* translators: %1$s is the post type name and %2$s is the error message. */ 783 'message' => sprintf( __( '%1$s could not be created: %2$s' ), $singular_name, $error->get_error_message() ), 784 ); 785 wp_send_json_error( $data ); 786 } else { 787 $post = $r; 788 $data = array( 789 'postId' => $post->ID, 790 'url' => get_permalink( $post->ID ), 791 ); 792 wp_send_json_success( $data ); 793 } 794 } 795 796 /** 691 797 * Print the JavaScript templates used to render Menu Customizer components. 692 798 * 693 799 * Templates are imported into the JS use wp.template. … … 763 869 <span class="spinner"></span> 764 870 </div> 765 871 <button type="button" class="clear-results"><span class="screen-reader-text"><?php _e( 'Clear Results' ); ?></span></button> 766 <ul class="accordion-section-content " data-type="search"></ul>872 <ul class="accordion-section-content available-menu-items-list" data-type="search"></ul> 767 873 </div> 768 874 <div id="new-custom-menu-item" class="accordion-section"> 769 875 <h4 class="accordion-section-title" role="presentation"> … … 792 898 </div> 793 899 </div> 794 900 <?php 795 // Containers for per-post-type item browsing; items added with JS. 901 /** 902 * Filter the content types that do not allow new items to be created from nav menus. 903 * 904 * Types are formated as 'post_type'|'taxonomy' _ post_type_name; for example, 'taxonomy_post_format'. 905 * Taxonomies are not yet supported by this UI but will be in the future. Post types are only available 906 * here if `show_in_nav_menus` is true. 907 * 908 * @since 4.6.0 909 * 910 * @param array $types Array of disallowed types. 911 */ 912 $disallowed_new_content_types = apply_filters( 'customize_nav_menus_disallow_new_content_types', array( 'taxonomy_post_format' ) ); 913 914 // Containers for per-post-type item browsing; items are added with JS. 796 915 foreach ( $this->available_item_types() as $available_item_type ) { 797 916 $id = sprintf( 'available-menu-items-%s-%s', $available_item_type['type'], $available_item_type['object'] ); 798 917 ?> … … 808 927 <span class="toggle-indicator" aria-hidden="true"></span> 809 928 </button> 810 929 </h4> 811 <ul class="accordion-section-content" data-type="<?php echo esc_attr( $available_item_type['type'] ); ?>" data-object="<?php echo esc_attr( $available_item_type['object'] ); ?>"></ul> 930 <div class="accordion-section-content"> 931 <?php if ( 'post_type' === $available_item_type['type'] && ! in_array( $available_item_type['type'] . '_' . $available_item_type['object'], $disallowed_new_content_types ) ) : ?> 932 <?php $post_type_obj = get_post_type_object( $available_item_type['object'] ); ?> 933 <?php if ( current_user_can( $post_type_obj->cap->create_posts ) && current_user_can( $post_type_obj->cap->publish_posts ) ) : ?> 934 <div class="new-content-item"> 935 <input type="text" class="create-item-input" placeholder="<?php 936 /* translators: %s: Singular title of post type or taxonomy */ 937 printf( __( 'Create New %s' ), $post_type_obj->labels->singular_name ); ?>"> 938 <button type="button" class="button add-content"><?php _e( 'Add' ); ?></button> 939 </div> 940 <?php endif; ?> 941 <?php endif; ?> 942 <ul class="available-menu-items-list" data-type="<?php echo esc_attr( $available_item_type['type'] ); ?>" data-object="<?php echo esc_attr( $available_item_type['object'] ); ?>" data-type_label="<?php echo esc_attr( $available_item_type['label'] ); ?>"></ul> 943 </div> 812 944 </div> 813 945 <?php 814 946 } … … 876 1008 } 877 1009 878 1010 /** 1011 * Make the auto-draft status protected so that it can be queried. 1012 * 1013 * @since 4.6.0 1014 * @access public 1015 */ 1016 public function make_auto_draft_status_previewable() { 1017 global $wp_post_statuses; 1018 $wp_post_statuses['auto-draft']->protected = true; 1019 } 1020 1021 /** 1022 * Make the auto-draft status protected so that it can be queried. 1023 * 1024 * @since 4.6.0 1025 * @access public 1026 * 1027 * @param WP_Customize_Setting $setting Customizer Setting object. 1028 */ 1029 public function publish_auto_draft_posts( $setting ) { 1030 $value = $setting->post_value(); 1031 if ( ! empty ( $value ) ) { 1032 global $wpdb; 1033 foreach ( $value as $index => $post_id ) { 1034 $post = get_post( $post_id ); 1035 wp_publish_post( $post ); 1036 } 1037 } 1038 } 1039 1040 /** 879 1041 * Keep track of the arguments that are being passed to wp_nav_menu(). 880 1042 * 881 1043 * @since 4.3.0 -
src/wp-includes/class-wp-customize-setting.php
497 497 /** 498 498 * Fetch and sanitize the $_POST value for the setting. 499 499 * 500 * During a save request prior to save, post_value() provides the new value while value() does not. 501 * 500 502 * @since 3.4.0 501 503 * 502 504 * @param mixed $default A default value which is used as a fallback. Default is null. -
src/wp-includes/script-loader.php
461 461 $scripts->add( 'customize-nav-menus', "/wp-admin/js/customize-nav-menus$suffix.js", array( 'jquery', 'wp-backbone', 'customize-controls', 'accordion', 'nav-menu' ), false, 1 ); 462 462 $scripts->add( 'customize-preview-nav-menus', "/wp-includes/js/customize-preview-nav-menus$suffix.js", array( 'jquery', 'wp-util', 'customize-preview', 'customize-selective-refresh' ), false, 1 ); 463 463 464 $scripts->add( 'customize-posts', "/wp-admin/js/customize-posts$suffix.js", array( 'jquery', 'wp-backbone', 'customize-controls' ), false, 1 ); 465 464 466 $scripts->add( 'accordion', "/wp-admin/js/accordion$suffix.js", array( 'jquery' ), false, 1 ); 465 467 466 468 $scripts->add( 'shortcode', "/wp-includes/js/shortcode$suffix.js", array( 'underscore' ), false, 1 );