Ticket #21483: 21483.diff
File 21483.diff, 18.0 KB (added by , 10 years ago) |
---|
-
src/wp-admin/css/customize-controls.css
10 10 font-size: 14px; 11 11 } 12 12 13 #customize-controls img { 14 max-width: 100%; 15 } 16 13 17 #customize-controls .submit { 14 18 text-align: center; 15 19 } … … 414 418 margin-right: 5px; 415 419 } 416 420 421 .customize-control-upload .remove-file, 422 .customize-control-image .remove-file, 423 .customize-control-background_image .remove-file { 424 margin-left: 15px; 425 vertical-align: middle; 426 } 427 428 .customize-control-upload .file-preview, 429 .customize-control-image .file-preview, 430 .customize-control-background_image .file-preview { 431 margin-bottom: 6px; 432 } 433 434 .customize-control-upload .action-container, 435 .customize-control-image .action-container, 436 .customize-control-background_image .action-container { 437 margin-bottom: 12px; 438 } 439 417 440 #customize-preview iframe { 418 441 width: 100%; 419 442 height: 100%; -
src/wp-admin/js/customize-controls.js
179 179 * @augments wp.customize.Class 180 180 */ 181 181 api.UploadControl = api.Control.extend({ 182 183 /** 184 * Setup control and do event bindings. 185 */ 182 186 ready: function() { 183 var control = this;187 // this.container is a jQuery object of the container element. 184 188 185 this.params.removed = this.params.removed || ''; 189 // Set up preview, preview container, and button elements. Cache for re-use. 190 this.$previewContainer = $( '<div class="file-preview"></div> ' ).appendTo( this.container ); 191 this.$preview = $( '<p></p>' ).prependTo( this.$previewContainer ); // @todo actual previews, and a better way to override in subclasses. 192 this.$actionContainer = $( '<div class="action-container"></div>' ).appendTo( this.container ); 193 this.$button = $( '<a class="button upload-button" href="#">' + 'Select File' + '</a>' ).appendTo( this.$actionContainer ); // @todo i18n 194 this.$removeButton = $( '<a href="#" class="remove-file">' + 'Remove' + '</a>' ).appendTo( this.$actionContainer ); // @todo i18n 186 195 187 this.success = $.proxy( this.success, this ); 196 // Shortcut so that we don't have to use _.bind every time we add a callback. 197 _.bindAll( this, 'removeFile', 'upload', 'render', 'select' ); 188 198 189 this.uploader = $.extend({ 190 container: this.container, 191 browser: this.container.find('.upload'), 192 dropzone: this.container.find('.upload-dropzone'), 193 success: this.success, 194 plupload: {}, 195 params: {} 196 }, this.uploader || {} ); 199 // Ensure clicking "remove image" removes the image. 200 this.$removeButton.on( 'click', this.removeFile ); 197 201 198 if ( control.params.extensions ) { 199 control.uploader.plupload.filters = [{ 200 title: api.l10n.allowedFiles, 201 extensions: control.params.extensions 202 }]; 202 // Bind upload button 203 this.$button.on( 'click', this.upload ); 204 205 // Call render method whenever setting is changed. 206 this.setting.bind( 'change', this.render ); 207 208 // Do initial rendering. 209 this.render(); 210 }, 211 212 /** 213 * Remember that _.bind was used to maintain `this` as the control 214 * object rather than the usual jQuery way of binding to the DOM element. 215 */ 216 upload: function( event ) { 217 event.preventDefault(); 218 219 if ( ! this.frame ) { 220 this.initFrame(); 203 221 } 204 222 205 if ( control.params.context )206 control.uploader.params['post_data[context]'] = this.params.context;223 this.frame.open(); 224 }, 207 225 208 if ( api.settings.theme.stylesheet ) 209 control.uploader.params['post_data[theme]'] = api.settings.theme.stylesheet; 226 /** 227 * Set the media frame so that it can be reused and accessed when needed. 228 */ 229 initFrame: function() { 230 this.frame = wp.media({ 231 // The title of the media modal 232 title: 'Select File', // @todo i18n 210 233 211 this.uploader = new wp.Uploader( this.uploader ); 234 // Restrict to specified mime type. 235 // @todo try to map $extensions to this in PHP. 236 library: { 237 type: this.params.mime_type 238 }, 212 239 213 this.remover = this.container.find('.remove');214 this.remover.on( 'click keydown', function( event ) {215 if ( event.type === 'keydown' && 13 !== event.which ) // enter216 return;240 button: { 241 // Change the submit button label. 242 text: 'Choose File' 243 }, 217 244 218 control.setting.set( control.params.removed ); 219 event.preventDefault(); 245 multiple: false 220 246 }); 221 247 222 this.removerVisibility = $.proxy( this.removerVisibility, this ); 223 this.setting.bind( this.removerVisibility ); 224 this.removerVisibility( this.setting.get() ); 248 // When a file is selected, run a callback. 249 this.frame.on( 'select', this.select ); 225 250 }, 226 success: function( attachment ) { 227 this.setting.set( attachment.get('url') ); 251 252 /** 253 * Fired when an image is selected in the media modal. Gets the selected 254 * image information, and sets it within the control. 255 */ 256 select: function() { 257 // Get the attachment from the modal frame. 258 var attachment = this.frame.state().get( 'selection' ).first().toJSON(); 259 260 // Set the Customizer setting; the callback takes care of rendering. 261 this.setting( attachment.url ); 228 262 }, 229 removerVisibility: function( to ) { 230 this.remover.toggle( to != this.params.removed ); 263 264 /** 265 * Called on init and whenever a setting is changed. 266 * 267 * @todo figure out the best way to render by type. 268 */ 269 render: function() { 270 var value = this.setting(); 271 if ( value ) { 272 // Show a preview of some sort (just the plaintext URL for now). 273 this.$preview.text( value ); 274 this.$previewContainer.show(); 275 this.$button.text( 'Change File' ); 276 this.$removeButton.show(); 277 } else { 278 this.$button.text( 'Select File' ); 279 this.$removeButton.hide(); 280 this.$previewContainer.hide(); 281 } 282 }, 283 284 // @todo handle the previewing part here instead of render, to facilitate subclasses? 285 preview: function() { 286 287 }, 288 289 /** 290 * Called when the "Remove" link is clicked. Empties the setting. 291 * @param {object} event jQuery Event object from click event 292 */ 293 removeFile: function( event ) { 294 event.preventDefault(); 295 this.setting( '' ); 231 296 } 232 297 }); 233 298 … … 238 303 * @augments wp.customize.Class 239 304 */ 240 305 api.ImageControl = api.UploadControl.extend({ 241 ready: function() { 242 var control = this, 243 panels; 306 307 /** 308 * Set the media frame so that it can be reused and accessed when needed. 309 */ 310 initFrame: function() { 311 this.frame = wp.media({ 312 // The title of the media modal 313 title: 'Select Image', // @todo i18n 244 314 245 this.uploader = { 246 init: function() { 247 var fallback, button; 315 // Restrict to specified mime type. 316 // @todo try to map $extensions to this in PHP. 317 library: { 318 type: this.params.mime_type 319 }, 248 320 249 if ( this.supports.dragdrop ) 250 return; 321 button: { 322 // Change the submit button label. 323 text: 'Choose Image' 324 }, 251 325 252 // Maintain references while wrapping the fallback button. 253 fallback = control.container.find( '.upload-fallback' ); 254 button = fallback.children().detach(); 255 256 this.browser.detach().empty().append( button ); 257 fallback.append( this.browser ).show(); 258 } 259 }; 260 261 api.UploadControl.prototype.ready.call( this ); 262 263 this.thumbnail = this.container.find('.preview-thumbnail img'); 264 this.thumbnailSrc = $.proxy( this.thumbnailSrc, this ); 265 this.setting.bind( this.thumbnailSrc ); 266 267 this.library = this.container.find('.library'); 268 269 // Generate tab objects 270 this.tabs = {}; 271 panels = this.library.find('.library-content'); 272 273 this.library.children('ul').children('li').each( function() { 274 var link = $(this), 275 id = link.data('customizeTab'), 276 panel = panels.filter('[data-customize-tab="' + id + '"]'); 277 278 control.tabs[ id ] = { 279 both: link.add( panel ), 280 link: link, 281 panel: panel 282 }; 326 multiple: false 283 327 }); 284 328 285 // Bind tab switch events 286 this.library.children('ul').on( 'click keydown', 'li', function( event ) { 287 if ( event.type === 'keydown' && 13 !== event.which ) // enter 288 return; 329 // When a file is selected, run a callback. 330 this.frame.on( 'select', this.select ); 331 }, 289 332 290 var id = $(this).data('customizeTab'), 291 tab = control.tabs[ id ]; 292 293 event.preventDefault(); 294 295 if ( tab.link.hasClass('library-selected') ) 296 return; 297 298 control.selected.both.removeClass('library-selected'); 299 control.selected = tab; 300 control.selected.both.addClass('library-selected'); 301 }); 302 303 // Bind events to switch image urls. 304 this.library.on( 'click keydown', 'a', function( event ) { 305 if ( event.type === 'keydown' && 13 !== event.which ) // enter 306 return; 307 308 var value = $(this).data('customizeImageValue'); 309 310 if ( value ) { 311 control.setting.set( value ); 312 event.preventDefault(); 333 render: function() { 334 var value = this.setting(); 335 if ( value ) { 336 // Maybe setup preview. 337 if ( ! this.$img ) { 338 this.$previewContainer.html( '' ); 339 this.$img = $( '<img class="preview-image" >' ).appendTo( this.$previewContainer ); 313 340 } 314 });315 341 316 if ( this.tabs.uploaded ) { 317 this.tabs.uploaded.target = this.library.find('.uploaded-target'); 318 if ( ! this.tabs.uploaded.panel.find('.thumbnail').length ) 319 this.tabs.uploaded.both.addClass('hidden'); 342 // Show a preview thumbnail. 343 this.$img.attr( 'src', value ); 344 this.$previewContainer.show(); 345 this.$button.text( 'Change Image' ); 346 this.$removeButton.show(); 347 } else { 348 // Hide thumbnail, show button 349 this.$button.text( 'Select Image' ); 350 this.$removeButton.hide(); 351 this.$previewContainer.hide(); 320 352 } 321 322 // Select a tab323 panels.each( function() {324 var tab = control.tabs[ $(this).data('customizeTab') ];325 326 // Select the first visible tab.327 if ( ! tab.link.hasClass('hidden') ) {328 control.selected = tab;329 tab.both.addClass('library-selected');330 return false;331 }332 });333 334 this.dropdownInit();335 },336 success: function( attachment ) {337 api.UploadControl.prototype.success.call( this, attachment );338 339 // Add the uploaded image to the uploaded tab.340 if ( this.tabs.uploaded && this.tabs.uploaded.target.length ) {341 this.tabs.uploaded.both.removeClass('hidden');342 343 // @todo: Do NOT store this on the attachment model. That is bad.344 attachment.element = $( '<a href="#" class="thumbnail"></a>' )345 .data( 'customizeImageValue', attachment.get('url') )346 .append( '<img src="' + attachment.get('url')+ '" />' )347 .appendTo( this.tabs.uploaded.target );348 }349 },350 thumbnailSrc: function( to ) {351 if ( /^(https?:)?\/\//.test( to ) )352 this.thumbnail.prop( 'src', to ).show();353 else354 this.thumbnail.hide();355 353 } 356 354 }); 357 355 -
src/wp-includes/class-wp-customize-control.php
541 541 */ 542 542 class WP_Customize_Upload_Control extends WP_Customize_Control { 543 543 public $type = 'upload'; 544 public $removed = ''; 545 public $context; 546 public $extensions = array(); 544 public $mime_type = ''; 545 public $removed = ''; // unused 546 public $context; // unused 547 public $extensions = array(); // unused @todo auto-convert to mime_type and/or enforce in media library. 547 548 548 549 /** 549 550 * Enqueue control related scripts/styles. … … 551 552 * @since 3.4.0 552 553 */ 553 554 public function enqueue() { 554 wp_enqueue_ script( 'wp-plupload');555 wp_enqueue_media(); 555 556 } 556 557 557 558 /** … … 563 564 public function to_json() { 564 565 parent::to_json(); 565 566 566 $this->json['removed'] = $this->removed; 567 568 if ( $this->context ) 569 $this->json['context'] = $this->context; 570 571 if ( $this->extensions ) 572 $this->json['extensions'] = implode( ',', $this->extensions ); 567 $this->json['mime_type'] = $this->mime_type; 573 568 } 574 569 575 570 /** … … 579 574 */ 580 575 public function render_content() { 581 576 ?> 582 <label> 583 <?php if ( ! empty( $this->label ) ) : ?> 584 <span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span> 585 <?php endif; 586 if ( ! empty( $this->description ) ) : ?> 587 <span class="description customize-control-description"><?php echo $this->description; ?></span> 588 <?php endif; ?> 589 <div> 590 <a href="#" class="button-secondary upload"><?php _e( 'Upload' ); ?></a> 591 <a href="#" class="remove"><?php _e( 'Remove' ); ?></a> 592 </div> 593 </label> 577 <?php if ( ! empty( $this->label ) ) : ?> 578 <span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span> 579 <?php endif; 580 if ( ! empty( $this->description ) ) : ?> 581 <span class="description customize-control-description"><?php echo $this->description; ?></span> 582 <?php endif; ?> 583 <?php // the rest is rendered with JS ?> 594 584 <?php 595 585 } 596 586 } … … 604 594 */ 605 595 class WP_Customize_Image_Control extends WP_Customize_Upload_Control { 606 596 public $type = 'image'; 607 public $get_url; 608 public $statuses; 609 public $extensions = array( 'jpg', 'jpeg', 'gif', 'png' ); 610 611 protected $tabs = array(); 612 613 /** 614 * Constructor. 615 * 616 * @since 3.4.0 617 * @uses WP_Customize_Upload_Control::__construct() 618 * 619 * @param WP_Customize_Manager $manager 620 * @param string $id 621 * @param array $args 622 */ 623 public function __construct( $manager, $id, $args ) { 624 $this->statuses = array( '' => __('No Image') ); 625 626 parent::__construct( $manager, $id, $args ); 627 628 $this->add_tab( 'upload-new', __('Upload New'), array( $this, 'tab_upload_new' ) ); 629 $this->add_tab( 'uploaded', __('Uploaded'), array( $this, 'tab_uploaded' ) ); 630 631 // Early priority to occur before $this->manager->prepare_controls(); 632 add_action( 'customize_controls_init', array( $this, 'prepare_control' ), 5 ); 633 } 634 635 /** 636 * Prepares the control. 637 * 638 * If no tabs exist, removes the control from the manager. 639 * 640 * @since 3.4.2 641 */ 642 public function prepare_control() { 643 if ( ! $this->tabs ) 644 $this->manager->remove_control( $this->id ); 645 } 646 647 /** 648 * Refresh the parameters passed to the JavaScript via JSON. 649 * 650 * @since 3.4.0 651 * @uses WP_Customize_Upload_Control::to_json() 652 */ 653 public function to_json() { 654 parent::to_json(); 655 $this->json['statuses'] = $this->statuses; 656 } 657 658 /** 659 * Render the control's content. 660 * 661 * @since 3.4.0 662 */ 663 public function render_content() { 664 $src = $this->value(); 665 if ( isset( $this->get_url ) ) 666 $src = call_user_func( $this->get_url, $src ); 667 668 ?> 669 <div class="customize-image-picker"> 670 <?php if ( ! empty( $this->label ) ) : ?> 671 <span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span> 672 <?php endif; 673 if ( ! empty( $this->description ) ) : ?> 674 <span class="description customize-control-description"><?php echo $this->description; ?></span> 675 <?php endif; ?> 676 677 <div class="customize-control-content"> 678 <div class="dropdown preview-thumbnail" tabindex="0"> 679 <div class="dropdown-content"> 680 <?php if ( empty( $src ) ): ?> 681 <img style="display:none;" /> 682 <?php else: ?> 683 <img src="<?php echo esc_url( set_url_scheme( $src ) ); ?>" /> 684 <?php endif; ?> 685 <div class="dropdown-status"></div> 686 </div> 687 <div class="dropdown-arrow"></div> 688 </div> 689 </div> 690 691 <div class="library"> 692 <ul> 693 <?php foreach ( $this->tabs as $id => $tab ): ?> 694 <li data-customize-tab='<?php echo esc_attr( $id ); ?>' tabindex='0'> 695 <?php echo esc_html( $tab['label'] ); ?> 696 </li> 697 <?php endforeach; ?> 698 </ul> 699 <?php foreach ( $this->tabs as $id => $tab ): ?> 700 <div class="library-content" data-customize-tab='<?php echo esc_attr( $id ); ?>'> 701 <?php call_user_func( $tab['callback'] ); ?> 702 </div> 703 <?php endforeach; ?> 704 </div> 705 706 <div class="actions"> 707 <a href="#" class="remove"><?php _e( 'Remove Image' ); ?></a> 708 </div> 709 </div> 710 <?php 711 } 712 713 /** 714 * Add a tab to the control. 715 * 716 * @since 3.4.0 717 * 718 * @param string $id 719 * @param string $label 720 * @param mixed $callback 721 */ 722 public function add_tab( $id, $label, $callback ) { 723 $this->tabs[ $id ] = array( 724 'label' => $label, 725 'callback' => $callback, 726 ); 727 } 728 729 /** 730 * Remove a tab from the control. 731 * 732 * @since 3.4.0 733 * 734 * @param string $id 735 */ 736 public function remove_tab( $id ) { 737 unset( $this->tabs[ $id ] ); 738 } 739 740 /** 741 * @since 3.4.0 742 */ 743 public function tab_upload_new() { 744 if ( ! _device_can_upload() ) { 745 echo '<p>' . sprintf( __('The web browser on your device cannot be used to upload files. You may be able to use the <a href="%s">native app for your device</a> instead.'), 'http://apps.wordpress.org/' ) . '</p>'; 746 } else { 747 ?> 748 <div class="upload-dropzone"> 749 <?php _e('Drop a file here or <a href="#" class="upload">select a file</a>.'); ?> 750 </div> 751 <div class="upload-fallback"> 752 <span class="button-secondary"><?php _e('Select File'); ?></span> 753 </div> 754 <?php 755 } 756 } 757 758 /** 759 * @since 3.4.0 760 */ 761 public function tab_uploaded() { 762 ?> 763 <div class="uploaded-target"></div> 764 <?php 765 } 766 767 /** 768 * @since 3.4.0 769 * 770 * @param string $url 771 * @param string $thumbnail_url 772 */ 773 public function print_tab_image( $url, $thumbnail_url = null ) { 774 $url = set_url_scheme( $url ); 775 $thumbnail_url = ( $thumbnail_url ) ? set_url_scheme( $thumbnail_url ) : $url; 776 ?> 777 <a href="#" class="thumbnail" data-customize-image-value="<?php echo esc_url( $url ); ?>"> 778 <img src="<?php echo esc_url( $thumbnail_url ); ?>" /> 779 </a> 780 <?php 781 } 597 public $mime_type = 'image'; 782 598 } 783 599 784 600 /** … … 806 622 'get_url' => 'get_background_image', 807 623 ) ); 808 624 809 if ( $this->setting->default ) 810 $this->add_tab( 'default', __('Default'), array( $this, 'tab_default_background' ) ); 625 if ( $this->setting->default ) { 626 // @todo 627 } 811 628 } 812 629 813 630 /**