diff --git a/src/wp-includes/js/media-audiovideo.js b/src/wp-includes/js/media-audiovideo.js new file mode 100644 index 0000000000..173a203ec3 --- /dev/null +++ b/src/wp-includes/js/media-audiovideo.js @@ -0,0 +1,985 @@ +/* global _wpMediaViewsL10n */ + +(function ($, _, Backbone) { + var media = wp.media, l10n = typeof _wpMediaViewsL10n === 'undefined' ? {} : _wpMediaViewsL10n; + + /** + * @mixin + */ + wp.media.mixin = { + + /** + * Pauses every instance of MediaElementPlayer + */ + pauseAllPlayers: function () { + var p; + if ( window.mejs && window.mejs.players ) { + for ( p in window.mejs.players ) { + window.mejs.players[p].pause(); + } + } + }, + + /** + * Utility to identify the user's browser + */ + ua: { + is : function (browser) { + var passes = false, ua = window.navigator.userAgent; + + switch ( browser ) { + case 'oldie': + passes = ua.match(/MSIE [6-8]/gi) !== null; + break; + case 'ie': + passes = ua.match(/MSIE/gi) !== null; + break; + case 'ff': + passes = ua.match(/firefox/gi) !== null; + break; + case 'opera': + passes = ua.match(/OPR/) !== null; + break; + case 'safari': + passes = ua.match(/safari/gi) !== null && ua.match(/chrome/gi) === null; + break; + case 'chrome': + passes = ua.match(/safari/gi) && ua.match(/chrome/gi) !== null; + break; + } + + return passes; + } + }, + + /** + * Specify compatibility for native playback by browser + */ + compat :{ + 'opera' : { + audio: ['ogg', 'wav'], + video: ['ogg', 'webm'] + }, + 'chrome' : { + audio: ['ogg', 'mpeg', 'x-ms-wma'], + video: ['ogg', 'webm', 'mp4', 'm4v', 'mpeg'] + }, + 'ff' : { + audio: ['ogg', 'mpeg'], + video: ['ogg', 'webm'] + }, + 'safari' : { + audio: ['mpeg', 'wav'], + video: ['mp4', 'm4v', 'mpeg', 'x-ms-wmv', 'quicktime'] + }, + 'ie' : { + audio: ['mpeg'], + video: ['mp4', 'm4v', 'mpeg'] + } + }, + + /** + * Determine if the passed media contains a that provides + * native playback in the user's browser + * + * @param {jQuery} media + * @returns {Boolean} + */ + isCompatible: function ( media ) { + if ( ! media.find( 'source' ).length ) { + return false; + } + + var ua = this.ua, test = false, found = false, sources; + + if ( ua.is( 'oldIE' ) ) { + return false; + } + + sources = media.find( 'source' ); + + _.find( this.compat, function (supports, browser) { + if ( ua.is( browser ) ) { + found = true; + _.each( sources, function (elem) { + var audio = new RegExp( 'audio\/(' + supports.audio.join('|') + ')', 'gi' ), + video = new RegExp( 'video\/(' + supports.video.join('|') + ')', 'gi' ); + + if ( elem.type.match( video ) !== null || elem.type.match( audio ) !== null ) { + test = true; + } + } ); + } + + return test || found; + } ); + + return test; + }, + + /** + * Override the MediaElement method for removing a player. + * MediaElement tries to pull the audio/video tag out of + * its container and re-add it to the DOM. + */ + removePlayer: function() { + var t = this.player, featureIndex, feature; + + // invoke features cleanup + for ( featureIndex in t.options.features ) { + feature = t.options.features[featureIndex]; + if ( t['clean' + feature] ) { + try { + t['clean' + feature](t); + } catch (e) {} + } + } + + if ( ! t.isDynamic ) { + t.$node.remove(); + } + + if ( 'native' !== t.media.pluginType ) { + t.media.remove(); + } + + delete window.mejs.players[t.id]; + + t.container.remove(); + t.globalUnbind(); + delete t.node.player; + }, + + /** + * Allows any class that has set 'player' to a MediaElementPlayer + * instance to remove the player when listening to events. + * + * Examples: modal closes, shortcode properties are removed, etc. + */ + unsetPlayer : function() { + if ( this.player ) { + wp.media.mixin.pauseAllPlayers(); + wp.media.mixin.removePlayer.apply( this ); + this.player = false; + } + } + }; + + /** + * Autowire "collection"-type shortcodes + */ + wp.media.playlist = new wp.media.collection({ + tag: 'playlist', + type : 'audio', + editTitle : l10n.editPlaylistTitle, + defaults : { + id: wp.media.view.settings.post.id, + style: 'light', + tracklist: true, + tracknumbers: true, + images: true, + artists: true + } + }); + + wp.media['video-playlist'] = new wp.media.collection({ + tag: 'video-playlist', + type : 'video', + editTitle : l10n.editVideoPlaylistTitle, + defaults : { + id: wp.media.view.settings.post.id, + style: 'light', + tracklist: false, + tracknumbers: false, + images: true + } + }); + + /** + * Shortcode modeling for audio + * `edit()` prepares the shortcode for the media modal + * `shortcode()` builds the new shortcode after update + * + * @namespace + */ + wp.media.audio = { + coerce : wp.media.coerce, + + defaults : { + id : wp.media.view.settings.post.id, + src : '', + loop : false, + autoplay : false, + preload : 'none' + }, + + edit : function (data) { + var frame, shortcode = wp.shortcode.next( 'audio', data ).shortcode; + frame = wp.media({ + frame: 'audio', + state: 'audio-details', + metadata: _.defaults( + shortcode.attrs.named, + wp.media.audio.defaults + ) + }); + + return frame; + }, + + shortcode : function (shortcode) { + var self = this; + + _.each( wp.media.audio.defaults, function( value, key ) { + shortcode[ key ] = self.coerce( shortcode, key ); + + if ( value === shortcode[ key ] ) { + delete shortcode[ key ]; + } + }); + + return wp.shortcode.string({ + tag: 'audio', + attrs: shortcode + }); + } + }; + + /** + * Shortcode modeling for video + * `edit()` prepares the shortcode for the media modal + * `shortcode()` builds the new shortcode after update + * + * @namespace + */ + wp.media.video = { + coerce : wp.media.coerce, + + defaults : { + id : wp.media.view.settings.post.id, + src : '', + poster : '', + loop : false, + autoplay : false, + preload : 'metadata', + content : '' + }, + + edit : function (data) { + var frame, + defaults = this.defaults, + shortcode = wp.shortcode.next( 'video', data ).shortcode, + attrs; + + attrs = shortcode.attrs.named; + attrs.content = shortcode.content; + + frame = wp.media({ + frame: 'video', + state: 'video-details', + metadata: _.defaults( attrs, defaults ) + }); + + return frame; + }, + + shortcode : function (shortcode) { + var self = this, content = shortcode.content; + delete shortcode.content; + + _.each( this.defaults, function( value, key ) { + shortcode[ key ] = self.coerce( shortcode, key ); + + if ( value === shortcode[ key ] ) { + delete shortcode[ key ]; + } + }); + + return wp.shortcode.string({ + tag: 'video', + attrs: shortcode, + content: content + }); + } + }; + + /** + * wp.media.model.PostMedia + * + * @constructor + * @augments Backbone.Model + **/ + media.model.PostMedia = Backbone.Model.extend({ + initialize: function() { + this.attachment = false; + }, + + setSource: function ( attachment ) { + this.attachment = attachment; + this.extension = attachment.get('filename' ).split('.').pop(); + + if ( this.get( 'src' ) && this.extension === this.get( 'src' ).split('.').pop() ) { + this.unset( 'src' ); + } + + if ( _.contains( wp.media.view.settings.embedExts, this.extension ) ) { + this.set( this.extension, this.attachment.get( 'url' ) ); + } else { + this.unset( this.extension ); + } + }, + + changeAttachment: function( attachment ) { + var self = this; + + this.setSource( attachment ); + + this.unset( 'src' ); + _.each( _.without( wp.media.view.settings.embedExts, this.extension ), function (ext) { + self.unset( ext ); + } ); + } + }); + + /** + * wp.media.controller.AudioDetails + * + * @constructor + * @augments wp.media.controller.State + * @augments Backbone.Model + */ + media.controller.AudioDetails = media.controller.State.extend({ + defaults: _.defaults({ + id: 'audio-details', + toolbar: 'audio-details', + title: l10n.audioDetailsTitle, + content: 'audio-details', + menu: 'audio-details', + router: false, + attachment: false, + priority: 60, + editing: false + }, media.controller.Library.prototype.defaults ), + + initialize: function( options ) { + this.media = options.media; + media.controller.State.prototype.initialize.apply( this, arguments ); + } + }); + + /** + * wp.media.controller.VideoDetails + * + * @constructor + * @augments wp.media.controller.State + * @augments Backbone.Model + */ + media.controller.VideoDetails = media.controller.State.extend({ + defaults: _.defaults({ + id: 'video-details', + toolbar: 'video-details', + title: l10n.videoDetailsTitle, + content: 'video-details', + menu: 'video-details', + router: false, + attachment: false, + priority: 60, + editing: false + }, media.controller.Library.prototype.defaults ), + + initialize: function( options ) { + this.media = options.media; + media.controller.State.prototype.initialize.apply( this, arguments ); + } + }); + + /** + * wp.media.view.MediaFrame.MediaDetails + * + * @constructor + * @augments wp.media.view.MediaFrame.Select + * @augments wp.media.view.MediaFrame + * @augments wp.media.view.Frame + * @augments wp.media.View + * @augments wp.Backbone.View + * @augments Backbone.View + * @mixes wp.media.controller.StateMachine + */ + media.view.MediaFrame.MediaDetails = media.view.MediaFrame.Select.extend({ + defaults: { + id: 'media', + url: '', + menu: 'media-details', + content: 'media-details', + toolbar: 'media-details', + type: 'link', + priority: 120 + }, + + initialize: function( options ) { + this.DetailsView = options.DetailsView; + this.cancelText = options.cancelText; + this.addText = options.addText; + + this.media = new media.model.PostMedia( options.metadata ); + this.options.selection = new media.model.Selection( this.media.attachment, { multiple: false } ); + media.view.MediaFrame.Select.prototype.initialize.apply( this, arguments ); + }, + + bindHandlers: function() { + var menu = this.defaults.menu; + + media.view.MediaFrame.Select.prototype.bindHandlers.apply( this, arguments ); + + this.on( 'menu:create:' + menu, this.createMenu, this ); + this.on( 'content:render:' + menu, this.renderDetailsContent, this ); + this.on( 'menu:render:' + menu, this.renderMenu, this ); + this.on( 'toolbar:render:' + menu, this.renderDetailsToolbar, this ); + }, + + renderDetailsContent: function() { + var view = new this.DetailsView({ + controller: this, + model: this.state().media, + attachment: this.state().media.attachment + }).render(); + + this.content.set( view ); + }, + + renderMenu: function( view ) { + var lastState = this.lastState(), + previous = lastState && lastState.id, + frame = this; + + view.set({ + cancel: { + text: this.cancelText, + priority: 20, + click: function() { + if ( previous ) { + frame.setState( previous ); + } else { + frame.close(); + } + } + }, + separateCancel: new media.View({ + className: 'separator', + priority: 40 + }) + }); + + }, + + renderDetailsToolbar: function() { + this.toolbar.set( new media.view.Toolbar({ + controller: this, + items: { + select: { + style: 'primary', + text: l10n.update, + priority: 80, + + click: function() { + var controller = this.controller, + state = controller.state(); + + controller.close(); + + state.trigger( 'update', controller.media.toJSON() ); + + // Restore and reset the default state. + controller.setState( controller.options.state ); + controller.reset(); + } + } + } + }) ); + }, + + renderReplaceToolbar: function() { + this.toolbar.set( new media.view.Toolbar({ + controller: this, + items: { + replace: { + style: 'primary', + text: l10n.replace, + priority: 80, + + click: function() { + var controller = this.controller, + state = controller.state(), + selection = state.get( 'selection' ), + attachment = selection.single(); + + controller.media.changeAttachment( attachment ); + + state.trigger( 'replace', controller.media.toJSON() ); + + // Restore and reset the default state. + controller.setState( controller.options.state ); + controller.reset(); + } + } + } + }) ); + }, + + renderAddSourceToolbar: function() { + this.toolbar.set( new media.view.Toolbar({ + controller: this, + items: { + replace: { + style: 'primary', + text: this.addText, + priority: 80, + + click: function() { + var controller = this.controller, + state = controller.state(), + selection = state.get( 'selection' ), + attachment = selection.single(); + + controller.media.setSource( attachment ); + + state.trigger( 'add-source', controller.media.toJSON() ); + + // Restore and reset the default state. + controller.setState( controller.options.state ); + controller.reset(); + } + } + } + }) ); + } + }); + + /** + * wp.media.view.MediaFrame.AudioDetails + * + * @constructor + * @augments wp.media.view.MediaFrame.MediaDetails + * @augments wp.media.view.MediaFrame.Select + * @augments wp.media.view.MediaFrame + * @augments wp.media.view.Frame + * @augments wp.media.View + * @augments wp.Backbone.View + * @augments Backbone.View + * @mixes wp.media.controller.StateMachine + */ + media.view.MediaFrame.AudioDetails = media.view.MediaFrame.MediaDetails.extend({ + defaults: { + id: 'audio', + url: '', + menu: 'audio-details', + content: 'audio-details', + toolbar: 'audio-details', + type: 'link', + title: l10n.audioDetailsTitle, + priority: 120 + }, + + initialize: function( options ) { + options.DetailsView = media.view.AudioDetails; + options.cancelText = l10n.audioDetailsCancel; + options.addText = l10n.audioAddSourceTitle; + + media.view.MediaFrame.MediaDetails.prototype.initialize.call( this, options ); + }, + + bindHandlers: function() { + media.view.MediaFrame.MediaDetails.prototype.bindHandlers.apply( this, arguments ); + + this.on( 'toolbar:render:replace-audio', this.renderReplaceToolbar, this ); + this.on( 'toolbar:render:add-audio-source', this.renderAddSourceToolbar, this ); + }, + + createStates: function() { + this.states.add([ + new media.controller.AudioDetails( { + media: this.media, + editable: false, + menu: 'audio-details' + } ), + + new media.controller.MediaLibrary( { + type: 'audio', + id: 'replace-audio', + title: l10n.audioReplaceTitle, + toolbar: 'replace-audio', + media: this.media, + menu: 'audio-details' + } ), + + new media.controller.MediaLibrary( { + type: 'audio', + id: 'add-audio-source', + title: l10n.audioAddSourceTitle, + toolbar: 'add-audio-source', + media: this.media, + menu: false + } ) + ]); + } + }); + + /** + * wp.media.view.MediaFrame.VideoDetails + * + * @constructor + * @augments wp.media.view.MediaFrame.MediaDetails + * @augments wp.media.view.MediaFrame.Select + * @augments wp.media.view.MediaFrame + * @augments wp.media.view.Frame + * @augments wp.media.View + * @augments wp.Backbone.View + * @augments Backbone.View + * @mixes wp.media.controller.StateMachine + */ + media.view.MediaFrame.VideoDetails = media.view.MediaFrame.MediaDetails.extend({ + defaults: { + id: 'video', + url: '', + menu: 'video-details', + content: 'video-details', + toolbar: 'video-details', + type: 'link', + title: l10n.videoDetailsTitle, + priority: 120 + }, + + initialize: function( options ) { + options.DetailsView = media.view.VideoDetails; + options.cancelText = l10n.videoDetailsCancel; + options.addText = l10n.videoAddSourceTitle; + + media.view.MediaFrame.MediaDetails.prototype.initialize.call( this, options ); + }, + + bindHandlers: function() { + media.view.MediaFrame.MediaDetails.prototype.bindHandlers.apply( this, arguments ); + + this.on( 'toolbar:render:replace-video', this.renderReplaceToolbar, this ); + this.on( 'toolbar:render:add-video-source', this.renderAddSourceToolbar, this ); + this.on( 'toolbar:render:select-poster-image', this.renderSelectPosterImageToolbar, this ); + this.on( 'toolbar:render:add-track', this.renderAddTrackToolbar, this ); + }, + + createStates: function() { + this.states.add([ + new media.controller.VideoDetails({ + media: this.media, + editable: false, + menu: 'video-details' + }), + + new media.controller.MediaLibrary( { + type: 'video', + id: 'replace-video', + title: l10n.videoReplaceTitle, + toolbar: 'replace-video', + media: this.media, + menu: 'video-details' + } ), + + new media.controller.MediaLibrary( { + type: 'video', + id: 'add-video-source', + title: l10n.videoAddSourceTitle, + toolbar: 'add-video-source', + media: this.media, + menu: false + } ), + + new media.controller.MediaLibrary( { + type: 'image', + id: 'select-poster-image', + title: l10n.videoSelectPosterImageTitle, + toolbar: 'select-poster-image', + media: this.media, + menu: 'video-details' + } ), + + new media.controller.MediaLibrary( { + type: 'text', + id: 'add-track', + title: l10n.videoAddTrackTitle, + toolbar: 'add-track', + media: this.media, + menu: 'video-details' + } ) + ]); + }, + + renderSelectPosterImageToolbar: function() { + this.toolbar.set( new media.view.Toolbar({ + controller: this, + items: { + replace: { + style: 'primary', + text: l10n.videoSelectPosterImageTitle, + priority: 80, + + click: function() { + var controller = this.controller, + state = controller.state(), + selection = state.get( 'selection' ), + attachment = selection.single(); + + controller.media.set( 'poster', attachment.get( 'url' ) ); + + state.trigger( 'set-poster-image', controller.media.toJSON() ); + + // Restore and reset the default state. + controller.setState( controller.options.state ); + controller.reset(); + } + } + } + }) ); + }, + + renderAddTrackToolbar: function() { + this.toolbar.set( new media.view.Toolbar({ + controller: this, + items: { + replace: { + style: 'primary', + text: l10n.videoAddTrackTitle, + priority: 80, + + click: function() { + var controller = this.controller, + state = controller.state(), + selection = state.get( 'selection' ), + attachment = selection.single(), + content = controller.media.get( 'content' ); + + if ( -1 === content.indexOf( attachment.get( 'url' ) ) ) { + content += [ + '' + ].join(''); + + controller.media.set( 'content', content ); + } + + state.trigger( 'add-track', controller.media.toJSON() ); + + // Restore and reset the default state. + controller.setState( controller.options.state ); + controller.reset(); + } + } + } + }) ); + } + }); + + /** + * wp.media.view.AudioDetails + * + * @contructor + * @augments wp.media.view.MediaDetails + * @augments wp.media.view.Settings.AttachmentDisplay + * @augments wp.media.view.Settings + * @augments wp.media.View + * @augments wp.Backbone.View + * @augments Backbone.View + */ + media.view.AudioDetails = media.view.MediaDetails.extend({ + className: 'audio-details', + template: media.template('audio-details'), + + setMedia: function() { + var audio = this.$('.wp-audio-shortcode'); + + if ( audio.find( 'source' ).length ) { + if ( audio.is(':hidden') ) { + audio.show(); + } + this.media = media.view.MediaDetails.prepareSrc( audio.get(0) ); + } else { + audio.hide(); + this.media = false; + } + + return this; + } + }); + + /** + * wp.media.view.VideoDetails + * + * @contructor + * @augments wp.media.view.MediaDetails + * @augments wp.media.view.Settings.AttachmentDisplay + * @augments wp.media.view.Settings + * @augments wp.media.View + * @augments wp.Backbone.View + * @augments Backbone.View + */ + media.view.VideoDetails = media.view.MediaDetails.extend({ + className: 'video-details', + template: media.template('video-details'), + + setMedia: function() { + var video = this.$('.wp-video-shortcode'); + + if ( video.find( 'source' ).length ) { + if ( video.is(':hidden') ) { + video.show(); + } + + if ( ! video.hasClass('youtube-video') ) { + this.media = media.view.MediaDetails.prepareSrc( video.get(0) ); + } else { + this.media = video.get(0); + } + } else { + video.hide(); + this.media = false; + } + + return this; + } + }); + + _.extend( wp.media.playlist, { + /** + * Determine how many audio and video files the user has uploaded + */ + counts : (function (settings) { + var counts = {}; + + return function () { + if ( ! _.isEmpty( counts ) ) { + return counts; + } + + var a = 0, v = 0; + _.each( settings.attachmentCounts, function (total, mime) { + var type; + if ( -1 < mime.indexOf('/') ) { + type = mime.split('/')[0]; + + total = parseInt(total, 10); + + switch ( type ) { + case 'audio': + a += total; + break; + case 'video': + v += total; + break; + } + } + } ); + + counts.audio = a; + counts.video = v; + + return counts; + }; + }(media.view.settings)), + + /** + * Return the playlist states for MediaFrame.Post + * + * @param {Object} options + * @returns {Array} + */ + states : function (options) { + return [ + new media.controller.Library({ + id: 'playlist', + title: l10n.createPlaylistTitle, + priority: 60, + toolbar: 'main-playlist', + filterable: 'uploaded', + multiple: 'add', + editable: false, + + library: media.query( _.defaults({ + type: 'audio' + }, options.library ) ) + }), + + // Playlist states. + new media.controller.CollectionEdit({ + type: 'audio', + collectionType: 'playlist', + title: l10n.editPlaylistTitle, + SettingsView: media.view.Settings.Playlist, + library: options.selection, + editing: options.editing, + menu: 'playlist', + dragInfoText: l10n.playlistDragInfo, + dragInfo: false + }), + + new media.controller.CollectionAdd({ + type: 'audio', + collectionType: 'playlist', + title: l10n.addToPlaylistTitle + }) + ]; + }, + + /** + * Return the video-playlist states for MediaFrame.Post + * + * @param {Object} options + * @returns {Array} + */ + videoStates : function (options) { + return [ + new media.controller.Library({ + id: 'video-playlist', + title: l10n.createVideoPlaylistTitle, + priority: 60, + toolbar: 'main-video-playlist', + filterable: 'uploaded', + multiple: 'add', + editable: false, + + library: media.query( _.defaults({ + type: 'video' + }, options.library ) ) + }), + + // Video Playlist states. + new media.controller.CollectionEdit({ + type: 'video', + collectionType: 'video-playlist', + title: l10n.editVideoPlaylistTitle, + SettingsView: media.view.Settings.Playlist, + library: options.selection, + editing: options.editing, + menu: 'video-playlist', + dragInfoText: l10n.videoPlaylistDragInfo, + dragInfo: false + }), + + new media.controller.CollectionAdd({ + type: 'video', + collectionType: 'video-playlist', + title: l10n.addToVideoPlaylistTitle + }) + ]; + } + } ); + + function init() { + $(document.body) + .on( 'click', '.wp-switch-editor', wp.media.mixin.pauseAllPlayers ) + .on( 'click', '.add-media-source', function () { + media.frame.setState('add-' + media.frame.defaults.id + '-source'); + } ); + } + + $( init ); + +}(jQuery, _, Backbone)); \ No newline at end of file diff --git a/src/wp-includes/js/media-editor.js b/src/wp-includes/js/media-editor.js index ff657bce54..d0b4308810 100644 --- a/src/wp-includes/js/media-editor.js +++ b/src/wp-includes/js/media-editor.js @@ -10,6 +10,26 @@ */ var workflows = {}; + /** + * A helper mixin function to avoid truthy and falsey values being + * passed as an input that expects booleans. If key is undefined in the map, + * but has a default value, set it. + * + * @param {object} attrs Map of props from a shortcode or settings. + * @param {string} key The key within the passed map to check for a value. + * @returns {mixed|undefined} The original or coerced value of key within attrs + */ + wp.media.coerce = function ( attrs, key ) { + if ( _.isUndefined( attrs[ key ] ) && ! _.isUndefined( this.defaults[ key ] ) ) { + attrs[ key ] = this.defaults[ key ]; + } else if ( 'true' === attrs[ key ] ) { + attrs[ key ] = true; + } else if ( 'false' === attrs[ key ] ) { + attrs[ key ] = false; + } + return attrs[ key ]; + }; + /** * wp.media.string * @namespace @@ -274,176 +294,11 @@ } }; - /** - * @mixin - */ - wp.media.mixin = { - /** - * A helper function to avoid truthy and falsey values being - * passed as an input that expects booleans. If key is undefined in the map, - * but has a default value, set it. - * - * @param {object} attrs Map of props from a shortcode or settings. - * @param {string} key The key within the passed map to check for a value. - * @returns {mixed|undefined} The original or coerced value of key within attrs - */ - coerce: function ( attrs, key ) { - if ( _.isUndefined( attrs[ key ] ) && ! _.isUndefined( this.defaults[ key ] ) ) { - attrs[ key ] = this.defaults[ key ]; - } else if ( 'true' === attrs[ key ] ) { - attrs[ key ] = true; - } else if ( 'false' === attrs[ key ] ) { - attrs[ key ] = false; - } - return attrs[ key ]; - }, - - pauseAllPlayers: function () { - var p; - if ( window.mejs && window.mejs.players ) { - for ( p in window.mejs.players ) { - window.mejs.players[p].pause(); - } - } - }, - - ua: { - is : function (browser) { - var passes = false, ua = window.navigator.userAgent; - - switch ( browser ) { - case 'oldie': - passes = ua.match(/MSIE [6-8]/gi) !== null; - break; - case 'ie': - passes = ua.match(/MSIE/gi) !== null; - break; - case 'ff': - passes = ua.match(/firefox/gi) !== null; - break; - case 'opera': - passes = ua.match(/OPR/) !== null; - break; - case 'safari': - passes = ua.match(/safari/gi) !== null && ua.match(/chrome/gi) === null; - break; - case 'chrome': - passes = ua.match(/safari/gi) && ua.match(/chrome/gi) !== null; - break; - } - - return passes; - } - }, - - compat :{ - 'opera' : { - audio: ['ogg', 'wav'], - video: ['ogg', 'webm'] - }, - 'chrome' : { - audio: ['ogg', 'mpeg', 'x-ms-wma'], - video: ['ogg', 'webm', 'mp4', 'm4v', 'mpeg'] - }, - 'ff' : { - audio: ['ogg', 'mpeg'], - video: ['ogg', 'webm'] - }, - 'safari' : { - audio: ['mpeg', 'wav'], - video: ['mp4', 'm4v', 'mpeg', 'x-ms-wmv', 'quicktime'] - }, - 'ie' : { - audio: ['mpeg'], - video: ['mp4', 'm4v', 'mpeg'] - } - }, - - isCompatible: function ( media ) { - if ( ! media.find( 'source' ).length ) { - return false; - } - - var ua = this.ua, test = false, found = false, sources; - - if ( ua.is( 'oldIE' ) ) { - return false; - } - - sources = media.find( 'source' ); - - _.find( this.compat, function (supports, browser) { - if ( ua.is( browser ) ) { - found = true; - _.each( sources, function (elem) { - var audio = new RegExp( 'audio\/(' + supports.audio.join('|') + ')', 'gi' ), - video = new RegExp( 'video\/(' + supports.video.join('|') + ')', 'gi' ); - - if ( elem.type.match( video ) !== null || elem.type.match( audio ) !== null ) { - test = true; - } - } ); - } - - return test || found; - } ); - - return test; - }, - - /** - * Override the MediaElement method for removing a player. - * MediaElement tries to pull the audio/video tag out of - * its container and re-add it to the DOM. - */ - removePlayer: function() { - var t = this.player, featureIndex, feature; - - // invoke features cleanup - for ( featureIndex in t.options.features ) { - feature = t.options.features[featureIndex]; - if ( t['clean' + feature] ) { - try { - t['clean' + feature](t); - } catch (e) {} - } - } - - if ( ! t.isDynamic ) { - t.$node.remove(); - } - - if ( 'native' !== t.media.pluginType ) { - t.media.remove(); - } - - delete window.mejs.players[t.id]; - - t.container.remove(); - t.globalUnbind(); - delete t.node.player; - }, - - /** - * Allows any class that has set 'player' to a MediaElementPlayer - * instance to remove the player when listening to events. - * - * Examples: modal closes, shortcode properties are removed, etc. - */ - unsetPlayer : function() { - if ( this.player ) { - wp.media.mixin.pauseAllPlayers(); - wp.media.mixin.removePlayer.apply( this ); - this.player = false; - } - } - }; - wp.media.collection = function(attributes) { var collections = {}; return _.extend( attributes, { - coerce : wp.media.mixin.coerce, + coerce : wp.media.coerce, /** * Retrieve attachments based on the properties of the passed shortcode * @@ -669,133 +524,6 @@ } }); - wp.media.playlist = new wp.media.collection({ - tag: 'playlist', - type : 'audio', - editTitle : wp.media.view.l10n.editPlaylistTitle, - defaults : { - id: wp.media.view.settings.post.id, - style: 'light', - tracklist: true, - tracknumbers: true, - images: true, - artists: true - } - }); - - wp.media['video-playlist'] = new wp.media.collection({ - tag: 'video-playlist', - type : 'video', - editTitle : wp.media.view.l10n.editVideoPlaylistTitle, - defaults : { - id: wp.media.view.settings.post.id, - style: 'light', - tracklist: false, - tracknumbers: false, - images: true - } - }); - - /** - * @namespace - */ - wp.media.audio = { - coerce : wp.media.mixin.coerce, - - defaults : { - id : wp.media.view.settings.post.id, - src : '', - loop : false, - autoplay : false, - preload : 'none' - }, - - edit : function (data) { - var frame, shortcode = wp.shortcode.next( 'audio', data ).shortcode; - frame = wp.media({ - frame: 'audio', - state: 'audio-details', - metadata: _.defaults( - shortcode.attrs.named, - wp.media.audio.defaults - ) - }); - - return frame; - }, - - shortcode : function (shortcode) { - var self = this; - - _.each( wp.media.audio.defaults, function( value, key ) { - shortcode[ key ] = self.coerce( shortcode, key ); - - if ( value === shortcode[ key ] ) { - delete shortcode[ key ]; - } - }); - - return wp.shortcode.string({ - tag: 'audio', - attrs: shortcode - }); - } - }; - - /** - * @namespace - */ - wp.media.video = { - coerce : wp.media.mixin.coerce, - - defaults : { - id : wp.media.view.settings.post.id, - src : '', - poster : '', - loop : false, - autoplay : false, - preload : 'metadata', - content : '' - }, - - edit : function (data) { - var frame, - defaults = this.defaults, - shortcode = wp.shortcode.next( 'video', data ).shortcode, - attrs; - - attrs = shortcode.attrs.named; - attrs.content = shortcode.content; - - frame = wp.media({ - frame: 'video', - state: 'video-details', - metadata: _.defaults( attrs, defaults ) - }); - - return frame; - }, - - shortcode : function (shortcode) { - var self = this, content = shortcode.content; - delete shortcode.content; - - _.each( this.defaults, function( value, key ) { - shortcode[ key ] = self.coerce( shortcode, key ); - - if ( value === shortcode[ key ] ) { - delete shortcode[ key ]; - } - }); - - return wp.shortcode.string({ - tag: 'video', - attrs: shortcode, - content: content - }); - } - }; - /** * wp.media.featuredImage * @namespace @@ -1279,17 +1007,10 @@ if ( elem.hasClass( 'gallery' ) ) { options.state = 'gallery'; options.title = wp.media.view.l10n.createGalleryTitle; - } else if ( elem.hasClass( 'playlist' ) ) { - options.state = 'playlist'; - options.title = wp.media.view.l10n.createPlaylistTitle; - } else if ( elem.hasClass( 'video-playlist' ) ) { - options.state = 'video-playlist'; - options.title = wp.media.view.l10n.createVideoPlaylistTitle; } wp.media.editor.open( editor, options ); - }) - .on( 'click', '.wp-switch-editor', wp.media.mixin.pauseAllPlayers ); + }); // Initialize and render the Editor drag-and-drop uploader. new wp.media.view.EditorUploader().render(); diff --git a/src/wp-includes/js/media-models.js b/src/wp-includes/js/media-models.js index 2e406d7a3c..86d31b78b0 100644 --- a/src/wp-includes/js/media-models.js +++ b/src/wp-includes/js/media-models.js @@ -40,6 +40,8 @@ window.wp = window.wp || {}; delete attributes.frame; + media.frame = frame; + return frame; }; @@ -452,44 +454,6 @@ window.wp = window.wp || {}; } }); - /** - * wp.media.model.PostMedia - * - * @constructor - * @augments Backbone.Model - **/ - media.model.PostMedia = Backbone.Model.extend({ - initialize: function() { - this.attachment = false; - }, - - setSource: function ( attachment ) { - this.attachment = attachment; - this.extension = attachment.get('filename' ).split('.').pop(); - - if ( this.get( 'src' ) && this.extension === this.get( 'src' ).split('.').pop() ) { - this.unset( 'src' ); - } - - if ( _.contains( wp.media.view.settings.embedExts, this.extension ) ) { - this.set( this.extension, this.attachment.get( 'url' ) ); - } else { - this.unset( this.extension ); - } - }, - - changeAttachment: function( attachment ) { - var self = this; - - this.setSource( attachment ); - - this.unset( 'src' ); - _.each( _.without( wp.media.view.settings.embedExts, this.extension ), function (ext) { - self.unset( ext ); - } ); - } - }); - /** * wp.media.model.Attachments * diff --git a/src/wp-includes/js/media-views.js b/src/wp-includes/js/media-views.js index 4c2114c472..34234466d3 100644 --- a/src/wp-includes/js/media-views.js +++ b/src/wp-includes/js/media-views.js @@ -765,58 +765,6 @@ } }); - /** - * wp.media.controller.AudioDetails - * - * @constructor - * @augments wp.media.controller.State - * @augments Backbone.Model - */ - media.controller.AudioDetails = media.controller.State.extend({ - defaults: _.defaults({ - id: 'audio-details', - toolbar: 'audio-details', - title: l10n.audioDetailsTitle, - content: 'audio-details', - menu: 'audio-details', - router: false, - attachment: false, - priority: 60, - editing: false - }, media.controller.Library.prototype.defaults ), - - initialize: function( options ) { - this.media = options.media; - media.controller.State.prototype.initialize.apply( this, arguments ); - } - }); - - /** - * wp.media.controller.VideoDetails - * - * @constructor - * @augments wp.media.controller.State - * @augments Backbone.Model - */ - media.controller.VideoDetails = media.controller.State.extend({ - defaults: _.defaults({ - id: 'video-details', - toolbar: 'video-details', - title: l10n.videoDetailsTitle, - content: 'video-details', - menu: 'video-details', - router: false, - attachment: false, - priority: 60, - editing: false - }, media.controller.Library.prototype.defaults ), - - initialize: function( options ) { - this.media = options.media; - media.controller.State.prototype.initialize.apply( this, arguments ); - } - }); - /** * wp.media.controller.CollectionEdit * @@ -1930,7 +1878,7 @@ }, createStates: function() { - var options = this.options; + var options = this.options, counts; // Add the default states. this.states.add([ @@ -1990,75 +1938,18 @@ type: 'image', collectionType: 'gallery', title: l10n.addToGalleryTitle - }), - - new media.controller.Library({ - id: 'playlist', - title: l10n.createPlaylistTitle, - priority: 60, - toolbar: 'main-playlist', - filterable: 'uploaded', - multiple: 'add', - editable: false, - - library: media.query( _.defaults({ - type: 'audio' - }, options.library ) ) - }), - - // Playlist states. - new media.controller.CollectionEdit({ - type: 'audio', - collectionType: 'playlist', - title: l10n.editPlaylistTitle, - SettingsView: media.view.Settings.Playlist, - library: options.selection, - editing: options.editing, - menu: 'playlist', - dragInfoText: l10n.playlistDragInfo, - dragInfo: false - }), - - new media.controller.CollectionAdd({ - type: 'audio', - collectionType: 'playlist', - title: l10n.addToPlaylistTitle - }), - - new media.controller.Library({ - id: 'video-playlist', - title: l10n.createVideoPlaylistTitle, - priority: 60, - toolbar: 'main-video-playlist', - filterable: 'uploaded', - multiple: 'add', - editable: false, - - library: media.query( _.defaults({ - type: 'video' - }, options.library ) ) - }), - - // Video Playlist states. - new media.controller.CollectionEdit({ - type: 'video', - collectionType: 'video-playlist', - title: l10n.editVideoPlaylistTitle, - SettingsView: media.view.Settings.Playlist, - library: options.selection, - editing: options.editing, - menu: 'video-playlist', - dragInfoText: l10n.videoPlaylistDragInfo, - dragInfo: false - }), - - new media.controller.CollectionAdd({ - type: 'video', - collectionType: 'video-playlist', - title: l10n.addToVideoPlaylistTitle }) ]); + counts = media.playlist.counts(); + + if ( counts.audio ) { + this.states.add( media.playlist.states(options) ); + } + + if ( counts.video ) { + this.states.add( media.playlist.videoStates(options) ); + } if ( media.view.settings.post.featuredImageId ) { this.states.add( new media.controller.FeaturedImage() ); @@ -2745,391 +2636,6 @@ }); - /** - * wp.media.view.MediaFrame.MediaDetails - * - * @constructor - * @augments wp.media.view.MediaFrame.Select - * @augments wp.media.view.MediaFrame - * @augments wp.media.view.Frame - * @augments wp.media.View - * @augments wp.Backbone.View - * @augments Backbone.View - * @mixes wp.media.controller.StateMachine - */ - media.view.MediaFrame.MediaDetails = media.view.MediaFrame.Select.extend({ - defaults: { - id: 'media', - url: '', - menu: 'media-details', - content: 'media-details', - toolbar: 'media-details', - type: 'link', - priority: 120 - }, - - initialize: function( options ) { - this.DetailsView = options.DetailsView; - this.cancelText = options.cancelText; - this.addText = options.addText; - - this.media = new media.model.PostMedia( options.metadata ); - this.options.selection = new media.model.Selection( this.media.attachment, { multiple: false } ); - media.view.MediaFrame.Select.prototype.initialize.apply( this, arguments ); - }, - - bindHandlers: function() { - var menu = this.defaults.menu; - - media.view.MediaFrame.Select.prototype.bindHandlers.apply( this, arguments ); - - this.on( 'menu:create:' + menu, this.createMenu, this ); - this.on( 'content:render:' + menu, this.renderDetailsContent, this ); - this.on( 'menu:render:' + menu, this.renderMenu, this ); - this.on( 'toolbar:render:' + menu, this.renderDetailsToolbar, this ); - }, - - renderDetailsContent: function() { - var view = new this.DetailsView({ - controller: this, - model: this.state().media, - attachment: this.state().media.attachment - }).render(); - - this.content.set( view ); - }, - - renderMenu: function( view ) { - var lastState = this.lastState(), - previous = lastState && lastState.id, - frame = this; - - view.set({ - cancel: { - text: this.cancelText, - priority: 20, - click: function() { - if ( previous ) { - frame.setState( previous ); - } else { - frame.close(); - } - } - }, - separateCancel: new media.View({ - className: 'separator', - priority: 40 - }) - }); - - }, - - renderDetailsToolbar: function() { - this.toolbar.set( new media.view.Toolbar({ - controller: this, - items: { - select: { - style: 'primary', - text: l10n.update, - priority: 80, - - click: function() { - var controller = this.controller, - state = controller.state(); - - controller.close(); - - state.trigger( 'update', controller.media.toJSON() ); - - // Restore and reset the default state. - controller.setState( controller.options.state ); - controller.reset(); - } - } - } - }) ); - }, - - renderReplaceToolbar: function() { - this.toolbar.set( new media.view.Toolbar({ - controller: this, - items: { - replace: { - style: 'primary', - text: l10n.replace, - priority: 80, - - click: function() { - var controller = this.controller, - state = controller.state(), - selection = state.get( 'selection' ), - attachment = selection.single(); - - controller.media.changeAttachment( attachment ); - - state.trigger( 'replace', controller.media.toJSON() ); - - // Restore and reset the default state. - controller.setState( controller.options.state ); - controller.reset(); - } - } - } - }) ); - }, - - renderAddSourceToolbar: function() { - this.toolbar.set( new media.view.Toolbar({ - controller: this, - items: { - replace: { - style: 'primary', - text: this.addText, - priority: 80, - - click: function() { - var controller = this.controller, - state = controller.state(), - selection = state.get( 'selection' ), - attachment = selection.single(); - - controller.media.setSource( attachment ); - - state.trigger( 'add-source', controller.media.toJSON() ); - - // Restore and reset the default state. - controller.setState( controller.options.state ); - controller.reset(); - } - } - } - }) ); - } - }); - - /** - * wp.media.view.MediaFrame.AudioDetails - * - * @constructor - * @augments wp.media.view.MediaFrame.MediaDetails - * @augments wp.media.view.MediaFrame.Select - * @augments wp.media.view.MediaFrame - * @augments wp.media.view.Frame - * @augments wp.media.View - * @augments wp.Backbone.View - * @augments Backbone.View - * @mixes wp.media.controller.StateMachine - */ - media.view.MediaFrame.AudioDetails = media.view.MediaFrame.MediaDetails.extend({ - defaults: { - id: 'audio', - url: '', - menu: 'audio-details', - content: 'audio-details', - toolbar: 'audio-details', - type: 'link', - title: l10n.audioDetailsTitle, - priority: 120 - }, - - initialize: function( options ) { - options.DetailsView = media.view.AudioDetails; - options.cancelText = l10n.audioDetailsCancel; - options.addText = l10n.audioAddSourceTitle; - - media.view.MediaFrame.MediaDetails.prototype.initialize.call( this, options ); - }, - - bindHandlers: function() { - media.view.MediaFrame.MediaDetails.prototype.bindHandlers.apply( this, arguments ); - - this.on( 'toolbar:render:replace-audio', this.renderReplaceToolbar, this ); - this.on( 'toolbar:render:add-audio-source', this.renderAddSourceToolbar, this ); - }, - - createStates: function() { - this.states.add([ - new media.controller.AudioDetails( { - media: this.media, - editable: false, - menu: 'audio-details' - } ), - - new media.controller.MediaLibrary( { - type: 'audio', - id: 'replace-audio', - title: l10n.audioReplaceTitle, - toolbar: 'replace-audio', - media: this.media, - menu: 'audio-details' - } ), - - new media.controller.MediaLibrary( { - type: 'audio', - id: 'add-audio-source', - title: l10n.audioAddSourceTitle, - toolbar: 'add-audio-source', - media: this.media, - menu: 'audio-details' - } ) - ]); - } - }); - - /** - * wp.media.view.MediaFrame.VideoDetails - * - * @constructor - * @augments wp.media.view.MediaFrame.MediaDetails - * @augments wp.media.view.MediaFrame.Select - * @augments wp.media.view.MediaFrame - * @augments wp.media.view.Frame - * @augments wp.media.View - * @augments wp.Backbone.View - * @augments Backbone.View - * @mixes wp.media.controller.StateMachine - */ - media.view.MediaFrame.VideoDetails = media.view.MediaFrame.MediaDetails.extend({ - defaults: { - id: 'video', - url: '', - menu: 'video-details', - content: 'video-details', - toolbar: 'video-details', - type: 'link', - title: l10n.videoDetailsTitle, - priority: 120 - }, - - initialize: function( options ) { - options.DetailsView = media.view.VideoDetails; - options.cancelText = l10n.videoDetailsCancel; - options.addText = l10n.videoAddSourceTitle; - - media.view.MediaFrame.MediaDetails.prototype.initialize.call( this, options ); - }, - - bindHandlers: function() { - media.view.MediaFrame.MediaDetails.prototype.bindHandlers.apply( this, arguments ); - - this.on( 'toolbar:render:replace-video', this.renderReplaceToolbar, this ); - this.on( 'toolbar:render:add-video-source', this.renderAddSourceToolbar, this ); - this.on( 'toolbar:render:select-poster-image', this.renderSelectPosterImageToolbar, this ); - this.on( 'toolbar:render:add-track', this.renderAddTrackToolbar, this ); - }, - - createStates: function() { - this.states.add([ - new media.controller.VideoDetails({ - media: this.media, - editable: false, - menu: 'video-details' - }), - - new media.controller.MediaLibrary( { - type: 'video', - id: 'replace-video', - title: l10n.videoReplaceTitle, - toolbar: 'replace-video', - media: this.media, - menu: 'video-details' - } ), - - new media.controller.MediaLibrary( { - type: 'video', - id: 'add-video-source', - title: l10n.videoAddSourceTitle, - toolbar: 'add-video-source', - media: this.media, - menu: 'video-details' - } ), - - new media.controller.MediaLibrary( { - type: 'image', - id: 'select-poster-image', - title: l10n.videoSelectPosterImageTitle, - toolbar: 'select-poster-image', - media: this.media, - menu: 'video-details' - } ), - - new media.controller.MediaLibrary( { - type: 'text', - id: 'add-track', - title: l10n.videoAddTrackTitle, - toolbar: 'add-track', - media: this.media, - menu: 'video-details' - } ) - ]); - }, - - renderSelectPosterImageToolbar: function() { - this.toolbar.set( new media.view.Toolbar({ - controller: this, - items: { - replace: { - style: 'primary', - text: l10n.videoSelectPosterImageTitle, - priority: 80, - - click: function() { - var controller = this.controller, - state = controller.state(), - selection = state.get( 'selection' ), - attachment = selection.single(); - - controller.media.set( 'poster', attachment.get( 'url' ) ); - - state.trigger( 'set-poster-image', controller.media.toJSON() ); - - // Restore and reset the default state. - controller.setState( controller.options.state ); - controller.reset(); - } - } - } - }) ); - }, - - renderAddTrackToolbar: function() { - this.toolbar.set( new media.view.Toolbar({ - controller: this, - items: { - replace: { - style: 'primary', - text: l10n.videoAddTrackTitle, - priority: 80, - - click: function() { - var controller = this.controller, - state = controller.state(), - selection = state.get( 'selection' ), - attachment = selection.single(), - content = controller.media.get( 'content' ); - - if ( -1 === content.indexOf( attachment.get( 'url' ) ) ) { - content += [ - '' - ].join(''); - - controller.media.set( 'content', content ); - } - - state.trigger( 'add-track', controller.media.toJSON() ); - - // Restore and reset the default state. - controller.setState( controller.options.state ); - controller.reset(); - } - } - } - }) ); - } - }); - /** * wp.media.view.Modal * @@ -6662,75 +6168,6 @@ } }); - /** - * wp.media.view.AudioDetails - * - * @contructor - * @augments wp.media.view.MediaDetails - * @augments wp.media.view.Settings.AttachmentDisplay - * @augments wp.media.view.Settings - * @augments wp.media.View - * @augments wp.Backbone.View - * @augments Backbone.View - */ - media.view.AudioDetails = media.view.MediaDetails.extend({ - className: 'audio-details', - template: media.template('audio-details'), - - setMedia: function() { - var audio = this.$('.wp-audio-shortcode'); - - if ( audio.find( 'source' ).length ) { - if ( audio.is(':hidden') ) { - audio.show(); - } - this.media = media.view.MediaDetails.prepareSrc( audio.get(0) ); - } else { - audio.hide(); - this.media = false; - } - - return this; - } - }); - - /** - * wp.media.view.VideoDetails - * - * @contructor - * @augments wp.media.view.MediaDetails - * @augments wp.media.view.Settings.AttachmentDisplay - * @augments wp.media.view.Settings - * @augments wp.media.View - * @augments wp.Backbone.View - * @augments Backbone.View - */ - media.view.VideoDetails = media.view.MediaDetails.extend({ - className: 'video-details', - template: media.template('video-details'), - - setMedia: function() { - var video = this.$('.wp-video-shortcode'); - - if ( video.find( 'source' ).length ) { - if ( video.is(':hidden') ) { - video.show(); - } - - if ( ! video.hasClass('youtube-video') ) { - this.media = media.view.MediaDetails.prepareSrc( video.get(0) ); - } else { - this.media = video.get(0); - } - } else { - video.hide(); - this.media = false; - } - - return this; - } - }); - /** * wp.media.view.Spinner * diff --git a/src/wp-includes/js/mediaelement/wp-mediaelement.css b/src/wp-includes/js/mediaelement/wp-mediaelement.css index b4ae4da99b..728d9849a5 100644 --- a/src/wp-includes/js/mediaelement/wp-mediaelement.css +++ b/src/wp-includes/js/mediaelement/wp-mediaelement.css @@ -26,6 +26,7 @@ .media-embed-details .embed-media-settings { padding-top: 0; + top: 28px; } .media-embed-details .instructions { @@ -44,10 +45,6 @@ color: #f00; } -.media-embed-details .embed-media-settings { - top: 0; -} - .media-embed-details .embed-media-settings .checkbox-setting { width: 100px; clear: none; diff --git a/src/wp-includes/media-template.php b/src/wp-includes/media-template.php index 946e506a88..0cad787ba3 100644 --- a/src/wp-includes/media-template.php +++ b/src/wp-includes/media-template.php @@ -765,14 +765,19 @@ function wp_print_media_templates() {