WordPress.org

Make WordPress Core

Ticket #37974: 37974.3.diff

File 37974.3.diff, 38.7 KB (added by bradyvercher, 3 years ago)
  • src/wp-admin/css/customize-controls.css

     
    12191219        outline: none;
    12201220}
    12211221
     1222.reordering .add-new-item,
    12221223.reordering .add-new-widget,
    12231224.reordering .add-new-menu-item {
    12241225        opacity: 0.2;
     
    12821283        color: #00a0d2;
    12831284}
    12841285
     1286.wp-reorder-nav {
     1287        display: none;
     1288        background-color: #fff;
     1289        position: absolute;
     1290        top: 0;
     1291        right: 0;
     1292}
     1293
     1294.reordering .wp-reorder-nav,
     1295.wp-reorder-nav.is-active {
     1296        display: block;
     1297}
     1298
     1299.wp-reorder-nav button,
    12851300.widget-reorder-nav span,
    12861301.menu-item-reorder-nav button {
    12871302        position: relative;
     
    12961311        outline: none;
    12971312}
    12981313
     1314.wp-reorder-nav button,
    12991315.menu-item-reorder-nav button {
    13001316        width: 30px;
    13011317        height: 40px;
     
    13051321        box-shadow: none;
    13061322}
    13071323
     1324.wp-reorder-nav button:before,
    13081325.widget-reorder-nav span:before,
    13091326.menu-item-reorder-nav button:before {
    13101327        display: inline-block;
     
    13201337        -moz-osx-font-smoothing: grayscale;
    13211338}
    13221339
     1340.wp-reorder-nav button:hover,
     1341.wp-reorder-nav button:focus,
    13231342.widget-reorder-nav span:hover,
    13241343.widget-reorder-nav span:focus,
    13251344.menu-item-reorder-nav button:hover,
     
    13281347        background: #eee;
    13291348}
    13301349
     1350.wp-reorder-nav button {
     1351        width: 33px;
     1352        height: 38px;
     1353}
     1354
     1355.wp-reorder-nav button:before {
     1356        font: normal 20px/38px dashicons;
     1357}
     1358
     1359.move-item-down:before,
    13311360.move-widget-down:before,
    13321361.menus-move-down:before {
    13331362        content: "\f347";
    13341363}
    13351364
     1365.move-item-up:before,
    13361366.move-widget-up:before,
    13371367.menus-move-up:before {
    13381368        content: "\f343";
     
    13431373.move-up-disabled .menus-move-up,
    13441374.move-down-disabled .menus-move-down,
    13451375.move-right-disabled .menus-move-right,
    1346 .move-left-disabled .menus-move-left {
     1376.move-left-disabled .menus-move-left,
     1377.wp-item:first-child .move-item-up,
     1378.wp-item:last-child .move-item-down {
    13471379        color: #d5d5d5;
    13481380        background-color: #fff;
    13491381        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-drawer-open .drawer-toggle:before {
     42        transform: rotate( 45deg );
     43}
     44
     45#customize-control-front_page_sections {
     46        border-top: 1px solid #ddd;
     47        margin-top: 8px;
     48        padding-top: 16px;
     49}
     50
     51
     52/* Drawer
     53------------------------------------------------------------------------------*/
     54
     55.customize-drawer {
     56        background: #eee;
     57        border-right: 1px solid #ddd;
     58        display: block;
     59        margin: 0;
     60        overflow-x: hidden;
     61        overflow-y: auto;
     62        position: absolute;
     63        top: 0;
     64        right: 0;
     65        bottom: 0;
     66        left: -301px;
     67        transition: left 0.18s;
     68        visibility: hidden;
     69        width: 300px;
     70        z-index: 4;
     71}
     72
     73.customize-drawer-notice {
     74        padding: 15px;
     75}
     76
     77.customize-drawer.is-open {
     78        left: 0;
     79        visibility: visible;
     80}
     81
     82.drawer-is-open .wp-full-overlay-main {
     83        left: 300px;
     84}
     85
     86
     87/* Sortable Item List
     88------------------------------------------------------------------------------*/
     89
     90.wp-items-list {
     91        list-style: none;
     92        margin: 0 0 10px 0;
     93        padding: 0;
     94        position: relative;
     95}
     96
     97.wp-item {
     98        background: #fff;
     99        margin: -1px 0 0 0;
     100        padding: 0;
     101}
     102
     103.wp-item-header {
     104        border: 1px solid #dfdfdf;
     105        background: #fff;
     106        position: relative;
     107}
     108
     109.wp-item-delete {
     110        display: none;
     111        height: 100%;
     112        position: absolute;
     113        top: 0;
     114        right: 0;
     115        bottom: 0;
     116        text-align: center;
     117        vertical-align: middle;
     118        width: 40px;
     119}
     120
     121.wp-item-title {
     122        cursor: move;
     123        margin: 0;
     124        padding: 10px 20px;
     125        position: relative;
     126        word-wrap: break-word;
     127}
     128
     129.wp-item.ui-sortable-helper {
     130        background: #f9f9f9;
     131        border: 1px solid #dfdfdf;
     132}
     133
     134.wp-item.ui-sortable-placeholder {
     135        background: transparent;
     136        border: 1px dashed #a0a5aa;
     137        margin-top: 0;
     138        margin-bottom: 1px;
     139}
     140
     141.wp-item:hover .wp-item-header {
     142        border-color: #999;
     143        z-index: 1;
     144}
     145
     146.customize-control.is-drawer-open .wp-item-delete {
     147        display: block;
     148}
     149
     150.customize-control.is-drawer-open .wp-reorder-nav {
     151        display: none;
     152}
     153
     154
     155/* Search
     156------------------------------------------------------------------------------*/
     157
     158.search-group {
     159        border-bottom: 1px solid #ddd;
     160        margin: 0;
     161        padding: 12px 15px;
     162        position: relative;
     163}
     164
     165.search-group-field {
     166        padding: 6px 10px;
     167        width: 100%;
     168}
     169
     170.search-group-spinner {
     171        margin: 0;
     172        position: absolute;
     173        top: 19px;
     174        right: 20px;
     175}
     176
     177.search-group-button-clear {
     178        background: transparent;
     179        border-width: 0;
     180        cursor: pointer;
     181        display: none;
     182        height: 20px;
     183        padding: 0;
     184        position: absolute;
     185        top: 19px;
     186        right: 20px;
     187        text-align: center;
     188        width: 20px;
     189}
     190
     191.search-group-button-clear.is-active {
     192        display: block;
     193}
     194
     195.search-group.is-searching .search-group-button-clear {
     196        display: none;
     197}
     198
     199
     200/* Search Results
     201------------------------------------------------------------------------------*/
     202
     203.search-results {
     204        padding: 1px 0 15px;
     205}
     206
     207.search-results ul {
     208        margin: -2px 0 0;
     209}
     210
     211.search-results-item {
     212        background: #fff;
     213        border-color: #ddd;
     214        border-style: solid;
     215        border-width: 1px 0;
     216        clear: both;
     217        cursor: pointer;
     218        line-height: 10px;
     219        margin: -1px 0 0 0;
     220        padding: 10px 15px;
     221        position: relative;
     222}
     223
     224.search-results-item-title {
     225        display: block;
     226        font-size: 13px;
     227        font-weight: 600;
     228        line-height: 20px;
     229        padding-left: 20px;
     230        word-wrap: break-word;
     231}
     232
     233.search-results-item-type {
     234        color: #666;
     235        float: right;
     236        font-size: 12px;
     237        line-height: 20px;
     238        text-align: right;
     239}
     240
     241.search-results-item-add {
     242        color: #82878c;
     243        height: 38px;
     244        position: absolute;
     245        top: 1px;
     246        left: 1px;
     247        width: 30px;
     248}
     249
     250.search-results-item-add:before {
     251        -webkit-border-radius: 50%;
     252        border-radius: 50%;
     253        content: "\f543";
     254        height: 20px;
     255        position: relative;
     256        top: 0;
     257        left: 2px;
     258}
     259
     260.search-results-item:hover {
     261        border-color: #999;
     262        color: #0073aa;
     263        z-index: 1;
     264}
     265
     266.search-results-item:hover .search-results-item-add:before {
     267        color: #0073aa;
     268}
     269
     270.search-results-item.is-selected .search-results-item-add:before {
     271        content: "\f147";
     272}
  • 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                open: function() {
     19                        this.set( 'status', 'open' );
     20                },
     21
     22                toggle: function() {
     23                        if ( 'open' === this.get( 'status' ) ) {
     24                                this.close();
     25                        } else {
     26                                this.open();
     27                        }
     28                }
     29        });
     30
     31        api.DrawerManager = Backbone.Collection.extend({
     32                model: api.DrawerModel,
     33
     34                initialize: function() {
     35                        this.on( 'change:status', this.closeOtherDrawers );
     36                },
     37
     38                closeOtherDrawers: function( model ) {
     39                        if ( 'open' === model.get( 'status' ) ) {
     40                                _.chain( this.models ).without( model ).invoke( 'close' );
     41                        }
     42                }
     43        });
     44
     45        api.DrawerView = wp.Backbone.View.extend({
     46                tagName: 'div',
     47                className: 'customize-drawer',
     48
     49                initialize: function( options ) {
     50                        this.controller = options.controller;
     51                        this.listenTo( this.controller, 'change:status', this.updateStatusClass );
     52                },
     53
     54                updateStatusClass: function() {
     55                        if ( 'open' === this.controller.get( 'status' ) ) {
     56                                this.$el.addClass( 'is-open' );
     57                        } else {
     58                                this.$el.removeClass( 'is-open' );
     59                        }
     60                }
     61        });
     62
     63        api.PostCollection.PostModel = Backbone.Model.extend({
     64                defaults: {
     65                        title: '',
     66                        order: 0
     67                }
     68        });
     69
     70        api.PostCollection.PostsCollection = Backbone.Collection.extend({
     71                model: api.PostCollection.PostModel,
     72
     73                comparator: function( post ) {
     74                        return parseInt( post.get( 'order' ), 10 );
     75                }
     76        });
     77
     78        api.PostCollection.ControlView = wp.Backbone.View.extend({
     79                initialize: function( options ) {
     80                        this.control = options.control;
     81                        this.setting = options.setting;
     82
     83                        this.listenTo( this.collection, 'add remove reset sort', this.updateSetting );
     84                        this.listenTo( this.control.drawer, 'change:status', this.maybeTriggerSearch );
     85                        this.listenTo( this.control.drawer, 'change:status', this.updateStatusClass );
     86                },
     87
     88                render: function() {
     89                        this.views.add([
     90                                new api.PostCollection.ItemListView({
     91                                        collection: this.collection,
     92                                        control: this.control,
     93                                        parent: this
     94                                }),
     95                                new api.PostCollection.AddNewItemButtonView({
     96                                        control: this.control
     97                                })
     98                        ]);
     99
     100                        return this;
     101                },
     102
     103                maybeTriggerSearch: function() {
     104                        if ( 'open' === this.control.drawer.get( 'status' ) && this.control.results.length < 1 ) {
     105                                this.control.search();
     106                        }
     107                },
     108
     109                updateSetting: function() {
     110                        var postIds = this.collection.sort({ silent: true }).pluck( 'id' ).join( ',' );
     111                        this.setting.set( postIds );
     112                },
     113
     114                updateStatusClass: function() {
     115                        if ( 'open' === this.control.drawer.get( 'status' ) ) {
     116                                this.$el.addClass( 'is-drawer-open' );
     117                        } else {
     118                                this.$el.removeClass( 'is-drawer-open' );
     119                        }
     120                }
     121        });
     122
     123        api.PostCollection.AddNewItemButtonView = wp.Backbone.View.extend({
     124                className: 'drawer-toggle add-new-item button button-secondary alignright',
     125                tagName: 'button',
     126
     127                events: {
     128                        click: 'toggleDrawer'
     129                },
     130
     131                initialize: function( options ) {
     132                        this.control = options.control;
     133                },
     134
     135                render: function() {
     136                        this.$el.text( this.control.l10n.addPosts );
     137                        return this;
     138                },
     139
     140                toggleDrawer: function( e ) {
     141                        e.preventDefault();
     142                        this.control.drawer.toggle();
     143                }
     144        });
     145
     146        api.PostCollection.ItemListView = wp.Backbone.View.extend({
     147                className: 'wp-items-list',
     148                tagName: 'ol',
     149
     150                initialize: function( options ) {
     151                        this.control = options.control;
     152
     153                        this.listenTo( this.collection, 'add', this.addItem );
     154                        this.listenTo( this.collection, 'add remove', this.updateOrder );
     155                        this.listenTo( this.collection, 'reset', this.render );
     156                },
     157
     158                render: function() {
     159                        this.$el.empty();
     160                        this.collection.each( this.addItem, this );
     161                        this.initializeSortable();
     162                        return this;
     163                },
     164
     165                initializeSortable: function() {
     166                        this.$el.sortable({
     167                                axis: 'y',
     168                                delay: 150,
     169                                forceHelperSize: true,
     170                                forcePlaceholderSize: true,
     171                                opacity: 0.6,
     172                                start: function( e, ui ) {
     173                                        ui.placeholder.css( 'visibility', 'visible' );
     174                                },
     175                                update: _.bind(function() {
     176                                        this.updateOrder();
     177                                }, this )
     178                        });
     179                },
     180
     181                addItem: function( item ) {
     182                        var itemView = new api.PostCollection.ItemView({
     183                                control: this.control,
     184                                model: item,
     185                                parent: this
     186                        });
     187
     188                        this.$el.append( itemView.render().el );
     189                },
     190
     191                moveDown: function( model ) {
     192                        var index = this.collection.indexOf( model ),
     193                                $items = this.$el.children();
     194
     195                        if ( index < this.collection.length - 1 ) {
     196                                $items.eq( index ).insertAfter( $items.eq( index + 1 ) );
     197                                this.updateOrder();
     198                                wp.a11y.speak( this.control.l10n.movedDown );
     199                        }
     200                },
     201
     202                moveUp: function( model ) {
     203                        var index = this.collection.indexOf( model ),
     204                                $items = this.$el.children();
     205
     206                        if ( index > 0 ) {
     207                                $items.eq( index ).insertBefore( $items.eq( index - 1 ) );
     208                                this.updateOrder();
     209                                wp.a11y.speak( this.control.l10n.movedUp );
     210                        }
     211                },
     212
     213                updateOrder: function() {
     214                        _.each( this.$el.find( 'li' ), function( item, i ) {
     215                                var id = $( item ).data( 'post-id' );
     216                                this.collection.get( id ).set( 'order', i );
     217                        }, this );
     218
     219                        this.collection.sort();
     220                }
     221        });
     222
     223        api.PostCollection.ItemView = wp.Backbone.View.extend({
     224                tagName: 'li',
     225                className: 'wp-item',
     226                template: wp.template( 'wp-item' ),
     227
     228                events: {
     229                        'click .js-remove': 'destroy',
     230                        'click .move-item-up': 'moveUp',
     231                        'click .move-item-down': 'moveDown'
     232                },
     233
     234                initialize: function( options ) {
     235                        this.control = options.control;
     236                        this.parent = options.parent;
     237                        this.listenTo( this.model, 'destroy', this.remove );
     238                },
     239
     240                render: function() {
     241                        var data = _.extend( this.model.toJSON(), {
     242                                l10n: this.control.l10n
     243                        });
     244
     245                        this.$el.html( this.template( data ) );
     246                        this.$el.data( 'post-id', this.model.get( 'id' ) );
     247
     248                        return this;
     249                },
     250
     251                moveDown: function( e ) {
     252                        e.preventDefault();
     253                        this.parent.moveDown( this.model );
     254                },
     255
     256                moveUp: function( e ) {
     257                        e.preventDefault();
     258                        this.parent.moveUp( this.model );
     259                },
     260
     261                /**
     262                 * Destroy the view's model.
     263                 *
     264                 * Avoid syncing to the server by triggering an event instead of
     265                 * calling destroy() directly on the model.
     266                 */
     267                destroy: function() {
     268                        this.model.trigger( 'destroy', this.model );
     269                },
     270
     271                remove: function() {
     272                        this.$el.remove();
     273                }
     274        });
     275
     276        api.PostCollection.DrawerNoticeView = wp.Backbone.View.extend({
     277                tagName: 'div',
     278                className: 'customize-drawer-notice',
     279
     280                initialize: function( options ) {
     281                        this.control = options.control;
     282                        this.listenTo( this.control.state, 'change:notice', this.render );
     283                },
     284
     285                render: function() {
     286                        var notice = this.control.state.get( 'notice' );
     287                        this.$el.toggle( !! notice.length ).text( notice );
     288                        return this;
     289                }
     290        });
     291
     292        api.PostCollection.SearchGroupView = wp.Backbone.View.extend({
     293                tagName: 'div',
     294                className: 'search-group',
     295                template: wp.template( 'search-group' ),
     296
     297                events: {
     298                        'input input': 'search'
     299                },
     300
     301                initialize: function( options ) {
     302                        this.control = options.control;
     303                },
     304
     305                render: function() {
     306                        this.$el.html( this.template({ l10n: this.control.l10n }) );
     307                        this.$field = this.$el.find( '.search-group-field' );
     308                        this.$spinner = this.$el.append( '<span class="search-group-spinner spinner" />' ).find( '.spinner' );
     309
     310                        this.views.add([
     311                                new api.PostCollection.ClearResultsButtonView({
     312                                        collection: this.collection,
     313                                        control: this.control,
     314                                        parent: this
     315                                })
     316                        ]);
     317
     318                        return this;
     319                },
     320
     321                search: function() {
     322                        var view = this;
     323
     324                        this.$el.addClass( 'is-searching' );
     325                        this.$spinner.addClass( 'is-active' );
     326
     327                        clearTimeout( this.timeout );
     328                        this.timeout = setTimeout(function() {
     329                                view.control.search( view.$field.val() )
     330                                        .always(function() {
     331                                                view.$el.removeClass( 'is-searching' );
     332                                                view.$spinner.removeClass( 'is-active' );
     333                                        });
     334                        }, 300 );
     335                }
     336        });
     337
     338        api.PostCollection.ClearResultsButtonView = wp.Backbone.View.extend({
     339                tagName: 'button',
     340                className: 'search-group-button-clear customize-button-delete customize-dashicon',
     341
     342                events: {
     343                        'click': 'clearResults'
     344                },
     345
     346                initialize: function( options ) {
     347                        this.control = options.control;
     348                        this.parent = options.parent;
     349                        this.listenTo( this.collection, 'add remove reset', this.toggleVisibility );
     350                },
     351
     352                render: function() {
     353                        this.$el.html( $( '<span />', {
     354                                'class': 'screen-reader-text',
     355                                text: this.control.l10n.clearResults
     356                        }) );
     357                        this.toggleVisibility();
     358                        return this;
     359                },
     360
     361                clearResults: function() {
     362                        this.collection.reset();
     363                        this.parent.$field.val( '' );
     364                },
     365
     366                toggleVisibility: function() {
     367                        this.$el.toggleClass( 'is-active', !! this.collection.length && '' !== this.parent.$field.val() );
     368                }
     369        });
     370
     371        api.PostCollection.SearchResultsView = wp.Backbone.View.extend({
     372                tagName: 'div',
     373                className: 'search-results',
     374
     375                initialize: function( options ) {
     376                        this.control = options.control;
     377                        this.listenTo( this.collection, 'reset', this.render );
     378                },
     379
     380                render: function() {
     381                        this.$list = this.$el.html( '<ul />' ).find( 'ul' );
     382
     383                        if ( this.collection.length ) {
     384                                this.collection.each( this.addItem, this );
     385                        } else {
     386                                this.$el.empty();
     387                        }
     388
     389                        return this;
     390                },
     391
     392                addItem: function( model ) {
     393                        this.views.add( 'ul', new api.PostCollection.SearchResultView({
     394                                control: this.control,
     395                                model: model
     396                        }));
     397                }
     398        });
     399
     400        api.PostCollection.SearchResultView = wp.Backbone.View.extend({
     401                tagName: 'li',
     402                className: 'search-results-item',
     403                template: wp.template( 'search-result' ),
     404
     405                events: {
     406                        'click': 'addSection'
     407                },
     408
     409                initialize: function( options ) {
     410                        this.control = options.control;
     411                        this.listenTo( this.control.posts, 'add remove reset', this.updateSelectedClass );
     412                },
     413
     414                render: function() {
     415                        var data = _.extend( this.model.toJSON(), {
     416                                l10n: this.control.l10n
     417                        });
     418
     419                        this.$el.html( this.template( data ) );
     420                        this.updateSelectedClass();
     421
     422                        return this;
     423                },
     424
     425                addSection: function() {
     426                        this.control.posts.add( this.model );
     427                },
     428
     429                updateSelectedClass: function() {
     430                        this.$el.toggleClass( 'is-selected', this.control.posts.contains( this.model ) );
     431                }
     432        });
     433
     434        api.PostCollection.PostCollectionControl = api.Control.extend({
     435                ready: function() {
     436                        var controlView, drawerView,
     437                                control = this,
     438                                section = api.section( this.section() );
     439
     440                        this.drawer = new api.DrawerModel();
     441                        api.drawerManager.add( this.drawer );
     442
     443                        this.posts = new api.PostCollection.PostsCollection( this.params.posts );
     444                        this.results = new api.PostCollection.PostsCollection();
     445                        delete this.params.posts;
     446
     447                        this.l10n = this.params.l10n;
     448                        delete this.params.l10n;
     449
     450                        this.state = new Backbone.Model({
     451                                notice: ''
     452                        });
     453
     454                        controlView = new api.PostCollection.ControlView({
     455                                el: this.container,
     456                                collection: this.posts,
     457                                control: this,
     458                                data: this.params,
     459                                setting: this.setting
     460                        });
     461
     462                        controlView.render();
     463
     464                        drawerView = new api.DrawerView({
     465                                controller: this.drawer
     466                        });
     467
     468                        drawerView.views.set([
     469                                new api.PostCollection.SearchGroupView({
     470                                        collection: this.results,
     471                                        control: this
     472                                }),
     473                                new api.PostCollection.DrawerNoticeView({
     474                                        control: this
     475                                }),
     476                                new api.PostCollection.SearchResultsView({
     477                                        collection: this.results,
     478                                        control: this
     479                                })
     480                        ]);
     481
     482                        $( '.wp-full-overlay' ).append( drawerView.render().$el );
     483
     484                        section.expanded.bind(function( isOpen ) {
     485                                if ( ! isOpen ) {
     486                                        control.drawer.close();
     487                                }
     488                        });
     489                },
     490
     491                search: function( query ) {
     492                        var control = this;
     493
     494                        return wp.ajax.post( 'customize_find_posts', {
     495                                s: query,
     496                                post_types: this.params.postTypes,
     497                                not_in: this.params.exclude,
     498                                wp_customize: 'on',
     499                                _ajax_nonce: this.params.searchNonce
     500                        }).done(function( response ) {
     501                                control.results.reset( response );
     502                                control.state.set( 'notice', '' );
     503                        }).fail(function( response ) {
     504                                control.results.reset();
     505                                control.state.set( 'notice', response );
     506                        });
     507                }
     508        });
     509
     510        /**
     511         * Extends wp.customize.controlConstructor with control constructor for
     512         * post_collection.
     513         */
     514        $.extend( api.controlConstructor, {
     515                post_collection: api.PostCollection.PostCollectionControl
     516        });
     517
     518        /**
     519         * Create a global drawer manager.
     520         */
     521        api.drawerManager = new api.DrawerManager();
     522
     523        /**
     524         * Toggle an HTML class on the body when drawers are opened or closed.
     525         */
     526        $( document ).ready(function() {
     527                var $body = $( document.body );
     528
     529                api.drawerManager.on( 'change:status', function() {
     530                        if ( api.drawerManager.findWhere({ status: 'open' }) ) {
     531                                $body.addClass( 'drawer-is-open' );
     532                        } else {
     533                                $body.removeClass( 'drawer-is-open' );
     534                        }
     535                });
     536        });
     537
     538        /**
     539         * Toggle the front_page_sections control based on the 'show_on_front'
     540         * setting value.
     541         */
     542        api.bind( 'ready', function() {
     543                api( 'show_on_front', function( setting ) {
     544                        api.control( 'front_page_sections', function( control ) {
     545                                var toggleVisibility = function( value ) {
     546                                        control.container.toggle( 'page' === value );
     547                                };
     548
     549                                toggleVisibility( setting() );
     550                                setting.bind( toggleVisibility );
     551                        });
     552                });
     553        });
     554
     555})( window.wp, jQuery );
  • src/wp-includes/canonical.php

     
    655655                exit;
    656656        }
    657657}
     658
     659/**
     660 * Redirect front page section permalinks to the fragment on the front page.
     661 *
     662 * @since 4.7.0
     663 */
     664function wp_redirect_front_page_sections() {
     665        $object_id = get_queried_object_id();
     666
     667        if (
     668                ! current_theme_supports( 'front-page-sections' )
     669                || is_front_page()
     670                || is_home()
     671                || ! is_singular()
     672                || ! is_front_page_section( $object_id )
     673        ) {
     674                return;
     675        }
     676
     677        wp_redirect( get_front_page_section_url( $object_id ) );
     678        exit;
     679}
  • 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
     
    19701972                $this->register_control_type( 'WP_Customize_Cropped_Image_Control' );
    19711973                $this->register_control_type( 'WP_Customize_Site_Icon_Control' );
    19721974                $this->register_control_type( 'WP_Customize_Theme_Control' );
     1975                $this->register_control_type( 'WP_Customize_Post_Collection_Control' );
    19731976
    19741977                /* Themes */
    19751978
     
    23022305                        'section' => 'static_front_page',
    23032306                        'type' => 'dropdown-pages',
    23042307                ) );
     2308
     2309                if ( current_theme_supports( 'front-page-sections' ) ) {
     2310                        $this->add_setting( 'front_page_sections', array(
     2311                                'sanitize_callback' => array( $this, 'sanitize_id_list' ),
     2312                        ) );
     2313
     2314                        $this->add_control( new WP_Customize_Post_Collection_Control( $this, 'front_page_sections', array(
     2315                                'label'       => __( 'Front page sections' ),
     2316                                'description' => '',
     2317                                'section'     => 'static_front_page',
     2318                                'settings'    => 'front_page_sections',
     2319                                'post_types'  => get_theme_support( 'front-page-sections', 'post_types' ),
     2320                                'exclude'     => array(
     2321                                        get_option( 'page_on_front' ),
     2322                                        get_option( 'page_for_posts' ),
     2323                                ),
     2324                                'l10n'        => array(
     2325                                        'addPost'                => __( 'Add Section' ),
     2326                                        'addPosts'               => __( 'Add Sections' ),
     2327                                        'movedUp'                => __( 'Section moved up' ),
     2328                                        'movedDown'              => __( 'Section moved down' ),
     2329                                        'removePost'             => __( 'Remove Section' ),
     2330                                        'searchPosts'            => __( 'Search Sections' ),
     2331                                        'searchPostsPlaceholder' => __( 'Search sections&hellip;' ),
     2332                                ),
     2333                        ) ) );
     2334                }
    23052335        }
    23062336
    23072337        /**
     
    23812411        public function _render_custom_logo_partial() {
    23822412                return get_custom_logo();
    23832413        }
     2414
     2415        /**
     2416         * Ajax handler for finding posts.
     2417         *
     2418         * @since 4.7.0
     2419         *
     2420         * @see wp_ajax_find_posts()
     2421         */
     2422        public function ajax_find_posts() {
     2423                check_ajax_referer( 'find-posts' );
     2424
     2425                $post_types = array();
     2426
     2427                if ( ! empty( $_POST['post_types'] ) ) {
     2428                        $post_type_names = array_map( 'sanitize_text_field', wp_unslash( $_POST['post_types'] ) );
     2429                        foreach ( $post_type_names as $post_type ) {
     2430                                $post_types[ $post_type ] = get_post_type_object( $post_type );
     2431                        }
     2432                }
     2433
     2434                if ( empty( $post_types ) ) {
     2435                        $post_types['post'] = get_post_type_object( 'post' );
     2436                }
     2437
     2438                $args = array(
     2439                        'post_type'      => array_keys( $post_types ),
     2440                        'post_status'    => 'publish',
     2441                        'post__not_in'   => isset( $_POST['not_in'] ) ? wp_parse_id_list( $_POST['not_in'] ) : array(),
     2442                        'posts_per_page' => 50,
     2443                );
     2444
     2445                if ( ! empty( $_POST['s'] ) ) {
     2446                        $args['s'] = sanitize_text_field( wp_unslash( $_POST['s'] ) );
     2447                }
     2448
     2449                $posts = get_posts( $args );
     2450
     2451                if ( ! $posts ) {
     2452                        wp_send_json_error( __( 'No results found.' ) );
     2453                }
     2454
     2455                foreach ( $posts as $post ) {
     2456                        $data[] = array(
     2457                                'id'    => $post->ID,
     2458                                'title' => $post->post_title,
     2459                                'type'  => get_post_type_object( $post->post_type )->labels->singular_name,
     2460                        );
     2461                }
     2462
     2463                wp_send_json_success( $data );
     2464        }
     2465
     2466        /**
     2467         * Sanitization callback for lists of IDs.
     2468         *
     2469         * @since 4.7.0
     2470         *
     2471         * @param string $value Setting value.
     2472         * @return string Comma-separated list of IDs.
     2473         */
     2474        public function sanitize_id_list( $value ) {
     2475                return implode( ',', array_unique( array_filter( wp_parse_id_list( $value ) ) ) );
     2476        }
    23842477}
  • src/wp-includes/class-wp-query.php

     
    974974                                        $qv['page'] = $qv['paged'];
    975975                                        unset($qv['paged']);
    976976                                }
     977                                // Add section pages, if they exist.
     978                                $front_page_sections = array_filter( wp_parse_id_list( get_theme_mod( 'front_page_sections' ) ) );
     979                                if ( $front_page_sections ) {
     980                                        array_unshift( $front_page_sections, $qv['page_id'] );
     981                                        unset( $qv['page_id'] );
     982
     983                                        $qv['post__in'] = $front_page_sections;
     984                                        $qv['orderby'] = 'post__in';
     985                                }
    977986                        }
    978987                }
    979988
     
    10051014                        }
    10061015                }
    10071016
    1008                 if ( $qv['page_id'] ) {
     1017                if ( ! empty( $qv['page_id'] ) ) {
    10091018                        if  ( 'page' == get_option('show_on_front') && $qv['page_id'] == get_option('page_for_posts') ) {
    10101019                                $this->is_page = false;
    10111020                                $this->is_home = true;
  • 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         * Array of post IDs to exclude from search results.
     36         *
     37         * @since 4.7.0
     38         * @access public
     39         * @var array
     40         */
     41        public $exclude = array();
     42
     43        /**
     44         * Localization strings.
     45         *
     46         * @since 4.7.0
     47         * @access public
     48         * @var array
     49         */
     50        public $l10n = array();
     51
     52        /**
     53         * Constructor.
     54         *
     55         * @since 4.7.0
     56         *
     57         * @param WP_Customize_Manager $manager Customizer bootstrap instance.
     58         * @param string               $id      Control ID.
     59         * @param array                $args    Optional. Arguments to override class property defaults.
     60         */
     61        public function __construct( $manager, $id, $args = array() ) {
     62                parent::__construct( $manager, $id, $args );
     63
     64                $this->l10n = wp_parse_args( $this->l10n, array(
     65                        'addPost'                => __( 'Add Post' ),
     66                        'addPosts'               => __( 'Add Posts' ),
     67                        'clearResults'           => __( 'Clear Results' ),
     68                        'moveUp'                 => __( 'Move up' ),
     69                        'moveDown'               => __( 'Move down' ),
     70                        'movedUp'                => __( 'Post moved up' ),
     71                        'movedDown'              => __( 'Post moved down' ),
     72                        'removePost'             => __( 'Remove Post' ),
     73                        'searchPosts'            => __( 'Search Posts' ),
     74                        'searchPostsPlaceholder' => __( 'Search posts&hellip;' ),
     75                ) );
     76        }
     77
     78        /**
     79         * Enqueue control related scripts/styles.
     80         *
     81         * @since 4.7.0
     82         */
     83        public function enqueue() {
     84                wp_enqueue_style( 'customize-post-collection' );
     85                wp_enqueue_script( 'customize-post-collection' );
     86
     87                add_action( 'customize_controls_print_footer_scripts', array( 'WP_Customize_Post_Collection_Control', 'print_templates' ) );
     88        }
     89
     90        /**
     91         * Refresh the parameters passed to the JavaScript via JSON.
     92         *
     93         * @since 4.7.0
     94         * @uses WP_Customize_Control::to_json()
     95         */
     96        public function to_json() {
     97                parent::to_json();
     98
     99                $this->json['exclude']     = $this->exclude;
     100                $this->json['l10n']        = $this->l10n;
     101                $this->json['posts']       = $this->get_posts();
     102                $this->json['postTypes']   = $this->post_types;
     103                $this->json['searchNonce'] = wp_create_nonce( 'find-posts' );
     104        }
     105
     106        /**
     107         * Don't render any content for this control from PHP.
     108         *
     109         * @since 4.7.0
     110         *
     111         * @see WP_Customize_Post_Collection_Control::content_template()
     112         */
     113        public function render_content() {}
     114
     115        /**
     116         * An Underscore (JS) template for this control's content (but not its container).
     117         *
     118         * @see WP_Customize_Control::print_template()
     119         *
     120         * @since 4.7.0
     121         */
     122        protected function content_template() {
     123                ?>
     124                <label>
     125                        <# if ( data.label ) { #>
     126                                <span class="customize-control-title">{{ data.label }}</span>
     127                        <# } #>
     128                        <# if ( data.description ) { #>
     129                                <span class="description customize-control-description">{{{ data.description }}}</span>
     130                        <# } #>
     131                </label>
     132                <?php
     133        }
     134
     135        /**
     136         * Print JavaScript templates in the Customizer footer.
     137         *
     138         * @since 4.7.0
     139         */
     140        public static function print_templates() {
     141                ?>
     142                <script type="text/html" id="tmpl-wp-item">
     143                        <div class="wp-item-header">
     144                                <h4 class="wp-item-title">{{ data.title }}</h4>
     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 is-active">
     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                </script>
     156
     157                <script type="text/html" id="tmpl-search-group">
     158                        <label class="screen-reader-text" for="search-group-field">{{ data.l10n.searchPosts }}</label>
     159                        <input type="text" id="search-group-field" placeholder="{{{ data.l10n.searchPostsPlaceholder }}}" class="search-group-field">
     160                </script>
     161
     162                <script type="text/html" id="tmpl-search-result">
     163                        <span class="search-results-item-type">{{ data.type }}</span>
     164                        <span class="search-results-item-title">{{ data.title }}</span>
     165
     166                        <button type="button" class="search-results-item-add customize-dashicon button-link">
     167                                <span class="screen-reader-text">{{ data.l10n.addPost }}</span>
     168                        </button>
     169                </script>
     170                <?php
     171        }
     172
     173        /**
     174         * Retrieve posts.
     175         *
     176         * @since 4.7.0
     177         *
     178         * @return array
     179         */
     180        protected function get_posts() {
     181                $data  = array();
     182                $value = $this->value();
     183
     184                if ( ! empty( $value ) ) {
     185                        $posts = get_posts( array(
     186                                'post_type'      => $this->post_types,
     187                                'post_status'    => 'any',
     188                                'post__in'       => array_map( 'absint', explode( ',', $value ) ),
     189                                'orderby'        => 'post__in',
     190                                'posts_per_page' => 20,
     191                        ) );
     192                }
     193
     194                if ( ! empty( $posts ) ) {
     195                        $i = 0;
     196                        foreach ( $posts as $post ) {
     197                                $data[] = array(
     198                                        'id'    => $post->ID,
     199                                        'title' => $post->post_title,
     200                                        'order' => ++$i,
     201                                );
     202                        }
     203                }
     204
     205                return $data;
     206        }
     207}
  • src/wp-includes/default-filters.php

     
    427427
    428428// Canonical
    429429add_action( 'template_redirect', 'redirect_canonical' );
     430add_action( 'template_redirect', 'wp_redirect_front_page_sections' );
    430431add_action( 'template_redirect', 'wp_redirect_admin_locations', 1000 );
    431432
    432433// Shortcodes
  • src/wp-includes/link-template.php

     
    312312function get_page_link( $post = false, $leavename = false, $sample = false ) {
    313313        $post = get_post( $post );
    314314
    315         if ( 'page' == get_option( 'show_on_front' ) && $post->ID == get_option( 'page_on_front' ) )
     315        if ( 'page' == get_option( 'show_on_front' ) && $post->ID == get_option( 'page_on_front' ) ) {
    316316                $link = home_url('/');
    317         else
     317        } elseif ( is_front_page_section( $post->ID ) ) {
     318                $link = get_front_page_section_url( $post->ID );
     319        } else {
    318320                $link = _get_page_link( $post, $leavename, $sample );
     321        }
    319322
    320323        /**
    321324         * Filters the permalink for a page.
     
    41424145         */
    41434146        return apply_filters( 'parent_theme_file_path', $path, $file );
    41444147}
     4148
     4149/**
     4150 * Retrieve the URL for a front page section.
     4151 *
     4152 * @since 4.7.0
     4153 *
     4154 * @param int|WP_Post $post Post ID or object.
     4155 * @return string
     4156 */
     4157function get_front_page_section_url( $post ) {
     4158        return home_url( '#post-' . get_post( $post )->ID );
     4159}
  • src/wp-includes/post-template.php

     
    479479                        $classes[] = 'format-standard';
    480480        }
    481481
     482        // Front page sections.
     483        if ( is_front_page() && is_front_page_section( $post->ID ) ) {
     484                $classes[] = 'front-page-section';
     485        }
     486
    482487        $post_password_required = post_password_required( $post->ID );
    483488
    484489        // Post requires password.
     
    17941799        echo $rows;
    17951800        echo "</ul>";
    17961801}
     1802
     1803/**
     1804 * Whether a post is a front page section.
     1805 *
     1806 * @since 4.7.0
     1807 *
     1808 * @param int $post_id Post ID.
     1809 * @return bool
     1810 */
     1811function is_front_page_section( $post_id ) {
     1812        $section_ids = array_filter( wp_parse_id_list( get_theme_mod( 'front_page_sections' ) ) );
     1813        return in_array( intval( $post_id ), $section_ids, true );
     1814}
  • 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