From cba1ae06af6f77ff729e9b014e9f717df39e3eac Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Tue, 15 Jul 2014 22:17:58 +0000 Subject: [PATCH] Simplify creation of audio, video, and playlist MCE views by placing them in iframe sandboxes. Wins: * Eliminates duplication of code between PHP and JS * Views can load JS without messing with TinyMCE and scope * MEjs doesn't break when it loads a file plugin-mode. This allows any file type the MEjs supports to play in MCE views. * YouTube now works as the source for video. * Users can still style the views, editor stylesheets are included in these sandboxes. * Audio and Video URLs and `[embed]`s are no longer broken. * Remove the crazy compat code necessary to determine what file types play in what browser. * Remove unneeded Underscore templates. * Remove the compat code for playlists. See #28905. git-svn-id: https://develop.svn.wordpress.org/trunk@29179 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/js/mce-view.js | 446 +++++------------- src/wp-includes/js/media-audiovideo.js | 97 ---- .../js/mediaelement/wp-mediaelement.css | 4 + .../js/mediaelement/wp-playlist.js | 37 +- src/wp-includes/media-template.php | 29 -- 5 files changed, 139 insertions(+), 474 deletions(-) diff --git a/src/wp-includes/js/mce-view.js b/src/wp-includes/js/mce-view.js index 87d96efce4..96fc01974e 100644 --- a/src/wp-includes/js/mce-view.js +++ b/src/wp-includes/js/mce-view.js @@ -104,6 +104,65 @@ window.wp = window.wp || {}; } }, this ); }, + + /* jshint scripturl: true */ + createIframe: function ( content ) { + var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver, + iframe, iframeDoc, i, resize, + dom = tinymce.DOM; + + if ( content.indexOf( '' + + '' + + '' + + '' + + '' + + '' + + content + + '' + + '' + ); + iframeDoc.close(); + + resize = function() { + $( iframe ).height( $( iframeDoc.body ).height() ); + }; + + if ( MutationObserver ) { + new MutationObserver( _.debounce( function() { + resize(); + }, 100 ) ) + .observe( iframeDoc.body, { + attributes: true, + childList: true, + subtree: true + } ); + } else { + for ( i = 1; i < 6; i++ ) { + setTimeout( resize, i * 700 ); + } + } + } else { + this.setContent( content ); + } + }, + setError: function( message, dashicon ) { this.setContent( '
' + @@ -421,54 +480,60 @@ window.wp = window.wp || {}; wp.mce.av = { loaded: false, - View: _.extend( {}, wp.media.mixin, { + View: { overlay: true, + action: 'parse-media-shortcode', + initialize: function( options ) { - this.players = []; this.shortcode = options.shortcode; - _.bindAll( this, 'setPlayer', 'pausePlayers' ); - $( this ).on( 'ready', this.setPlayer ); - $( this ).on( 'ready', function( event, editor ) { - editor.on( 'hide', this.pausePlayers ); - } ); - $( document ).on( 'media:edit', this.pausePlayers ); + this.fetching = false; + + _.bindAll( this, 'createIframe', 'setNode', 'fetch' ); + $( this ).on( 'ready', this.setNode ); }, - /** - * Creates the player instance for the current node - * - * @global MediaElementPlayer - * - * @param {Event} event - * @param {Object} editor - * @param {HTMLElement} node - */ - setPlayer: function( event, editor, node ) { - var self = this, - media; - - media = $( node ).find( '.wp-' + this.shortcode.tag + '-shortcode' ); - - if ( ! this.isCompatible( media ) ) { - media.closest( '.wpview-wrap' ).addClass( 'wont-play' ); - media.replaceWith( '

' + media.find( 'source' ).eq(0).prop( 'src' ) + '

