/* global tinymce */ /** * 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, mediaWindows = [], windowIdx = 0, waitInterval = 50, 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() { return ''; }, loadingPlaceholder: function() { return '' + '
\u00a0
' + '\u00a0
', 'wrap' ); $( this ).trigger( 'ready' ); this.rendered( true ); } }, unbind: function() {}, getEditors: function( callback ) { var editors = []; _.each( tinymce.editors, function( editor ) { if ( editor.plugins.wpview ) { if ( callback ) { callback( editor ); } editors.push( editor ); } }, this ); return editors; }, getNodes: function( callback ) { var nodes = [], self = this; this.getEditors( function( editor ) { $( editor.getBody() ) .find( '[data-wpview-text="' + self.encodedText + '"]' ) .each( function ( i, node ) { if ( callback ) { callback( editor, node, $( node ).find( '.wpview-content' ).get( 0 ) ); } nodes.push( node ); } ); } ); return nodes; }, setContent: function( html, option ) { this.getNodes( function ( editor, node, content ) { var el = ( option === 'wrap' || option === 'replace' ) ? node : content, insert = html; if ( _.isString( insert ) ) { insert = editor.dom.createFragment( insert ); } if ( option === 'replace' ) { editor.dom.replace( insert, el ); } else { el.innerHTML = ''; el.appendChild( insert ); } } ); }, /* jshint scripturl: true */ setIframes: function ( head, body ) { var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver, importStyles = this.type === 'video' || this.type === 'audio' || this.type === 'playlist'; if ( head || body.indexOf( '/g, '>' ); } ); } this.getNodes( function ( editor, node, content ) { var dom = editor.dom, styles = '', bodyClasses = editor.getBody().className || '', iframe, iframeDoc, i, resize; content.innerHTML = ''; head = head || ''; if ( importStyles ) { if ( ! wp.mce.views.sandboxStyles ) { tinymce.each( dom.$( 'link[rel="stylesheet"]', editor.getDoc().head ), function( link ) { if ( link.href && link.href.indexOf( 'skins/lightgray/content.min.css' ) === -1 && link.href.indexOf( 'skins/wordpress/wp-content.css' ) === -1 ) { styles += dom.getOuterHTML( link ) + '\n'; } }); wp.mce.views.sandboxStyles = styles; } else { styles = wp.mce.views.sandboxStyles; } } // Seems Firefox needs a bit of time to insert/set the view nodes, or the iframe will fail // especially when switching Text => Visual. setTimeout( function() { iframe = dom.add( content, 'iframe', { src: tinymce.Env.ie ? 'javascript:""' : '', frameBorder: '0', allowTransparency: 'true', scrolling: 'no', 'class': 'wpview-sandbox', style: { width: '100%', display: 'block' } } ); iframeDoc = iframe.contentWindow.document; iframeDoc.open(); iframeDoc.write( '' + '' + '' + '' + head + styles + '' + '' + '' + body + '' + '' ); iframeDoc.close(); resize = function() { // Make sure the iframe still exists. iframe.contentWindow && $( 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 ); } } if ( importStyles ) { editor.on( 'wp-body-class-change', function() { iframeDoc.body.className = editor.getBody().className; }); } }, waitInterval ); }); } else { this.setContent( body ); } }, setError: function( message, dashicon ) { this.setContent( '' + message + '
' + '' + this.original + '
', 'replace' ); } } else if ( this.error.statusText ) { this.setError( this.error.statusText, 'admin-media' ); } else if ( this.original ) { this.setContent( '' + this.original + '
', 'replace' ); } }, stopPlayers: function( remove ) { var rem = remove === 'remove'; this.getNodes( function( editor, node, content ) { var p, win, iframe = $( 'iframe.wpview-sandbox', content ).get(0); if ( iframe && ( win = iframe.contentWindow ) && win.mejs ) { // Sometimes ME.js may show a "Download File" placeholder and player.remove() doesn't exist there. try { for ( p in win.mejs.players ) { win.mejs.players[p].pause(); if ( rem ) { win.mejs.players[p].remove(); } } } catch( er ) {} } }); }, unbind: function() { this.stopPlayers( 'remove' ); } }, /** * 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' } ) ); /** * TinyMCE handler for the audio shortcode * * @mixes wp.mce.av */ wp.mce.views.register( 'audio', _.extend( {}, wp.mce.av, { state: 'audio-details' } ) ); /** * 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' ] } ) ); /** * TinyMCE handler for the embed shortcode */ wp.mce.embedMixin = { View: _.extend( {}, wp.mce.av.View, { overlay: true, action: 'parse-embed', initialize: function( options ) { this.content = options.content; this.original = options.url || options.shortcode.string(); if ( options.url ) { this.shortcode = media.embed.shortcode( { url: options.url } ); } else { this.shortcode = options.shortcode; } _.bindAll( this, 'setIframes', 'setNodes', 'fetch' ); $( this ).on( 'ready', this.setNodes ); this.fetch(); } } ), edit: function( node ) { var embed = media.embed, self = this, frame, data, isURL = 'embedURL' === this.type; $( document ).trigger( 'media:edit' ); data = window.decodeURIComponent( $( node ).attr('data-wpview-text') ); frame = embed.edit( data, isURL ); frame.on( 'close', function() { frame.detach(); } ); frame.state( 'embed' ).props.on( 'change:url', function (model, url) { if ( ! url ) { return; } frame.state( 'embed' ).metadata = model.toJSON(); } ); frame.state( 'embed' ).on( 'select', function() { var shortcode; if ( isURL ) { shortcode = frame.state( 'embed' ).metadata.url; } else { shortcode = embed.shortcode( frame.state( 'embed' ).metadata ).string(); } $( node ).attr( 'data-wpview-text', window.encodeURIComponent( shortcode ) ); wp.mce.views.refreshView( self, shortcode ); frame.detach(); } ); frame.open(); } }; wp.mce.views.register( 'embed', _.extend( {}, wp.mce.embedMixin ) ); wp.mce.views.register( 'embedURL', _.extend( {}, wp.mce.embedMixin, { toView: function( content ) { var re = /(?:^|)(https?:\/\/[^\s"]+?)(?:<\/p>\s*|$)/gi, match = re.exec( tinymce.trim( content ) ); if ( ! match ) { return; } return { index: match.index, content: match[0], options: { url: match[1] } }; } } ) ); }(jQuery));