WordPress.org

Make WordPress Core

Ticket #26631: 26631.4.diff

File 26631.4.diff, 58.9 KB (added by wonderboymusic, 6 years ago)
  • src/wp-admin/js/media-upload.js

     
    2828                } else if ( h.indexOf('[gallery') !== -1 ) {
    2929                        if ( ed.plugins.wpgallery )
    3030                                h = ed.plugins.wpgallery._do_gallery(h);
     31                } else if ( h.indexOf('[playlist') !== -1 ) {
     32                        if ( ed.plugins.wpplaylist )
     33                                h = ed.plugins.wpplaylist._do_playlist(h);
    3134                } else if ( h.indexOf('[embed') === 0 ) {
    3235                        if ( ed.plugins.wordpress )
    3336                                h = ed.plugins.wordpress._setEmbed(h);
  • src/wp-content/themes/twentyfourteen/functions.php

     
    111111                'max_posts' => 6,
    112112        ) );
    113113
     114        add_post_type_support( 'attachment:audio', 'thumbnail' );
     115        add_post_type_support( 'attachment:video', 'thumbnail' );
     116        add_theme_support( 'post-thumbnails', array( 'post', 'attachment:audio', 'attachment:video' ) );
     117
    114118        // This theme uses its own gallery styles.
    115119        add_filter( 'use_default_gallery_style', '__return_false' );
    116120}
  • src/wp-includes/class-wp-editor.php

     
    233233                                                'wordpress',
    234234                                                'wpeditimage',
    235235                                                'wpgallery',
     236                                                'wpplaylist',
    236237                                                'wplink',
    237238                                                'wpdialogs',
    238239                                        ) ) );
  • src/wp-includes/css/editor.css

     
    463463}
    464464
    465465#wp_editbtns,
    466 #wp_gallerybtns {
     466#wp_gallerybtns,
     467#wp_playlistbtns {
    467468        padding: 2px;
    468469        position: absolute;
    469470        display: none;
     
    473474#wp_editimgbtn,
    474475#wp_delimgbtn,
    475476#wp_editgallery,
    476 #wp_delgallery {
     477#wp_delgallery,
     478#wp_editplaylist,
     479#wp_delplaylist {
    477480        border-color: #999;
    478481        background-color: #eee;
    479482        margin: 2px;
     
    487490#wp_editimgbtn:hover,
    488491#wp_delimgbtn:hover,
    489492#wp_editgallery:hover,
    490 #wp_delgallery:hover {
     493#wp_delgallery:hover,
     494#wp_editplaylist:hover,
     495#wp_delplaylist:hover {
    491496        border-color: #555;
    492497        background-color: #ccc;
    493498}
  • src/wp-includes/css/media-views.css

     
    585585        box-shadow: 0 4px 4px -4px rgba( 0, 0, 0, 0.1 );
    586586}
    587587
    588 .media-frame .media-toolbar .add-to-gallery {
     588.media-frame .media-toolbar .add-to-gallery, .media-frame .media-toolbar .add-to-playlist {
    589589        display: none;
    590590}
    591591
     
    14061406        margin: 1.4em 0 0.4em;
    14071407}
    14081408
    1409 .gallery-settings {
     1409.gallery-settings, .playlist-settings {
    14101410        overflow: hidden;
    14111411}
    14121412
     
    16741674        .media-modal-close {
    16751675                right: 10px;
    16761676        }
    1677        
     1677
    16781678        /* Text inputs need to be 16px, or they force zooming on iOS */
    16791679        .media-frame input[type="text"],
    16801680        .media-frame input[type="password"],
     
    17221722        .media-frame-title {
    17231723                display: none;
    17241724        }
    1725        
     1725
    17261726        .media-frame-toolbar {
    17271727                position: absolute;
    17281728                bottom: 0px;
     
    17471747        .attachment-details h3 {
    17481748                margin-top: 45px;
    17491749        }
    1750                
     1750
    17511751        /* Shorten right-side links so they don't overlap the close button */
    17521752        .media-menu a:nth-child(2),
    17531753        .media-menu a:last-child {
     
    17731773                top: 84px;
    17741774                left: 0;
    17751775        }
    1776        
     1776
    17771777        .media-frame-content {
    17781778                left: 0;
    17791779                top: 118px;
     
    17821782        .media-frame .attachments-browser {
    17831783                padding-bottom: 300px;
    17841784        }
    1785        
     1785
    17861786        .media-sidebar {
    17871787                border-bottom: 1px solid #dddddd;
    17881788        }
    1789        
     1789
    17901790        .media-modal {
    17911791                width: auto;
    17921792        }
     
    18551855                border-top: none;
    18561856        }
    18571857
    1858         .gallery-settings h3 {
     1858        .gallery-settings h3, .playlist-settings h3 {
    18591859                margin-top: 45px;
    18601860        }
    18611861
     
    18921892        .media-frame-content {
    18931893                top: 78px;
    18941894        }
    1895        
     1895
    18961896        .attachments-browser .attachments {
    18971897                top: 2px;
    18981898        }
  • src/wp-includes/js/media-editor.js

     
    44// -----------------------------
    55(function($){
    66        // Stores the editors' `wp.media.controller.Frame` instances.
    7         var workflows = {};
     7        var workflows = {}, cache = {};
    88
    99        wp.media.string = {
    1010                // Joins the `props` and `attachment` objects,
     
    204204                }
    205205        };
    206206
    207         wp.media.gallery = (function() {
    208                 var galleries = {};
    209 
    210                 return {
    211                         defaults: {
    212                                 order:      'ASC',
    213                                 id:         wp.media.view.settings.post.id,
    214                                 itemtag:    'dl',
    215                                 icontag:    'dt',
    216                                 captiontag: 'dd',
    217                                 columns:    '3',
    218                                 link:       'post',
    219                                 size:       'thumbnail',
    220                                 orderby:    'menu_order ID'
    221                         },
    222 
    223                         attachments: function( shortcode ) {
     207        wp.media.collection = {
     208                attachments : function ( prop, type ) {
     209                        return function( shortcode ) {
    224210                                var shortcodeString = shortcode.string(),
    225                                         result = galleries[ shortcodeString ],
     211                                        result = cache[ shortcodeString ],
    226212                                        attrs, args, query, others;
    227213
    228                                 delete galleries[ shortcodeString ];
     214                                delete cache[ shortcodeString ];
    229215
    230216                                if ( result )
    231217                                        return result;
    232218
    233219                                // Fill the default shortcode attributes.
    234                                 attrs = _.defaults( shortcode.attrs.named, wp.media.gallery.defaults );
     220                                attrs = _.defaults( shortcode.attrs.named, this.defaults );
    235221                                args  = _.pick( attrs, 'orderby', 'order' );
    236222
    237                                 args.type    = 'image';
     223                                args.type = type;
    238224                                args.perPage = -1;
    239225
    240226                                // Mark the `orderby` override attribute.
    241                                 if( undefined !== attrs.orderby )
     227                                if ( undefined !== attrs.orderby )
    242228                                        attrs._orderByField = attrs.orderby;
    243229
    244230                                if ( 'rand' === attrs.orderby )
     
    266252                                others = _.omit( attrs, 'id', 'ids', 'include', 'exclude', 'orderby', 'order' );
    267253
    268254                                query = wp.media.query( args );
    269                                 query.gallery = new Backbone.Model( others );
     255                                query.type = type;
     256                                query[ prop ] = new Backbone.Model( others );
    270257                                return query;
    271                         },
     258                        };
     259                },
    272260
    273                         shortcode: function( attachments ) {
    274                                 var props = attachments.props.toJSON(),
    275                                         attrs = _.pick( props, 'orderby', 'order' ),
    276                                         shortcode, clone;
     261                shortcodeAttrs : function ( prop, attachments ) {
     262                        var props = attachments.props.toJSON(),
     263                                attrs = _.pick( props, 'orderby', 'order', 'style' );
    277264
    278                                 if ( attachments.gallery )
    279                                         _.extend( attrs, attachments.gallery.toJSON() );
     265                        if ( attachments[ prop ] )
     266                                _.extend( attrs, attachments[ prop ].toJSON() );
    280267
    281                                 // Convert all gallery shortcodes to use the `ids` property.
    282                                 // Ignore `post__in` and `post__not_in`; the attachments in
    283                                 // the collection will already reflect those properties.
    284                                 attrs.ids = attachments.pluck('id');
     268                        // Convert all collection shortcodes to use the `ids` property.
     269                        // Ignore `post__in` and `post__not_in`; the attachments in
     270                        // the collection will already reflect those properties.
     271                        attrs.ids = attachments.pluck('id');
    285272
    286                                 // Copy the `uploadedTo` post ID.
    287                                 if ( props.uploadedTo )
    288                                         attrs.id = props.uploadedTo;
     273                        // Copy the `uploadedTo` post ID.
     274                        if ( props.uploadedTo )
     275                                attrs.id = props.uploadedTo;
    289276
    290                                 // Check if the gallery is randomly ordered.
     277                        // Check if the collection is randomly ordered.
     278                        delete attrs.orderby;
     279
     280                        if ( attrs._orderbyRandom )
     281                                attrs.orderby = 'rand';
     282                        else if ( attrs._orderByField && attrs._orderByField != 'rand' )
     283                                attrs.orderby = attrs._orderByField;
     284
     285                        delete attrs._orderbyRandom;
     286                        delete attrs._orderByField;
     287
     288                        // If the `ids` attribute is set and `orderby` attribute
     289                        // is the default value, clear it for cleaner output.
     290                        if ( attrs.ids && 'post__in' === attrs.orderby )
    291291                                delete attrs.orderby;
    292292
    293                                 if ( attrs._orderbyRandom )
    294                                         attrs.orderby = 'rand';
    295                                 else if ( attrs._orderByField && attrs._orderByField != 'rand' )
    296                                         attrs.orderby = attrs._orderByField;
     293                        // Remove default attributes from the shortcode.
     294                        _.each( this.defaults, function( value, key ) {
     295                                if ( value === attrs[ key ] )
     296                                        delete attrs[ key ];
     297                        });
    297298
    298                                 delete attrs._orderbyRandom;
    299                                 delete attrs._orderByField;
     299                        return attrs;
     300                },
    300301
    301                                 // If the `ids` attribute is set and `orderby` attribute
    302                                 // is the default value, clear it for cleaner output.
    303                                 if ( attrs.ids && 'post__in' === attrs.orderby )
    304                                         delete attrs.orderby;
     302                editSelection : function ( prop, shortcode ) {
     303                        var defaultPostId = wp.media[ prop ].defaults.id,
     304                                attachments, selection;
    305305
    306                                 // Remove default attributes from the shortcode.
    307                                 _.each( wp.media.gallery.defaults, function( value, key ) {
    308                                         if ( value === attrs[ key ] )
    309                                                 delete attrs[ key ];
    310                                 });
     306                        // Ignore the rest of the match object.
     307                        shortcode = shortcode.shortcode;
    311308
    312                                 shortcode = new wp.shortcode({
    313                                         tag:    'gallery',
    314                                         attrs:  attrs,
    315                                         type:   'single'
    316                                 });
     309                        if ( _.isUndefined( shortcode.get('id') ) && ! _.isUndefined( defaultPostId ) )
     310                                shortcode.set( 'id', defaultPostId );
    317311
    318                                 // Use a cloned version of the gallery.
    319                                 clone = new wp.media.model.Attachments( attachments.models, {
    320                                         props: props
    321                                 });
    322                                 clone.gallery = attachments.gallery;
    323                                 galleries[ shortcode.string() ] = clone;
     312                        attachments = wp.media[ prop ].attachments( shortcode );
    324313
    325                                 return shortcode;
    326                         },
     314                        selection = new wp.media.model.Selection( attachments.models, {
     315                                props:    attachments.props.toJSON(),
     316                                multiple: true
     317                        });
    327318
    328                         edit: function( content ) {
    329                                 var shortcode = wp.shortcode.next( 'gallery', content ),
    330                                         defaultPostId = wp.media.gallery.defaults.id,
    331                                         attachments, selection;
     319                        selection[ prop ] = attachments[ prop ];
    332320
    333                                 // Bail if we didn't match the shortcode or all of the content.
    334                                 if ( ! shortcode || shortcode.content !== content )
    335                                         return;
     321                        // Fetch the query's attachments, and then break ties from the
     322                        // query to allow for sorting.
     323                        selection.more().done( function() {
     324                                // Break ties with the query.
     325                                selection.props.set({ query: false });
     326                                selection.unmirror();
     327                                selection.props.unset('orderby');
     328                        });
    336329
    337                                 // Ignore the rest of the match object.
    338                                 shortcode = shortcode.shortcode;
     330                        return selection;
     331                },
    339332
    340                                 if ( _.isUndefined( shortcode.get('id') ) && ! _.isUndefined( defaultPostId ) )
    341                                         shortcode.set( 'id', defaultPostId );
     333                cacheShortcode : function ( prop, attachments, shortcode ) {
     334                        // Use a cloned version of the playlist.
     335                        var clone = new wp.media.model.Attachments( attachments.models, {
     336                                props: attachments.props.toJSON()
     337                        });
     338                        clone[ prop ] = attachments[ prop ];
     339                        cache[ shortcode.string() ] = clone;
    342340
    343                                 attachments = wp.media.gallery.attachments( shortcode );
     341                        return shortcode;
     342                },
    344343
    345                                 selection = new wp.media.model.Selection( attachments.models, {
    346                                         props:    attachments.props.toJSON(),
    347                                         multiple: true
    348                                 });
     344                getEditFrame : function ( args ) {
     345                        // Destroy the previous gallery frame.
     346                        if ( this.frame )
     347                                this.frame.dispose();
    349348
    350                                 selection.gallery = attachments.gallery;
     349                        // Store the current gallery frame.
     350                        this.frame = wp.media( _.extend( {
     351                                frame:     'post',
     352                                editing:   true,
     353                                multiple:  true,
     354                        }, args ) ).open();
    351355
    352                                 // Fetch the query's attachments, and then break ties from the
    353                                 // query to allow for sorting.
    354                                 selection.more().done( function() {
    355                                         // Break ties with the query.
    356                                         selection.props.set({ query: false });
    357                                         selection.unmirror();
    358                                         selection.props.unset('orderby');
    359                                 });
     356                        return this.frame;
     357                },
    360358
    361                                 // Destroy the previous gallery frame.
    362                                 if ( this.frame )
    363                                         this.frame.dispose();
     359                instance : function ( prop, args ) {
     360                        return {
     361                                attachments: this.attachments( prop, args.type ),
     362                                shortcode: function( attachments ) {
     363                                        var shortcode = new wp.shortcode({
     364                                                tag:    prop,
     365                                                attrs:  wp.media.collection.shortcodeAttrs( prop, attachments ),
     366                                                type:   'single'
     367                                        });
    364368
    365                                 // Store the current gallery frame.
    366                                 this.frame = wp.media({
    367                                         frame:     'post',
    368                                         state:     'gallery-edit',
    369                                         title:     wp.media.view.l10n.editGalleryTitle,
    370                                         editing:   true,
    371                                         multiple:  true,
    372                                         selection: selection
    373                                 }).open();
     369                                        return wp.media.collection.cacheShortcode( prop, attachments, shortcode );
     370                                },
     371                                edit: function( content ) {
     372                                        var shortcode = wp.shortcode.next( prop, content );
    374373
    375                                 return this.frame;
     374                                        // Bail if we didn't match the shortcode or all of the content.
     375                                        if ( ! shortcode || shortcode.content !== content )
     376                                                return;
     377
     378                                        return wp.media.collection.getEditFrame( {
     379                                                title:          args.title,
     380                                                state:          prop + '-edit',
     381                                                selection:      wp.media.collection.editSelection( prop, shortcode )
     382                                        } );
     383                                }
     384                        };
     385                }
     386        };
     387
     388        wp.media.gallery = (function() {
     389                var gallery = {
     390                        defaults : {
     391                                itemtag:    'dl',
     392                                icontag:    'dt',
     393                                captiontag: 'dd',
     394                                columns:    '3',
     395                                link:       'post',
     396                                size:       'thumbnail',
     397                                order: 'ASC',
     398                                id: wp.media.view.settings.post.id,
     399                                orderby : 'menu_order ID'
    376400                        }
    377401                };
     402
     403                return _.extend(gallery, wp.media.collection.instance( 'gallery', {
     404                        type : 'image',
     405                        title : wp.media.view.l10n.editGalleryTitle
     406                }));
    378407        }());
    379408
     409        wp.media.playlist = (function() {
     410                var playlist = {
     411                        defaults : {
     412                                id:         wp.media.view.settings.post.id,
     413                                style:          'light',
     414                                tracklist:      true,
     415                                tracknumbers: true
     416                        }
     417                };
     418
     419                return _.extend(playlist, wp.media.collection.instance( 'playlist', {
     420                        type : 'audio',
     421                        title : wp.media.view.l10n.editPlaylistTitle
     422                }));
     423        }());
     424
     425        wp.media['video-playlist'] = (function() {
     426                var playlist = {
     427                        defaults : {
     428                                id:         wp.media.view.settings.post.id,
     429                                style:          'dark',
     430                                tracklist:      false,
     431                                tracknumbers: false
     432                        }
     433                };
     434
     435                return _.extend(playlist, wp.media.collection.instance( 'video-playlist', {
     436                        type : 'video',
     437                        title : wp.media.view.l10n.editVideoPlaylistTitle
     438                }));
     439        }());
     440
    380441        wp.media.featuredImage = {
    381442                get: function() {
    382443                        return wp.media.view.settings.post.featuredImageId;
     
    480541                                } else if ( h.indexOf('[gallery') !== -1 ) {
    481542                                        if ( ed.plugins.wpgallery )
    482543                                                h = ed.plugins.wpgallery._do_gallery(h);
     544                                } else if ( h.indexOf('[playlist') !== -1 ) {
     545                                        if ( ed.plugins.wpplaylist )
     546                                                h = ed.plugins.wpplaylist._do_playlist(h);
    483547                                } else if ( h.indexOf('[embed') === 0 ) {
    484548                                        if ( ed.plugins.wordpress )
    485549                                                h = ed.plugins.wordpress._setEmbed(h);
     
    531595                                this.insert( wp.media.gallery.shortcode( selection ).string() );
    532596                        }, this );
    533597
     598                        workflow.state('playlist-edit').on( 'update', function( selection ) {
     599                                this.insert( wp.media.playlist.shortcode( selection ).string() );
     600                        }, this );
     601
     602                        workflow.state('video-playlist-edit').on( 'update', function( selection ) {
     603                                this.insert( wp.media['video-playlist'].shortcode( selection ).string() );
     604                        }, this );
     605
    534606                        workflow.state('embed').on( 'select', function() {
    535607                                var state = workflow.state(),
    536608                                        type = state.get('type'),
     
    704776                                if ( $this.hasClass( 'gallery' ) ) {
    705777                                        options.state = 'gallery';
    706778                                        options.title = wp.media.view.l10n.createGalleryTitle;
     779                                } else if ( $this.hasClass( 'playlist' ) ) {
     780                                        options.state = 'playlist';
     781                                        options.title = wp.media.view.l10n.createPlaylistTitle;
     782                                } else if ( $this.hasClass( 'video-playlist' ) ) {
     783                                        options.state = 'video-playlist';
     784                                        options.title = wp.media.view.l10n.createVideoPlaylistTitle;
    707785                                }
    708786
    709787                                wp.media.editor.open( editor, options );
  • src/wp-includes/js/media-views.js

     
    556556                }
    557557        });
    558558
    559         // wp.media.controller.GalleryEdit
    560         // -------------------------------
    561         media.controller.GalleryEdit = media.controller.Library.extend({
    562                 defaults: {
    563                         id:         'gallery-edit',
    564                         multiple:   false,
    565                         describe:   true,
    566                         edge:       199,
    567                         editing:    false,
    568                         sortable:   true,
    569                         searchable: false,
    570                         toolbar:    'gallery-edit',
    571                         content:    'browse',
    572                         title:      l10n.editGalleryTitle,
    573                         priority:   60,
    574                         dragInfo:   true,
     559        media.controller.CollectionEdit = function ( prop, args ) {
     560                return media.controller.Library.extend({
     561                        defaults : _.defaults(args.defaults || {}, {
     562                                id:         prop  + '-edit',
     563                                toolbar:    prop  + '-edit',
     564                                multiple:   false,
     565                                describe:   true,
     566                                edge:       199,
     567                                editing:    false,
     568                                sortable:   true,
     569                                searchable: false,
     570                                content:    'browse',
     571                                priority:   60,
     572                                dragInfo:   true,
    575573
    576                         // Don't sync the selection, as the Edit Gallery library
    577                         // *is* the selection.
    578                         syncSelection: false
    579                 },
     574                                // Don't sync the selection, as the Edit Gallery library
     575                                // *is* the selection.
     576                                syncSelection: false
     577                        }),
    580578
    581                 initialize: function() {
    582                         // If we haven't been provided a `library`, create a `Selection`.
    583                         if ( ! this.get('library') )
    584                                 this.set( 'library', new media.model.Selection() );
     579                        initialize: function() {
     580                                // If we haven't been provided a `library`, create a `Selection`.
     581                                if ( ! this.get('library') )
     582                                        this.set( 'library', new media.model.Selection() );
    585583
    586                         // The single `Attachment` view to be used in the `Attachments` view.
    587                         if ( ! this.get('AttachmentView') )
    588                                 this.set( 'AttachmentView', media.view.Attachment.EditLibrary );
    589                         media.controller.Library.prototype.initialize.apply( this, arguments );
    590                 },
     584                                // The single `Attachment` view to be used in the `Attachments` view.
     585                                if ( ! this.get('AttachmentView') )
     586                                        this.set( 'AttachmentView', media.view.Attachment.EditLibrary );
     587                                media.controller.Library.prototype.initialize.apply( this, arguments );
     588                        },
    591589
    592                 activate: function() {
    593                         var library = this.get('library');
     590                        activate: function() {
     591                                var library = this.get('library');
    594592
    595                         // Limit the library to images only.
    596                         library.props.set( 'type', 'image' );
     593                                // Limit the library to images only.
     594                                library.props.set( 'type', args.type );
    597595
    598                         // Watch for uploaded attachments.
    599                         this.get('library').observe( wp.Uploader.queue );
     596                                // Watch for uploaded attachments.
     597                                this.get('library').observe( wp.Uploader.queue );
    600598
    601                         this.frame.on( 'content:render:browse', this.gallerySettings, this );
     599                                this.frame.on( 'content:render:browse', this.settings, this );
    602600
    603                         media.controller.Library.prototype.activate.apply( this, arguments );
    604                 },
     601                                media.controller.Library.prototype.activate.apply( this, arguments );
     602                        },
    605603
    606                 deactivate: function() {
    607                         // Stop watching for uploaded attachments.
    608                         this.get('library').unobserve( wp.Uploader.queue );
     604                        deactivate: function() {
     605                                // Stop watching for uploaded attachments.
     606                                this.get('library').unobserve( wp.Uploader.queue );
    609607
    610                         this.frame.off( 'content:render:browse', this.gallerySettings, this );
     608                                this.frame.off( 'content:render:browse', this.settings, this );
    611609
    612                         media.controller.Library.prototype.deactivate.apply( this, arguments );
    613                 },
     610                                media.controller.Library.prototype.deactivate.apply( this, arguments );
     611                        },
    614612
    615                 gallerySettings: function( browser ) {
    616                         var library = this.get('library');
     613                        settings: function( browser ) {
     614                                var library = this.get('library'), obj = {};
    617615
    618                         if ( ! library || ! browser )
    619                                 return;
     616                                if ( ! library || ! browser )
     617                                        return;
    620618
    621                         library.gallery = library.gallery || new Backbone.Model();
     619                                library[ prop ] = library[ prop ] || new Backbone.Model();
    622620
    623                         browser.sidebar.set({
    624                                 gallery: new media.view.Settings.Gallery({
     621                                obj[ prop ] = new media.view.Settings[ args.settings ]({
    625622                                        controller: this,
    626                                         model:      library.gallery,
     623                                        model:      library[ prop ],
    627624                                        priority:   40
    628                                 })
    629                         });
     625                                });
    630626
    631                         browser.toolbar.set( 'reverse', {
    632                                 text:     l10n.reverseOrder,
    633                                 priority: 80,
     627                                browser.sidebar.set( obj );
    634628
    635                                 click: function() {
    636                                         library.reset( library.toArray().reverse() );
     629                                if ( args.dragInfoText ) {
     630                                        browser.toolbar.set( 'dragInfo', new media.View({
     631                                                el: $( '<div class="instructions">' + args.dragInfoText + '</div>' )[0],
     632                                                priority: -40
     633                                        }) );
    637634                                }
    638                         });
    639                 }
    640         });
    641635
    642         // wp.media.controller.GalleryAdd
    643         // ---------------------------------
    644         media.controller.GalleryAdd = media.controller.Library.extend({
    645                 defaults: _.defaults({
    646                         id:           'gallery-library',
    647                         filterable:   'uploaded',
    648                         multiple:     'add',
    649                         menu:         'gallery',
    650                         toolbar:      'gallery-add',
    651                         title:        l10n.addToGalleryTitle,
    652                         priority:     100,
     636                                browser.toolbar.set( 'reverse', {
     637                                        text:     l10n.reverseOrder,
     638                                        priority: 80,
    653639
    654                         // Don't sync the selection, as the Edit Gallery library
    655                         // *is* the selection.
    656                         syncSelection: false
    657                 }, media.controller.Library.prototype.defaults ),
     640                                        click: function() {
     641                                                library.reset( library.toArray().reverse() );
     642                                        }
     643                                });
     644                        }
     645                });
     646        };
    658647
    659                 initialize: function() {
    660                         // If we haven't been provided a `library`, create a `Selection`.
    661                         if ( ! this.get('library') )
    662                                 this.set( 'library', media.query({ type: 'image' }) );
     648        media.controller.CollectionAdd = function ( prop, args ) {
     649                return media.controller.Library.extend({
     650                        defaults: _.defaults({
     651                                id:           prop + '-library',
     652                                filterable:   'uploaded',
     653                                multiple:     'add',
     654                                menu:         prop,
     655                                toolbar:      prop + '-add',
     656                                priority:     100,
    663657
    664                         media.controller.Library.prototype.initialize.apply( this, arguments );
    665                 },
     658                                // Don't sync the selection, as the Edit Gallery library
     659                                // *is* the selection.
     660                                syncSelection: false
     661                        }, args.defaults || {}, media.controller.Library.prototype.defaults ),
     662                        initialize: function() {
     663                                // If we haven't been provided a `library`, create a `Selection`.
     664                                if ( ! this.get('library') )
     665                                        this.set( 'library', media.query({ type: args.type }) );
    666666
    667                 activate: function() {
    668                         var library = this.get('library'),
    669                                 edit    = this.frame.state('gallery-edit').get('library');
     667                                media.controller.Library.prototype.initialize.apply( this, arguments );
     668                        },
    670669
    671                         if ( this.editLibrary && this.editLibrary !== edit )
    672                                 library.unobserve( this.editLibrary );
     670                        activate: function() {
     671                                var library = this.get('library'),
     672                                        edit    = this.frame.state(prop + '-edit').get('library');
    673673
    674                         // Accepts attachments that exist in the original library and
    675                         // that do not exist in gallery's library.
    676                         library.validator = function( attachment ) {
    677                                 return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && media.model.Selection.prototype.validator.apply( this, arguments );
    678                         };
     674                                if ( this.editLibrary && this.editLibrary !== edit )
     675                                        library.unobserve( this.editLibrary );
    679676
    680                         // Reset the library to ensure that all attachments are re-added
    681                         // to the collection. Do so silently, as calling `observe` will
    682                         // trigger the `reset` event.
    683                         library.reset( library.mirroring.models, { silent: true });
    684                         library.observe( edit );
    685                         this.editLibrary = edit;
     677                                // Accepts attachments that exist in the original library and
     678                                // that do not exist in gallery's library.
     679                                library.validator = function( attachment ) {
     680                                        return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && media.model.Selection.prototype.validator.apply( this, arguments );
     681                                };
    686682
    687                         media.controller.Library.prototype.activate.apply( this, arguments );
     683                                // Reset the library to ensure that all attachments are re-added
     684                                // to the collection. Do so silently, as calling `observe` will
     685                                // trigger the `reset` event.
     686                                library.reset( library.mirroring.models, { silent: true });
     687                                library.observe( edit );
     688                                this.editLibrary = edit;
     689
     690                                media.controller.Library.prototype.activate.apply( this, arguments );
     691                        }
     692                });
     693        };
     694
     695        // wp.media.controller.GalleryEdit
     696        // -------------------------------
     697        media.controller.GalleryEdit = media.controller.CollectionEdit( 'gallery', {
     698                type: 'image',
     699                settings: 'Gallery',
     700                defaults: {
     701                        title: l10n.editGalleryTitle
    688702                }
    689703        });
    690704
     705        // wp.media.controller.GalleryAdd
     706        // ---------------------------------
     707        media.controller.GalleryAdd = media.controller.CollectionAdd( 'gallery', {
     708                type: 'image',
     709                defaults: {
     710                        title: l10n.addToGalleryTitle
     711                }
     712        });
     713
     714        // wp.media.controller.PlaylistEdit
     715        // -------------------------------
     716        media.controller.PlaylistEdit = media.controller.CollectionEdit( 'playlist', {
     717                type: 'audio',
     718                settings: 'Playlist',
     719                dragInfoText: l10n.playlistDragInfo,
     720                defaults: {
     721                        title: l10n.editPlaylistTitle,
     722                        dragInfo : false
     723                }
     724        });
     725
     726        // wp.media.controller.PlaylistAdd
     727        // ---------------------------------
     728        media.controller.PlaylistAdd = media.controller.CollectionAdd( 'playlist', {
     729                type: 'audio',
     730                defaults: {
     731                        title: l10n.addToPlaylistTitle
     732                }
     733        });
     734
     735        // wp.media.controller.PlaylistEdit
     736        // -------------------------------
     737        media.controller.VideoPlaylistEdit = media.controller.CollectionEdit( 'video-playlist', {
     738                type: 'video',
     739                settings: 'Playlist',
     740                dragInfoText: l10n.videoPlaylistDragInfo,
     741                defaults: {
     742                        title: l10n.editVideoPlaylistTitle,
     743                        dragInfo : false
     744                }
     745        });
     746
     747        // wp.media.controller.PlaylistAdd
     748        // ---------------------------------
     749        media.controller.VideoPlaylistAdd = media.controller.CollectionAdd( 'video-playlist', {
     750                type: 'video',
     751                defaults: {
     752                        title: l10n.addToVideoPlaylistTitle
     753                }
     754        });
     755
    691756        // wp.media.controller.FeaturedImage
    692757        // ---------------------------------
    693758        media.controller.FeaturedImage = media.controller.Library.extend({
     
    13081373                                        menu:    'gallery'
    13091374                                }),
    13101375
    1311                                 new media.controller.GalleryAdd()
     1376                                new media.controller.GalleryAdd(),
     1377
     1378                                new media.controller.Library({
     1379                                        id:         'playlist',
     1380                                        title:      l10n.createPlaylistTitle,
     1381                                        priority:   60,
     1382                                        toolbar:    'main-playlist',
     1383                                        filterable: 'uploaded',
     1384                                        multiple:   'add',
     1385                                        editable:   false,
     1386
     1387                                        library:  media.query( _.defaults({
     1388                                                type: 'audio',
     1389                                        }, options.library ) )
     1390                                }),
     1391
     1392                                // Gallery states.
     1393                                new media.controller.PlaylistEdit({
     1394                                        library: options.selection,
     1395                                        editing: options.editing,
     1396                                        menu:    'playlist'
     1397                                }),
     1398
     1399                                new media.controller.PlaylistAdd(),
     1400
     1401                                new media.controller.Library({
     1402                                        id:         'video-playlist',
     1403                                        title:      l10n.createVideoPlaylistTitle,
     1404                                        priority:   60,
     1405                                        toolbar:    'main-video-playlist',
     1406                                        filterable: 'uploaded',
     1407                                        multiple:   'add',
     1408                                        editable:   false,
     1409
     1410                                        library:  media.query( _.defaults({
     1411                                                type: 'video'
     1412                                        }, options.library ) )
     1413                                }),
     1414
     1415                                // Gallery states.
     1416                                new media.controller.VideoPlaylistEdit({
     1417                                        library: options.selection,
     1418                                        editing: options.editing,
     1419                                        menu:    'video-playlist'
     1420                                }),
     1421
     1422                                new media.controller.VideoPlaylistAdd()
    13121423                        ]);
    13131424
    13141425
     
    13201431                bindHandlers: function() {
    13211432                        media.view.MediaFrame.Select.prototype.bindHandlers.apply( this, arguments );
    13221433                        this.on( 'menu:create:gallery', this.createMenu, this );
     1434                        this.on( 'menu:create:playlist', this.createMenu, this );
     1435                        this.on( 'menu:create:video-playlist', this.createMenu, this );
    13231436                        this.on( 'toolbar:create:main-insert', this.createToolbar, this );
    13241437                        this.on( 'toolbar:create:main-gallery', this.createToolbar, this );
     1438                        this.on( 'toolbar:create:main-playlist', this.createToolbar, this );
     1439                        this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this );
    13251440                        this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this );
    13261441                        this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this );
    13271442
    13281443                        var handlers = {
    13291444                                        menu: {
    13301445                                                'default': 'mainMenu',
    1331                                                 'gallery': 'galleryMenu'
     1446                                                'gallery': 'galleryMenu',
     1447                                                'playlist': 'playlistMenu',
     1448                                                'video-playlist': 'playlistMenu'
    13321449                                        },
    13331450
    13341451                                        content: {
     
    13371454                                        },
    13381455
    13391456                                        toolbar: {
    1340                                                 'main-insert':      'mainInsertToolbar',
    1341                                                 'main-gallery':     'mainGalleryToolbar',
    1342                                                 'gallery-edit':     'galleryEditToolbar',
    1343                                                 'gallery-add':      'galleryAddToolbar'
     1457                                                'main-insert':          'mainInsertToolbar',
     1458                                                'main-gallery':         'mainGalleryToolbar',
     1459                                                'gallery-edit':         'galleryEditToolbar',
     1460                                                'gallery-add':          'galleryAddToolbar',
     1461                                                'main-playlist':        'mainPlaylistToolbar',
     1462                                                'playlist-edit':        'playlistEditToolbar',
     1463                                                'playlist-add':         'playlistAddToolbar',
     1464                                                'main-video-playlist': 'mainVideoPlaylistToolbar',
     1465                                                'video-playlist-edit': 'videoPlaylistEditToolbar',
     1466                                                'video-playlist-add': 'videoPlaylistAddToolbar'
    13441467                                        }
    13451468                                };
    13461469
     
    13841507                        });
    13851508                },
    13861509
     1510                playlistMenu: function( view ) {
     1511                        var lastState = this.lastState(),
     1512                                previous = lastState && lastState.id,
     1513                                frame = this;
     1514
     1515                        view.set({
     1516                                cancel: {
     1517                                        text:     l10n.cancelPlaylistTitle,
     1518                                        priority: 20,
     1519                                        click:    function() {
     1520                                                if ( previous )
     1521                                                        frame.setState( previous );
     1522                                                else
     1523                                                        frame.close();
     1524                                        }
     1525                                },
     1526                                separateCancel: new media.View({
     1527                                        className: 'separator',
     1528                                        priority: 60
     1529                                })
     1530                        });
     1531                },
     1532
     1533                videoPlaylistMenu: function( view ) {
     1534                        var lastState = this.lastState(),
     1535                                previous = lastState && lastState.id,
     1536                                frame = this;
     1537
     1538                        view.set({
     1539                                cancel: {
     1540                                        text:     l10n.cancelVideoPlaylistTitle,
     1541                                        priority: 20,
     1542                                        click:    function() {
     1543                                                if ( previous )
     1544                                                        frame.setState( previous );
     1545                                                else
     1546                                                        frame.close();
     1547                                        }
     1548                                },
     1549                                separateCancel: new media.View({
     1550                                        className: 'separator',
     1551                                        priority: 80
     1552                                })
     1553                        });
     1554                },
     1555
    13871556                // Content
    13881557                embedContent: function() {
    13891558                        var view = new media.view.Embed({
     
    14891658                        });
    14901659                },
    14911660
     1661                mainPlaylistToolbar: function( view ) {
     1662                        var controller = this;
     1663
     1664                        this.selectionStatusToolbar( view );
     1665
     1666                        view.set( 'playlist', {
     1667                                style:    'primary',
     1668                                text:     l10n.createNewPlaylist,
     1669                                priority: 100,
     1670                                requires: { selection: true },
     1671
     1672                                click: function() {
     1673                                        var selection = controller.state().get('selection'),
     1674                                                edit = controller.state('playlist-edit'),
     1675                                                models = selection.where({ type: 'audio' });
     1676
     1677                                        edit.set( 'library', new media.model.Selection( models, {
     1678                                                props:    selection.props.toJSON(),
     1679                                                multiple: true
     1680                                        }) );
     1681
     1682                                        this.controller.setState('playlist-edit');
     1683                                }
     1684                        });
     1685                },
     1686
     1687                mainVideoPlaylistToolbar: function( view ) {
     1688                        var controller = this;
     1689
     1690                        this.selectionStatusToolbar( view );
     1691
     1692                        view.set( 'video-playlist', {
     1693                                style:    'primary',
     1694                                text:     l10n.createNewVideoPlaylist,
     1695                                priority: 100,
     1696                                requires: { selection: true },
     1697
     1698                                click: function() {
     1699                                        var selection = controller.state().get('selection'),
     1700                                                edit = controller.state('video-playlist-edit'),
     1701                                                models = selection.where({ type: 'video' });
     1702
     1703                                        edit.set( 'library', new media.model.Selection( models, {
     1704                                                props:    selection.props.toJSON(),
     1705                                                multiple: true
     1706                                        }) );
     1707
     1708                                        this.controller.setState('video-playlist-edit');
     1709                                }
     1710                        });
     1711                },
     1712
    14921713                featuredImageToolbar: function( toolbar ) {
    14931714                        this.createSelectToolbar( toolbar, {
    14941715                                text:  l10n.setFeaturedImage,
     
    15511772                                        }
    15521773                                }
    15531774                        }) );
     1775                },
     1776
     1777                playlistEditToolbar: function() {
     1778                        var editing = this.state().get('editing');
     1779                        this.toolbar.set( new media.view.Toolbar({
     1780                                controller: this,
     1781                                items: {
     1782                                        insert: {
     1783                                                style:    'primary',
     1784                                                text:     editing ? l10n.updatePlaylist : l10n.insertPlaylist,
     1785                                                priority: 120,
     1786                                                requires: { library: true },
     1787
     1788                                                click: function() {
     1789                                                        var controller = this.controller,
     1790                                                                state = controller.state();
     1791
     1792                                                        controller.close();
     1793                                                        state.trigger( 'update', state.get('library') );
     1794
     1795                                                        // Restore and reset the default state.
     1796                                                        controller.setState( controller.options.state );
     1797                                                        controller.reset();
     1798                                                }
     1799                                        }
     1800                                }
     1801                        }) );
     1802                },
     1803
     1804                playlistAddToolbar: function() {
     1805                        this.toolbar.set( new media.view.Toolbar({
     1806                                controller: this,
     1807                                items: {
     1808                                        insert: {
     1809                                                style:    'primary',
     1810                                                text:     l10n.addToPlaylist,
     1811                                                priority: 120,
     1812                                                requires: { selection: true },
     1813
     1814                                                click: function() {
     1815                                                        var controller = this.controller,
     1816                                                                state = controller.state(),
     1817                                                                edit = controller.state('playlist-edit');
     1818
     1819                                                        edit.get('library').add( state.get('selection').models );
     1820                                                        state.trigger('reset');
     1821                                                        controller.setState('playlist-edit');
     1822                                                }
     1823                                        }
     1824                                }
     1825                        }) );
     1826                },
     1827
     1828                videoPlaylistEditToolbar: function() {
     1829                        var editing = this.state().get('editing');
     1830                        this.toolbar.set( new media.view.Toolbar({
     1831                                controller: this,
     1832                                items: {
     1833                                        insert: {
     1834                                                style:    'primary',
     1835                                                text:     editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist,
     1836                                                priority: 140,
     1837                                                requires: { library: true },
     1838
     1839                                                click: function() {
     1840                                                        var controller = this.controller,
     1841                                                                state = controller.state();
     1842
     1843                                                        controller.close();
     1844                                                        state.trigger( 'update', state.get('library') );
     1845
     1846                                                        // Restore and reset the default state.
     1847                                                        controller.setState( controller.options.state );
     1848                                                        controller.reset();
     1849                                                }
     1850                                        }
     1851                                }
     1852                        }) );
     1853                },
     1854
     1855                videoPlaylistAddToolbar: function() {
     1856                        this.toolbar.set( new media.view.Toolbar({
     1857                                controller: this,
     1858                                items: {
     1859                                        insert: {
     1860                                                style:    'primary',
     1861                                                text:     l10n.addToVideoPlaylist,
     1862                                                priority: 140,
     1863                                                requires: { selection: true },
     1864
     1865                                                click: function() {
     1866                                                        var controller = this.controller,
     1867                                                                state = controller.state(),
     1868                                                                edit = controller.state('video-playlist-edit');
     1869
     1870                                                        edit.get('library').add( state.get('selection').models );
     1871                                                        state.trigger('reset');
     1872                                                        controller.setState('video-playlist-edit');
     1873                                                }
     1874                                        }
     1875                                }
     1876                        }) );
    15541877                }
    15551878        });
    15561879
     
    37134036        });
    37144037
    37154038        /**
     4039         * wp.media.view.Settings.Gallery
     4040         */
     4041        media.view.Settings.Playlist = media.view.Settings.extend({
     4042                className: 'playlist-settings',
     4043                template:  media.template('playlist-settings')
     4044        });
     4045
     4046        /**
    37164047         * wp.media.view.Attachment.Details
    37174048         */
    37184049        media.view.Attachment.Details = media.view.Attachment.extend({
  • src/wp-includes/js/mediaelement/wp-mediaelement.css

     
    1313.me-cannotplay {
    1414        width: auto !important;
    1515}
     16
     17.wp-playlist {
     18        border: 1px solid #ccc;
     19        padding: 10px;
     20        margin: 12px 0 18px;
     21        font-size: 85%;
     22}
     23
     24.wp-playlist .mejs-container {
     25        margin: 0;
     26}
     27
     28.wp-playlist .mejs-controls .mejs-button button {
     29        outline: 0;
     30}
     31
     32.wp-playlist-audio {
     33
     34}
     35
     36.wp-playlist-video {
     37
     38}
     39
     40.wp-playlist-light {
     41        background: #fff;
     42}
     43
     44.wp-playlist-dark {
     45        color: #fff;
     46        background: #000;
     47}
     48
     49.wp-playlist-current-item {
     50        overflow: hidden;
     51        margin-bottom: 10px;
     52}
     53
     54.wp-playlist-current-item img {
     55        float: left;
     56        max-width: 60px;
     57        height: auto;
     58        margin-right: 10px;
     59}
     60
     61.wp-playlist-caption {
     62}
     63
     64.wp-caption-meta {
     65        display: block;
     66}
     67
     68.wp-caption-title {
     69        font-size: 100%;
     70}
     71
     72.wp-caption-album {
     73        font-style: italic;
     74}
     75
     76.wp-caption-artist {
     77        font-size: 80%;
     78        text-transform: uppercase;
     79}
     80
     81.wp-caption-by {
     82        font-size: 65%;
     83        font-weight: bold;
     84}
     85
     86.wp-playlist-item-length {
     87        position: absolute;
     88        right: 0;
     89        top: 0;
     90}
     91
     92.wp-playlist-tracks {
     93        margin-top: 10px;
     94        border-top: 1px solid #ccc;
     95}
     96
     97.wp-playlist-item {
     98        position: relative;
     99        cursor: pointer;
     100        border-bottom: 1px solid #ccc;
     101}
     102 No newline at end of file
  • src/wp-includes/js/mediaelement/wp-playlist.js

     
     1"use strict";
     2
     3/*globals window, document, $, jQuery */
     4
     5(function ($, _, Backbone) {
     6
     7        var WPPlaylistView = Backbone.View.extend({
     8                index : 0,
     9
     10                itemTemplate : wp.template('wp-playlist-item'),
     11
     12                currentTemplate : wp.template('wp-playlist-current-item'),
     13
     14                initialize : function () {
     15                        this.data = $.parseJSON( this.$('script').html() );
     16                        this.playerNode = this.$( this.data.type );
     17
     18                        this.tracks = new Backbone.Collection( this.data.tracks );
     19                        this.current = this.tracks.first();
     20                        this.currentNode = this.$( '.wp-playlist-current-item' );
     21                        this.renderCurrent();
     22
     23                        if ( this.data.tracklist ) {
     24                                this.renderTracks();
     25                        }
     26
     27                        this.playerNode.attr( 'src', this.current.get('src') );
     28
     29                        _.bindAll( this, 'bindPlayer', 'ended', 'clickTrack' );
     30
     31                        new MediaElementPlayer( this.playerNode.get(0), {
     32                                pluginPath : '/wp-includes/js/mediaelement/',
     33                                success: this.bindPlayer
     34                        });
     35                },
     36
     37                renderCurrent : function () {
     38                        this.currentNode.html( this.currentTemplate( this.current.toJSON() ) );
     39                },
     40
     41                renderTracks : function () {
     42                        var that = this, i = 1, tracklist = $( '<div class="wp-playlist-tracks"></div>' );
     43                        this.tracks.each(function (model) {
     44                                model.set( 'index', i );
     45                                tracklist.append( that.itemTemplate( model.toJSON() ) );
     46                                i += 1;
     47                        });
     48                        this.$el.append( tracklist );
     49                },
     50
     51                events : {
     52                        'click .wp-playlist-item' : 'clickTrack',
     53                        'click .wp-playlist-next' : 'next',
     54                        'click .wp-playlist-prev' : 'prev',
     55                },
     56
     57                bindPlayer : function (mejs) {
     58                        this.player = mejs;
     59                        this.player.addEventListener( 'ended', this.ended );
     60                },
     61
     62                clickTrack : function (e) {
     63                        this.index = this.$( '.wp-playlist-item' ).index( e.currentTarget );
     64                        this.setCurrent();
     65                },
     66
     67                ended : function () {
     68                        if ( this.index + 1 < this.tracks.length ) {
     69                                this.next();
     70                        } else {
     71                                this.player.pause();
     72                                this.index = 0;
     73                                this.current = this.tracks.at( this.index );
     74                                this.playerNode.attr( 'src', this.current.get( 'src' ) );
     75                                this.player.load();
     76                        }
     77                },
     78
     79                next : function () {
     80                        this.index = this.index + 1 >= this.tracks.length ? 0 : this.index + 1;
     81                        this.setCurrent();
     82                },
     83
     84                prev : function () {
     85                        this.index = this.index - 1 < 0 ? this.tracks.length - 1 : this.index - 1;
     86                        this.setCurrent();
     87                },
     88
     89                setCurrent : function () {
     90                        this.current = this.tracks.at( this.index );
     91                        this.renderCurrent();
     92
     93                        this.player.pause();
     94                        this.playerNode.attr( 'src', this.current.get( 'src' ) );
     95                        this.player.load();
     96                        this.player.play();
     97                }
     98        });
     99
     100    $(document).ready(function () {
     101                $('.wp-playlist').each(function () {
     102                        return new WPPlaylistView({ el: this });
     103                });
     104    });
     105
     106}(jQuery, _, Backbone));
     107 No newline at end of file
  • src/wp-includes/js/plupload/handlers.js

     
    2222                .appendTo( jQuery('#media-items' ) );
    2323
    2424        // Disable submit
    25         jQuery('#insert-gallery').prop('disabled', true);
     25        jQuery('#insert-gallery, #insert-playlist').prop('disabled', true);
    2626}
    2727
    2828function uploadStart() {
     
    4747
    4848        if ( max > hundredmb && file.size > hundredmb ) {
    4949                setTimeout(function(){
    50                        
     50
    5151                        if ( file.status < 3 && file.loaded === 0 ) { // not uploading
    5252                                wpFileError(file, pluploadL10n.big_upload_failed.replace('%1$s', '<a class="uploader-html" href="#">').replace('%2$s', '</a>'));
    5353                                up.stop(); // stops the whole queue
     
    6464        // Just one file, no need for collapsible part
    6565        if ( items.length == 1 ) {
    6666                items.addClass('open').find('.slidetoggle').show();
    67                 jQuery('.insert-gallery').hide();
     67                jQuery('.insert-gallery, .insert-playlist').hide();
    6868        } else if ( items.length > 1 ) {
    6969                items.removeClass('open');
    70                 // Only show Gallery button when there are at least two files.
    71                 jQuery('.insert-gallery').show();
     70                // Only show Gallery/Playlist buttons when there are at least two files.
     71                jQuery('.insert-gallery, .insert-playlist').show();
    7272        }
    7373
    7474        // Only show Save buttons when there is at least one file.
     
    171171                        success: function( ){
    172172                                var type,
    173173                                        item = jQuery('#media-item-' + fileObj.id);
    174                                
     174
    175175                                if ( type = jQuery('#type-of-' + fileObj.id).val() )
    176176                                        jQuery('#' + type + '-counter').text(jQuery('#' + type + '-counter').text()-0+1);
    177177
     
    257257}
    258258
    259259function uploadComplete() {
    260         jQuery('#insert-gallery').prop('disabled', false);
     260        jQuery('#insert-gallery, #insert-playlist').prop('disabled', false);
    261261}
    262262
    263263function switchUploader(s) {
  • src/wp-includes/js/swfupload/handlers.js

     
    2525        jQuery('.progress', '#media-item-' + fileObj.id).show();
    2626
    2727        // Disable submit and enable cancel
    28         jQuery('#insert-gallery').prop('disabled', true);
     28        jQuery('#insert-gallery, #insert-playlist').prop('disabled', true);
    2929        jQuery('#cancel-upload').prop('disabled', false);
    3030}
    3131
    3232function uploadStart(fileObj) {
    3333        try {
    3434                if ( typeof topWin.tb_remove != 'undefined' )
    35                         topWin.jQuery('#TB_overlay').unbind('click', topWin.tb_remove); 
     35                        topWin.jQuery('#TB_overlay').unbind('click', topWin.tb_remove);
    3636        } catch(e){}
    3737
    3838        return true;
     
    212212        else
    213213                jQuery('.savebutton').hide();
    214214
    215         // Only show Gallery button when there are at least two files.
    216         if ( items.length > 1 )
    217                 jQuery('.insert-gallery').show();
    218         else
    219                 jQuery('.insert-gallery').hide();
     215        // Only show Gallery/Playlist buttons when there are at least two files.
     216        if ( items.length > 1 ) {
     217                jQuery('.insert-gallery, .insert-playlist').show();
     218        } else {
     219                jQuery('.insert-gallery, .insert-playlist').hide();
     220        }
    220221}
    221222
    222223function uploadSuccess(fileObj, serverData) {
     
    238239        // If no more uploads queued, enable the submit button
    239240        if ( swfu.getStats().files_queued == 0 ) {
    240241                jQuery('#cancel-upload').prop('disabled', true);
    241                 jQuery('#insert-gallery').prop('disabled', false);
     242                jQuery('#insert-gallery, #insert-playlist').prop('disabled', false);
    242243        }
    243244}
    244245
  • src/wp-includes/js/tinymce/langs/wp-langs-en.js

     
    478478                add_audio: "Add Audio",
    479479                editgallery: "Edit Gallery",
    480480                delgallery: "Delete Gallery",
     481                editplaylist: "Edit Playlist",
     482                delplaylist: "Delete Playlist",
    481483                wp_fullscreen_desc: "Distraction Free Writing mode (Alt + Shift + W)"
    482484        });
    483485
  • src/wp-includes/js/tinymce/plugins/wpplaylist/plugin.js

     
     1/* global tinymce */
     2tinymce.PluginManager.add('wpplaylist', function( editor ) {
     3
     4        function parsePlaylist( content ) {
     5                return content
     6                        .replace( /\[video-playlist([^\]]*)\]/g, function( match, attr ) {
     7                                var data = tinymce.DOM.encode( attr );
     8
     9                                return '<span class="post-format-video"></span><img src="' + tinymce.Env.transparentSrc + '" class="wp-video-playlist mceItem" ' +
     10                                        'title="video-playlist' + data + '" data-mce-resize="false" data-mce-placeholder="1" />';
     11                        })
     12                        .replace( /\[playlist([^\]]*)\]/g, function( match, attr ) {
     13                                var data = tinymce.DOM.encode( attr );
     14
     15                                return '<span class="post-format-audio"></span><img src="' + tinymce.Env.transparentSrc + '" class="wp-playlist mceItem" ' +
     16                                        'title="playlist' + data + '" data-mce-resize="false" data-mce-placeholder="1" />';
     17                        });
     18        }
     19
     20        function getPlaylist( content ) {
     21                function getAttr( str, name ) {
     22                        name = new RegExp( name + '=\"([^\"]+)\"', 'g' ).exec( str );
     23                        return name ? tinymce.DOM.decode( name[1] ) : '';
     24                }
     25
     26                return content.replace( /(?:<p[^>]*>)*(<img[^>]+>)(?:<\/p>)*/g, function( match, image ) {
     27                        var cls = getAttr( image, 'class' );
     28
     29                        if ( cls.indexOf('wp-playlist') !== -1 ) {
     30                                return '<p>['+ tinymce.trim( getAttr( image, 'title' ) ) +']</p>';
     31                        } else if ( cls.indexOf('wp-video-playlist') !== -1 ) {
     32                                return '<p>['+ tinymce.trim( getAttr( image, 'title' ) ) +']</p>';
     33                        }
     34
     35                        return match;
     36                });
     37        }
     38
     39        // Register the command so that it can be invoked by using tinyMCE.activeEditor.execCommand('...');
     40        editor.addCommand( 'WP_Playlist', function() {
     41                var frame, node;
     42
     43                // Check if the `wp.media.playlist` API exists.
     44                if ( typeof wp === 'undefined' || ! wp.media || ! wp.media.playlist || ! wp.media['video-playlist'] ) {
     45                        return;
     46                }
     47
     48                node = editor.selection.getNode();
     49
     50                // Make sure we've selected a playlist node.
     51                if ( node.nodeName === 'IMG' ) {
     52                        if ( editor.dom.hasClass( node, 'wp-playlist' ) ) {
     53                                frame = wp.media.playlist.edit( '[' + editor.dom.getAttrib( node, 'title' ) + ']' );
     54                                frame.state('playlist-edit').on( 'update', function( selection ) {
     55                                        var shortcode = wp.media.playlist.shortcode( selection ).string().slice( 1, -1 );
     56                                        editor.dom.setAttrib( node, 'title', shortcode );
     57                                });
     58                        } else if ( editor.dom.hasClass( node, 'wp-video-playlist' ) ) {
     59                                frame = wp.media['video-playlist'].edit( '[' + editor.dom.getAttrib( node, 'title' ) + ']' );
     60                                frame.state('video-playlist-edit').on( 'update', function( selection ) {
     61                                        var shortcode = wp.media['video-playlist'].shortcode( selection ).string().slice( 1, -1 );
     62                                        editor.dom.setAttrib( node, 'title', shortcode );
     63                                });
     64                        }
     65                }
     66        });
     67/*
     68        editor.on( 'init', function( e ) {
     69        //      _createButtons()
     70
     71                // iOS6 doesn't show the buttons properly on click, show them on 'touchstart'
     72                if ( 'ontouchstart' in window ) {
     73                        editor.dom.events.bind( editor.getBody(), 'touchstart', function( e ) {
     74                                var target = e.target;
     75
     76                                if ( target.nodeName == 'IMG' && editor.dom.hasClass( target, 'wp-playlist' ) ) {
     77                                        editor.selection.select( target );
     78                                        editor.dom.events.cancel( e );
     79                                        editor.plugins.wordpress._hideButtons();
     80                                        editor.plugins.wordpress._showButtons( target, 'wp_playlistbtns' );
     81                                }
     82                        });
     83                }
     84        });
     85*/
     86        editor.on( 'mouseup', function( e ) {
     87                if ( e.target.nodeName === 'IMG' && ( editor.dom.hasClass( e.target, 'wp-playlist' ) || editor.dom.hasClass( e.target, 'wp-video-playlist' ) ) ) {
     88                        // Don't trigger on right-click
     89                        if ( e.button !== 2 ) {
     90                                if ( editor.dom.hasClass( e.target, 'wp-playlist-selected' ) ) {
     91                                        editor.execCommand('WP_Playlist');
     92                                        editor.dom.removeClass( e.target, 'wp-playlist-selected' );
     93                                } else {
     94                                        editor.dom.addClass( e.target, 'wp-playlist-selected' );
     95                                }
     96                        }
     97                } else {
     98                        editor.dom.removeClass( editor.dom.select( 'img.wp-playlist-selected' ), 'wp-playlist-selected' );
     99                }
     100        });
     101
     102        // Display 'playlist' instead of img in element path
     103        editor.on( 'ResolveName', function( e ) {
     104                var dom = editor.dom,
     105                        target = e.target;
     106
     107                if ( target.nodeName === 'IMG' && dom.hasClass( target, 'wp-playlist' ) ) {
     108                        e.name = 'playlist';
     109                } else if ( target.nodeName === 'IMG' && dom.hasClass( target, 'wp-video-playlist' ) ) {
     110                        e.name = 'video-playlist';
     111                }
     112        });
     113
     114        editor.on( 'BeforeSetContent', function( e ) {
     115                e.content = parsePlaylist( e.content );
     116        });
     117
     118        editor.on( 'PostProcess', function( e ) {
     119                if ( e.get ) {
     120                        e.content = getPlaylist( e.content );
     121                }
     122        });
     123
     124        return {
     125                _do_playlist: parsePlaylist,
     126                _get_playlist: getPlaylist
     127        };
     128});
     129 No newline at end of file
  • src/wp-includes/js/tinymce/skins/wordpress/wp-content.css

     
    108108    border-top: 3px dotted #bbb;
    109109}
    110110
    111 .mce-content-body img.wp-gallery {
     111.mce-content-body img.wp-gallery,
     112.mce-content-body img.wp-playlist,
     113.mce-content-body img.wp-video-playlist{
    112114        border: 1px dashed #888;
    113115        background: #f2f2f2 url("images/gallery.png") no-repeat scroll center center;
    114116        width: 99%;
     
    117119        cursor: pointer;
    118120}
    119121
    120 .mce-content-body img.wp-gallery:hover {
     122.mce-content-body img.wp-playlist {
     123        background: #f2f2f2;
     124}
     125
     126.mce-content-body img.wp-video-playlist {
     127        background-image: url("images/embedded.png");
     128}
     129
     130.mce-content-body img.wp-gallery:hover, .mce-content-body img.wp-playlist:hover {
    121131        background-color: #ededed;
    122132        border-style: solid;
    123133}
    124134
    125 .mce-content-body img.wp-gallery.wp-gallery-selected {
     135.mce-content-body img.wp-gallery.wp-gallery-selected, .mce-content-body img.wp-playlist.wp-playlist-selected {
    126136        background-color: #d8d8d8;
    127137        border-style: solid;
    128138}
  • src/wp-includes/media-template.php

     
    411411                </label>
    412412        </script>
    413413
     414        <script type="text/html" id="tmpl-playlist-settings">
     415                <h3><?php _e('Playlist Settings'); ?></h3>
     416
     417                <label class="setting">
     418                        <span><?php _e( 'Random Order' ); ?></span>
     419                        <input type="checkbox" data-setting="_orderbyRandom" />
     420                </label>
     421
     422                <label class="setting">
     423                        <span><?php _e('Style'); ?></span>
     424                        <select class="style" data-setting="style">
     425                                <option value="light">
     426                                        <?php esc_attr_e('Light'); ?>
     427                                </option>
     428                                <option value="dark">
     429                                        <?php esc_attr_e('Dark'); ?>
     430                                </option>
     431                        </select>
     432                </label>
     433
     434                <label class="setting">
     435                        <span><?php _e( 'Show Tracklist' ); ?></span>
     436                        <input type="checkbox" data-setting="tracklist" />
     437                </label>
     438
     439                <label class="setting">
     440                        <span><?php _e( 'Show Track Numbers' ); ?></span>
     441                        <input type="checkbox" data-setting="tracknums" />
     442                </label>
     443
     444                <label class="setting">
     445                        <span><?php _e( 'Show Images' ); ?></span>
     446                        <input type="checkbox" data-setting="images" />
     447                </label>
     448        </script>
     449
    414450        <script type="text/html" id="tmpl-embed-link-settings">
    415451                <label class="setting">
    416452                        <span><?php _e('Title'); ?></span>
  • src/wp-includes/media.php

     
    892892}
    893893
    894894/**
     895 * The Playlist shortcode.
     896 *
     897 * This implements the functionality of the Playlist Shortcode for displaying
     898 * WordPress audio or video files on a post.
     899 *
     900 * @since 3.9.0
     901 *
     902 * @param array $attr Attributes of the shortcode.
     903 * @return string $type Type of playlist. Defaults to audio, video is also supported
     904 */
     905function wp_get_playlist( $attr, $type ) {
     906        $post = get_post();
     907
     908        if ( ! in_array( $type, array( 'audio', 'video' ) ) ) {
     909                return '';
     910        }
     911
     912        static $instance = 0;
     913        $instance++;
     914
     915        if ( ! empty( $attr['ids'] ) ) {
     916                // 'ids' is explicitly ordered, unless you specify otherwise.
     917                if ( empty( $attr['orderby'] ) )
     918                        $attr['orderby'] = 'post__in';
     919                $attr['include'] = $attr['ids'];
     920        }
     921
     922        // Allow plugins/themes to override the default gallery template.
     923        $output = apply_filters( 'post_playlist', '', $attr, $type );
     924        if ( $output != '' )
     925                return $output;
     926
     927        // We're trusting author input, so let's at least make sure it looks like a valid orderby statement
     928        if ( isset( $attr['orderby'] ) ) {
     929                $attr['orderby'] = sanitize_sql_orderby( $attr['orderby'] );
     930                if ( ! $attr['orderby'] )
     931                        unset( $attr['orderby'] );
     932        }
     933
     934        extract( shortcode_atts( array(
     935                'order'         => 'ASC',
     936                'orderby'       => 'menu_order ID',
     937                'id'            => $post ? $post->ID : 0,
     938                'include'       => '',
     939                'exclude'   => '',
     940                'style'         => 'light',
     941                'tracklist' => 'audio' === $type,
     942                'tracknums' => 'audio' === $type,
     943                'images'        => true
     944        ), $attr, 'playlist' ) );
     945
     946        $id = intval( $id );
     947        if ( 'RAND' == $order )
     948                $orderby = 'none';
     949
     950        if ( ! empty( $include ) ) {
     951                $_attachments = get_posts( array( 'include' => $include, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => $type, 'order' => $order, 'orderby' => $orderby ) );
     952
     953                $attachments = array();
     954                foreach ( $_attachments as $key => $val ) {
     955                        $attachments[$val->ID] = $_attachments[$key];
     956                }
     957        } elseif ( ! empty( $exclude ) ) {
     958                $attachments = get_children( array('post_parent' => $id, 'exclude' => $exclude, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => $type, 'order' => $order, 'orderby' => $orderby ) );
     959        } else {
     960                $attachments = get_children( array('post_parent' => $id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => $type, 'order' => $order, 'orderby' => $orderby) );
     961        }
     962
     963        if ( empty( $attachments ) )
     964                return '';
     965
     966        if ( is_feed() ) {
     967                $output = "\n";
     968                foreach ( $attachments as $att_id => $attachment )
     969                        $output .= wp_get_attachment_link( $att_id, $size, true ) . "\n";
     970
     971                return $output;
     972        }
     973
     974        $supports_thumbs = ( current_theme_supports( 'post-thumbnails', "attachment:$type" ) && post_type_supports( "attachment:$type", 'thumbnail' ) ) || $images;
     975
     976        $data = compact( 'type', 'tracklist', 'tracknums', 'images', 'style' );
     977        $tracks = array();
     978        foreach ( $attachments as $attachment ) {
     979                $url = wp_get_attachment_url( $attachment->ID );
     980                $ftype = wp_check_filetype( $url, wp_get_mime_types() );
     981                $track = array(
     982                        'type' => $type,
     983                        'src' => $url,
     984                        'type' => $ftype['ext'],
     985                        'title' => get_the_title( $attachment->ID ),
     986                        'caption' => wptexturize( $attachment->post_excerpt ),
     987                        'description' => wptexturize( $attachment->post_content )
     988                );
     989
     990                $meta = wp_get_attachment_metadata( $attachment->ID );
     991                if ( ! empty( $meta ) ) {
     992                        $track['meta'] = array();
     993
     994                        $keys = array( 'title', 'artist', 'band', 'album', 'genre', 'year', 'length', 'length_formatted' );
     995                        foreach ( $keys as $key ) {
     996                                if ( ! empty( $meta[ $key ] ) ) {
     997                                        $track['meta'][ $key ] = $meta[ $key ];
     998                                }
     999                        }
     1000                }
     1001
     1002                if ( $supports_thumbs ) {
     1003                        $id = get_post_thumbnail_id( $attachment->ID );
     1004                        if ( ! empty( $id ) ) {
     1005                                list( $src, $width, $height ) = wp_get_attachment_image_src( $id, 'full' );
     1006                                $track['image'] = compact( 'src', 'width', 'height' );
     1007                        }
     1008                }
     1009
     1010                $tracks[] = $track;
     1011        }
     1012        $data['tracks'] = $tracks;
     1013
     1014        ob_start();
     1015
     1016        if ( 1 === $instance ):
     1017                wp_enqueue_style( 'wp-mediaelement' );
     1018                wp_enqueue_script( 'wp-playlist' );
     1019?>
     1020<script type="text/html" id="tmpl-wp-playlist-current-item">
     1021        <# if ( data.image ) { #>
     1022        <img src="{{{ data.image.src }}}"/>
     1023        <# } #>
     1024        <# if ( data.meta.title ) { #>
     1025        <div class="wp-playlist-caption">
     1026                <span class="wp-caption-meta wp-caption-title">&#8220;{{{ data.meta.title }}}&#8221;</span>
     1027                <span class="wp-caption-meta wp-caption-album">{{{ data.meta.album }}}</span>
     1028                <span class="wp-caption-meta wp-caption-artist">{{{ data.meta.artist }}}</span>
     1029        </div>
     1030        <# } else { #>
     1031        <div class="wp-playlist-caption">{{{ data.caption }}}</div>
     1032        <# } #>
     1033</script>
     1034<script type="text/html" id="tmpl-wp-playlist-item">
     1035        <div class="wp-playlist-item">
     1036                <# if ( data.meta.title ) { #>
     1037                <div class="wp-playlist-caption">
     1038                        {{{ data.index }}}.&nbsp;
     1039                        <span class="wp-caption-title">&#8220;{{{ data.meta.title }}}&#8221;</span>
     1040                        <span class="wp-caption-by"><?php _e( 'by' ) ?></span>
     1041                        <span class="wp-caption-artist">{{{ data.meta.artist }}}</span>
     1042                </div>
     1043                <# } else { #>
     1044                <div class="wp-playlist-caption">{{{ data.index }}}. {{{ data.caption }}}</div>
     1045                <# } #>
     1046                <# if ( data.meta.length_formatted ) { #>
     1047                <div class="wp-playlist-item-length">{{{ data.meta.length_formatted }}}</div>
     1048                <# } #>
     1049        </div>
     1050</script>
     1051        <?php endif ?>
     1052<div class="wp-playlist wp-<?php echo $type ?>-playlist wp-playlist-<?php echo $style ?>">
     1053        <div class="wp-playlist-current-item"></div>
     1054        <<?php echo $type ?> controls="controls" preload="metadata"></<?php echo $type ?>>
     1055        <div class="wp-playlist-next"></div>
     1056        <div class="wp-playlist-prev"></div>
     1057        <noscript>
     1058
     1059        </noscript>
     1060        <script type="application/json"><?php echo json_encode( $data ) ?></script>
     1061</div>
     1062        <?php
     1063        $output = ob_get_clean();
     1064
     1065        return $output;
     1066}
     1067
     1068function wp_playlist_shortcode( $attr ) {
     1069        return wp_get_playlist( $attr, 'audio' );
     1070}
     1071add_shortcode( 'playlist', 'wp_playlist_shortcode' );
     1072
     1073function wp_video_playlist_shortcode( $attr ) {
     1074        return wp_get_playlist( $attr, 'video' );
     1075}
     1076add_shortcode( 'video-playlist', 'wp_video_playlist_shortcode' );
     1077
     1078/**
    8951079 * Provide a No-JS Flash fallback as a last resort for audio / video
    8961080 *
    8971081 * @since 3.6.0
     
    19822166                'mediaLibraryTitle'  => __( 'Media Library' ),
    19832167                'insertMediaTitle'   => __( 'Insert Media' ),
    19842168                'createNewGallery'   => __( 'Create a new gallery' ),
     2169                'createNewPlaylist'   => __( 'Create a new playlist' ),
     2170                'createNewVideoPlaylist'   => __( 'Create a new video playlist' ),
    19852171                'returnToLibrary'    => __( '&#8592; Return to library' ),
    19862172                'allMediaItems'      => __( 'All media items' ),
    19872173                'noItemsFound'       => __( 'No items found.' ),
     
    20052191                'addToGallery'       => __( 'Add to gallery' ),
    20062192                'addToGalleryTitle'  => __( 'Add to Gallery' ),
    20072193                'reverseOrder'       => __( 'Reverse order' ),
     2194
     2195                // Playlist
     2196                'playlistDragInfo'    => __( 'Drag and drop to reorder tracks.' ),
     2197                'createPlaylistTitle' => __( 'Create Playlist' ),
     2198                'editPlaylistTitle'   => __( 'Edit Playlist' ),
     2199                'cancelPlaylistTitle' => __( '&#8592; Cancel Playlist' ),
     2200                'insertPlaylist'      => __( 'Insert playlist' ),
     2201                'updatePlaylist'      => __( 'Update playlist' ),
     2202                'addToPlaylist'       => __( 'Add to playlist' ),
     2203                'addToPlaylistTitle'  => __( 'Add to Playlist' ),
     2204
     2205                // Video Playlist
     2206                'videoPlaylistDragInfo'    => __( 'Drag and drop to reorder videos.' ),
     2207                'createVideoPlaylistTitle' => __( 'Create Video Playlist' ),
     2208                'editVideoPlaylistTitle'   => __( 'Edit Video Playlist' ),
     2209                'cancelVideoPlaylistTitle' => __( '&#8592; Cancel Video Playlist' ),
     2210                'insertVideoPlaylist'      => __( 'Insert Video playlist' ),
     2211                'updateVideoPlaylist'      => __( 'Update Video playlist' ),
     2212                'addToVideoPlaylist'       => __( 'Add to Video playlist' ),
     2213                'addToVideoPlaylistTitle'  => __( 'Add to Video Playlist' ),
    20082214        );
    20092215
    20102216        $settings = apply_filters( 'media_view_settings', $settings, $post );
  • src/wp-includes/script-loader.php

     
    311311                'pluginPath' => includes_url( 'js/mediaelement/', 'relative' ),
    312312        ) );
    313313
     314        $scripts->add( 'wp-playlist', "/wp-includes/js/mediaelement/wp-playlist.js", array( 'wp-util', 'backbone', 'mediaelement' ), false, 1 );
     315
    314316        $scripts->add( 'zxcvbn-async', "/wp-includes/js/zxcvbn-async$suffix.js", array(), '1.0' );
    315317        did_action( 'init' ) && $scripts->localize( 'zxcvbn-async', '_zxcvbnSettings', array(
    316318                'src' => empty( $guessed_url ) ? includes_url( '/js/zxcvbn.min.js' ) : $scripts->base_url . '/wp-includes/js/zxcvbn.min.js',