' ); - return; - } else { - media.closest( '.wpview-wrap' ).removeClass( 'wont-play' ); - if ( this.ua.is( 'ff' ) ) { - media.prop( 'preload', 'metadata' ); - } else { - media.prop( 'preload', 'none' ); - } + setNode: function () { + if ( this.parsed ) { + this.createIframe( this.parsed ); + } else if ( ! this.fetching ) { + this.fetch(); } + }, - media = wp.media.view.MediaDetails.prepareSrc( media.get(0) ); + fetch: function () { + var self = this; + this.fetching = true; - setTimeout( function() { - wp.mce.av.loaded = true; - self.players.push( new MediaElementPlayer( media, self.mejsSettings ) ); - }, wp.mce.av.loaded ? 10 : 500 ); + wp.ajax.send( this.action, { + data: { + post_ID: $( '#post_ID' ).val() || 0, + type: this.shortcode.tag, + shortcode: this.shortcode.string() + } + } ) + .always( function() { + self.fetching = false; + } ) + .done( function( response ) { + if ( response ) { + self.parsed = response; + self.createIframe( response ); + } + } ) + .fail( function( response ) { + if ( response && response.message ) { + if ( ( response.type === 'not-embeddable' && self.type === 'embed' ) || + response.type === 'not-ssl' ) { + + self.setError( response.message, 'admin-media' ); + } else { + self.setContent( '

' + self.original + '

