/* global tinymce, MediaElementPlayer, WPPlaylistView */ /** * Note: this API is "experimental" meaning that it will probably change * in the next few releases based on feedback from 3.9.0. * If you decide to use it, please follow the development closely. */ // Ensure the global `wp` object exists. window.wp = window.wp || {}; ( function( $ ) { 'use strict'; var views = {}, instances = {}, media = wp.media, viewOptions = ['encodedText']; // Create the `wp.mce` object if necessary. wp.mce = wp.mce || {}; /** * wp.mce.View * * A Backbone-like View constructor intended for use when rendering a TinyMCE View. The main difference is * that the TinyMCE View is not tied to a particular DOM node. * * @param {Object} [options={}] */ wp.mce.View = function( options ) { options = options || {}; this.type = options.type; _.extend( this, _.pick( options, viewOptions ) ); this.initialize.apply( this, arguments ); }; _.extend( wp.mce.View.prototype, { initialize: function() {}, getHtml: function() {}, render: function() { this.setContent( '
\u00a0
' + '\u00a0
', function( self, editor, node ) { $( self ).trigger( 'ready', [ editor, node ] ); }, 'wrap' ); }, unbind: function() {}, setContent: function( html, callback, option ) { _.each( tinymce.editors, function( editor ) { var self = this; if ( editor.plugins.wpview ) { $( editor.getBody() ) .find( '[data-wpview-text="' + this.encodedText + '"]' ) .each( function ( i, element ) { var contentWrap = $( element ).find( '.wpview-content' ), wrap = element; if ( contentWrap.length && option !== 'wrap' ) { element = contentWrap = contentWrap[0]; } if ( _.isString( html ) ) { if ( option === 'replace' ) { element = editor.dom.replace( editor.dom.createFragment( html ), wrap ); } else { editor.dom.setHTML( element, html ); } } else { if ( option === 'replace' ) { element = editor.dom.replace( html, wrap ); } else { element.appendChild( html ); } } if ( _.isFunction( callback ) ) { callback( self, editor, $( element ).find( '.wpview-content' )[0] ); } } ); } }, this ); }, setError: function( message, dashicon ) { this.setContent( '' + message + '
' + '' + 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' ); } } media = wp.media.view.MediaDetails.prepareSrc( media.get(0) ); setTimeout( function() { wp.mce.av.loaded = true; self.players.push( new MediaElementPlayer( media, self.mejsSettings ) ); }, wp.mce.av.loaded ? 10 : 500 ); }, /** * Pass data to the View's Underscore template and return the compiled output * * @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(); } } ), /** * Called when a TinyMCE view is clicked for editing. * - Parses the shortcode out of the element's data attribute * - Calls the `edit` method on the shortcode model * - Launches the model window * - Bind's an `update` callback which updates the element's data attribute * re-renders the view * * @param {HTMLElement} node */ edit: function( node ) { var media = wp.media[ this.type ], self = this, frame, data, callback; $( document ).trigger( 'media:edit' ); data = window.decodeURIComponent( $( node ).attr('data-wpview-text') ); frame = media.edit( data ); frame.on( 'close', function() { frame.detach(); } ); callback = function( selection ) { var shortcode = wp.media[ self.type ].shortcode( selection ).string(); $( node ).attr( 'data-wpview-text', window.encodeURIComponent( shortcode ) ); wp.mce.views.refreshView( self, shortcode ); frame.detach(); }; if ( _.isArray( self.state ) ) { _.each( self.state, function (state) { frame.state( state ).on( 'update', callback ); } ); } else { frame.state( self.state ).on( 'update', callback ); } frame.open(); } }; /** * TinyMCE handler for the video shortcode * * @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' ) } ) } ) ); /** * TinyMCE handler for the audio shortcode * * @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' ) } ) } ) ); /** * TinyMCE handler for the playlist shortcode * * @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(); } } ) } ) ); /** * 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(); 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( '