WordPress.org

Make WordPress Core

Ticket #37974: 37974.diff

File 37974.diff, 37.4 KB (added by bradyvercher, 3 years ago)

Customizer UI

  • src/wp-admin/css/customize-controls.css

     
    12821282        color: #00a0d2;
    12831283}
    12841284
     1285.reordering .add-new-item {
     1286        opacity: 0.2;
     1287        pointer-events: none;
     1288        cursor: not-allowed; /* doesn't work in conjunction with pointer-events */
     1289}
     1290
     1291.wp-reorder-nav {
     1292        display: none;
     1293        background-color: #fff;
     1294        position: absolute;
     1295        top: 0;
     1296        right: 0;
     1297}
     1298
     1299.reordering .wp-reorder-nav {
     1300        display: block;
     1301}
     1302
     1303.wp-reorder-nav button,
    12851304.widget-reorder-nav span,
    12861305.menu-item-reorder-nav button {
    12871306        position: relative;
     
    12961315        outline: none;
    12971316}
    12981317
     1318.wp-reorder-nav button,
    12991319.menu-item-reorder-nav button {
    13001320        width: 30px;
    13011321        height: 40px;
     
    13051325        box-shadow: none;
    13061326}
    13071327
     1328.wp-reorder-nav button:before,
    13081329.widget-reorder-nav span:before,
    13091330.menu-item-reorder-nav button:before {
    13101331        display: inline-block;
     
    13201341        -moz-osx-font-smoothing: grayscale;
    13211342}
    13221343
     1344.wp-reorder-nav button:hover,
     1345.wp-reorder-nav button:focus,
    13231346.widget-reorder-nav span:hover,
    13241347.widget-reorder-nav span:focus,
    13251348.menu-item-reorder-nav button:hover,
     
    13281351        background: #eee;
    13291352}
    13301353
     1354.wp-reorder-nav button {
     1355        width: 33px;
     1356        height: 38px;
     1357}
     1358
     1359.wp-reorder-nav button:before {
     1360        font: normal 20px/38px dashicons;
     1361}
     1362
     1363.move-item-up:before,
    13311364.move-widget-down:before,
    13321365.menus-move-down:before {
    13331366        content: "\f347";
    13341367}
    13351368
     1369.move-item-down:before,
    13361370.move-widget-up:before,
    13371371.menus-move-up:before {
    13381372        content: "\f343";
     
    13431377.move-up-disabled .menus-move-up,
    13441378.move-down-disabled .menus-move-down,
    13451379.move-right-disabled .menus-move-right,
    1346 .move-left-disabled .menus-move-left {
     1380.move-left-disabled .menus-move-left,
     1381.wp-item:first-child .move-item-up,
     1382.wp-item:last-child .move-item-down {
    13471383        color: #d5d5d5;
    13481384        background-color: #fff;
    13491385        cursor: default;
  • src/wp-admin/css/customize-post-collection.css

     
     1/* General
     2------------------------------------------------------------------------------*/
     3
     4.customize-dashicon,
     5.drawer-toggle:before {
     6        cursor: pointer;
     7        display: inline-block;
     8        font-family: dashicons;
     9        font-size: 20px;
     10        -webkit-font-smoothing: antialiased;
     11        font-style: normal;
     12        font-weight: normal;
     13        line-height: 1;
     14        position: relative;
     15        text-align: center;
     16        text-decoration: inherit;
     17        vertical-align: top;
     18}
     19
     20.drawer-toggle:before {
     21        content: "\f132";
     22        position: relative;
     23        left: -2px;
     24        top: -1px;
     25        transition: all 0.2s;
     26        vertical-align: middle;
     27}
     28
     29.customize-button-delete {
     30        color: #a00;
     31}
     32
     33.customize-button-delete:hover {
     34        color: #f00;
     35}
     36
     37.customize-button-delete.customize-dashicon:before {
     38        content: "\f335";
     39}
     40
     41.customize-control.is-open .drawer-toggle:before {
     42        transform: rotate( 45deg );
     43}
     44
     45
     46/* Drawer
     47------------------------------------------------------------------------------*/
     48
     49.customize-drawer {
     50        background: #eee;
     51        border-right: 1px solid #ddd;
     52        display: block;
     53        margin: 0;
     54        overflow-x: hidden;
     55        overflow-y: auto;
     56        position: absolute;
     57        top: 0;
     58        right: 0;
     59        bottom: 0;
     60        left: -301px;
     61        transition: left 0.18s;
     62        visibility: hidden;
     63        width: 300px;
     64        z-index: 4;
     65}
     66
     67.customize-drawer-notice {
     68        padding: 15px;
     69}
     70
     71.customize-drawer.is-open {
     72        left: 0;
     73        visibility: visible;
     74}
     75
     76.drawer-is-open .wp-full-overlay-main {
     77        left: 300px;
     78}
     79
     80
     81/* Sortable Item List
     82------------------------------------------------------------------------------*/
     83
     84.wp-items-list {
     85        list-style: none;
     86        margin: 0 0 10px 0;
     87        padding: 0;
     88        position: relative;
     89}
     90
     91.wp-item {
     92        background: #fff;
     93        margin: -1px 0 0 0;
     94        padding: 0;
     95}
     96
     97.wp-item-header {
     98        border: 1px solid #dfdfdf;
     99        background: #fff;
     100        position: relative;
     101}
     102
     103.wp-item-delete {
     104        display: none;
     105        height: 100%;
     106        position: absolute;
     107        top: 0;
     108        right: 0;
     109        bottom: 0;
     110        text-align: center;
     111        vertical-align: middle;
     112        width: 40px;
     113}
     114
     115.wp-item-title {
     116        cursor: move;
     117        margin: 0;
     118        padding: 10px 20px;
     119        position: relative;
     120}
     121
     122.wp-item-toggle {
     123        display: none;
     124        height: 100%;
     125        position: absolute;
     126        top: 0;
     127        right: 0;
     128        bottom: 0;
     129        text-align: right;
     130        vertical-align: middle;
     131        width: 40px;
     132}
     133
     134.wp-item-toggle:before {
     135        box-sizing: border-box;
     136        color: #aaa;
     137        content: "\f140"; /* Down arrow. */
     138        position: absolute;
     139        top: 10px;
     140        right: 10px;
     141        bottom: 10px;
     142        left: 0;
     143        vertical-align: middle;
     144}
     145
     146.wp-item-toggle:before:hover {
     147        color: #777;
     148}
     149
     150.wp-item-body {
     151        border-color: #999;
     152        border-style: solid;
     153        border-width: 0 1px 1px 1px;
     154        display: none;
     155        margin: 0;
     156        padding: 10px;
     157        position: relative;
     158}
     159
     160.wp-item-body input.regular-text {
     161        max-width: 100%;
     162}
     163
     164.wp-item-actions {
     165        clear: both;
     166        line-height: 1;
     167}
     168
     169.wp-item-actions a {
     170        cursor: pointer;
     171}
     172
     173.wp-item.ui-sortable-helper {
     174        background: #f9f9f9;
     175        border: 1px solid #dfdfdf;
     176}
     177
     178.wp-item.ui-sortable-placeholder {
     179        background: transparent;
     180        border: 1px dashed #a0a5aa;
     181        margin-top: 0;
     182        margin-bottom: 1px;
     183}
     184
     185.wp-item:hover .wp-item-header,
     186.wp-item.is-open .wp-item-header {
     187        border-color: #999;
     188        z-index: 1;
     189}
     190
     191.wp-item.is-open .wp-item-body {
     192        z-index: 1;
     193}
     194
     195.wp-item.is-open .wp-item-header {
     196        border-bottom: 1px solid #e5e5e5;
     197}
     198
     199.wp-item.is-open .wp-item-toggle:before {
     200        content: "\f142"; /* Up arrow.*/
     201}
     202
     203.wp-item.is-open .wp-item-body {
     204        display: block;
     205}
     206
     207.customize-control.is-open .wp-item-delete {
     208        display: block;
     209}
     210
     211.customize-control.is-open .wp-item-toggle {
     212        display: none;
     213}
     214
     215
     216/* Search
     217------------------------------------------------------------------------------*/
     218
     219.search-group {
     220        border-bottom: 1px solid #ddd;
     221        margin: 0;
     222        padding: 12px 15px;
     223        position: relative;
     224}
     225
     226.search-group-field {
     227        padding: 6px 10px;
     228        width: 100%;
     229}
     230
     231.search-group-spinner {
     232        margin: 0;
     233        position: absolute;
     234        top: 19px;
     235        right: 20px;
     236}
     237
     238.search-group-button-clear {
     239        background: transparent;
     240        border-width: 0;
     241        cursor: pointer;
     242        height: 20px;
     243        padding: 0;
     244        position: absolute;
     245        top: 19px;
     246        right: 20px;
     247        text-align: center;
     248        width: 20px;
     249}
     250
     251
     252/* Search Results
     253------------------------------------------------------------------------------*/
     254
     255.search-results {
     256        padding: 1px 15px 15px;
     257}
     258
     259.search-results-item {
     260        background: #fff;
     261        border: 1px solid #ddd;
     262        clear: both;
     263        cursor: pointer;
     264        line-height: 10px;
     265        margin: -1px 0 0 0;
     266        padding: 10px 15px;
     267        position: relative;
     268}
     269
     270.search-results-item-title {
     271        display: block;
     272        font-size: 13px;
     273        font-weight: 600;
     274        line-height: 20px;
     275        padding-left: 20px;
     276}
     277
     278.search-results-item-type {
     279        color: #666;
     280        float: right;
     281        font-size: 12px;
     282        line-height: 20px;
     283        text-align: right;
     284}
     285
     286.search-results-item-add {
     287        color: #82878c;
     288        height: 38px;
     289        position: absolute;
     290        top: 1px;
     291        left: 1px;
     292        width: 30px;
     293}
     294
     295.search-results-item-add:before {
     296        -webkit-border-radius: 50%;
     297        border-radius: 50%;
     298        content: "\f543";
     299        height: 20px;
     300        position: relative;
     301        top: 0;
     302        left: 2px;
     303}
     304
     305.search-results-item:hover {
     306        border-color: #999;
     307        color: #0073aa;
     308        z-index: 1;
     309}
     310
     311.search-results-item:hover .search-results-item-add:before {
     312        color: #0073aa;
     313}
     314
     315.is-selected .search-results-item-add:before {
     316        content: "\f147";
     317}
  • src/wp-admin/js/customize-post-collection.js

     
     1(function( wp, $ ) {
     2
     3        if ( ! wp || ! wp.customize ) { return; }
     4
     5        var api = wp.customize;
     6
     7        api.PostCollection = api.PostCollection || {};
     8
     9        api.DrawerModel = Backbone.Model.extend({
     10                defaults: {
     11                        status: 'closed'
     12                },
     13
     14                close: function() {
     15                        this.set( 'status', 'closed' );
     16                },
     17
     18                isOpen: function() {
     19                        return 'open' === this.get( 'status' );
     20                },
     21
     22                open: function() {
     23                        this.set( 'status', 'open' );
     24                },
     25
     26                toggle: function() {
     27                        if ( this.isOpen() ) {
     28                                this.close();
     29                        } else {
     30                                this.open();
     31                        }
     32                }
     33        });
     34
     35        api.DrawerManager = Backbone.Collection.extend({
     36                model: api.DrawerModel,
     37
     38                initialize: function() {
     39                        this.on( 'change:status', this.closeOtherDrawers );
     40                },
     41
     42                closeOtherDrawers: function( model ) {
     43                        if ( 'open' === model.get( 'status' ) ) {
     44                                _.chain( this.models ).without( model ).invoke( 'close' );
     45                        }
     46                }
     47        });
     48
     49        api.DrawerView = wp.Backbone.View.extend({
     50                tagName: 'div',
     51                className: 'customize-drawer',
     52
     53                initialize: function( options ) {
     54                        this.controller = options.controller;
     55                        this.listenTo( this.controller, 'change:status', this.updateStatusClass );
     56                },
     57
     58                updateStatusClass: function() {
     59                        if ( 'open' === this.controller.get( 'status' ) ) {
     60                                this.$el.addClass( 'is-open' );
     61                        } else {
     62                                this.$el.removeClass( 'is-open' );
     63                        }
     64                }
     65        });
     66
     67        api.PostCollection.PostModel = Backbone.Model.extend({
     68                defaults: {
     69                        title: '',
     70                        order: 0
     71                }
     72        });
     73
     74        api.PostCollection.PostsCollection = Backbone.Collection.extend({
     75                model: api.PostCollection.PostModel,
     76
     77                comparator: function( post ) {
     78                        return parseInt( post.get( 'order' ), 10 );
     79                }
     80        });
     81
     82        api.PostCollection.ControlView = wp.Backbone.View.extend({
     83                initialize: function( options ) {
     84                        this.control = options.control;
     85                        this.setting = options.setting;
     86
     87                        this.listenTo( this.collection, 'add remove reset sort', this.updateSetting );
     88                        this.listenTo( this.control.drawer, 'change:status', this.maybeTriggerSearch );
     89                        this.listenTo( this.control.drawer, 'change:status', this.updateStatusClass );
     90                },
     91
     92                render: function() {
     93                        this.views.add([
     94                                new api.PostCollection.ItemListView({
     95                                        collection: this.collection,
     96                                        control: this.control,
     97                                        parent: this
     98                                }),
     99
     100                                new api.PostCollection.ControlActionsView({
     101                                        collection: this.collection,
     102                                        control: this.control,
     103                                        parent: this
     104                                })
     105                        ]);
     106
     107                        return this;
     108                },
     109
     110                maybeTriggerSearch: function() {
     111                        if ( 'open' === this.control.drawer.get( 'status' ) && this.control.results.length < 1 ) {
     112                                this.control.search();
     113                        }
     114                },
     115
     116                updateSetting: function() {
     117                        var postIds = this.collection.sort({ silent: true }).pluck( 'id' ).join( ',' );
     118                        this.setting.set( postIds );
     119                },
     120
     121                updateStatusClass: function() {
     122                        if ( 'open' === this.control.drawer.get( 'status' ) ) {
     123                                this.$el.addClass( 'is-open' );
     124                        } else {
     125                                this.$el.removeClass( 'is-open' );
     126                        }
     127                }
     128        });
     129
     130        api.PostCollection.ControlActionsView = wp.Backbone.View.extend({
     131                className: 'actions',
     132                tagName: 'div',
     133
     134                initialize: function( options ) {
     135                        this.control = options.control;
     136                        this.parent = options.parent;
     137                },
     138
     139                render: function() {
     140                        this.views.add([
     141                                new api.PostCollection.AddNewItemButtonView({
     142                                        control: this.control
     143                                }),
     144                                new api.PostCollection.ReorderToggleButtonView({
     145                                        control: this.control
     146                                })
     147                        ]);
     148
     149                        return this;
     150                }
     151        });
     152
     153        api.PostCollection.AddNewItemButtonView = wp.Backbone.View.extend({
     154                className: 'drawer-toggle add-new-item button button-secondary alignright',
     155                tagName: 'button',
     156
     157                events: {
     158                        click: 'toggleDrawer'
     159                },
     160
     161                initialize: function( options ) {
     162                        this.control = options.control;
     163                },
     164
     165                render: function() {
     166                        this.$el.text( this.control.l10n.addPosts );
     167                        return this;
     168                },
     169
     170                toggleDrawer: function( e ) {
     171                        e.preventDefault();
     172                        this.control.drawer.toggle();
     173                }
     174        });
     175
     176        api.PostCollection.ReorderToggleButtonView = wp.Backbone.View.extend({
     177                className: 'reorder-toggle button-link',
     178                tagName: 'button',
     179
     180                events: {
     181                        click: 'toggleReordering'
     182                },
     183
     184                initialize: function( options ) {
     185                        this.control = options.control;
     186                        this.listenTo( this.control.posts, 'add remove reset', this.updateVisibility );
     187                },
     188
     189                render: function() {
     190                        this.$el.empty();
     191
     192                        this.$el.append( $( '<span />', {
     193                                'class': 'reorder',
     194                                text: this.control.l10n.reorder
     195                        }) );
     196
     197                        this.$el.append( $( '<span />', {
     198                                'class': 'reorder-done',
     199                                text: this.control.l10n.reorderDone
     200                        }) );
     201
     202                        this.updateVisibility();
     203                        return this;
     204                },
     205
     206                toggleReordering: function( e ) {
     207                        e.preventDefault();
     208                        this.control.drawer.close();
     209                        this.control.container.toggleClass( 'reordering' );
     210                },
     211
     212                updateVisibility: function() {
     213                        this.$el.toggle( !! this.control.posts.length );
     214                }
     215        });
     216
     217        api.PostCollection.ItemListView = wp.Backbone.View.extend({
     218                className: 'wp-items-list',
     219                tagName: 'ol',
     220
     221                initialize: function( options ) {
     222                        this.control = options.control;
     223
     224                        this.listenTo( this.collection, 'add', this.addItem );
     225                        this.listenTo( this.collection, 'add remove', this.updateOrder );
     226                        this.listenTo( this.collection, 'reset', this.render );
     227                },
     228
     229                render: function() {
     230                        this.$el.empty();
     231                        this.collection.each( this.addItem, this );
     232                        this.initializeSortable();
     233                        return this;
     234                },
     235
     236                initializeSortable: function() {
     237                        this.$el.sortable({
     238                                axis: 'y',
     239                                delay: 150,
     240                                forceHelperSize: true,
     241                                forcePlaceholderSize: true,
     242                                opacity: 0.6,
     243                                start: function( e, ui ) {
     244                                        ui.placeholder.css( 'visibility', 'visible' );
     245                                },
     246                                update: _.bind(function() {
     247                                        this.updateOrder();
     248                                }, this )
     249                        });
     250                },
     251
     252                addItem: function( item ) {
     253                        var itemView = new api.PostCollection.ItemView({
     254                                control: this.control,
     255                                model: item,
     256                                parent: this
     257                        });
     258
     259                        this.$el.append( itemView.render().el );
     260                },
     261
     262                moveDown: function( model ) {
     263                        var index = this.collection.indexOf( model ),
     264                                $items = this.$el.children();
     265
     266                        if ( index < this.collection.length - 1 ) {
     267                                $items.eq( index ).insertAfter( $items.eq( index + 1 ) );
     268                                this.updateOrder();
     269                                wp.a11y.speak( this.control.l10n.movedDown );
     270                        }
     271                },
     272
     273                moveUp: function( model ) {
     274                        var index = this.collection.indexOf( model ),
     275                                $items = this.$el.children();
     276
     277                        if ( index > 0 ) {
     278                                $items.eq( index ).insertBefore( $items.eq( index - 1 ) );
     279                                this.updateOrder();
     280                                wp.a11y.speak( this.control.l10n.movedUp );
     281                        }
     282                },
     283
     284                updateOrder: function() {
     285                        _.each( this.$el.find( 'li' ), function( item, i ) {
     286                                var id = $( item ).data( 'post-id' );
     287                                this.collection.get( id ).set( 'order', i );
     288                        }, this );
     289
     290                        this.collection.sort();
     291                }
     292        });
     293
     294        api.PostCollection.ItemView = wp.Backbone.View.extend({
     295                tagName: 'li',
     296                className: 'wp-item',
     297                template: wp.template( 'wp-item' ),
     298
     299                events: {
     300                        'click .js-toggle': 'toggleOpenStatus',
     301                        'click .js-close': 'minimize',
     302                        'click .js-remove': 'destroy',
     303                        'click .move-item-up': 'moveUp',
     304                        'click .move-item-down': 'moveDown'
     305                },
     306
     307                initialize: function( options ) {
     308                        this.control = options.control;
     309                        this.parent = options.parent;
     310                        this.listenTo( this.model, 'destroy', this.remove );
     311                },
     312
     313                render: function() {
     314                        var data = _.extend( this.model.toJSON(), {
     315                                l10n: this.control.l10n
     316                        });
     317
     318                        this.$el.html( this.template( data ) );
     319                        this.$el.data( 'post-id', this.model.get( 'id' ) );
     320
     321                        return this;
     322                },
     323
     324                minimize: function( e ) {
     325                        e.preventDefault();
     326                        this.$el.removeClass( 'is-open' );
     327                },
     328
     329                moveDown: function( e ) {
     330                        e.preventDefault();
     331                        this.parent.moveDown( this.model );
     332                },
     333
     334                moveUp: function( e ) {
     335                        e.preventDefault();
     336                        this.parent.moveUp( this.model );
     337                },
     338
     339                toggleOpenStatus: function( e ) {
     340                        e.preventDefault();
     341                        this.$el.toggleClass( 'is-open' );
     342                },
     343
     344                /**
     345                 * Destroy the view's model.
     346                 *
     347                 * Avoid syncing to the server by triggering an event instead of
     348                 * calling destroy() directly on the model.
     349                 */
     350                destroy: function() {
     351                        this.model.trigger( 'destroy', this.model );
     352                },
     353
     354                remove: function() {
     355                        this.$el.remove();
     356                }
     357        });
     358
     359        api.PostCollection.DrawerNoticeView = wp.Backbone.View.extend({
     360                tagName: 'div',
     361                className: 'customize-drawer-notice',
     362
     363                initialize: function( options ) {
     364                        this.control = options.control;
     365                        this.listenTo( this.control.state, 'change:notice', this.render );
     366                },
     367
     368                render: function() {
     369                        var notice = this.control.state.get( 'notice' );
     370                        this.$el.toggle( !! notice.length ).text( notice );
     371                        return this;
     372                }
     373        });
     374
     375        api.PostCollection.SearchGroupView = wp.Backbone.View.extend({
     376                tagName: 'div',
     377                className: 'search-group',
     378                template: wp.template( 'search-group' ),
     379
     380                events: {
     381                        'input input': 'search'
     382                },
     383
     384                initialize: function( options ) {
     385                        this.control = options.control;
     386                        this.parent = options.parent;
     387                },
     388
     389                render: function() {
     390                        this.$el.html( this.template({ l10n: this.control.l10n }) );
     391                        this.$field = this.$el.find( '.search-group-field' );
     392                        this.$spinner = this.$el.append( '<span class="search-group-spinner spinner" />' ).find( '.spinner' );
     393
     394                        this.views.add([
     395                                new api.PostCollection.ClearResultsButtonView({
     396                                        collection: this.collection,
     397                                        control: this.control,
     398                                        parent: this
     399                                })
     400                        ]);
     401
     402                        return this;
     403                },
     404
     405                search: function() {
     406                        var view = this;
     407
     408                        this.$spinner.addClass( 'is-active' );
     409
     410                        clearTimeout( this.timeout );
     411                        this.timeout = setTimeout(function() {
     412                                view.control.search( view.$field.val() )
     413                                        .always(function() {
     414                                                view.$spinner.removeClass( 'is-active' );
     415                                        });
     416                        }, 300 );
     417                }
     418        });
     419
     420        api.PostCollection.ClearResultsButtonView = wp.Backbone.View.extend({
     421                tagName: 'button',
     422                className: 'search-group-button-clear customize-button-delete customize-dashicon',
     423
     424                events: {
     425                        'click': 'clearResults'
     426                },
     427
     428                initialize: function( options ) {
     429                        this.control = options.control;
     430                        this.parent = options.parent;
     431                        this.listenTo( this.collection, 'add remove reset', this.toggleVisibility );
     432                },
     433
     434                render: function() {
     435                        this.$el.html( $( '<span />', {
     436                                'class': 'screen-reader-text',
     437                                text: this.control.l10n.clearResults
     438                        }) );
     439                        this.toggleVisibility();
     440                        return this;
     441                },
     442
     443                clearResults: function() {
     444                        this.collection.reset();
     445                        this.parent.$field.val( '' );
     446                },
     447
     448                toggleVisibility: function() {
     449                        this.$el.toggle( !! this.collection.length );
     450                }
     451        });
     452
     453        api.PostCollection.SearchResultsView = wp.Backbone.View.extend({
     454                tagName: 'div',
     455                className: 'search-results',
     456
     457                initialize: function( options ) {
     458                        this.control = options.control;
     459                        this.listenTo( this.collection, 'reset', this.render );
     460                },
     461
     462                render: function() {
     463                        this.$list = this.$el.html( '<ul />' ).find( 'ul' );
     464
     465                        if ( this.collection.length ) {
     466                                this.collection.each( this.addItem, this );
     467                        } else {
     468                                this.$el.empty();
     469                        }
     470
     471                        return this;
     472                },
     473
     474                addItem: function( model ) {
     475                        this.views.add( 'ul', new api.PostCollection.SearchResultView({
     476                                control: this.control,
     477                                model: model
     478                        }));
     479                }
     480        });
     481
     482        api.PostCollection.SearchResultView = wp.Backbone.View.extend({
     483                tagName: 'li',
     484                className: 'search-results-item',
     485                template: wp.template( 'search-result' ),
     486
     487                events: {
     488                        'click': 'addSection'
     489                },
     490
     491                initialize: function( options ) {
     492                        this.control = options.control;
     493                        this.listenTo( this.control.posts, 'add remove reset', this.updateSelectedClass );
     494                },
     495
     496                render: function() {
     497                        var data = _.extend( this.model.toJSON(), {
     498                                l10n: this.control.l10n
     499                        });
     500
     501                        this.$el.html( this.template( data ) );
     502                        this.updateSelectedClass();
     503
     504                        return this;
     505                },
     506
     507                addSection: function() {
     508                        this.control.posts.add( this.model );
     509                },
     510
     511                updateSelectedClass: function() {
     512                        this.$el.toggleClass( 'is-selected', this.control.posts.contains( this.model ) );
     513                }
     514        });
     515
     516        api.PostCollection.PostCollectionControl = api.Control.extend({
     517                ready: function() {
     518                        var drawerView,
     519                                control = this,
     520                                section = api.section( this.section() );
     521
     522                        this.drawer = new api.DrawerModel();
     523                        api.drawerManager.add( this.drawer );
     524
     525                        this.posts = new api.PostCollection.PostsCollection( this.params.posts );
     526                        this.results = new api.PostCollection.PostsCollection();
     527                        delete this.params.posts;
     528
     529                        this.l10n = this.params.l10n;
     530                        delete this.params.l10n;
     531
     532                        this.state = new Backbone.Model({
     533                                notice: ''
     534                        });
     535
     536                        this.view = new api.PostCollection.ControlView({
     537                                el: this.container,
     538                                collection: this.posts,
     539                                control: this,
     540                                data: this.params,
     541                                setting: this.setting
     542                        });
     543
     544                        this.view.render();
     545
     546                        drawerView = new api.DrawerView({
     547                                controller: this.drawer
     548                        });
     549
     550                        drawerView.views.set([
     551                                new api.PostCollection.SearchGroupView({
     552                                        collection: this.results,
     553                                        control: this
     554                                }),
     555                                new api.PostCollection.DrawerNoticeView({
     556                                        control: this
     557                                }),
     558                                new api.PostCollection.SearchResultsView({
     559                                        collection: this.results,
     560                                        control: this
     561                                })
     562                        ]);
     563
     564                        $( '.wp-full-overlay' ).append( drawerView.render().$el );
     565
     566                        section.expanded.bind(function( isOpen ) {
     567                                if ( ! isOpen ) {
     568                                        control.drawer.close();
     569                                }
     570                        });
     571                },
     572
     573                search: function( query ) {
     574                        var control = this;
     575
     576                        return wp.ajax.post( 'customize_find_posts', {
     577                                s: query,
     578                                post_types: this.params.postTypes,
     579                                //not_in: this.posts.pluck( 'id' ),
     580                                wp_customize: 'on',
     581                                _ajax_nonce: this.params.searchNonce
     582                        }).done(function( response ) {
     583                                control.results.reset( response );
     584                                control.state.set( 'notice', '' );
     585                        }).fail(function( response ) {
     586                                control.results.reset();
     587                                control.state.set( 'notice', response );
     588                        });
     589                }
     590        });
     591
     592        /**
     593         * Extends wp.customize.controlConstructor with control constructor for
     594         * post_collection.
     595         */
     596        $.extend( api.controlConstructor, {
     597                post_collection: api.PostCollection.PostCollectionControl
     598        });
     599
     600        /**
     601         * Create a global drawer manager.
     602         */
     603        api.drawerManager = new api.DrawerManager();
     604
     605        /**
     606         * Toggle an HTML class on the body when drawers are opened or closed.
     607         */
     608        $( document ).ready(function() {
     609                var $body = $( document.body );
     610
     611                api.drawerManager.on( 'change:status', function() {
     612                        if ( api.drawerManager.findWhere({ status: 'open' }) ) {
     613                                $body.addClass( 'drawer-is-open' );
     614                        } else {
     615                                $body.removeClass( 'drawer-is-open' );
     616                        }
     617                });
     618        });
     619
     620        /**
     621         * Toggle the front_page_sections control based on the 'show_on_front'
     622         * setting value.
     623         */
     624        api.bind( 'ready', function() {
     625                api( 'show_on_front', function( setting ) {
     626                        api.control( 'front_page_sections', function( control ) {
     627                                var toggleVisibility = function( value ) {
     628                                        control.container.toggle( 'page' === value );
     629                                };
     630
     631                                toggleVisibility( setting() );
     632                                setting.bind( toggleVisibility );
     633                        });
     634                });
     635        });
     636
     637})( window.wp, jQuery );
  • src/wp-includes/class-wp-customize-manager.php

     
    227227                require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-name-control.php' );
    228228                require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-auto-add-control.php' );
    229229                require_once( ABSPATH . WPINC . '/customize/class-wp-customize-new-menu-control.php' );
     230                require_once( ABSPATH . WPINC . '/customize/class-wp-customize-post-collection-control.php' );
    230231
    231232                require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menus-panel.php' );
    232233
     
    289290
    290291                add_action( 'wp_ajax_customize_save',           array( $this, 'save' ) );
    291292                add_action( 'wp_ajax_customize_refresh_nonces', array( $this, 'refresh_nonces' ) );
     293                add_action( 'wp_ajax_customize_find_posts',     array( $this, 'ajax_find_posts' ) );
    292294
    293295                add_action( 'customize_register',                 array( $this, 'register_controls' ) );
    294296                add_action( 'customize_register',                 array( $this, 'register_dynamic_settings' ), 11 ); // allow code to create settings first
     
    19631965                $this->register_control_type( 'WP_Customize_Cropped_Image_Control' );
    19641966                $this->register_control_type( 'WP_Customize_Site_Icon_Control' );
    19651967                $this->register_control_type( 'WP_Customize_Theme_Control' );
     1968                $this->register_control_type( 'WP_Customize_Post_Collection_Control' );
    19661969
    19671970                /* Themes */
    19681971
     
    22952298                        'section' => 'static_front_page',
    22962299                        'type' => 'dropdown-pages',
    22972300                ) );
     2301
     2302                if ( current_theme_supports( 'front-page-sections' ) ) {
     2303                        $this->add_setting( 'front_page_sections', array(
     2304                                'sanitize_callback' => array( $this, 'sanitize_id_list' ),
     2305                        ) );
     2306
     2307                        $this->add_control( new WP_Customize_Post_Collection_Control( $this, 'front_page_sections', array(
     2308                                'label'       => __( 'Front page sections' ),
     2309                                'description' => '',
     2310                                'section'     => 'static_front_page',
     2311                                'settings'    => 'front_page_sections',
     2312                                'post_types'  => get_theme_support( 'front-page-sections', 'post_types' ),
     2313                                'l10n'        => array(
     2314                                        'addPost'                => __( 'Add Section' ),
     2315                                        'addPosts'               => __( 'Add Sections' ),
     2316                                        'movedUp'                => __( 'Section moved up' ),
     2317                                        'movedDown'              => __( 'Section moved down' ),
     2318                                        'removePost'             => __( 'Remove Section' ),
     2319                                        'reorder'                => _x( 'Reorder', 'Reorder sections in Customizer' ),
     2320                                        'reorderDone'            => _x( 'Done', 'Cancel reordering sections in Customizer' ),
     2321                                        'searchPosts'            => __( 'Search Sections' ),
     2322                                        'searchPostsPlaceholder' => __( 'Search sections&hellip;' ),
     2323                                        'togglePost'             => __( 'Toggle Section' ),
     2324                                ),
     2325                        ) ) );
     2326                }
    22982327        }
    22992328
    23002329        /**
     
    23742403        public function _render_custom_logo_partial() {
    23752404                return get_custom_logo();
    23762405        }
     2406
     2407        /**
     2408         * Ajax handler for finding posts.
     2409         *
     2410         * @since 4.7.0
     2411         *
     2412         * @see wp_ajax_find_posts()
     2413         */
     2414        public function ajax_find_posts() {
     2415                check_ajax_referer( 'find-posts' );
     2416
     2417                $post_types = array();
     2418
     2419                if ( ! empty( $_POST['post_types'] ) ) {
     2420                        $post_type_names = array_map( 'sanitize_text_field', wp_unslash( $_POST['post_types'] ) );
     2421                        foreach ( $post_type_names as $post_type ) {
     2422                                $post_types[ $post_type ] = get_post_type_object( $post_type );
     2423                        }
     2424                }
     2425
     2426                if ( empty( $post_types ) ) {
     2427                        $post_types['post'] = get_post_type_object( 'post' );
     2428                }
     2429
     2430                $args = array(
     2431                        'post_type'      => array_keys( $post_types ),
     2432                        'post_status'    => 'publish',
     2433                        'post__not_in'   => isset( $_POST['not_in'] ) ? wp_parse_id_list( $_POST['not_in'] ) : array(),
     2434                        'posts_per_page' => 50,
     2435                );
     2436
     2437                if ( ! empty( $_POST['s'] ) ) {
     2438                        $args['s'] = sanitize_text_field( wp_unslash( $_POST['s'] ) );
     2439                }
     2440
     2441                $posts = get_posts( $args );
     2442
     2443                if ( ! $posts ) {
     2444                        wp_send_json_error( __( 'No results found.' ) );
     2445                }
     2446
     2447                foreach ( $posts as $post ) {
     2448                        $data[] = array(
     2449                                'id'    => $post->ID,
     2450                                'title' => $post->post_title,
     2451                                'type'  => get_post_type_object( $post->post_type )->labels->singular_name,
     2452                        );
     2453                }
     2454
     2455                wp_send_json_success( $data );
     2456        }
     2457
     2458        /**
     2459         * Sanitization callback for lists of IDs.
     2460         *
     2461         * @since 4.7.0
     2462         *
     2463         * @param string $value Setting value.
     2464         * @return string Comma-separated list of IDs.
     2465         */
     2466        public function sanitize_id_list( $value ) {
     2467                return implode( ',', array_unique( array_filter( wp_parse_id_list( $value ) ) ) );
     2468        }
    23772469}
  • src/wp-includes/customize/class-wp-customize-post-collection-control.php

     
     1<?php
     2/**
     3 * Customize API: WP_Customize_Post_Collection_Control class
     4 *
     5 * @package WordPress
     6 * @subpackage Customize
     7 * @since 4.7.0
     8 */
     9
     10/**
     11 * Customize Post Collection Control class.
     12 *
     13 * @since 4.7.0
     14 *
     15 * @see WP_Customize_Control
     16 */
     17class WP_Customize_Post_Collection_Control extends WP_Customize_Control {
     18        /**
     19         * Control type.
     20         *
     21         * @since 4.7.0
     22         * @var string
     23         */
     24        public $type = 'post_collection';
     25
     26        /**
     27         * Post types that can be added as sections..
     28         *
     29         * @since 4.7.0
     30         * @var array
     31         */
     32        public $post_types = array( 'page', 'post' );
     33
     34        /**
     35         * Localization strings.
     36         *
     37         * @since 4.7.0
     38         * @access public
     39         * @var array
     40         */
     41        public $l10n = array();
     42
     43        /**
     44         * Constructor.
     45         *
     46         * @since 4.7.0
     47         *
     48         * @param WP_Customize_Manager $manager Customizer bootstrap instance.
     49         * @param string               $id      Control ID.
     50         * @param array                $args    Optional. Arguments to override class property defaults.
     51         */
     52        public function __construct( $manager, $id, $args = array() ) {
     53                parent::__construct( $manager, $id, $args );
     54
     55                $this->l10n = wp_parse_args( $this->l10n, array(
     56                        'addPost'                => __( 'Add Post' ),
     57                        'addPosts'               => __( 'Add Posts' ),
     58                        'clearResults'           => __( 'Clear Results' ),
     59                        'moveUp'                 => __( 'Move up' ),
     60                        'moveDown'               => __( 'Move down' ),
     61                        'movedUp'                => __( 'Post moved up' ),
     62                        'movedDown'              => __( 'Post moved down' ),
     63                        'remove'                 => __( 'Remove' ),
     64                        'removePost'             => __( 'Remove Post' ),
     65                        'reorder'                => _x( 'Reorder', 'Reorder posts in Customizer' ),
     66                        'reorderDone'            => _x( 'Done', 'Cancel reordering posts in Customizer' ),
     67                        'searchPosts'            => __( 'Search Posts' ),
     68                        'searchPostsPlaceholder' => __( 'Search posts&hellip;' ),
     69                        'togglePost'             => __( 'Toggle Post' ),
     70                ) );
     71        }
     72
     73        /**
     74         * Enqueue control related scripts/styles.
     75         *
     76         * @since 4.7.0
     77         */
     78        public function enqueue() {
     79                wp_enqueue_style( 'customize-post-collection' );
     80                wp_enqueue_script( 'customize-post-collection' );
     81
     82                add_action( 'customize_controls_print_footer_scripts', array( 'WP_Customize_Post_Collection_Control', 'print_templates' ) );
     83        }
     84
     85        /**
     86         * Refresh the parameters passed to the JavaScript via JSON.
     87         *
     88         * @since 4.7.0
     89         * @uses WP_Customize_Control::to_json()
     90         */
     91        public function to_json() {
     92                parent::to_json();
     93
     94                $this->json['l10n']        = $this->l10n;
     95                $this->json['posts']       = $this->get_posts();
     96                $this->json['postTypes']   = $this->post_types;
     97                $this->json['searchNonce'] = wp_create_nonce( 'find-posts' );
     98        }
     99
     100        /**
     101         * Don't render any content for this control from PHP.
     102         *
     103         * @since 4.7.0
     104         *
     105         * @see WP_Customize_Post_Collection_Control::content_template()
     106         */
     107        public function render_content() {}
     108
     109        /**
     110         * An Underscore (JS) template for this control's content (but not its container).
     111         *
     112         * @see WP_Customize_Control::print_template()
     113         *
     114         * @since 4.7.0
     115         */
     116        protected function content_template() {
     117                ?>
     118                <label>
     119                        <# if ( data.label ) { #>
     120                                <span class="customize-control-title">{{ data.label }}</span>
     121                        <# } #>
     122                        <# if ( data.description ) { #>
     123                                <span class="description customize-control-description">{{{ data.description }}}</span>
     124                        <# } #>
     125                </label>
     126                <?php
     127        }
     128
     129        /**
     130         * Print JavaScript templates in the Customizer footer.
     131         *
     132         * @since 4.7.0
     133         */
     134        public static function print_templates() {
     135                ?>
     136                <script type="text/html" id="tmpl-wp-item">
     137                        <div class="wp-item-header">
     138                                <h4 class="wp-item-title">
     139                                        <span class="text">{{{ data.title }}}</span>
     140                                </h4>
     141
     142                                <button type="button" class="wp-item-toggle customize-dashicon button-link js-toggle">
     143                                        <span class="screen-reader-text">{{ data.l10n.togglePost }}</span>
     144                                </button>
     145
     146                                <button type="button" class="wp-item-delete customize-button-delete customize-dashicon button-link js-remove">
     147                                        <span class="screen-reader-text">{{ data.l10n.removePost }}</span>
     148                                </button>
     149
     150                                <div class="wp-reorder-nav">
     151                                        <button class="move-item-down" tabindex="0">{{ data.l10n.moveDown }}</button>
     152                                        <button class="move-item-up" tabindex="0">{{ data.l10n.moveUp }}</button>
     153                                </div>
     154                        </div>
     155
     156                        <div class="wp-item-body">
     157                                <div class="wp-item-actions">
     158                                        <a class="wp-item-delete customize-button-delete js-remove">{{ data.l10n.remove }}</a>
     159                                </div>
     160                        </div>
     161                </script>
     162
     163                <script type="text/html" id="tmpl-search-group">
     164                        <label class="screen-reader-text" for="search-group-field">{{ data.l10n.searchPosts }}</label>
     165                        <input type="text" id="search-group-field" placeholder="{{{ data.l10n.searchPostsPlaceholder }}}" class="search-group-field">
     166                </script>
     167
     168                <script type="text/html" id="tmpl-search-result">
     169                        <span class="search-results-item-type">{{ data.type }}</span>
     170                        <span class="search-results-item-title">{{ data.title }}</span>
     171
     172                        <button type="button" class="search-results-item-add customize-dashicon button-link">
     173                                <span class="screen-reader-text">{{ data.l10n.addPost }}</span>
     174                        </button>
     175                </script>
     176                <?php
     177        }
     178
     179        /**
     180         * Retrieve posts.
     181         *
     182         * @since 4.7.0
     183         *
     184         * @return array
     185         */
     186        protected function get_posts() {
     187                $data  = array();
     188                $value = $this->value();
     189
     190                if ( ! empty( $value ) ) {
     191                        $posts = get_posts( array(
     192                                'post_type'      => $this->post_types,
     193                                'post_status'    => 'any',
     194                                'post__in'       => array_map( 'absint', explode( ',', $value ) ),
     195                                'orderby'        => 'post__in',
     196                                'posts_per_page' => 20,
     197                        ) );
     198                }
     199
     200                if ( ! empty( $posts ) ) {
     201                        $i = 0;
     202                        foreach ( $posts as $post ) {
     203                                $data[] = array(
     204                                        'id'    => $post->ID,
     205                                        'title' => $post->post_title,
     206                                        'order' => ++$i,
     207                                );
     208                        }
     209                }
     210
     211                return $data;
     212        }
     213}
  • src/wp-includes/script-loader.php

     
    476476        $scripts->add( 'customize-nav-menus', "/wp-admin/js/customize-nav-menus$suffix.js", array( 'jquery', 'wp-backbone', 'customize-controls', 'accordion', 'nav-menu' ), false, 1 );
    477477        $scripts->add( 'customize-preview-nav-menus', "/wp-includes/js/customize-preview-nav-menus$suffix.js", array( 'jquery', 'wp-util', 'customize-preview', 'customize-selective-refresh' ), false, 1 );
    478478
     479        $scripts->add( 'customize-post-collection', "/wp-admin/js/customize-post-collection$suffix.js", array( 'jquery', 'jquery-ui-sortable', 'jquery-ui-droppable', 'wp-backbone', 'customize-controls' ), false, 1 );
     480
    479481        $scripts->add( 'accordion', "/wp-admin/js/accordion$suffix.js", array( 'jquery' ), false, 1 );
    480482
    481483        $scripts->add( 'shortcode', "/wp-includes/js/shortcode$suffix.js", array( 'underscore' ), false, 1 );
     
    819821
    820822        $styles->add( 'wp-admin', false, array( 'dashicons', 'common', 'forms', 'admin-menu', 'dashboard', 'list-tables', 'edit', 'revisions', 'media', 'themes', 'about', 'nav-menus', 'widgets', 'site-icon', 'l10n' ) );
    821823
    822         $styles->add( 'login',               "/wp-admin/css/login$suffix.css", array( 'dashicons', 'buttons', 'forms', 'l10n' ) );
    823         $styles->add( 'install',             "/wp-admin/css/install$suffix.css", array( 'buttons' ) );
    824         $styles->add( 'wp-color-picker',     "/wp-admin/css/color-picker$suffix.css" );
    825         $styles->add( 'customize-controls',  "/wp-admin/css/customize-controls$suffix.css", array( 'wp-admin', 'colors', 'ie', 'imgareaselect' ) );
    826         $styles->add( 'customize-widgets',   "/wp-admin/css/customize-widgets$suffix.css", array( 'wp-admin', 'colors' ) );
    827         $styles->add( 'customize-nav-menus', "/wp-admin/css/customize-nav-menus$suffix.css", array( 'wp-admin', 'colors' ) );
    828         $styles->add( 'press-this',          "/wp-admin/css/press-this$suffix.css", array( 'buttons' ) );
     824        $styles->add( 'login',                     "/wp-admin/css/login$suffix.css", array( 'dashicons', 'buttons', 'forms', 'l10n' ) );
     825        $styles->add( 'install',                   "/wp-admin/css/install$suffix.css", array( 'buttons' ) );
     826        $styles->add( 'wp-color-picker',           "/wp-admin/css/color-picker$suffix.css" );
     827        $styles->add( 'customize-controls',        "/wp-admin/css/customize-controls$suffix.css", array( 'wp-admin', 'colors', 'ie', 'imgareaselect' ) );
     828        $styles->add( 'customize-widgets',         "/wp-admin/css/customize-widgets$suffix.css", array( 'wp-admin', 'colors' ) );
     829        $styles->add( 'customize-nav-menus',       "/wp-admin/css/customize-nav-menus$suffix.css", array( 'wp-admin', 'colors' ) );
     830        $styles->add( 'customize-post-collection', "/wp-admin/css/customize-post-collection$suffix.css", array( 'wp-admin', 'colors', 'customize-controls' ) );
     831        $styles->add( 'press-this',                "/wp-admin/css/press-this$suffix.css", array( 'buttons' ) );
    829832
    830833        $styles->add( 'ie', "/wp-admin/css/ie$suffix.css" );
    831834        $styles->add_data( 'ie', 'conditional', 'lte IE 7' );
  • src/wp-includes/theme.php

     
    17301730
    17311731                                return false;
    17321732                        }
     1733
     1734                case 'front-page-sections' :
     1735                        if ( ! is_array( $args ) ) {
     1736                                $args = array( 0 => array() );
     1737                        }
     1738
     1739                        $args[0] = wp_parse_args( $args[0], array(
     1740                                'post_types' => array( 'page' ),
     1741                        ) );
     1742
     1743                        break;
    17331744        }
    17341745
    17351746        $_wp_theme_features[ $feature ] = $args;
     
    18221833                case 'custom-logo' :
    18231834                case 'custom-header' :
    18241835                case 'custom-background' :
     1836                case 'front-page-sections' :
    18251837                        if ( isset( $_wp_theme_features[ $feature ][0][ $args[0] ] ) )
    18261838                                return $_wp_theme_features[ $feature ][0][ $args[0] ];
    18271839                        return false;
     
    20822094                return;
    20832095        }
    20842096
    2085         require_once ABSPATH . WPINC . '/class-wp-customize-manager.php'; 
     2097        require_once ABSPATH . WPINC . '/class-wp-customize-manager.php';
    20862098        $GLOBALS['wp_customize'] = new WP_Customize_Manager();
    20872099}
    20882100