', null, 'replace' ); + } + } else if ( response && response.statusText ) { + self.setError( response.statusText, 'admin-media' ); + } + } ); }, /** @@ -477,19 +542,12 @@ window.wp = window.wp || {}; * @returns {string} */ getHtml: function() { - var attrs = this.shortcode.attrs.named; - attrs.content = this.shortcode.content; - - return this.template({ model: _.defaults( - attrs, - wp.media[ this.shortcode.tag ].defaults ) - }); - }, - - unbind: function() { - this.unsetPlayers(); + if ( ! this.parsed ) { + return ''; + } + return this.parsed; } - } ), + }, /** * Called when a TinyMCE view is clicked for editing. @@ -537,10 +595,7 @@ window.wp = window.wp || {}; * @mixes wp.mce.av */ wp.mce.views.register( 'video', _.extend( {}, wp.mce.av, { - state: 'video-details', - View: _.extend( {}, wp.mce.av.View, { - template: media.template( 'editor-video' ) - } ) + state: 'video-details' } ) ); /** @@ -549,10 +604,7 @@ window.wp = window.wp || {}; * @mixes wp.mce.av */ wp.mce.views.register( 'audio', _.extend( {}, wp.mce.av, { - state: 'audio-details', - View: _.extend( {}, wp.mce.av.View, { - template: media.template( 'editor-audio' ) - } ) + state: 'audio-details' } ) ); /** @@ -561,274 +613,34 @@ window.wp = window.wp || {}; * @mixes wp.mce.av */ wp.mce.views.register( 'playlist', _.extend( {}, wp.mce.av, { - state: ['playlist-edit', 'video-playlist-edit'], - View: _.extend( {}, wp.media.mixin, { - template: media.template( 'editor-playlist' ), - overlay: true, - - initialize: function( options ) { - this.players = []; - this.data = {}; - this.attachments = []; - this.shortcode = options.shortcode; - - $( this ).on( 'ready', function( event, editor ) { - editor.on( 'hide', this.pausePlayers ); - } ); - $( document ).on( 'media:edit', this.pausePlayers ); - - this.fetch(); - - $( this ).on( 'ready', this.setPlaylist ); - }, - - /** - * Asynchronously fetch the shortcode's attachments - */ - fetch: function() { - this.attachments = wp.media.playlist.attachments( this.shortcode ); - this.dfd = this.attachments.more().done( _.bind( this.render, this ) ); - }, - - setPlaylist: function( event, editor, element ) { - if ( ! this.data.tracks ) { - return; - } - - this.players.push( new WPPlaylistView( { - el: $( element ).find( '.wp-playlist' ).get( 0 ), - metadata: this.data - } ).player ); - }, - - /** - * Set the data that will be used to compile the Underscore template, - * compile the template, and then return it. - * - * @returns {string} - */ - getHtml: function() { - var data = this.shortcode.attrs.named, - model = wp.media.playlist, - options, - attachments, - tracks = []; - - // Don't render errors while still fetching attachments - if ( this.dfd && 'pending' === this.dfd.state() && ! this.attachments.length ) { - return ''; - } - - _.each( model.defaults, function( value, key ) { - data[ key ] = model.coerce( data, key ); - }); - - options = { - type: data.type, - style: data.style, - tracklist: data.tracklist, - tracknumbers: data.tracknumbers, - images: data.images, - artists: data.artists - }; - - if ( ! this.attachments.length ) { - return this.template( options ); - } - - attachments = this.attachments.toJSON(); - - _.each( attachments, function( attachment ) { - var size = {}, resize = {}, track = { - src : attachment.url, - type : attachment.mime, - title : attachment.title, - caption : attachment.caption, - description : attachment.description, - meta : attachment.meta - }; - - if ( 'video' === data.type ) { - size.width = attachment.width; - size.height = attachment.height; - if ( media.view.settings.contentWidth ) { - resize.width = media.view.settings.contentWidth - 22; - resize.height = Math.ceil( ( size.height * resize.width ) / size.width ); - if ( ! options.width ) { - options.width = resize.width; - options.height = resize.height; - } - } else { - if ( ! options.width ) { - options.width = attachment.width; - options.height = attachment.height; - } - } - track.dimensions = { - original : size, - resized : _.isEmpty( resize ) ? size : resize - }; - } else { - options.width = 400; - } - - track.image = attachment.image; - track.thumb = attachment.thumb; - - tracks.push( track ); - } ); - - options.tracks = tracks; - this.data = options; - - return this.template( options ); - }, - - unbind: function() { - this.unsetPlayers(); - } - } ) + state: [ 'playlist-edit', 'video-playlist-edit' ] } ) ); /** * TinyMCE handler for the embed shortcode */ - wp.mce.embedView = _.extend( {}, wp.media.mixin, { - overlay: true, - initialize: function( options ) { - this.players = []; - this.content = options.content; - this.fetching = false; - this.parsed = false; - this.original = options.url || options.shortcode.string(); + wp.mce.embedMixin = { + View: _.extend( {}, wp.mce.av.View, { + overlay: true, + action: 'parse-embed', + initialize: function( options ) { + this.content = options.content; + this.fetching = false; + this.parsed = false; + this.original = options.url || options.shortcode.string(); - if ( options.url ) { - this.shortcode = '[embed]' + options.url + '[/embed]'; - } else { - this.shortcode = options.shortcode.string(); - } - - _.bindAll( this, 'setHtml', 'setNode', 'fetch' ); - $( this ).on( 'ready', this.setNode ); - }, - unbind: function() { - var self = this; - _.each( this.players, function ( player ) { - player.pause(); - self.removePlayer( player ); - } ); - this.players = []; - }, - setNode: function () { - if ( this.parsed ) { - this.setHtml( this.parsed ); - this.parseMediaShortcodes(); - } else if ( ! this.fetching ) { - this.fetch(); - } - }, - fetch: function () { - var self = this; - - this.fetching = true; - - wp.ajax.send( 'parse-embed', { - data: { - post_ID: $( '#post_ID' ).val() || 0, - shortcode: this.shortcode - } - } ) - .always( function() { - self.fetching = false; - } ) - .done( function( response ) { - if ( response ) { - self.parsed = response; - self.setHtml( response ); - } - } ) - .fail( function( response ) { - if ( response && response.message ) { - if ( ( response.type === 'not-embeddable' && self.type === 'embed' ) || - response.type === 'not-ssl' ) { - - self.setError( response.message, 'admin-media' ); - } else { - self.setContent( '

' + self.original + '

', null, 'replace' ); - } - } else if ( response && response.statusText ) { - self.setError( response.statusText, 'admin-media' ); - } - } ); - }, - /* jshint scripturl: true */ - setHtml: function ( content ) { - var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver, - iframe, iframeDoc, i, resize, - dom = tinymce.DOM; - - if ( content.indexOf( '' + - '' + - '' + - '' + - '' + - '' + - content + - '' + - '' - ); - iframeDoc.close(); - - resize = function() { - $( iframe ).height( $( iframeDoc ).height() ); - }; - - if ( MutationObserver ) { - new MutationObserver( _.debounce( function() { - resize(); - }, 100 ) ) - .observe( iframeDoc.body, { - attributes: true, - childList: true, - subtree: true + if ( options.url ) { + this.shortcode = media.embed.shortcode( { + url: options.url } ); } else { - for ( i = 1; i < 6; i++ ) { - setTimeout( resize, i * 700 ); - } + this.shortcode = options.shortcode; } - } else { - this.setContent( content ); + + _.bindAll( this, 'createIframe', 'setNode', 'fetch' ); + $( this ).on( 'ready', this.setNode ); } - - this.parseMediaShortcodes(); - }, - parseMediaShortcodes: function () { - var self = this; - $( '.wp-audio-shortcode, .wp-video-shortcode', this.node ).each( function ( i, element ) { - self.players.push( new MediaElementPlayer( element, self.mejsSettings ) ); - } ); - } - } ); - - wp.mce.embedMixin = { - View: wp.mce.embedView, + } ), edit: function( node ) { var embed = media.embed, self = this, diff --git a/src/wp-includes/js/media-audiovideo.js b/src/wp-includes/js/media-audiovideo.js index 0c6f31e5b3..4bf622f764 100644 --- a/src/wp-includes/js/media-audiovideo.js +++ b/src/wp-includes/js/media-audiovideo.js @@ -46,103 +46,6 @@ } ); }, - /** - * 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 = /MSIE /.test( ua ) || ( /Trident\//.test( ua ) && /rv:\d/.test( ua ) ); // IE11 - 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) !== null && 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'], - 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 diff --git a/src/wp-includes/js/mediaelement/wp-mediaelement.css b/src/wp-includes/js/mediaelement/wp-mediaelement.css index 371faa6398..c95b88b1dd 100644 --- a/src/wp-includes/js/mediaelement/wp-mediaelement.css +++ b/src/wp-includes/js/mediaelement/wp-mediaelement.css @@ -111,6 +111,10 @@ video.wp-video-shortcode, line-height: 1.5; } +.wp-admin .wp-playlist { + margin: 0 0 18px; +} + .wp-playlist video { display: inline-block; max-width: 100%; diff --git a/src/wp-includes/js/mediaelement/wp-playlist.js b/src/wp-includes/js/mediaelement/wp-playlist.js index f42457398d..9c0fb2c589 100644 --- a/src/wp-includes/js/mediaelement/wp-playlist.js +++ b/src/wp-includes/js/mediaelement/wp-playlist.js @@ -7,7 +7,6 @@ initialize : function (options) { this.index = 0; this.settings = {}; - this.compatMode = $( 'body' ).hasClass( 'wp-admin' ) && $( '#content_ifr' ).length; this.data = options.metadata || $.parseJSON( this.$('script').html() ); this.playerNode = this.$( this.data.type ); @@ -27,9 +26,7 @@ this.renderTracks(); } - if ( this.isCompatibleSrc() ) { - this.playerNode.attr( 'src', this.current.get( 'src' ) ); - } + this.playerNode.attr( 'src', this.current.get( 'src' ) ); _.bindAll( this, 'bindPlayer', 'bindResetPlayer', 'setPlayer', 'ended', 'clickTrack' ); @@ -47,25 +44,7 @@ bindResetPlayer : function (mejs) { this.bindPlayer( mejs ); - if ( this.isCompatibleSrc() ) { - this.playCurrentSrc(); - } - }, - - isCompatibleSrc: function () { - var testNode; - - if ( this.compatMode ) { - testNode = $( '' ); - - if ( ! wp.media.mixin.isCompatible( testNode ) ) { - this.playerNode.removeAttr( 'src' ); - this.playerNode.removeAttr( 'poster' ); - return; - } - } - - return true; + this.playCurrentSrc(); }, setPlayer: function (force) { @@ -76,9 +55,7 @@ } if (force) { - if ( this.isCompatibleSrc() ) { - this.playerNode.attr( 'src', this.current.get( 'src' ) ); - } + this.playerNode.attr( 'src', this.current.get( 'src' ) ); this.settings.success = this.bindResetPlayer; } @@ -188,11 +165,9 @@ }); $(document).ready(function () { - if ( ! $( 'body' ).hasClass( 'wp-admin' ) || $( 'body' ).hasClass( 'about-php' ) ) { - $('.wp-playlist').each(function () { - return new WPPlaylistView({ el: this }); - }); - } + $('.wp-playlist').each( function() { + return new WPPlaylistView({ el: this }); + } ); }); window.WPPlaylistView = WPPlaylistView; diff --git a/src/wp-includes/media-template.php b/src/wp-includes/media-template.php index 87642197cb..40a20a4712 100644 --- a/src/wp-includes/media-template.php +++ b/src/wp-includes/media-template.php @@ -1216,35 +1216,6 @@ function wp_print_media_templates() { <# } #> - - - - - - - -