Make WordPress Core

Ticket #21785: 21785-customizer-header.diff

File 21785-customizer-header.diff, 68.7 KB (added by ehg, 11 years ago)
  • src/wp-admin/css/customize-controls.css

    diff --git src/wp-admin/css/customize-controls.css src/wp-admin/css/customize-controls.css
    index c76a6e5..3bd297f 100644
    body { 
    455455        -webkit-overflow-scrolling: touch;
    456456}
    457457
     458/** Header control **/
     459
     460#customize-control-header_image .current {
     461        margin-bottom: 8px;
     462}
     463
     464#customize-control-header_image .uploaded {
     465        margin-bottom: 18px;
     466}
     467
     468/* Header control: current image container */
     469
     470#customize-control-header_image .current .container {
     471        overflow: hidden;
     472        border-radius: 2px;
     473}
     474
     475#customize-control-header_image .placeholder {
     476        width: 100%;
     477        position: relative;
     478        background: #262626;
     479        text-align: center;
     480        cursor: default;
     481}
     482
     483#customize-control-header_image .inner {
     484        display: none;
     485        position: absolute;
     486        width: 100%;
     487        height: 18px;
     488        margin-top: -9px;
     489        top: 50%;
     490}
     491
     492body:not(.blue) #customize-control-header_image .inner {
     493        color: #eee;
     494}
     495
     496/* Header control: overlay "close" button */
     497
     498#customize-control-header_image .header-view {
     499        position: relative;
     500}
     501
     502#customize-control-header_image .uploaded .header-view .close {
     503        visibility: hidden;
     504        position: absolute;
     505        top: 10px;
     506        right: 10px;
     507        z-index: 1;
     508        width: 20px;
     509        height: 20px;
     510        background: url('../images/close-button.light.png');
     511        background-size: 20px 20px;
     512        text-indent: -999px;
     513}
     514
     515#customize-control-header_image .header-view:hover .close {
     516        visibility: visible;
     517}
     518
     519/* Header control: randomiz(s)er */
     520
     521#customize-control-header_image .random.placeholder {
     522        cursor: pointer;
     523        border-radius: 2px;
     524        height: 40px;
     525}
     526
     527#customize-control-header_image .random .inner {
     528        display: block;
     529}
     530
     531#customize-control-header_image .dice {
     532        font-size: 16px;
     533        vertical-align: -1px;
     534}
     535
     536#customize-control-header_image .placeholder:hover .dice {
     537        -webkit-animation: dice-color-change 3s infinite;
     538        -moz-animation: dice-color-change 3s infinite;
     539        -ms-animation: dice-color-change 3s infinite;
     540        animation: dice-color-change 3s infinite;
     541}
     542
     543@-webkit-keyframes dice-color-change {
     544        0% { color: #d4b146; }
     545        50% { color: #ef54b0; }
     546        75% { color: #7190d3; }
     547        100% { color: #d4b146; }
     548}
     549
     550@-moz-keyframes dice-color-change {
     551        0% { color: #d4b146; }
     552        50% { color: #ef54b0; }
     553        75% { color: #7190d3; }
     554        100% { color: #d4b146; }
     555}
     556
     557@-ms-keyframes dice-color-change {
     558        0% { color: #d4b146; }
     559        50% { color: #ef54b0; }
     560        75% { color: #7190d3; }
     561        100% { color: #d4b146; }
     562}
     563
     564@keyframes dice-color-change {
     565        0% { color: #d4b146; }
     566        50% { color: #ef54b0; }
     567        75% { color: #7190d3; }
     568        100% { color: #d4b146; }
     569}
     570
     571/* Header control: actions and choices */
     572
     573#customize-control-header_image .actions {
     574        margin-bottom: 32px;
     575}
     576
     577#customize-control-header_image .choice {
     578        position: relative;
     579        display: block;
     580        margin-bottom: 9px;
     581}
     582
     583#customize-control-header_image .choice.random:before {
     584        position: absolute;
     585        content: attr(data-label);
     586        left: 0;
     587        top: 0;
     588}
     589
     590#customize-control-header_image .uploaded div:last-child > .choice {
     591        margin-bottom: 0;
     592}
     593
     594#customize-control-header_image .choices hr {
     595        visibility: hidden;
     596}
     597
     598#customize-control-header_image img {
     599        width: 100%;
     600        border-radius: 2px;
     601}
     602
     603#customize-control-header_image .remove {
     604        float: left;
     605        margin-right: 3px;
     606}
     607
     608#customize-control-header_image .new {
     609        float: right;
     610}
     611
     612
    458613/** Handle cheaters. */
    459614body.cheatin {
    460615        min-width: 0;
  • src/wp-admin/custom-header.php

    diff --git src/wp-admin/custom-header.php src/wp-admin/custom-header.php
    index 633dc65..1c6ee7e 100644
    class Custom_Image_Header { 
    4343        var $default_headers = array();
    4444
    4545        /**
    46          * Holds custom headers uploaded by the user
     46         * Holds custom headers uploaded by the user.
    4747         *
    4848         * @var array
    4949         * @since 3.2.0
    class Custom_Image_Header { 
    6161        var $page = '';
    6262
    6363        /**
     64         * ID of the attachment image from which we are cropping a new one.
     65         *
     66         * @var int
     67         * @since 3.9.0
     68         */
     69        public $parent_attachment_id;
     70
     71        /**
    6472         * Constructor - Register administration header callback.
    6573         *
    6674         * @since 2.1.0
    class Custom_Image_Header { 
    7280                $this->admin_header_callback = $admin_header_callback;
    7381                $this->admin_image_div_callback = $admin_image_div_callback;
    7482
     83                if ( current_theme_supports( 'custom-header' ) ) {
     84                        add_action( 'customize_save_after', array( $this, 'set_last_used' ) );
     85                        add_action( 'wp_ajax_header_crop', array( $this, 'ajax_header_crop' ) );
     86                        add_action( 'wp_ajax_header_add', array( $this, 'ajax_header_add' ) );
     87                        add_action( 'wp_ajax_header_remove', array( __CLASS__, 'ajax_header_remove' ) );
     88                        add_filter( 'wp_prepare_attachment_for_js', array( $this, 'add_parent_attachment_js' ) , 11, 3);
     89                        add_filter( 'wp_header_image_attachment_metadata', array( $this, 'add_parent_attachment_id' ) , 11, 1);
     90                }
     91
    7592                add_action( 'admin_menu', array( $this, 'init' ) );
    7693        }
    7794
    class Custom_Image_Header { 
    93110                add_action("admin_head-$page", array($this, 'js'), 50);
    94111                if ( $this->admin_header_callback )
    95112                        add_action("admin_head-$page", $this->admin_header_callback, 51);
     113
    96114        }
    97115
    98116        /**
    wp_nonce_field( 'custom-header-options', '_wpnonce-custom-header-options' ); ?> 
    819837                $attachment_id = absint( $_POST['attachment_id'] );
    820838                $original = get_attached_file($attachment_id);
    821839
    822 
    823                 $max_width = 0;
    824                 // For flex, limit size of image displayed to 1500px unless theme says otherwise
    825                 if ( current_theme_supports( 'custom-header', 'flex-width' ) )
    826                         $max_width = 1500;
    827 
    828                 if ( current_theme_supports( 'custom-header', 'max-width' ) )
    829                         $max_width = max( $max_width, get_theme_support( 'custom-header', 'max-width' ) );
    830                 $max_width = max( $max_width, get_theme_support( 'custom-header', 'width' ) );
    831 
    832                 if ( ( current_theme_supports( 'custom-header', 'flex-height' ) && ! current_theme_supports( 'custom-header', 'flex-width' ) ) || $_POST['width'] > $max_width )
    833                         $dst_height = absint( $_POST['height'] * ( $max_width / $_POST['width'] ) );
    834                 elseif ( current_theme_supports( 'custom-header', 'flex-height' ) && current_theme_supports( 'custom-header', 'flex-width' ) )
    835                         $dst_height = absint( $_POST['height'] );
    836                 else
    837                         $dst_height = get_theme_support( 'custom-header', 'height' );
    838 
    839                 if ( ( current_theme_supports( 'custom-header', 'flex-width' ) && ! current_theme_supports( 'custom-header', 'flex-height' ) ) || $_POST['width'] > $max_width )
    840                         $dst_width = absint( $_POST['width'] * ( $max_width / $_POST['width'] ) );
    841                 elseif ( current_theme_supports( 'custom-header', 'flex-width' ) && current_theme_supports( 'custom-header', 'flex-height' ) )
    842                         $dst_width = absint( $_POST['width'] );
    843                 else
    844                         $dst_width = get_theme_support( 'custom-header', 'width' );
     840                extract( $this->get_header_dimensions( array(
     841                        'width' => $_POST['width'],
     842                        'height' => $_POST['height'],
     843                ) ) );
    845844
    846845                if ( empty( $_POST['skip-cropping'] ) )
    847846                        $cropped = wp_crop_image( $attachment_id, (int) $_POST['x1'], (int) $_POST['y1'], (int) $_POST['width'], (int) $_POST['height'], $dst_width, $dst_height );
    wp_nonce_field( 'custom-header-options', '_wpnonce-custom-header-options' ); ?> 
    856855                /** This filter is documented in wp-admin/custom-header.php */
    857856                $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication
    858857
    859                 $parent = get_post($attachment_id);
    860                 $parent_url = $parent->guid;
    861                 $url = str_replace( basename( $parent_url ), basename( $cropped ), $parent_url );
    862 
    863                 $size = @getimagesize( $cropped );
    864                 $image_type = ( $size ) ? $size['mime'] : 'image/jpeg';
     858                $object = $this->create_attachment_object( $cropped, $attachment_id );
    865859
    866                 // Construct the object array
    867                 $object = array(
    868                         'ID' => $attachment_id,
    869                         'post_title' => basename($cropped),
    870                         'post_content' => $url,
    871                         'post_mime_type' => $image_type,
    872                         'guid' => $url,
    873                         'context' => 'custom-header'
    874                 );
    875860                if ( ! empty( $_POST['create-new-attachment'] ) )
    876861                        unset( $object['ID'] );
    877862
    878863                // Update the attachment
    879                 $attachment_id = wp_insert_attachment( $object, $cropped );
    880                 wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $cropped ) );
     864                $attachment_id = $this->insert_attachment( $object, $cropped );
    881865
    882866                $width = $dst_width;
    883867                $height = $dst_height;
     868                $url = $object['guid'];
    884869                $this->set_header_image( compact( 'url', 'attachment_id', 'width', 'height' ) );
    885870
    886871                // cleanup
    wp_nonce_field( 'custom-header-options', '_wpnonce-custom-header-options' ); ?> 
    10411026                set_theme_mod( 'header_image', $default );
    10421027                set_theme_mod( 'header_image_data', (object) $default_data );
    10431028        }
     1029
     1030        /**
     1031         * Calculate dst_width and dst_height based on what the currently selected theme supports.
     1032         *
     1033         * @return array dst_height and dst_width of header image.
     1034         */
     1035        final public function get_header_dimensions( $dimensions ) {
     1036                $max_width = 0;
     1037                $width = absint( $dimensions['width'] );
     1038                $height = absint( $dimensions['height'] );
     1039                $theme_height = get_theme_support( 'custom-header', 'height' );
     1040                $theme_width = get_theme_support( 'custom-header', 'width' );
     1041                $has_flex_width = current_theme_supports( 'custom-header', 'flex-width' );
     1042                $has_flex_height = current_theme_supports( 'custom-header', 'flex-height' );
     1043                $has_max_width = current_theme_supports( 'custom-header', 'max-width' ) ;
     1044                $dst = array();
     1045
     1046                // For flex, limit size of image displayed to 1500px unless theme says otherwise
     1047                if ( $has_flex_width )
     1048                        $max_width = 1500;
     1049
     1050                if ( $has_max_width )
     1051                        $max_width = max( $max_width, get_theme_support( 'custom-header', 'max-width' ) );
     1052                $max_width = max( $max_width, $theme_width );
     1053
     1054                if ( $has_flex_height && ( ! $has_flex_width || $width > $max_width ) )
     1055                        $dst['dst_height'] = absint( $height * ( $max_width / $width ) );
     1056                elseif ( $has_flex_height && $has_flex_width )
     1057                        $dst['dst_height'] = $height;
     1058                else
     1059                        $dst['dst_height'] = $theme_height;
     1060
     1061                if ( $has_flex_width && ( ! $has_flex_height || $width > $max_width ) )
     1062                        $dst['dst_width'] = absint( $width * ( $max_width / $width ) );
     1063                elseif ( $has_flex_width && $has_flex_height )
     1064                        $dst['dst_width'] = $width;
     1065                else
     1066                        $dst['dst_width'] = $theme_width;
     1067
     1068                return $dst;
     1069        }
     1070
     1071        /**
     1072         * Create an attachment 'object'.
     1073         *
     1074         * @param string $cropped Cropped image URL.
     1075         * @param int $parent_attachment_id Attachment ID of parent image.
     1076         *
     1077         * @return array Attachment object.
     1078         */
     1079        final public function create_attachment_object( $cropped, $parent_attachment_id ) {
     1080                $parent = get_post( $parent_attachment_id );
     1081                $parent_url = $parent->guid;
     1082                $url = str_replace( basename( $parent_url ), basename( $cropped ), $parent_url );
     1083
     1084                $size = @getimagesize( $cropped );
     1085                $image_type = ( $size ) ? $size['mime'] : 'image/jpeg';
     1086
     1087                $object = array(
     1088                        'ID' => $parent_attachment_id,
     1089                        'post_title' => basename($cropped),
     1090                        'post_content' => $url,
     1091                        'post_mime_type' => $image_type,
     1092                        'guid' => $url,
     1093                        'context' => 'custom-header'
     1094                );
     1095
     1096                return $object;
     1097        }
     1098
     1099        /**
     1100         * Insert an attachment & its metadata.
     1101         *
     1102         * @param array $object Attachment object.
     1103         * @param string $cropped Cropped image URL.
     1104         *
     1105         * @return int Attachment ID.
     1106         */
     1107        final public function insert_attachment( $object, $cropped ) {
     1108                $attachment_id = wp_insert_attachment( $object, $cropped );
     1109                $metadata = wp_generate_attachment_metadata( $attachment_id, $cropped );
     1110                /**
     1111                 * Allows us to insert custom meta data for an attachment.
     1112                 *
     1113                 */
     1114                $metadata = apply_filters( 'wp_header_image_attachment_metadata', $metadata );
     1115                wp_update_attachment_metadata( $attachment_id, $metadata );
     1116                return $attachment_id;
     1117        }
     1118
     1119        function ajax_check_nonce( $nonce, $attachment_id = null ) {
     1120                if ( ! isset( $nonce ) || ! wp_verify_nonce( $nonce,  'crop-image_' . $attachment_id ) ) {
     1121                        wp_die( __( 'Cheatin’ uh?' ) );
     1122                }
     1123        }
     1124
     1125        /**
     1126         * Gets attachment uploaded by Media Manager, crops it, then saves it as a
     1127         * new object. Returns JSON-encoded object details.
     1128         */
     1129        function ajax_header_crop() {
     1130                $data = $_POST['data'];
     1131                $this->ajax_check_nonce( $data['nonces']['crop'], $data['id'] );
     1132                if ( ! current_theme_supports( 'custom-header', 'uploads' ) )
     1133                        wp_die( __( 'Cheatin’ uh?' ) );
     1134
     1135                if ( ! empty( $data['skip-cropping'] ) && ! ( current_theme_supports( 'custom-header', 'flex-height' ) || current_theme_supports( 'custom-header', 'flex-width' ) ) )
     1136                        wp_die( __( 'Cheatin’ uh?' ) );
     1137
     1138                $crop_details = $data['cropDetails'];
     1139
     1140                $dimensions = $this->get_header_dimensions( array(
     1141                        'width' => $crop_details['width'],
     1142                        'height' => $crop_details['height'],
     1143                ) );
     1144
     1145                $attachment_id = absint( $data['id'] );
     1146
     1147                $cropped = wp_crop_image( $attachment_id, (int) $crop_details['x1'], (int) $crop_details['y1'], (int) $crop_details['width'], (int) $crop_details['height'], (int) $dimensions['dst_width'], (int) $dimensions['dst_height'] );
     1148
     1149                if ( ! $cropped || is_wp_error( $cropped ) )
     1150                        wp_die( __( 'Image could not be processed. Please go back and try again.' ), __( 'Image Processing Error' ) );
     1151
     1152                $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication
     1153
     1154                $object = $this->create_attachment_object( $cropped, $attachment_id );
     1155
     1156                unset( $object['ID'] );
     1157
     1158                $this->parent_attachment_id = $attachment_id;
     1159                $new_attachment_id = $this->insert_attachment( $object, $cropped );
     1160
     1161                $object['attachment_id'] = $new_attachment_id;
     1162                $object['width']         = $dimensions['dst_width'];
     1163                $object['height']        = $dimensions['dst_height'];
     1164
     1165                echo json_encode($object);
     1166                die();
     1167        }
     1168
     1169        /**
     1170         * Given an attachment ID for a header image, updates its "last used"
     1171         * timestamp to now.
     1172         *
     1173         * Triggered when the user tries adds a new header image from the
     1174         * Media Manager, even if s/he doesn't save that change.
     1175         */
     1176        function ajax_header_add() {
     1177                $data = $_POST['data'];
     1178                check_ajax_referer( 'header-add', 'nonce' );
     1179
     1180                $attachment_id = absint( $data['attachment_id'] );
     1181                if ( $attachment_id < 1 )
     1182                        return;
     1183
     1184                $key = '_wp_attachment_custom_header_last_used_' . get_stylesheet();
     1185                update_post_meta( $attachment_id, $key, time() );
     1186                update_post_meta( $attachment_id, '_wp_attachment_is_custom_header', get_stylesheet() );
     1187
     1188                die();
     1189        }
     1190
     1191        /**
     1192         * Given an attachment ID for a header image, unsets it as a user-uploaded
     1193         * header image for the current theme.
     1194         *
     1195         * Triggered when the user clicks the overlay "X" button next to each image
     1196         * choice in the Customizer's Header tool.
     1197         */
     1198        function ajax_header_remove() {
     1199                $data = $_POST['data'];
     1200                check_ajax_referer( 'header-remove', 'nonce' );
     1201
     1202                $attachment_id = absint( $data['attachment_id'] );
     1203                if ( $attachment_id < 1 )
     1204                        return;
     1205
     1206                $key = '_wp_attachment_custom_header_last_used_' . get_stylesheet();
     1207                delete_post_meta( $attachment_id, $key );
     1208                delete_post_meta( $attachment_id, '_wp_attachment_is_custom_header', get_stylesheet() );
     1209
     1210                die();
     1211        }
     1212
     1213        function set_last_used( $manager ) {
     1214                $data = $manager->get_setting( 'header_image_data' )->post_value();
     1215
     1216                if ( !isset( $data['attachment_id'] ) )
     1217                        return;
     1218
     1219                $attachment_id = $data['attachment_id'];
     1220                $key = '_wp_attachment_custom_header_last_used_' . get_stylesheet();
     1221                update_post_meta( $attachment_id, $key, time() );
     1222        }
     1223
     1224        function add_parent_attachment_id( $metadata ) {
     1225                $metadata['parent_attachment_id'] = $this->parent_attachment_id;
     1226                return $metadata;
     1227        }
     1228
     1229        function add_parent_attachment_js($response, $attachment, $meta ){
     1230                $response['parentAttachmentId'] = isset( $meta['parent_attachment_id'] ) ? $meta['parent_attachment_id'] : 0;
     1231                return $response;
     1232        }
    10441233}
  • src/wp-admin/js/customize-controls.js

    diff --git src/wp-admin/images/close-button.light.png src/wp-admin/images/close-button.light.png
    new file mode 100644
    index 0000000..5da989b
    Binary files /dev/null and src/wp-admin/images/close-button.light.png differ
    diff --git src/wp-admin/js/customize-controls.js src/wp-admin/js/customize-controls.js
    index 3a05ad4..d16a94b 100644
     
    306306                }
    307307        });
    308308
     309        api.HeaderControl = api.Control.extend({
     310                ready: function() {
     311                        this.btnRemove        = $('.actions .remove');
     312                        this.btnNew           = $('.actions .new');
     313
     314                        _.bindAll(this, 'openMM', 'removeImage');
     315
     316                        this.btnNew.on( 'click', this.openMM );
     317                        this.btnRemove.on( 'click', this.removeImage );
     318
     319                        api.HeaderTool.currentHeader = new api.HeaderTool.ImageModel();
     320
     321                        new api.HeaderTool.CurrentView({
     322                                model: api.HeaderTool.currentHeader,
     323                                el: '.current .container'
     324                        });
     325
     326                        new api.HeaderTool.ChoiceListView({
     327                                collection: api.HeaderTool.UploadsList = new api.HeaderTool.ChoiceList(),
     328                                el: '.choices .uploaded .list'
     329                        });
     330
     331                        new api.HeaderTool.ChoiceListView({
     332                                collection: api.HeaderTool.DefaultsList = new api.HeaderTool.DefaultsList(),
     333                                el: '.choices .default .list'
     334                        });
     335
     336                        api.HeaderTool.combinedList = api.HeaderTool.CombinedList = new api.HeaderTool.CombinedList([
     337                                api.HeaderTool.UploadsList,
     338                                api.HeaderTool.DefaultsList
     339                        ]);
     340                },
     341
     342                /**
     343                 * Returns a set of options, computed from the attached image data and
     344                 * theme-specific data, to be fed to the imgAreaSelect plugin in
     345                 * wp.media.view.Cropper.
     346                 *
     347                 * @param {wp.media.model.Attachment} attachment
     348                 * @param {wp.media.controller.Cropper} controller
     349                 * @returns {Object} Options
     350                 */
     351                calculateImageSelectOptions: function(attachment, controller) {
     352                        var xInit = parseInt(_wpCustomizeHeader.data.width, 10),
     353                                yInit = parseInt(_wpCustomizeHeader.data.height, 10),
     354                                flexWidth = !! parseInt(_wpCustomizeHeader.data['flex-width'], 10),
     355                                flexHeight = !! parseInt(_wpCustomizeHeader.data['flex-height'], 10),
     356                                ratio, xImg, yImg, realHeight, realWidth,
     357                                imgSelectOptions;
     358
     359                        realWidth = attachment.get('width');
     360                        realHeight = attachment.get('height');
     361
     362                        this.headerImage = new api.HeaderTool.ImageModel();
     363                        this.headerImage.set({
     364                                themeWidth: xInit,
     365                                themeHeight: yInit,
     366                                themeFlexWidth: flexWidth,
     367                                themeFlexHeight: flexHeight,
     368                                imageWidth: realWidth,
     369                                imageHeight: realHeight
     370                        });
     371
     372                        controller.set( 'canSkipCrop', ! this.headerImage.shouldBeCropped() );
     373
     374                        ratio = xInit / yInit;
     375                        xImg = realWidth;
     376                        yImg = realHeight;
     377
     378                        if ( xImg / yImg > ratio ) {
     379                                yInit = yImg;
     380                                xInit = yInit * ratio;
     381                        } else {
     382                                xInit = xImg;
     383                                yInit = xInit / ratio;
     384                        }
     385
     386                        imgSelectOptions = {
     387                                handles: true,
     388                                keys: true,
     389                                instance: true,
     390                                persistent: true,
     391                                parent: this.$el,
     392                                imageWidth: realWidth,
     393                                imageHeight: realHeight,
     394                                x1: 0,
     395                                y1: 0,
     396                                x2: xInit,
     397                                y2: yInit
     398                        };
     399
     400                        if (flexHeight === false && flexWidth === false) {
     401                                imgSelectOptions.aspectRatio = xInit + ':' + yInit;
     402                        }
     403                        if (flexHeight === false ) {
     404                                imgSelectOptions.maxHeight = yInit;
     405                        }
     406                        if (flexWidth === false ) {
     407                                imgSelectOptions.maxWidth = xInit;
     408                        }
     409
     410                        return imgSelectOptions;
     411                },
     412
     413                /**
     414                 * Sets up and opens the Media Manager in order to select an image.
     415                 * Depending on both the size of the image and the properties of the
     416                 * current theme, a cropping step after selection may be required or
     417                 * skippable.
     418                 *
     419                 * @param {event} event
     420                 */
     421                openMM: function(event) {
     422                        var title, suggestedWidth, suggestedHeight,
     423                                l10n = _wpMediaViewsL10n;
     424
     425                        event.preventDefault();
     426
     427                        suggestedWidth = l10n.suggestedWidth.replace('%d', _wpCustomizeHeader.data.width);
     428                        suggestedHeight = l10n.suggestedHeight.replace('%d', _wpCustomizeHeader.data.height);
     429
     430                        title = {
     431                                html: l10n.chooseImage + ' <span class="suggested-dimensions">' +
     432                                                        suggestedWidth + ' ' + suggestedHeight +'</span>',
     433                                text: l10n.chooseImage
     434                        };
     435
     436                        frame = wp.media({
     437                                title: title,
     438                                library: {
     439                                        type: 'image'
     440                                },
     441                                button: {
     442                                        text: l10n.selectAndCrop,
     443                                        close: false
     444                                },
     445                                multiple: false,
     446                                imgSelectOptions: this.calculateImageSelectOptions
     447                        });
     448
     449                        frame.states.add([new wp.media.controller.Cropper()]);
     450
     451                        frame.on('select', function() {
     452                                frame.setState('cropper');
     453                        });
     454
     455                        frame.on('cropped', function(croppedImage) {
     456                                var url = croppedImage.post_content,
     457                                        attachmentId = croppedImage.attachment_id,
     458                                        w = croppedImage.width,
     459                                        h = croppedImage.height;
     460                                this.setImageFromURL(url, attachmentId, w, h);
     461                        }, this);
     462
     463                        frame.on('skippedcrop', function(selection) {
     464                                var url = selection.get('url'),
     465                                        w = selection.get('width'),
     466                                        h = selection.get('height');
     467                                this.setImageFromURL(url, selection.id, w, h);
     468                        }, this);
     469
     470                        frame.open();
     471                },
     472
     473                /**
     474                 * Creates a new wp.customize.HeaderTool.ImageModel from provided
     475                 * header image data and inserts it into the user-uploaded headers
     476                 * collection.
     477                 *
     478                 * @param {String} url
     479                 * @param {Number} attachmentId
     480                 * @param {Number} width
     481                 * @param {Number} height
     482                 */
     483                setImageFromURL: function(url, attachmentId, width, height) {
     484                        var choice, data = {};
     485
     486                        data.url = url;
     487                        data.thumbnail_url = url;
     488
     489                        if (attachmentId)
     490                                data.attachment_id = attachmentId;
     491
     492                        if (width)
     493                                data.width = width;
     494
     495                        if (height)
     496                                data.height = height;
     497
     498                        choice = new api.HeaderTool.ImageModel({
     499                                header: data,
     500                                choice: url.split('/').pop()
     501                        });
     502                        api.HeaderTool.UploadsList.add(choice);
     503                        api.HeaderTool.currentHeader.set(choice.toJSON());
     504                        choice.save();
     505                        choice.importImage();
     506                },
     507
     508                /**
     509                 * Triggers the necessary events to deselect an image which was set as
     510                 * the currently selected one.
     511                 */
     512                removeImage: function() {
     513                        api.HeaderTool.currentHeader.trigger('hide');
     514                        api.HeaderTool.CombinedList.trigger('control:removeImage');
     515                }
     516
     517        });
     518
    309519        // Change objects contained within the main customize object to Settings.
    310520        api.defaultConstructor = api.Setting;
    311521
     
    686896        api.controlConstructor = {
    687897                color:  api.ColorControl,
    688898                upload: api.UploadControl,
    689                 image:  api.ImageControl
     899                image:  api.ImageControl,
     900                header: api.HeaderControl
    690901        };
    691902
    692903        $( function() {
     
    9611172                        });
    9621173                });
    9631174
    964                 // Handle header image data
    965                 api.control( 'header_image', function( control ) {
    966                         control.setting.bind( function( to ) {
    967                                 if ( to === control.params.removed )
    968                                         control.settings.data.set( false );
    969                         });
    970 
    971                         control.library.on( 'click', 'a', function() {
    972                                 control.settings.data.set( $(this).data('customizeHeaderImageData') );
    973                         });
    974 
    975                         control.uploader.success = function( attachment ) {
    976                                 var data;
    977 
    978                                 api.ImageControl.prototype.success.call( control, attachment );
    979 
    980                                 data = {
    981                                         attachment_id: attachment.get('id'),
    982                                         url:           attachment.get('url'),
    983                                         thumbnail_url: attachment.get('url'),
    984                                         height:        attachment.get('height'),
    985                                         width:         attachment.get('width')
    986                                 };
    987 
    988                                 attachment.element.data( 'customizeHeaderImageData', data );
    989                                 control.settings.data.set( data );
    990                         };
    991                 });
    992 
    9931175                api.trigger( 'ready' );
    9941176
    9951177                // Make sure left column gets focus
  • new file src/wp-admin/js/header-models.js

    diff --git src/wp-admin/js/header-models.js src/wp-admin/js/header-models.js
    new file mode 100644
    index 0000000..945bc23
    - +  
     1/* globals jQuery, _wpCustomizeHeader */
     2;( function( $, wp ) {
     3        var api = wp.customize;
     4        api.HeaderTool = {};
     5
     6
     7        /**
     8         * wp.customize.HeaderTool.ImageModel
     9         *
     10         * A header image. This is where saves via the Customizer API are
     11         * abstracted away, plus our own AJAX calls to add images to and remove
     12         * images from the user's recently uploaded images setting on the server.
     13         * These calls are made regardless of whether the user actually saves new
     14         * Customizer settings.
     15         *
     16         * @constructor
     17         * @augments Backbone.Model
     18         */
     19        api.HeaderTool.ImageModel = Backbone.Model.extend({
     20                defaults: function() {
     21                        return {
     22                                header: {
     23                                        attachment_id: 0,
     24                                        url: '',
     25                                        timestamp: Date.now(),
     26                                        thumbnail_url: ''
     27                                },
     28                                choice: '',
     29                                hidden: false,
     30                                random: false
     31                        };
     32                },
     33
     34                initialize: function() {
     35                        this.on('hide', this.hide, this);
     36                },
     37
     38                hide: function() {
     39                        this.set('choice', '');
     40                        api('header_image').set('remove-header');
     41                        api('header_image_data').set('remove-header');
     42                },
     43
     44                destroy: function() {
     45                        var data = this.get('header'),
     46                                curr = api.HeaderTool.currentHeader.get('header').attachment_id;
     47
     48                        // If the image we're removing is also the current header, unset
     49                        // the latter
     50                        if (curr && data.attachment_id == curr)
     51                                api.HeaderTool.currentHeader.trigger('hide');
     52
     53                        $.post(_wpCustomizeSettings.url.ajax, {
     54                                wp_customize: 'on',
     55                                theme: api.settings.theme.stylesheet,
     56                                dataType: 'json',
     57                                action: 'header_remove',
     58                                nonce: _wpCustomizeHeader.nonces.remove,
     59                                data: data
     60                        });
     61
     62                        this.trigger('destroy', this, this.collection);
     63                },
     64
     65                save: function() {
     66                        if (this.get('random')) {
     67                                api('header_image').set(this.get('header').random);
     68                                api('header_image_data').set(this.get('header').random);
     69                        } else {
     70                                if (this.get('header').defaultName) {
     71                                        api('header_image').set(this.get('header').url);
     72                                        api('header_image_data').set(this.get('header').defaultName);
     73                                } else {
     74                                        api('header_image').set(this.get('header').url);
     75                                        api('header_image_data').set(this.get('header'));
     76                                }
     77                        }
     78
     79                        api.HeaderTool.combinedList.trigger('control:setImage', this);
     80                },
     81
     82                importImage: function() {
     83                        var data = this.get('header');
     84                        if (data.attachment_id === undefined)
     85                                return;
     86
     87                        $.post(_wpCustomizeSettings.url.ajax, {
     88                                wp_customize: 'on',
     89                                theme: api.settings.theme.stylesheet,
     90                                dataType: 'json',
     91                                action: 'header_add',
     92                                nonce: _wpCustomizeHeader.nonces.add,
     93                                data: data
     94                        });
     95                },
     96
     97                shouldBeCropped: function() {
     98                        if (this.get('themeFlexWidth') === true &&
     99                                                this.get('themeFlexHeight') === true) {
     100                                return false;
     101                        }
     102
     103                        if (this.get('themeFlexWidth') === true &&
     104                                         this.get('themeHeight') === this.get('imageHeight')) {
     105                                return false;
     106                        }
     107
     108                        if (this.get('themeFlexHeight') === true &&
     109                                         this.get('themeWidth') === this.get('imageWidth')) {
     110                                return false;
     111                        }
     112
     113                        if (this.get('themeWidth') === this.get('imageWidth') &&
     114                                         this.get('themeHeight') === this.get('imageHeight')) {
     115                                return false;
     116                        }
     117
     118                        return true;
     119                }
     120        });
     121
     122
     123        /**
     124         * wp.customize.HeaderTool.ChoiceList
     125         *
     126         * @constructor
     127         * @augments Backbone.Collection
     128         */
     129        api.HeaderTool.ChoiceList = Backbone.Collection.extend({
     130                model: api.HeaderTool.ImageModel,
     131
     132                // Ordered from most recently used to least
     133                comparator: function(model) {
     134                        return -model.get('header').timestamp;
     135                },
     136
     137                initialize: function() {
     138                        var current = api.HeaderTool.currentHeader.get('choice').replace(/^https?:\/\//, ''),
     139                                isRandom = this.isRandomChoice(api.get().header_image);
     140
     141                        // Overridable by an extending class
     142                        if (!this.type)
     143                                this.type = 'uploaded';
     144
     145                        // Overridable by an extending class
     146                        if (!this.data)
     147                                this.data = _wpCustomizeHeader.uploads;
     148
     149                        if (isRandom) {
     150                                // So that when adding data we don't hide regular images
     151                                current = api.get().header_image;
     152                        }
     153
     154                        this.on('control:setImage', this.setImage, this);
     155                        this.on('control:removeImage', this.removeImage, this);
     156                        this.on('add', this.maybeAddRandomChoice, this);
     157
     158                        _.each(this.data, function(elt, index) {
     159                                if (!elt.attachment_id)
     160                                        elt.defaultName = index;
     161
     162                                this.add({
     163                                        header: elt,
     164                                        choice: elt.url.split('/').pop(),
     165                                        hidden: current == elt.url.replace(/^https?:\/\//, '')
     166                                }, { silent: true });
     167                        }, this);
     168
     169                        if (this.size() > 0)
     170                                this.addRandomChoice(current);
     171                },
     172
     173                maybeAddRandomChoice: function() {
     174                        if (this.size() === 1)
     175                                this.addRandomChoice();
     176                },
     177
     178                addRandomChoice: function(initialChoice) {
     179                        var isRandomSameType = RegExp(this.type).test(initialChoice),
     180                                randomChoice = 'random-' + this.type + '-image';
     181
     182                        this.add({
     183                                header: {
     184                                        timestamp: 0,
     185                                        random: randomChoice,
     186                                        width: 245,
     187                                        height: 41
     188                                },
     189                                choice: randomChoice,
     190                                random: true,
     191                                hidden: isRandomSameType
     192                        });
     193                },
     194
     195                isRandomChoice: function(choice) {
     196                        return /^random-(uploaded|default)-image$/.test(choice);
     197                },
     198
     199                shouldHideTitle: function() {
     200                        return _.every(this.pluck('hidden'));
     201                },
     202
     203                setImage: function(model) {
     204                        this.each(function(m) {
     205                                m.set('hidden', false);
     206                        });
     207
     208                        if (model) {
     209                                model.set('hidden', true);
     210                                // Bump images to top except for special "Randomize" images
     211                                if (!model.get('random')) {
     212                                        model.get('header').timestamp = Date.now();
     213                                        this.sort();
     214                                }
     215                        }
     216                },
     217
     218                removeImage: function() {
     219                        this.each(function(m) {
     220                                m.set('hidden', false);
     221                        });
     222                },
     223
     224                shown: function() {
     225                        var filtered = this.where({ hidden: false });
     226                        return new api.HeaderTool.ChoiceList( filtered );
     227                }
     228        });
     229
     230
     231        /**
     232         * wp.customize.HeaderTool.DefaultsList
     233         *
     234         * @constructor
     235         * @augments wp.customize.HeaderTool.ChoiceList
     236         * @augments Backbone.Collection
     237         */
     238        api.HeaderTool.DefaultsList = api.HeaderTool.ChoiceList.extend({
     239                initialize: function() {
     240                        this.type = 'default';
     241                        this.data = _wpCustomizeHeader.defaults;
     242                        api.HeaderTool.ChoiceList.prototype.initialize.apply(this);
     243                }
     244        });
     245
     246})( jQuery, this.wp );
  • new file src/wp-admin/js/header-views.js

    diff --git src/wp-admin/js/header-views.js src/wp-admin/js/header-views.js
    new file mode 100644
    index 0000000..a9957b5
    - +  
     1/* globals jQuery, _, Backbone, _wpMediaViewsL10n, _wpCustomizeHeader */
     2;( function( $, wp, _ ) {
     3        if ( ! wp || ! wp.customize ) { return; }
     4        var api = wp.customize, frame, CombinedList, UploadsList, DefaultsList;
     5
     6
     7        /**
     8         * wp.customize.HeaderTool.CurrentView
     9         *
     10         * Displays the currently selected header image, or a placeholder in lack
     11         * thereof.
     12         *
     13         * Instantiate with model wp.customize.HeaderTool.currentHeader.
     14         *
     15         * @constructor
     16         * @augments Backbone.View
     17         */
     18        api.HeaderTool.CurrentView = Backbone.View.extend({
     19                template: _.template($('#tmpl-header-current').html()),
     20
     21                initialize: function() {
     22                        this.listenTo(this.model, 'change', this.render);
     23                        this.render();
     24                },
     25
     26                render: function() {
     27                        this.$el.html(this.template(this.model.toJSON()));
     28                        this.setPlaceholder();
     29                        this.setButtons();
     30                        return this;
     31                },
     32
     33                getHeight: function() {
     34                        var image = this.$el.find('img'),
     35                                saved = this.model.get('savedHeight'),
     36                                height = image.height() || saved;
     37
     38                        if (image.length) {
     39                                this.$el.find('.inner').hide();
     40                        } else {
     41                                this.$el.find('.inner').show();
     42                        }
     43
     44                        // happens at ready
     45                        if (!height) {
     46                                var d = api.get().header_image_data;
     47
     48                                if (d && d.width && d.height) {
     49                                        var w = d.width,
     50                                                h = d.height;
     51                                        // hardcoded container width
     52                                        height = 260 / w * h;
     53                                }
     54                                // fallback for when no image is set
     55                                else height = 40;
     56                        }
     57
     58                        return height;
     59                },
     60
     61                setPlaceholder: function(_height) {
     62                        var height = _height || this.getHeight();
     63                        this.model.set('savedHeight', height);
     64                        this.$el
     65                                .add(this.$el.find('.placeholder'))
     66                                .height(height);
     67                },
     68
     69                setButtons: function() {
     70                        var elements = $('.actions .remove');
     71                        if (this.model.get('choice'))
     72                                elements.show();
     73                        else
     74                                elements.hide();
     75                }
     76        });
     77
     78
     79        /**
     80         * wp.customize.HeaderTool.ChoiceView
     81         *
     82         * Represents a choosable header image, be it user-uploaded,
     83         * theme-suggested or a special Randomize choice.
     84         *
     85         * Takes a wp.customize.HeaderTool.ImageModel.
     86         *
     87         * Manually changes model wp.customize.HeaderTool.currentHeader via the
     88         * `select` method.
     89         *
     90         * @constructor
     91         * @augments Backbone.View
     92         */
     93        (function () { // closures FTW
     94        var lastHeight = 0;
     95        api.HeaderTool.ChoiceView = Backbone.View.extend({
     96                template: _.template($('#tmpl-header-choice').html()),
     97
     98                className: 'header-view',
     99
     100                events: {
     101                        'click .choice,.random': 'select',
     102                        'click .close': 'removeImage'
     103                },
     104
     105                initialize: function() {
     106                        var properties = [
     107                                this.model.get('header').url,
     108                                this.model.get('choice')
     109                        ];
     110
     111                        this.listenTo(this.model, 'change', this.render);
     112                        if (_.contains(properties, api.get().header_image))
     113                                api.HeaderTool.currentHeader.set(this.extendedModel());
     114                },
     115
     116                render: function() {
     117                        var model = this.model;
     118
     119                        this.$el.html(this.template(this.extendedModel()));
     120
     121                        if (model.get('random'))
     122                                this.setPlaceholder(40);
     123                        else
     124                                lastHeight = this.getHeight();
     125
     126                        this.$el.toggleClass('hidden', model.get('hidden'));
     127                        return this;
     128                },
     129
     130                extendedModel: function() {
     131                        var c = this.model.get('collection'),
     132                                t = _wpCustomizeHeader.l10n[c.type] || '';
     133
     134                        return _.extend(this.model.toJSON(), {
     135                                // -1 to exclude the randomize button
     136                                nImages: c.size() - 1,
     137                                type: t
     138                        });
     139                },
     140
     141                getHeight: api.HeaderTool.CurrentView.prototype.getHeight,
     142
     143                setPlaceholder: api.HeaderTool.CurrentView.prototype.setPlaceholder,
     144
     145                select: function() {
     146                        this.model.save();
     147                        api.HeaderTool.currentHeader.set(this.extendedModel());
     148                },
     149
     150                removeImage: function(e) {
     151                        e.stopPropagation();
     152                        this.model.destroy();
     153                        this.remove();
     154                }
     155        });
     156        })();
     157
     158
     159        /**
     160         * wp.customize.HeaderTool.ChoiceListView
     161         *
     162         * A container for ChoiceViews. These choices should be of one same type:
     163         * user-uploaded headers or theme-defined ones.
     164         *
     165         * Takes a wp.customize.HeaderTool.ChoiceList.
     166         *
     167         * @constructor
     168         * @augments Backbone.View
     169         */
     170        api.HeaderTool.ChoiceListView = Backbone.View.extend({
     171                slimScrollOptions: {
     172                        disableFadeOut: true,
     173                        allowPageScroll: true,
     174                        height: 'auto'
     175                },
     176
     177                initialize: function() {
     178                        this.listenTo(this.collection, 'add', this.addOne);
     179                        this.listenTo(this.collection, 'remove', this.render);
     180                        this.listenTo(this.collection, 'sort', this.render);
     181                        this.listenTo(this.collection, 'change:hidden', this.toggleTitle);
     182                        this.listenTo(this.collection, 'change:hidden', this.setMaxListHeight);
     183                        this.render();
     184                },
     185
     186                render: function() {
     187                        this.$el.empty();
     188                        this.collection.each(this.addOne, this);
     189                        this.toggleTitle();
     190                        if (this.$el.parents().hasClass('uploaded')) {
     191                                this.setMaxListHeight();
     192                        }
     193                },
     194
     195                setMaxListHeight: function() {
     196                        if (this.$el.parents().hasClass('uploaded')) {
     197                                var uploaded = this.$el.parents('.uploaded'),
     198                                        height   = this.maxListHeight();
     199
     200                                uploaded.height(height);
     201                                this.$el.slimScroll(this.slimScrollOptions);
     202                        }
     203                },
     204
     205                maxListHeight: function() {
     206                        var shown = this.collection.shown(),
     207                                imgsHeight = shown.reduce( function(memo, img, index) {
     208                                        var imgMargin = (shown.length - 1)  === index ? 0 : 9,
     209                                                height = (260 / img.get('header').width) * img.get('header').height;
     210
     211                                        return memo + height + 5 + imgMargin;
     212                                }, 0);
     213                        return Math.min( Math.ceil(imgsHeight), 180 );
     214                },
     215
     216                addOne: function(choice) {
     217                        var view;
     218                        choice.set({ collection: this.collection });
     219                        view = new api.HeaderTool.ChoiceView({ model: choice });
     220                        this.$el.append(view.render().el);
     221                },
     222
     223                toggleTitle: function() {
     224                        var title = this.$el.parents().prev('.customize-control-title');
     225                        if (this.collection.shouldHideTitle())
     226                                title.hide();
     227                        else
     228                                title.show();
     229                }
     230        });
     231
     232
     233        /**
     234         * wp.customize.HeaderTool.CombinedList
     235         *
     236         * Aggregates wp.customize.HeaderTool.ChoiceList collections (or any
     237         * Backbone object, really) and acts as a bus to feed them events.
     238         *
     239         * @constructor
     240         * @augments Backbone.View
     241         */
     242        api.HeaderTool.CombinedList = Backbone.View.extend({
     243                initialize: function(collections) {
     244                        this.collections = collections;
     245                        this.on('all', this.propagate, this);
     246                },
     247                propagate: function(event, arg) {
     248                        _.each(this.collections, function(collection) {
     249                                collection.trigger(event, arg);
     250                        });
     251                },
     252        });
     253
     254})( jQuery, this.wp, _ );
  • src/wp-includes/class-wp-customize-control.php

    diff --git src/wp-includes/class-wp-customize-control.php src/wp-includes/class-wp-customize-control.php
    index fde8561..5529513 100644
    class WP_Customize_Background_Image_Control extends WP_Customize_Image_Control { 
    691691        }
    692692}
    693693
    694 /**
    695  * Customize Header Image Control Class
    696  *
    697  * @package WordPress
    698  * @subpackage Customize
    699  * @since 3.4.0
    700  */
    701 class WP_Customize_Header_Image_Control extends WP_Customize_Image_Control {
    702         /**
    703          * The processed default headers.
    704          * @since 3.4.2
    705          * @var array
    706          */
    707         protected $default_headers;
    708 
    709         /**
    710          * The uploaded headers.
    711          * @since 3.4.2
    712          * @var array
    713          */
    714         protected $uploaded_headers;
     694class WP_Customize_Header_Image_Control extends WP_Customize_Control {
     695        public $type = 'header';
    715696
    716         /**
    717          * Constructor.
    718          *
    719          * @since 3.4.0
    720          * @uses WP_Customize_Image_Control::__construct()
    721          * @uses WP_Customize_Image_Control::add_tab()
    722          *
    723          * @param WP_Customize_Manager $manager
    724          */
    725697        public function __construct( $manager ) {
    726698                parent::__construct( $manager, 'header_image', array(
    727699                        'label'    => __( 'Header Image' ),
    class WP_Customize_Header_Image_Control extends WP_Customize_Image_Control { 
    733705                        'context'  => 'custom-header',
    734706                        'removed'  => 'remove-header',
    735707                        'get_url'  => 'get_header_image',
    736                         'statuses' => array(
    737                                 ''                      => __('Default'),
    738                                 'remove-header'         => __('No Image'),
    739                                 'random-default-image'  => __('Random Default Image'),
    740                                 'random-uploaded-image' => __('Random Uploaded Image'),
     708                ) );
     709
     710        }
     711
     712        public function to_json() {
     713                parent::to_json();
     714        }
     715
     716        public function enqueue() {
     717                wp_enqueue_media();
     718                wp_enqueue_script( 'customize-header-views' );
     719
     720                $this->prepare_control();
     721
     722                wp_localize_script( 'customize-header-views', '_wpCustomizeHeader', array(
     723                        'data' => array(
     724                                'width' => absint( get_theme_support( 'custom-header', 'width' ) ),
     725                                'height' => absint( get_theme_support( 'custom-header', 'height' ) ),
     726                                'flex-width' => absint( get_theme_support( 'custom-header', 'flex-width' ) ),
     727                                'flex-height' => absint( get_theme_support( 'custom-header', 'flex-height' ) ),
     728                                'currentImgSrc' => $this->get_current_image_src(),
     729                        ),
     730                        'nonces' => array(
     731                                'add' => wp_create_nonce( 'header-add' ),
     732                                'remove' => wp_create_nonce( 'header-remove' ),
     733                        ),
     734                        'l10n' => array(
     735                                /* translators: header images uploaded by user */
     736                                'uploaded' => __( 'uploaded' ),
     737                                /* translators: header images suggested by the current theme */
     738                                'default' => __( 'suggested' )
     739                        ),
     740                        'uploads' => $this->uploaded_headers,
     741                        'defaults' => $this->default_headers
     742                ) );
     743
     744                parent::enqueue();
     745        }
     746
     747        public function get_default_header_images() {
     748                global $custom_image_header;
     749
     750                // Get *the* default image if there is one
     751                $default = get_theme_support( 'custom-header', 'default-image' );
     752
     753                if ( ! $default ) // If not,
     754                        return $custom_image_header->default_headers; // easy peasy.
     755
     756                $default = sprintf( $default,
     757                        get_template_directory_uri(),
     758                        get_stylesheet_directory_uri() );
     759
     760                $header_images = array();
     761                $already_has_default = false;
     762
     763                // Get the whole set of default images
     764                $default_header_images = $custom_image_header->default_headers;
     765                foreach ( $default_header_images as $k => $h ) {
     766                        if ( $h['url'] == $default ) {
     767                                $already_has_default = true;
     768                                break;
     769                        }
     770                }
     771
     772                // If *the one true image* isn't included in the default set, add it in
     773                // first position
     774                if ( ! $already_has_default ) {
     775                        $header_images['default'] = array(
     776                                'url' => $default,
     777                                'thumbnail_url' => $default,
     778                                'description' => 'Default'
     779                        );
     780                }
     781
     782                // The rest of the set comes after
     783                $header_images = array_merge( $header_images, $default_header_images );
     784
     785                return $header_images;
     786        }
     787
     788        public function get_uploaded_header_images() {
     789                $key = '_wp_attachment_custom_header_last_used_' . get_stylesheet();
     790                $header_images = array();
     791
     792                $headers_not_dated = get_posts( array(
     793                        'post_type' => 'attachment',
     794                        'meta_key' => '_wp_attachment_is_custom_header',
     795                        'meta_value' => get_option('stylesheet'),
     796                        'orderby' => 'none',
     797                        'nopaging' => true,
     798                        'meta_query' => array(
     799                                array(
     800                                        'key' => '_wp_attachment_is_custom_header',
     801                                        'value' => get_option( 'stylesheet' ),
     802                                        'compare' => 'LIKE'
     803                                ),
     804                                array(
     805                                        'key' => $key,
     806                                        'value' => 'this string must not be empty',
     807                                        'compare' => 'NOT EXISTS'
     808                                ),
    741809                        )
    742810                ) );
    743811
    744                 // Remove the upload tab.
    745                 $this->remove_tab( 'upload-new' );
     812                $headers_dated = get_posts( array(
     813                        'post_type' => 'attachment',
     814                        'meta_key' => $key,
     815                        'orderby' => 'meta_value_num',
     816                        'order' => 'DESC',
     817                        'nopaging' => true,
     818                        'meta_query' => array(
     819                                array(
     820                                        'key' => '_wp_attachment_is_custom_header',
     821                                        'value' => get_option( 'stylesheet' ),
     822                                        'compare' => 'LIKE'
     823                                ),
     824                        ),
     825                ) );
     826
     827                $limit = apply_filters( 'custom_header_uploaded_limit', 15 );
     828                $headers = array_merge( $headers_dated, $headers_not_dated );
     829                $headers = array_slice( $headers, 0, $limit );
     830
     831                foreach ( (array) $headers as $header ) {
     832                        $url = esc_url_raw( $header->guid );
     833                        $header_data = wp_get_attachment_metadata( $header->ID );
     834                        $timestamp = get_post_meta( $header->ID,
     835                                '_wp_attachment_custom_header_last_used_' . get_stylesheet(),
     836                                true );
     837
     838                        $h = array(
     839                                'attachment_id' => $header->ID,
     840                                'url'           => $url,
     841                                'thumbnail_url' => $url,
     842                                'timestamp'     => $timestamp ? $timestamp : 0,
     843                        );
     844
     845                        if ( isset( $header_data['width'] ) )
     846                                $h['width'] = $header_data['width'];
     847                        if ( isset( $header_data['height'] ) )
     848                                $h['height'] = $header_data['height'];
     849
     850                        $header_images[] = $h;
     851                }
     852
     853                return $header_images;
    746854        }
    747855
    748         /**
    749          * Prepares the control.
    750          *
    751          * If no tabs exist, removes the control from the manager.
    752          *
    753          * @since 3.4.2
    754          */
    755856        public function prepare_control() {
    756857                global $custom_image_header;
    757858                if ( empty( $custom_image_header ) )
    758                         return parent::prepare_control();
     859                        return;
    759860
    760861                // Process default headers and uploaded headers.
    761862                $custom_image_header->process_default_headers();
    762                 $this->default_headers = $custom_image_header->default_headers;
    763                 $this->uploaded_headers = get_uploaded_header_images();
     863                $this->default_headers = $this->get_default_header_images();
     864                $this->uploaded_headers = $this->get_uploaded_header_images();
     865        }
    764866
    765                 if ( $this->default_headers )
    766                         $this->add_tab( 'default',  __('Default'),  array( $this, 'tab_default_headers' ) );
     867        function print_header_image_template() {
     868                ?>
     869                <script type="text/template" id="tmpl-header-choice">
     870                        <% if (random) { %>
     871
     872                        <div class="placeholder random">
     873                                <div class="inner">
     874                                        <span><span class="dice">&#9860;</span>
     875                                                <?php /* translators: "nImages" is a number, "type" is either "uploaded" or "suggested" */ ?>
     876                                                <?php _e( 'Randomize <%- nImages %> <%- type %> headers' ); ?>
     877                                        </span>
     878                                </div>
     879                        </div>
    767880
    768                 if ( ! $this->uploaded_headers )
    769                         $this->remove_tab( 'uploaded' );
     881                        <% } else { %>
    770882
    771                 return parent::prepare_control();
    772         }
     883                        <% if (type == 'uploaded') { %>
     884                        <a href="#" class="close">X</a>
     885                        <% } %>
    773886
    774         /**
    775          * @since 3.4.0
    776          *
    777          * @param mixed $choice Which header image to select. (@see Custom_Image_Header::get_header_image() )
    778          * @param array $header
    779          */
    780         public function print_header_image( $choice, $header ) {
    781                 $header['url']           = set_url_scheme( $header['url'] );
    782                 $header['thumbnail_url'] = set_url_scheme( $header['thumbnail_url'] );
     887                        <a href="#" class="choice thumbnail %>"
     888                                data-customize-image-value="<%- header.url %>"
     889                                data-customize-header-image-data="<%- JSON.stringify(header) %>">
     890                                <img src="<%- header.thumbnail_url %>">
     891                        </a>
    783892
    784                 $header_image_data = array( 'choice' => $choice );
    785                 foreach ( array( 'attachment_id', 'width', 'height', 'url', 'thumbnail_url' ) as $key ) {
    786                         if ( isset( $header[ $key ] ) )
    787                                 $header_image_data[ $key ] = $header[ $key ];
    788                 }
     893                        <% } %>
     894                </script>
    789895
     896                <script type="text/template" id="tmpl-header-current">
     897                        <% if (choice) { %>
     898                                <% if (random) { %>
    790899
    791                 ?>
    792                 <a href="#" class="thumbnail"
    793                         data-customize-image-value="<?php echo esc_url( $header['url'] ); ?>"
    794                         data-customize-header-image-data="<?php echo esc_attr( json_encode( $header_image_data ) ); ?>">
    795                         <img src="<?php echo esc_url( $header['thumbnail_url'] ); ?>" />
    796                 </a>
     900                        <div class="placeholder">
     901                                <div class="inner">
     902                                        <span><span class="dice">&#9860;</span>
     903                                                <?php /* translators: "nImages" is a number, "type" is either "uploaded" or "suggested" */ ?>
     904                                                <?php _e( 'Randomizing <%- nImages %> <%- type %> headers' ); ?>
     905                                        </span>
     906                                </div>
     907                        </div>
     908
     909                                <% } else { %>
     910
     911                        <img src="<%- header.thumbnail_url %>" />
     912
     913                                <% } %>
     914                        <% } else { %>
     915
     916                        <div class="placeholder">
     917                                <div class="inner">
     918                                        <span>
     919                                                No image set.
     920                                        </span>
     921                                </div>
     922                        </div>
     923
     924                        <% } %>
     925                </script>
    797926                <?php
    798927        }
    799928
    800         /**
    801          * @since 3.4.0
    802          */
    803         public function tab_uploaded() {
    804                 ?><div class="uploaded-target"></div><?php
    805 
    806                 foreach ( $this->uploaded_headers as $choice => $header )
    807                         $this->print_header_image( $choice, $header );
     929        public function get_current_image_src() {
     930                $src = $this->value();
     931                if ( isset( $this->get_url ) ) {
     932                        $src = call_user_func( $this->get_url, $src );
     933                        return $src;
     934                }
     935                return null;
    808936        }
    809937
    810         /**
    811          * @since 3.4.0
    812          */
    813         public function tab_default_headers() {
    814                 foreach ( $this->default_headers as $choice => $header )
    815                         $this->print_header_image( $choice, $header );
     938        public function render_content() {
     939                $this->print_header_image_template();
     940                $visibility = $this->get_current_image_src() ? '' : ' style="display:none" ';
     941                $width = absint( get_theme_support( 'custom-header', 'width' ) );
     942                $height = absint( get_theme_support( 'custom-header', 'height' ) );
     943                ?>
     944
     945
     946                <div class="customize-control-content">
     947                        <p class="customizer-section-intro">
     948                                <?php _e( 'Personalize your blog with your own header image.' ); ?>
     949                                <?php
     950                                if ( $width && $height ) {
     951                                        printf( __( 'While you can crop images to your liking after clicking <strong>%s</strong>, your theme recommends a header size of <strong>%dx%d</strong> pixels.' ),
     952                                                _x( 'Add new', 'new image', 'custom-header' ), $width, $height );
     953                                } else {
     954                                        if ( $width )
     955                                                printf( __( 'While you can crop images to your liking after clicking <strong>%s</strong>, your theme recommends a header width of <strong>%d</strong> pixels.' ),
     956                                                        _x( 'Add new', 'new image', 'custom-header' ), $width );
     957                                        if ( $height )
     958                                                printf( __( 'While you can crop images to your liking after clicking <strong>%s</strong>, your theme recommends a header height of <strong>%d</strong> pixels.' ),
     959                                                        _x( 'Add new', 'new image', 'custom-header' ), $height );
     960                                }
     961                                ?>
     962                        </p>
     963                        <div class="current">
     964                                <span class="customize-control-title">
     965                                        <?php _e( 'Current header', 'custom-header' ); ?>
     966                                </span>
     967                                <div class="container">
     968                                </div>
     969                        </div>
     970                        <div class="actions">
     971                                <?php /* translators: Hide as in hide header image via the Customizer */ ?>
     972                                <a href="#" <?php echo $visibility ?> class="button remove"><?php _e( 'Hide', 'custom-header' ); ?></a>
     973                                <?php /* translators: New as in add new header image via the Customizer */ ?>
     974                                <a href="#" class="button new"><?php _ex( 'Add new', 'new image', 'custom-header' ); ?></a>
     975                                <div style="clear:both"></div>
     976                        </div>
     977                        <div class="choices">
     978                                <span class="customize-control-title header-previously-uploaded">
     979                                        <?php _e( 'Previously uploaded', 'custom-header' ); ?>
     980                                </span>
     981                                <div class="uploaded">
     982                                        <div class="list">
     983                                        </div>
     984                                </div>
     985                                <span class="customize-control-title header-default">
     986                                        <?php _e( 'Suggested', 'custom-header' ); ?>
     987                                </span>
     988                                <div class="default">
     989                                        <div class="list">
     990                                        </div>
     991                                </div>
     992                        </div>
     993                </div>
     994                <?php
    816995        }
    817 }
    818  No newline at end of file
     996
     997}
  • new file src/wp-includes/js/jquery/jquery.slimscroll.js

    diff --git src/wp-includes/js/jquery/jquery.slimscroll.js src/wp-includes/js/jquery/jquery.slimscroll.js
    new file mode 100644
    index 0000000..8f813ee
    - +  
     1/*! Copyright (c) 2011 Piotr Rochala (http://rocha.la)
     2 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
     3 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
     4 *
     5 * Version: 1.3.2
     6 *
     7 */
     8(function($) {
     9
     10  jQuery.fn.extend({
     11    slimScroll: function(options) {
     12
     13      var defaults = {
     14
     15        // width in pixels of the visible scroll area
     16        width : 'auto',
     17
     18        // height in pixels of the visible scroll area
     19        height : '250px',
     20
     21        // width in pixels of the scrollbar and rail
     22        size : '7px',
     23
     24        // scrollbar color, accepts any hex/color value
     25        color: '#000',
     26
     27        // scrollbar position - left/right
     28        position : 'right',
     29
     30        // distance in pixels between the side edge and the scrollbar
     31        distance : '1px',
     32
     33        // default scroll position on load - top / bottom / $('selector')
     34        start : 'top',
     35
     36        // sets scrollbar opacity
     37        opacity : .4,
     38
     39        // enables always-on mode for the scrollbar
     40        alwaysVisible : false,
     41
     42        // check if we should hide the scrollbar when user is hovering over
     43        disableFadeOut : false,
     44
     45        // sets visibility of the rail
     46        railVisible : false,
     47
     48        // sets rail color
     49        railColor : '#333',
     50
     51        // sets rail opacity
     52        railOpacity : .2,
     53
     54        // whether  we should use jQuery UI Draggable to enable bar dragging
     55        railDraggable : true,
     56
     57        // defautlt CSS class of the slimscroll rail
     58        railClass : 'slimScrollRail',
     59
     60        // defautlt CSS class of the slimscroll bar
     61        barClass : 'slimScrollBar',
     62
     63        // defautlt CSS class of the slimscroll wrapper
     64        wrapperClass : 'slimScrollDiv',
     65
     66        // check if mousewheel should scroll the window if we reach top/bottom
     67        allowPageScroll : false,
     68
     69        // scroll amount applied to each mouse wheel step
     70        wheelStep : 20,
     71
     72        // scroll amount applied when user is using gestures
     73        touchScrollStep : 200,
     74
     75        // sets border radius
     76        borderRadius: '7px',
     77
     78        // sets border radius of the rail
     79        railBorderRadius : '7px'
     80      };
     81
     82      var o = $.extend(defaults, options);
     83
     84      // do it for every element that matches selector
     85      this.each(function(){
     86
     87      var isOverPanel, isOverBar, isDragg, queueHide, touchDif,
     88        barHeight, percentScroll, lastScroll,
     89        divS = '<div></div>',
     90        minBarHeight = 30,
     91        releaseScroll = false;
     92
     93        // used in event handlers and for better minification
     94        var me = $(this);
     95
     96        // ensure we are not binding it again
     97        if (me.parent().hasClass(o.wrapperClass))
     98        {
     99            // start from last bar position
     100            var offset = me.scrollTop();
     101
     102            // find bar and rail
     103            bar = me.parent().find('.' + o.barClass);
     104            rail = me.parent().find('.' + o.railClass);
     105
     106            getBarHeight();
     107
     108            // check if we should scroll existing instance
     109            if ($.isPlainObject(options))
     110            {
     111              // Pass height: auto to an existing slimscroll object to force a resize after contents have changed
     112              if ( 'height' in options && options.height == 'auto' ) {
     113                me.parent().css('height', 'auto');
     114                me.css('height', 'auto');
     115                var height = me.parent().parent().height();
     116                me.parent().css('height', height);
     117                me.css('height', height);
     118              }
     119
     120              if ('scrollTo' in options)
     121              {
     122                // jump to a static point
     123                offset = parseInt(o.scrollTo);
     124              }
     125              else if ('scrollBy' in options)
     126              {
     127                // jump by value pixels
     128                offset += parseInt(o.scrollBy);
     129              }
     130              else if ('destroy' in options)
     131              {
     132                // remove slimscroll elements
     133                bar.remove();
     134                rail.remove();
     135                me.unwrap();
     136                return;
     137              }
     138
     139              // scroll content by the given offset
     140              scrollContent(offset, false, true);
     141            }
     142
     143            return;
     144        }
     145
     146        // optionally set height to the parent's height
     147        o.height = (options.height == 'auto') ? me.parent().height() : options.height;
     148
     149        // wrap content
     150        var wrapper = $(divS)
     151          .addClass(o.wrapperClass)
     152          .css({
     153            position: 'relative',
     154            overflow: 'hidden',
     155            width: o.width,
     156            height: o.height
     157          });
     158
     159        // update style for the div
     160        me.css({
     161          overflow: 'hidden',
     162          width: o.width,
     163          height: o.height
     164        });
     165
     166        // create scrollbar rail
     167        var rail = $(divS)
     168          .addClass(o.railClass)
     169          .css({
     170            width: o.size,
     171            height: '100%',
     172            position: 'absolute',
     173            top: 0,
     174            display: (o.alwaysVisible && o.railVisible) ? 'block' : 'none',
     175            'border-radius': o.railBorderRadius,
     176            background: o.railColor,
     177            opacity: o.railOpacity,
     178            zIndex: 90
     179          });
     180
     181        // create scrollbar
     182        var bar = $(divS)
     183          .addClass(o.barClass)
     184          .css({
     185            background: o.color,
     186            width: o.size,
     187            position: 'absolute',
     188            top: 0,
     189            opacity: o.opacity,
     190            display: o.alwaysVisible ? 'block' : 'none',
     191            'border-radius' : o.borderRadius,
     192            BorderRadius: o.borderRadius,
     193            MozBorderRadius: o.borderRadius,
     194            WebkitBorderRadius: o.borderRadius,
     195            zIndex: 99
     196          });
     197
     198        // set position
     199        var posCss = (o.position == 'right') ? { right: o.distance } : { left: o.distance };
     200        rail.css(posCss);
     201        bar.css(posCss);
     202
     203        // wrap it
     204        me.wrap(wrapper);
     205
     206        // append to parent div
     207        me.parent().append(bar);
     208        me.parent().append(rail);
     209
     210        // make it draggable and no longer dependent on the jqueryUI
     211        if (o.railDraggable){
     212          bar.bind("mousedown", function(e) {
     213            var $doc = $(document);
     214            isDragg = true;
     215            t = parseFloat(bar.css('top'));
     216            pageY = e.pageY;
     217
     218            $doc.bind("mousemove.slimscroll", function(e){
     219              currTop = t + e.pageY - pageY;
     220              bar.css('top', currTop);
     221              scrollContent(0, bar.position().top, false);// scroll content
     222            });
     223
     224            $doc.bind("mouseup.slimscroll", function(e) {
     225              isDragg = false;hideBar();
     226              $doc.unbind('.slimscroll');
     227            });
     228            return false;
     229          }).bind("selectstart.slimscroll", function(e){
     230            e.stopPropagation();
     231            e.preventDefault();
     232            return false;
     233          });
     234        }
     235
     236        // on rail over
     237        rail.hover(function(){
     238          showBar();
     239        }, function(){
     240          hideBar();
     241        });
     242
     243        // on bar over
     244        bar.hover(function(){
     245          isOverBar = true;
     246        }, function(){
     247          isOverBar = false;
     248        });
     249
     250        // show on parent mouseover
     251        me.hover(function(){
     252          isOverPanel = true;
     253          showBar();
     254          hideBar();
     255        }, function(){
     256          isOverPanel = false;
     257          hideBar();
     258        });
     259
     260        // support for mobile
     261        me.bind('touchstart', function(e,b){
     262          if (e.originalEvent.touches.length)
     263          {
     264            // record where touch started
     265            touchDif = e.originalEvent.touches[0].pageY;
     266          }
     267        });
     268
     269        me.bind('touchmove', function(e){
     270          // prevent scrolling the page if necessary
     271          if(!releaseScroll)
     272          {
     273                      e.originalEvent.preventDefault();
     274                      }
     275          if (e.originalEvent.touches.length)
     276          {
     277            // see how far user swiped
     278            var diff = (touchDif - e.originalEvent.touches[0].pageY) / o.touchScrollStep;
     279            // scroll content
     280            scrollContent(diff, true);
     281            touchDif = e.originalEvent.touches[0].pageY;
     282          }
     283        });
     284
     285        // set up initial height
     286        getBarHeight();
     287
     288        // check start position
     289        if (o.start === 'bottom')
     290        {
     291          // scroll content to bottom
     292          bar.css({ top: me.outerHeight() - bar.outerHeight() });
     293          scrollContent(0, true);
     294        }
     295        else if (o.start !== 'top')
     296        {
     297          // assume jQuery selector
     298          scrollContent($(o.start).position().top, null, true);
     299
     300          // make sure bar stays hidden
     301          if (!o.alwaysVisible) { bar.hide(); }
     302        }
     303
     304        // attach scroll events
     305        attachWheel();
     306
     307        function _onWheel(e)
     308        {
     309          // use mouse wheel only when mouse is over
     310          if (!isOverPanel) { return; }
     311
     312          var e = e || window.event;
     313
     314          var delta = 0;
     315          if (e.wheelDelta) { delta = -e.wheelDelta/120; }
     316          if (e.detail) { delta = e.detail / 3; }
     317
     318          var target = e.target || e.srcTarget || e.srcElement;
     319          if ($(target).closest('.' + o.wrapperClass).is(me.parent())) {
     320            // scroll content
     321            scrollContent(delta, true);
     322          }
     323
     324          // stop window scroll
     325          if (e.preventDefault && !releaseScroll) { e.preventDefault(); }
     326          if (!releaseScroll) { e.returnValue = false; }
     327        }
     328
     329        function scrollContent(y, isWheel, isJump)
     330        {
     331          releaseScroll = false;
     332          var delta = y;
     333          var maxTop = me.outerHeight() - bar.outerHeight();
     334
     335          if (isWheel)
     336          {
     337            // move bar with mouse wheel
     338            delta = parseInt(bar.css('top')) + y * parseInt(o.wheelStep) / 100 * bar.outerHeight();
     339
     340            // move bar, make sure it doesn't go out
     341            delta = Math.min(Math.max(delta, 0), maxTop);
     342
     343            // if scrolling down, make sure a fractional change to the
     344            // scroll position isn't rounded away when the scrollbar's CSS is set
     345            // this flooring of delta would happened automatically when
     346            // bar.css is set below, but we floor here for clarity
     347            delta = (y > 0) ? Math.ceil(delta) : Math.floor(delta);
     348
     349            // scroll the scrollbar
     350            bar.css({ top: delta + 'px' });
     351          }
     352
     353          // calculate actual scroll amount
     354          percentScroll = parseInt(bar.css('top')) / (me.outerHeight() - bar.outerHeight());
     355          delta = percentScroll * (me[0].scrollHeight - me.outerHeight());
     356
     357          if (isJump)
     358          {
     359            delta = y;
     360            var offsetTop = delta / me[0].scrollHeight * me.outerHeight();
     361            offsetTop = Math.min(Math.max(offsetTop, 0), maxTop);
     362            bar.css({ top: offsetTop + 'px' });
     363          }
     364
     365          // scroll content
     366          me.scrollTop(delta);
     367
     368          // fire scrolling event
     369          me.trigger('slimscrolling', ~~delta);
     370
     371          // ensure bar is visible
     372          showBar();
     373
     374          // trigger hide when scroll is stopped
     375          hideBar();
     376        }
     377
     378        function attachWheel()
     379        {
     380          if (window.addEventListener)
     381          {
     382            this.addEventListener('DOMMouseScroll', _onWheel, false );
     383            this.addEventListener('mousewheel', _onWheel, false );
     384          }
     385          else
     386          {
     387            document.attachEvent("onmousewheel", _onWheel)
     388          }
     389        }
     390
     391        function getBarHeight()
     392        {
     393          // calculate scrollbar height and make sure it is not too small
     394          barHeight = Math.max((me.outerHeight() / me[0].scrollHeight) * me.outerHeight(), minBarHeight);
     395          bar.css({ height: barHeight + 'px' });
     396
     397          // hide scrollbar if content is not long enough
     398          var display = barHeight == me.outerHeight() ? 'none' : 'block';
     399          bar.css({ display: display });
     400        }
     401
     402        function showBar()
     403        {
     404          // recalculate bar height
     405          getBarHeight();
     406          clearTimeout(queueHide);
     407
     408          // when bar reached top or bottom
     409          if (percentScroll == ~~percentScroll)
     410          {
     411            //release wheel
     412            releaseScroll = o.allowPageScroll;
     413
     414            // publish approporiate event
     415            if (lastScroll != percentScroll)
     416            {
     417                var msg = (~~percentScroll == 0) ? 'top' : 'bottom';
     418                me.trigger('slimscroll', msg);
     419            }
     420          }
     421          else
     422          {
     423            releaseScroll = false;
     424          }
     425          lastScroll = percentScroll;
     426
     427          // show only when required
     428          if(barHeight >= me.outerHeight()) {
     429            //allow window scroll
     430            releaseScroll = true;
     431            return;
     432          }
     433          bar.stop(true,true).fadeIn('fast');
     434          if (o.railVisible) { rail.stop(true,true).fadeIn('fast'); }
     435        }
     436
     437        function hideBar()
     438        {
     439          // only hide when options allow it
     440          if (!o.alwaysVisible)
     441          {
     442            queueHide = setTimeout(function(){
     443              if (!(o.disableFadeOut && isOverPanel) && !isOverBar && !isDragg)
     444              {
     445                bar.fadeOut('slow');
     446                rail.fadeOut('slow');
     447              }
     448            }, 1000);
     449          }
     450        }
     451
     452      });
     453
     454      // maintain chainability
     455      return this;
     456    }
     457  });
     458
     459  jQuery.fn.extend({
     460    slimscroll: jQuery.fn.slimScroll
     461  });
     462
     463})(jQuery);
  • new file src/wp-includes/js/jquery/jquery.slimscroll.min.js

    diff --git src/wp-includes/js/jquery/jquery.slimscroll.min.js src/wp-includes/js/jquery/jquery.slimscroll.min.js
    new file mode 100644
    index 0000000..09785e7
    - +  
     1/*! Copyright (c) 2011 Piotr Rochala (http://rocha.la)
     2 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
     3 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
     4 *
     5 * Version: 1.3.2
     6 *
     7 */
     8(function(f){jQuery.fn.extend({slimScroll:function(g){var a=f.extend({width:"auto",height:"250px",size:"7px",color:"#000",position:"right",distance:"1px",start:"top",opacity:0.4,alwaysVisible:!1,disableFadeOut:!1,railVisible:!1,railColor:"#333",railOpacity:0.2,railDraggable:!0,railClass:"slimScrollRail",barClass:"slimScrollBar",wrapperClass:"slimScrollDiv",allowPageScroll:!1,wheelStep:20,touchScrollStep:200,borderRadius:"7px",railBorderRadius:"7px"},g);this.each(function(){function u(d){if(r){d=d||
     9window.event;var c=0;d.wheelDelta&&(c=-d.wheelDelta/120);d.detail&&(c=d.detail/3);f(d.target||d.srcTarget||d.srcElement).closest("."+a.wrapperClass).is(b.parent())&&m(c,!0);d.preventDefault&&!k&&d.preventDefault();k||(d.returnValue=!1)}}function m(d,f,g){k=!1;var e=d,h=b.outerHeight()-c.outerHeight();f&&(e=parseInt(c.css("top"))+d*parseInt(a.wheelStep)/100*c.outerHeight(),e=Math.min(Math.max(e,0),h),e=0<d?Math.ceil(e):Math.floor(e),c.css({top:e+"px"}));l=parseInt(c.css("top"))/(b.outerHeight()-c.outerHeight());
     10e=l*(b[0].scrollHeight-b.outerHeight());g&&(e=d,d=e/b[0].scrollHeight*b.outerHeight(),d=Math.min(Math.max(d,0),h),c.css({top:d+"px"}));b.scrollTop(e);b.trigger("slimscrolling",~~e);v();p()}function C(){window.addEventListener?(this.addEventListener("DOMMouseScroll",u,!1),this.addEventListener("mousewheel",u,!1)):document.attachEvent("onmousewheel",u)}function w(){s=Math.max(b.outerHeight()/b[0].scrollHeight*b.outerHeight(),D);c.css({height:s+"px"});var a=s==b.outerHeight()?"none":"block";c.css({display:a})}
     11function v(){w();clearTimeout(A);l==~~l?(k=a.allowPageScroll,B!=l&&b.trigger("slimscroll",0==~~l?"top":"bottom")):k=!1;B=l;s>=b.outerHeight()?k=!0:(c.stop(!0,!0).fadeIn("fast"),a.railVisible&&h.stop(!0,!0).fadeIn("fast"))}function p(){a.alwaysVisible||(A=setTimeout(function(){a.disableFadeOut&&r||x||y||(c.fadeOut("slow"),h.fadeOut("slow"))},1E3))}var r,x,y,A,z,s,l,B,D=30,k=!1,b=f(this);if(b.parent().hasClass(a.wrapperClass)){var n=b.scrollTop(),c=b.parent().find("."+a.barClass),h=b.parent().find("."+
     12a.railClass);w();if(f.isPlainObject(g)){if("height"in g&&"auto"==g.height){b.parent().css("height","auto");b.css("height","auto");var q=b.parent().parent().height();b.parent().css("height",q);b.css("height",q)}if("scrollTo"in g)n=parseInt(a.scrollTo);else if("scrollBy"in g)n+=parseInt(a.scrollBy);else if("destroy"in g){c.remove();h.remove();b.unwrap();return}m(n,!1,!0)}}else{a.height="auto"==g.height?b.parent().height():g.height;n=f("<div></div>").addClass(a.wrapperClass).css({position:"relative",
     13overflow:"hidden",width:a.width,height:a.height});b.css({overflow:"hidden",width:a.width,height:a.height});var h=f("<div></div>").addClass(a.railClass).css({width:a.size,height:"100%",position:"absolute",top:0,display:a.alwaysVisible&&a.railVisible?"block":"none","border-radius":a.railBorderRadius,background:a.railColor,opacity:a.railOpacity,zIndex:90}),c=f("<div></div>").addClass(a.barClass).css({background:a.color,width:a.size,position:"absolute",top:0,opacity:a.opacity,display:a.alwaysVisible?
     14"block":"none","border-radius":a.borderRadius,BorderRadius:a.borderRadius,MozBorderRadius:a.borderRadius,WebkitBorderRadius:a.borderRadius,zIndex:99}),q="right"==a.position?{right:a.distance}:{left:a.distance};h.css(q);c.css(q);b.wrap(n);b.parent().append(c);b.parent().append(h);a.railDraggable&&c.bind("mousedown",function(a){var b=f(document);y=!0;t=parseFloat(c.css("top"));pageY=a.pageY;b.bind("mousemove.slimscroll",function(a){currTop=t+a.pageY-pageY;c.css("top",currTop);m(0,c.position().top,!1)});
     15b.bind("mouseup.slimscroll",function(a){y=!1;p();b.unbind(".slimscroll")});return!1}).bind("selectstart.slimscroll",function(a){a.stopPropagation();a.preventDefault();return!1});h.hover(function(){v()},function(){p()});c.hover(function(){x=!0},function(){x=!1});b.hover(function(){r=!0;v();p()},function(){r=!1;p()});b.bind("touchstart",function(a,b){a.originalEvent.touches.length&&(z=a.originalEvent.touches[0].pageY)});b.bind("touchmove",function(b){k||b.originalEvent.preventDefault();b.originalEvent.touches.length&&
     16(m((z-b.originalEvent.touches[0].pageY)/a.touchScrollStep,!0),z=b.originalEvent.touches[0].pageY)});w();"bottom"===a.start?(c.css({top:b.outerHeight()-c.outerHeight()}),m(0,!0)):"top"!==a.start&&(m(f(a.start).position().top,null,!0),a.alwaysVisible||c.hide());C()}});return this}});jQuery.fn.extend({slimscroll:jQuery.fn.slimScroll})})(jQuery);
  • src/wp-includes/script-loader.php

    diff --git src/wp-includes/script-loader.php src/wp-includes/script-loader.php
    index 98e4a87..945f192 100644
    function wp_default_scripts( &$scripts ) { 
    198198        $scripts->add( 'jquery-table-hotkeys', "/wp-includes/js/jquery/jquery.table-hotkeys$suffix.js", array('jquery', 'jquery-hotkeys'), false, 1 );
    199199        $scripts->add( 'jquery-touch-punch', "/wp-includes/js/jquery/jquery.ui.touch-punch.js", array('jquery-ui-widget', 'jquery-ui-mouse'), '0.2.2', 1 );
    200200        $scripts->add( 'jquery-masonry', "/wp-includes/js/jquery/jquery.masonry.min.js", array('jquery'), '2.1.05', 1 );
     201        $scripts->add( 'jquery-slimscroll', "/wp-includes/js/jquery/jquery.slimscroll$suffix.js", array( 'jquery' ), '1.3.2', 1 );
    201202
    202203        $scripts->add( 'thickbox', "/wp-includes/js/thickbox/thickbox.js", array('jquery'), '3.1-20121105', 1 );
    203204        did_action( 'init' ) && $scripts->localize( 'thickbox', 'thickboxL10n', array(
    function wp_default_scripts( &$scripts ) { 
    375376                'allowedFiles' => __( 'Allowed Files' ),
    376377        ) );
    377378
     379        $scripts->add( 'customize-header-models',  "/wp-admin/js/header-models.js",  array( 'underscore', 'backbone' ), false, 1 );
     380        $scripts->add( 'customize-header-views',  "/wp-admin/js/header-views.js",  array( 'jquery', 'underscore', 'jquery-slimscroll', 'imgareaselect', 'customize-header-models' ), false, 1 );
     381
    378382        $scripts->add( 'accordion', "/wp-admin/js/accordion$suffix.js", array( 'jquery' ), false, 1 );
    379383
    380384        $scripts->add( 'shortcode', "/wp-includes/js/shortcode$suffix.js", array( 'underscore' ), false, 1 );
    function wp_default_styles( &$styles ) { 
    594598        $styles->add( 'login',              "/wp-admin/css/login$suffix.css", array( 'buttons', 'open-sans', 'dashicons' ) );
    595599        $styles->add( 'install',            "/wp-admin/css/install$suffix.css", array( 'buttons', 'open-sans' ) );
    596600        $styles->add( 'wp-color-picker',    "/wp-admin/css/color-picker$suffix.css" );
    597         $styles->add( 'customize-controls', "/wp-admin/css/customize-controls$suffix.css", array( 'wp-admin', 'colors', 'ie' ) );
     601        $styles->add( 'customize-controls', "/wp-admin/css/customize-controls$suffix.css", array( 'wp-admin', 'colors', 'ie', 'imgareaselect' ) );
    598602        $styles->add( 'ie',                 "/wp-admin/css/ie$suffix.css" );
    599603
    600604        $styles->add_data( 'ie', 'conditional', 'lte IE 7' );