diff --git a/src/wp-includes/class-wp-editor.php b/src/wp-includes/class-wp-editor.php index bda3664764..99d61fcc8b 100644 --- a/src/wp-includes/class-wp-editor.php +++ b/src/wp-includes/class-wp-editor.php @@ -343,9 +343,16 @@ final class _WP_Editors { $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; $version = 'ver=' . $GLOBALS['wp_version']; $dashicons = includes_url( "css/dashicons$suffix.css?$version" ); + $mediaelement = includes_url( "js/mediaelement/mediaelementplayer.min.css?$version" ); + $wpmediaelement = includes_url( "js/mediaelement/wp-mediaelement.css?$version" ); // WordPress default stylesheet and dashicons - $mce_css = array( $dashicons, self::$baseurl . '/skins/wordpress/wp-content.css' ); + $mce_css = array( + $dashicons, + $mediaelement, + $wpmediaelement, + self::$baseurl . '/skins/wordpress/wp-content.css' + ); // load editor_style.css if the current theme supports it if ( ! empty( $GLOBALS['editor_styles'] ) && is_array( $GLOBALS['editor_styles'] ) ) { diff --git a/src/wp-includes/js/mce-view.js b/src/wp-includes/js/mce-view.js index 0881680b77..e1b67eda99 100644 --- a/src/wp-includes/js/mce-view.js +++ b/src/wp-includes/js/mce-view.js @@ -1,4 +1,4 @@ -/* global tinymce */ +/* global tinymce, _wpmejsSettings, MediaElementPlayer */ // Ensure the global `wp` object exists. window.wp = window.wp || {}; @@ -31,10 +31,14 @@ window.wp = window.wp || {}; var html = this.getHtml(); // Search all tinymce editor instances and update the placeholders _.each( tinymce.editors, function( editor ) { - var doc; + var doc, self = this; if ( editor.plugins.wpview ) { doc = editor.getDoc(); - $( doc ).find( '[data-wpview-text="' + this.encodedText + '"]' ).html( html ); + $( doc ).find( '[data-wpview-text="' + this.encodedText + '"]' ).each(function (i, elem) { + var node = $( elem ); + node.html( html ); + $( self ).trigger( 'ready', elem ); + }); } }, this ); } @@ -178,7 +182,7 @@ window.wp = window.wp || {}; /** * Refresh views after an update is made - * + * * @param view {object} being refreshed * @param text {string} textual representation of the view */ @@ -204,9 +208,9 @@ window.wp = window.wp || {}; return instances[ encodedText ]; }, - /** + /** * render( scope ) - * + * * Renders any view instances inside a DOM node `scope`. * * View instances are detected by the presence of wrapper elements. @@ -302,4 +306,110 @@ window.wp = window.wp || {}; }; wp.mce.views.register( 'gallery', wp.mce.gallery ); + + wp.mce.media = { + toView: function( content ) { + var match = wp.shortcode.next( this.shortcode, content ); + + if ( ! match ) { + return; + } + + return { + index: match.index, + content: match.content, + options: { + shortcode: match.shortcode + } + }; + }, + + edit: function( node ) { + var p, + media = wp.media[ this.shortcode ], + self = this, + frame, data; + + for (p in window.mejs.players) { + window.mejs.players[p].pause(); + } + + data = window.decodeURIComponent( $( node ).data('wpview-text') ); + frame = media.edit( data ); + frame.on( 'close', function () { + frame.detach(); + } ); + frame.state( self.shortcode + '-details' ).on( 'update', function( selection ) { + var shortcode = wp.media[ self.shortcode ].update( selection ).string(); + $( node ).attr( 'data-wpview-text', window.encodeURIComponent( shortcode ) ); + wp.mce.views.refreshView( self, shortcode ); + frame.detach(); + } ); + frame.open(); + } + }; + + wp.mce.media.ViewMixin = wp.mce.View.extend({ + initialize: function( options ) { + this.shortcode = options.shortcode; + _.bindAll( this, 'setPlayer' ); + $(this).on( 'ready', this.setPlayer ); + }, + + setPlayer: function (e, node) { + // if the ready event fires on an empty node + if ( ! node ) { + return; + } + + var id, + media, + settings = {}, + className = '.wp-' + this.shortcode.tag + '-shortcode'; + + media = $( node ).find( className ); + + if ( media.hasClass( 'rendered' ) ) { + id = media.closest( className ).attr( 'id' ); + window.mejs.players[ id ].remove(); + } else { + media.addClass( 'rendered' ); + } + + media.prop( 'preload', 'none' ); + media = media.get(0); + + if ( ! _.isUndefined( window._wpmejsSettings ) ) { + settings.pluginPath = _wpmejsSettings.pluginPath; + } + + media = wp.media.view.MediaDetails.prepareSrc( media ); + new MediaElementPlayer( media, settings ); + }, + + getHtml: function() { + var attrs = this.shortcode.attrs.named; + return this.template({ model: attrs }); + } + }); + + wp.mce.video = _.extend( {}, wp.mce.media, { + shortcode: 'video', + View: wp.mce.media.ViewMixin.extend({ + className: 'editor-video', + template: media.template('editor-video') + }) + } ); + + wp.mce.views.register( 'video', wp.mce.video ); + + wp.mce.audio = _.extend( {}, wp.mce.media, { + shortcode: 'audio', + View: wp.mce.media.ViewMixin.extend({ + className: 'editor-audio', + template: media.template('editor-audio') + }) + } ); + + wp.mce.views.register( 'audio', wp.mce.audio ); }(jQuery)); diff --git a/src/wp-includes/js/media-editor.js b/src/wp-includes/js/media-editor.js index bdb72abb32..093ad99f5a 100644 --- a/src/wp-includes/js/media-editor.js +++ b/src/wp-includes/js/media-editor.js @@ -581,20 +581,24 @@ return frame; }, - shortcode : function (shortcode) { - var self = this; + update : function (model) { + var self = this, content; - _.each( wp.media.audio.defaults, function( value, key ) { - shortcode[ key ] = self.coerce( shortcode, key ); + _.each( this.defaults, function( value, key ) { + model[ key ] = self.coerce( model, key ); - if ( value === shortcode[ key ] ) { - delete shortcode[ key ]; + if ( value === model[ key ] ) { + delete model[ key ]; } }); - return wp.shortcode.string({ - tag: 'audio', - attrs: shortcode + content = model.content; + delete model.content; + + return new wp.shortcode({ + tag: 'audio', + attrs: model, + content: content }); } }, wp.media.mixin); @@ -631,21 +635,23 @@ return frame; }, - shortcode : function (shortcode) { - var self = this, content = shortcode.content; - delete shortcode.content; + update : function (model) { + var self = this, content = ''; _.each( this.defaults, function( value, key ) { - shortcode[ key ] = self.coerce( shortcode, key ); + model[ key ] = self.coerce( model, key ); - if ( value === shortcode[ key ] ) { - delete shortcode[ key ]; + if ( value === model[ key ] ) { + delete model[ key ]; } }); - return wp.shortcode.string({ - tag: 'video', - attrs: shortcode, + content = model.content; + delete model.content; + + return new wp.shortcode({ + tag: 'video', + attrs: model, content: content }); } @@ -1111,38 +1117,47 @@ * @global wp.media.view.l10n */ init: function() { - $(document.body).on( 'click', '.insert-media', function( event ) { - var elem = $( event.currentTarget ), - editor = elem.data('editor'), - options = { - frame: 'post', - state: 'insert', - title: wp.media.view.l10n.addMedia, - multiple: true - }; + $(document.body) + .on( 'click', '.insert-media', function( event ) { + var elem = $( event.currentTarget ), + editor = elem.data('editor'), + options = { + frame: 'post', + state: 'insert', + title: wp.media.view.l10n.addMedia, + multiple: true + }; - event.preventDefault(); + event.preventDefault(); - // Remove focus from the `.insert-media` button. - // Prevents Opera from showing the outline of the button - // above the modal. - // - // See: http://core.trac.wordpress.org/ticket/22445 - elem.blur(); + // Remove focus from the `.insert-media` button. + // Prevents Opera from showing the outline of the button + // above the modal. + // + // See: http://core.trac.wordpress.org/ticket/22445 + elem.blur(); - 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; - } + 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 ); - }); + wp.media.editor.open( editor, options ); + }) + .on( 'click', '.wp-switch-editor', function () { + var p; + if ( window.mejs && window.mejs.players ) { + for ( p in window.mejs.players ) { + window.mejs.players[p].pause(); + } + } + } ); // 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 92517e23c3..334d33a386 100644 --- a/src/wp-includes/js/media-models.js +++ b/src/wp-includes/js/media-models.js @@ -467,6 +467,7 @@ window.wp = window.wp || {}; this.attachment = attachment; this.extension = attachment.get('filename' ).split('.').pop(); + this.unset( 'src' ); if ( _.contains( wp.media.view.settings.embedExts, this.extension ) ) { this.set( this.extension, this.attachment.get( 'url' ) ); } else { diff --git a/src/wp-includes/js/media-views.js b/src/wp-includes/js/media-views.js index 36a47727dc..d8979df8c7 100644 --- a/src/wp-includes/js/media-views.js +++ b/src/wp-includes/js/media-views.js @@ -6542,26 +6542,6 @@ }, this.options ); }, - /** - * When multiple players in the DOM contain the same src, things get weird. - * - * @param {HTMLElement} media - * @returns {HTMLElement} - */ - prepareSrc : function (media) { - var i = wp.media.view.MediaDetails.instances++; - _.each( $(media).find('source'), function (source) { - source.src = [ - source.src, - source.src.indexOf('?') > -1 ? '&' : '?', - '_=', - i - ].join(''); - }); - - return media; - }, - removeSetting : function (e) { var wrap = $( e.currentTarget ).parent(), setting; @@ -6633,9 +6613,10 @@ }, unsetPlayer : function() { + var p; if ( this.player ) { - if ( _.isUndefined( this.mejs.pluginType ) ) { - this.mejs.pause(); + for ( p in window.mejs.players ) { + window.mejs.players[p].pause(); } this.removePlayer(); this.player = false; @@ -6675,7 +6656,30 @@ this.$( '.embed-media-settings' ).scrollTop( 0 ); } }, { - instances : 0 + instances : 0, + + /** + * When multiple players in the DOM contain the same src, things get weird. + * + * @param {HTMLElement} media + * @returns {HTMLElement} + */ + prepareSrc : function (media) { + var i = wp.media.view.MediaDetails.instances++; + if ( 0 === i ) { + i = (new Date()).getTime(); + } + _.each( $(media).find('source'), function (source) { + source.src = [ + source.src, + source.src.indexOf('?') > -1 ? '&' : '?', + '_=', + i + ].join(''); + }); + + return media; + } }); /** @@ -6694,13 +6698,23 @@ template: media.template('audio-details'), setMedia: function() { - var audio = this.$('.wp-audio-shortcode'); + var className = '.wp-audio-shortcode', + audio; + + audio = this.$( className ); if ( audio.find( 'source' ).length ) { if ( audio.is(':hidden') ) { audio.show(); } - this.media = this.prepareSrc( audio.get(0) ); + + audio = audio.get(0); + + if ( $( className ).length > 0 ) { + audio = media.view.MediaDetails.prepareSrc( audio ); + } + + this.media = audio; } else { audio.hide(); this.media = false; @@ -6726,18 +6740,25 @@ template: media.template('video-details'), setMedia: function() { - var video = this.$('.wp-video-shortcode'); + var className = '.wp-video-shortcode', + video, + yt; + + video = this.$( className ); + yt = video.hasClass('youtube-video'); if ( video.find( 'source' ).length ) { if ( video.is(':hidden') ) { video.show(); } - if ( ! video.hasClass('youtube-video') ) { - this.media = this.prepareSrc( video.get(0) ); - } else { - this.media = video.get(0); + video = video.get(0); + + if ( ! yt ) { + video = media.view.MediaDetails.prepareSrc( video ); } + + this.media = video; } else { video.hide(); this.media = false; diff --git a/src/wp-includes/js/tinymce/plugins/wpgallery/plugin.js b/src/wp-includes/js/tinymce/plugins/wpgallery/plugin.js index d33dbca088..8c2a800341 100644 --- a/src/wp-includes/js/tinymce/plugins/wpgallery/plugin.js +++ b/src/wp-includes/js/tinymce/plugins/wpgallery/plugin.js @@ -25,8 +25,8 @@ tinymce.PluginManager.add('wpgallery', function( editor ) { } function replaceAVShortcodes( content ) { - var testRegex = /\[(video-playlist|audio|video|playlist)[^\]]*\]/, - replaceRegex = /\[(video-playlist|audio|video|playlist)[^\]]*\]([\s\S]*?\[\/\1\])?/; + var testRegex = /\[(video-playlist|playlist)[^\]]*\]/, + replaceRegex = /\[(video-playlist|playlist)[^\]]*\]([\s\S]*?\[\/\1\])?/; while ( testRegex.test( content ) ) { content = content.replace( replaceRegex, replaceCallback ); @@ -92,31 +92,6 @@ tinymce.PluginManager.add('wpgallery', function( editor ) { editor.dom.setAttrib( node, 'data-wp-media', window.encodeURIComponent( shortcode ) ); frame.detach(); }); - } else if ( editor.dom.hasClass( node, 'wp-video' ) ) { - frame = wp.media.video.edit( data ); - frame.on( 'close', function () { - frame.detach(); - } ); - frame.state( 'video-details' ).on( - 'update replace add-source select-poster-image add-track', - function ( selection ) { - var shortcode = wp.media.video.shortcode( selection ); - editor.dom.setAttrib( node, 'data-wp-media', window.encodeURIComponent( shortcode ) ); - frame.detach(); - } - ); - frame.open(); - } else if ( editor.dom.hasClass( node, 'wp-audio' ) ) { - frame = wp.media.audio.edit( data ); - frame.on( 'close', function () { - frame.detach(); - } ); - frame.state( 'audio-details' ).on( 'update replace add-source', function ( selection ) { - var shortcode = wp.media.audio.shortcode( selection ); - editor.dom.setAttrib( node, 'data-wp-media', window.encodeURIComponent( shortcode ) ); - frame.detach(); - } ); - frame.open(); } else { // temp window.console && window.console.log( 'Edit AV shortcode ' + data ); diff --git a/src/wp-includes/js/tinymce/skins/wordpress/wp-content.css b/src/wp-includes/js/tinymce/skins/wordpress/wp-content.css index f23dfbdce1..cdf27a8959 100644 --- a/src/wp-includes/js/tinymce/skins/wordpress/wp-content.css +++ b/src/wp-includes/js/tinymce/skins/wordpress/wp-content.css @@ -237,14 +237,32 @@ img::selection { /** * Gallery preview */ -.wpview-type-gallery { +.wpview-type-gallery, +.wpview-type-audio, +.wpview-type-video { position: relative; padding: 0 0 12px; margin-bottom: 16px; cursor: pointer; } - .wpview-type-gallery:after { +.wpview-type-audio { + padding: 32px 0 0; +} + +.wpview-type-video { + padding: 16px 0 0; +} + +.wpview-type-audio audio, +.wpview-type-video video { + display: inline-block; + max-width: 100%; +} + + .wpview-type-gallery:after, + .wpview-type-audio:after, + .wpview-type-video:after { content: ''; display: block; height: 0; @@ -252,11 +270,18 @@ img::selection { visibility: hidden; } - .wpview-type-gallery.selected { +.wpview-type-gallery.selected { background-color: #efefef; } -.wpview-type-gallery .toolbar { +.wpview-type-audio, +.wpview-type-video { + background-color: #efefef; +} + +.wpview-type-gallery .toolbar, +.wpview-type-audio .toolbar, +.wpview-type-video .toolbar { position: absolute; top: 0; left: 0; @@ -264,13 +289,21 @@ img::selection { color: white; padding: 4px; display: none; + z-index: 100; +} + +.wpview-type-audio .toolbar, +.wpview-type-video .toolbar { + display: block; } .wpview-type-gallery.selected .toolbar { display: block; } -.wpview-type-gallery .toolbar span { +.wpview-type-gallery .toolbar span, +.wpview-type-audio .toolbar span +.wpview-type-video .toolbar span { cursor: pointer; } diff --git a/src/wp-includes/media-template.php b/src/wp-includes/media-template.php index 8fa9afd9d4..34f7015ff6 100644 --- a/src/wp-includes/media-template.php +++ b/src/wp-includes/media-template.php @@ -7,6 +7,97 @@ * @since 3.5.0 */ +/** + * Output the markup for a audio tag to be used in an Underscore template + * when data.model is passed. + * + * @since 3.9.0 + */ +function wp_underscore_audio_template() { + $audio_types = wp_get_audio_extensions(); +?> + + +<# +var isYouTube = ! _.isEmpty( data.model.src ) && data.model.src.match(/youtube|youtu\.be/); + w = ! data.model.width || data.model.width > 640 ? 640 : data.model.width, + h = ! data.model.height ? 360 : data.model.height; + +if ( data.model.width && w !== data.model.width ) { + h = Math.ceil( ( h * w ) / data.model.width ); +} +#> + +
+ + + +