]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/js/tinymce/plugins/wpview/plugin.js
WordPress 4.2-scripts
[autoinstalls/wordpress.git] / wp-includes / js / tinymce / plugins / wpview / plugin.js
1 /* global tinymce */
2
3 /**
4  * WordPress View plugin.
5  */
6 tinymce.PluginManager.add( 'wpview', function( editor ) {
7         var $ = editor.$,
8                 selected,
9                 Env = tinymce.Env,
10                 VK = tinymce.util.VK,
11                 TreeWalker = tinymce.dom.TreeWalker,
12                 toRemove = false,
13                 firstFocus = true,
14                 _noop = function() { return false; },
15                 isios = /iPad|iPod|iPhone/.test( navigator.userAgent ),
16                 cursorInterval,
17                 lastKeyDownNode,
18                 setViewCursorTries,
19                 focus,
20                 execCommandView,
21                 execCommandBefore,
22                 toolbar;
23
24         function getView( node ) {
25                 return getParent( node, 'wpview-wrap' );
26         }
27
28         /**
29          * Returns the node or a parent of the node that has the passed className.
30          * Doing this directly is about 40% faster
31          */
32         function getParent( node, className ) {
33                 while ( node && node.parentNode ) {
34                         if ( node.className && ( ' ' + node.className + ' ' ).indexOf( ' ' + className + ' ' ) !== -1 ) {
35                                 return node;
36                         }
37
38                         node = node.parentNode;
39                 }
40
41                 return false;
42         }
43
44         function _stop( event ) {
45                 event.stopPropagation();
46         }
47
48         function setViewCursor( before, view ) {
49                 var location = before ? 'before' : 'after',
50                         offset = before ? 0 : 1;
51                 deselect();
52                 editor.selection.setCursorLocation( editor.dom.select( '.wpview-selection-' + location, view )[0], offset );
53                 editor.nodeChanged();
54         }
55
56         function handleEnter( view, before, key ) {
57                 var dom = editor.dom,
58                         padNode = dom.create( 'p' );
59
60                 if ( ! ( Env.ie && Env.ie < 11 ) ) {
61                         padNode.innerHTML = '<br data-mce-bogus="1">';
62                 }
63
64                 if ( before ) {
65                         view.parentNode.insertBefore( padNode, view );
66                 } else {
67                         dom.insertAfter( padNode, view );
68                 }
69
70                 deselect();
71
72                 if ( before && key === VK.ENTER ) {
73                         setViewCursor( before, view );
74                 } else {
75                         editor.selection.setCursorLocation( padNode, 0 );
76                 }
77
78                 editor.nodeChanged();
79         }
80
81         function removeView( view ) {
82                 editor.undoManager.transact( function() {
83                         handleEnter( view );
84                         wp.mce.views.remove( editor, view );
85                 });
86         }
87
88         function select( viewNode ) {
89                 var clipboard,
90                         dom = editor.dom;
91
92                 if ( ! viewNode ) {
93                         return;
94                 }
95
96                 if ( viewNode !== selected ) {
97                         // Make sure that the editor is focused.
98                         // It is possible that the editor is not focused when the mouse event fires
99                         // without focus, the selection will not work properly.
100                         editor.getBody().focus();
101
102                         deselect();
103                         selected = viewNode;
104                         dom.setAttrib( viewNode, 'data-mce-selected', 1 );
105
106                         clipboard = dom.create( 'div', {
107                                 'class': 'wpview-clipboard',
108                                 'contenteditable': 'true'
109                         }, wp.mce.views.getText( viewNode ) );
110
111                         editor.dom.select( '.wpview-body', viewNode )[0].appendChild( clipboard );
112
113                         // Both of the following are necessary to prevent manipulating the selection/focus
114                         dom.bind( clipboard, 'beforedeactivate focusin focusout', _stop );
115                         dom.bind( selected, 'beforedeactivate focusin focusout', _stop );
116
117                         // select the hidden div
118                         if ( isios ) {
119                                 editor.selection.select( clipboard );
120                         } else {
121                                 editor.selection.select( clipboard, true );
122                         }
123                 }
124
125                 editor.nodeChanged();
126                 editor.fire( 'wpview-selected', viewNode );
127         }
128
129         /**
130          * Deselect a selected view and remove clipboard
131          */
132         function deselect() {
133                 var clipboard,
134                         dom = editor.dom;
135
136                 if ( selected ) {
137                         clipboard = editor.dom.select( '.wpview-clipboard', selected )[0];
138                         dom.unbind( clipboard );
139                         dom.remove( clipboard );
140
141                         dom.unbind( selected, 'beforedeactivate focusin focusout click mouseup', _stop );
142                         dom.setAttrib( selected, 'data-mce-selected', null );
143                 }
144
145                 selected = null;
146         }
147
148         // Check if the `wp.mce` API exists.
149         if ( typeof wp === 'undefined' || ! wp.mce ) {
150                 return {
151                         getView: _noop
152                 };
153         }
154
155         // Remove the content of view wrappers from HTML string
156         function emptyViews( content ) {
157                 content = content.replace( /<div[^>]+data-wpview-text="([^"]+)"[^>]*>[\s\S]+?wpview-selection-after[^>]+>[^<>]*<\/p>\s*<\/div>/g, function( all, match ) {
158                         return '<p>' + window.decodeURIComponent( match ) + '</p>';
159                 });
160
161                 return content.replace( / data-wpview-marker="[^"]+"/g, '' );
162         }
163
164         // Prevent adding undo levels on changes inside a view wrapper
165         editor.on( 'BeforeAddUndo', function( event ) {
166                 if ( event.level.content ) {
167                         event.level.content = emptyViews( event.level.content );
168                 }
169         });
170
171         // When the editor's content changes, scan the new content for
172         // matching view patterns, and transform the matches into
173         // view wrappers.
174         editor.on( 'BeforeSetContent', function( event ) {
175                 var node;
176
177                 if ( ! event.selection ) {
178                         wp.mce.views.unbind();
179                 }
180
181                 if ( ! event.content ) {
182                         return;
183                 }
184
185                 if ( ! event.load ) {
186                         if ( selected ) {
187                                 removeView( selected );
188                         }
189
190                         node = editor.selection.getNode();
191
192                         if ( node && node !== editor.getBody() && /^\s*https?:\/\/\S+\s*$/i.test( event.content ) ) {
193                                 // When a url is pasted or inserted, only try to embed it when it is in an empty paragrapgh.
194                                 node = editor.dom.getParent( node, 'p' );
195
196                                 if ( node && /^[\s\uFEFF\u00A0]*$/.test( $( node ).text() || '' ) ) {
197                                         // Make sure there are no empty inline elements in the <p>
198                                         node.innerHTML = '';
199                                 } else {
200                                         return;
201                                 }
202                         }
203                 }
204
205                 event.content = wp.mce.views.setMarkers( event.content );
206         });
207
208         // When pasting strip all tags and check if the string is an URL.
209         // Then replace the pasted content with the cleaned URL.
210         editor.on( 'pastePreProcess', function( event ) {
211                 var pastedStr = event.content;
212
213                 if ( pastedStr ) {
214                         pastedStr = tinymce.trim( pastedStr.replace( /<[^>]+>/g, '' ) );
215
216                         if ( /^https?:\/\/\S+$/i.test( pastedStr ) ) {
217                                 event.content = pastedStr;
218                         }
219                 }
220         });
221
222         // When the editor's content has been updated and the DOM has been
223         // processed, render the views in the document.
224         editor.on( 'SetContent', function() {
225                 wp.mce.views.render();
226         });
227
228         // Set the cursor before or after a view when clicking next to it.
229         editor.on( 'click', function( event ) {
230                 var x = event.clientX,
231                         y = event.clientY,
232                         body = editor.getBody(),
233                         bodyRect = body.getBoundingClientRect(),
234                         first = body.firstChild,
235                         last = body.lastChild,
236                         firstRect, lastRect, view;
237
238                 if ( ! first || ! last ) {
239                         return;
240                 }
241
242                 firstRect = first.getBoundingClientRect();
243                 lastRect = last.getBoundingClientRect();
244
245                 if ( y < firstRect.top && ( view = getView( first ) ) ) {
246                         setViewCursor( true, view );
247                         event.preventDefault();
248                 } else if ( y > lastRect.bottom && ( view = getView( last ) ) ) {
249                         setViewCursor( false, view );
250                         event.preventDefault();
251                 } else if ( x < bodyRect.left || x > bodyRect.right ) {
252                         tinymce.each( editor.dom.select( '.wpview-wrap' ), function( view ) {
253                                 var rect = view.getBoundingClientRect();
254
255                                 if ( y < rect.top ) {
256                                         return false;
257                                 }
258
259                                 if ( y >= rect.top && y <= rect.bottom ) {
260                                         if ( x < bodyRect.left ) {
261                                                 setViewCursor( true, view );
262                                                 event.preventDefault();
263                                         } else if ( x > bodyRect.right ) {
264                                                 setViewCursor( false, view );
265                                                 event.preventDefault();
266                                         }
267
268                                         return false;
269                                 }
270                         });
271                 }
272         });
273
274         editor.on( 'init', function() {
275                 var scrolled = false,
276                         selection = editor.selection,
277                         MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
278
279                 // When a view is selected, ensure content that is being pasted
280                 // or inserted is added to a text node (instead of the view).
281                 editor.on( 'BeforeSetContent', function() {
282                         var walker, target,
283                                 view = getView( selection.getNode() );
284
285                         // If the selection is not within a view, bail.
286                         if ( ! view ) {
287                                 return;
288                         }
289
290                         if ( ! view.nextSibling || getView( view.nextSibling ) ) {
291                                 // If there are no additional nodes or the next node is a
292                                 // view, create a text node after the current view.
293                                 target = editor.getDoc().createTextNode('');
294                                 editor.dom.insertAfter( target, view );
295                         } else {
296                                 // Otherwise, find the next text node.
297                                 walker = new TreeWalker( view.nextSibling, view.nextSibling );
298                                 target = walker.next();
299                         }
300
301                         // Select the `target` text node.
302                         selection.select( target );
303                         selection.collapse( true );
304                 });
305
306                 editor.dom.bind( editor.getDoc(), 'touchmove', function() {
307                         scrolled = true;
308                 });
309
310                 editor.on( 'mousedown mouseup click touchend', function( event ) {
311                         var view = getView( event.target );
312
313                         firstFocus = false;
314
315                         // Contain clicks inside the view wrapper
316                         if ( view ) {
317                                 event.stopImmediatePropagation();
318                                 event.preventDefault();
319
320                                 if ( event.type === 'touchend' && scrolled ) {
321                                         scrolled = false;
322                                 } else {
323                                         select( view );
324                                 }
325
326                                 // Returning false stops the ugly bars from appearing in IE11 and stops the view being selected as a range in FF.
327                                 // Unfortunately, it also inhibits the dragging of views to a new location.
328                                 return false;
329                         } else {
330                                 if ( event.type === 'touchend' || event.type === 'mousedown' ) {
331                                         deselect();
332                                 }
333                         }
334
335                         if ( event.type === 'touchend' && scrolled ) {
336                                 scrolled = false;
337                         }
338                 }, true );
339
340                 if ( MutationObserver ) {
341                         new MutationObserver( function() {
342                                 editor.fire( 'wp-body-class-change' );
343                         } )
344                         .observe( editor.getBody(), {
345                                 attributes: true,
346                                 attributeFilter: ['class']
347                         } );
348                 }
349         });
350
351         function resetViews( rootNode ) {
352                 // Replace view nodes
353                 $( 'div[data-wpview-text]', rootNode ).each( function( i, node ) {
354                         var $node = $( node ),
355                                 text = window.decodeURIComponent( $node.attr( 'data-wpview-text' ) || '' );
356
357                         if ( text && node.parentNode ) {
358                                 $node.replaceWith( $( editor.dom.create('p') ).text( text ) );
359                         }
360                 });
361
362                 // Remove marker attributes
363                 $( 'p[data-wpview-marker]', rootNode ).attr( 'data-wpview-marker', null );
364         }
365
366         editor.on( 'PreProcess', function( event ) {
367                 // Replace the view nodes with their text in the DOM clone.
368                 resetViews( event.node );
369         }, true );
370
371         editor.on( 'hide', function() {
372                 // Replace the view nodes with their text directly in the editor body.
373                 wp.mce.views.unbind();
374                 deselect();
375                 resetViews( editor.getBody() );
376         });
377
378         // Excludes arrow keys, delete, backspace, enter, space bar.
379         // Ref: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.keyCode
380         function isSpecialKey( key ) {
381                 return ( ( key <= 47 && key !== VK.SPACEBAR && key !== VK.ENTER && key !== VK.DELETE && key !== VK.BACKSPACE && ( key < 37 || key > 40 ) ) ||
382                         key >= 224 || // OEM or non-printable
383                         ( key >= 144 && key <= 150 ) || // Num Lock, Scroll Lock, OEM
384                         ( key >= 91 && key <= 93 ) || // Windows keys
385                         ( key >= 112 && key <= 135 ) ); // F keys
386         }
387
388         // (De)select views when arrow keys are used to navigate the content of the editor.
389         editor.on( 'keydown', function( event ) {
390                 var key = event.keyCode,
391                         dom = editor.dom,
392                         selection = editor.selection,
393                         node, view, cursorBefore, cursorAfter,
394                         range, clonedRange, tempRange;
395
396                 if ( selected ) {
397                         // Ignore key presses that involve the command or control key, but continue when in combination with backspace or v.
398                         // Also ignore the F# keys.
399                         if ( ( ( event.metaKey || event.ctrlKey ) && key !== VK.BACKSPACE && key !== 86 ) || ( key >= 112 && key <= 123 ) ) {
400                                 // Remove the view when pressing cmd/ctrl+x on keyup, otherwise the browser can't copy the content.
401                                 if ( ( event.metaKey || event.ctrlKey ) && key === 88 ) {
402                                         toRemove = selected;
403                                 }
404                                 return;
405                         }
406
407                         view = getView( selection.getNode() );
408
409                         // If the caret is not within the selected view, deselect the view and bail.
410                         if ( view !== selected ) {
411                                 deselect();
412                                 return;
413                         }
414
415                         if ( key === VK.LEFT ) {
416                                 setViewCursor( true, view );
417                                 event.preventDefault();
418                         } else if ( key === VK.UP ) {
419                                 if ( view.previousSibling ) {
420                                         if ( getView( view.previousSibling ) ) {
421                                                 setViewCursor( true, view.previousSibling );
422                                         } else {
423                                                 deselect();
424                                                 selection.select( view.previousSibling, true );
425                                                 selection.collapse();
426                                         }
427                                 } else {
428                                         setViewCursor( true, view );
429                                 }
430                                 event.preventDefault();
431                         } else if ( key === VK.RIGHT ) {
432                                 setViewCursor( false, view );
433                                 event.preventDefault();
434                         } else if ( key === VK.DOWN ) {
435                                 if ( view.nextSibling ) {
436                                         if ( getView( view.nextSibling ) ) {
437                                                 setViewCursor( false, view.nextSibling );
438                                         } else {
439                                                 deselect();
440                                                 selection.setCursorLocation( view.nextSibling, 0 );
441                                         }
442                                 } else {
443                                         setViewCursor( false, view );
444                                 }
445
446                                 event.preventDefault();
447                         // Ignore keys that don't insert anything.
448                         } else if ( ! isSpecialKey( key ) ) {
449                                 removeView( selected );
450
451                                 if ( key === VK.ENTER || key === VK.DELETE || key === VK.BACKSPACE ) {
452                                         event.preventDefault();
453                                 }
454                         }
455                 } else {
456                         if ( event.metaKey || event.ctrlKey || ( key >= 112 && key <= 123 ) ) {
457                                 return;
458                         }
459
460                         node = selection.getNode();
461                         lastKeyDownNode = node;
462                         view = getView( node );
463
464                         // Make sure we don't delete part of a view.
465                         // If the range ends or starts with the view, we'll need to trim it.
466                         if ( ! selection.isCollapsed() ) {
467                                 range = selection.getRng();
468
469                                 if ( view = getView( range.endContainer ) ) {
470                                         clonedRange = range.cloneRange();
471                                         selection.select( view.previousSibling, true );
472                                         selection.collapse();
473                                         tempRange = selection.getRng();
474                                         clonedRange.setEnd( tempRange.endContainer, tempRange.endOffset );
475                                         selection.setRng( clonedRange );
476                                 } else if ( view = getView( range.startContainer ) ) {
477                                         clonedRange = range.cloneRange();
478                                         clonedRange.setStart( view.nextSibling, 0 );
479                                         selection.setRng( clonedRange );
480                                 }
481                         }
482
483                         if ( ! view ) {
484                                 // Make sure we don't eat any content.
485                                 if ( event.keyCode === VK.BACKSPACE ) {
486                                         if ( editor.dom.isEmpty( node ) ) {
487                                                 if ( view = getView( node.previousSibling ) ) {
488                                                         setViewCursor( false, view );
489                                                         editor.dom.remove( node );
490                                                         event.preventDefault();
491                                                 }
492                                         } else if ( ( range = selection.getRng() ) &&
493                                                         range.startOffset === 0 &&
494                                                         range.endOffset === 0 &&
495                                                         ( view = getView( node.previousSibling ) ) ) {
496                                                 setViewCursor( false, view );
497                                                 event.preventDefault();
498                                         }
499                                 }
500                                 return;
501                         }
502
503                         if ( ! ( ( cursorBefore = dom.hasClass( view, 'wpview-selection-before' ) ) ||
504                                         ( cursorAfter = dom.hasClass( view, 'wpview-selection-after' ) ) ) ) {
505                                 return;
506                         }
507
508                         if ( isSpecialKey( key ) ) {
509                                 // ignore
510                                 return;
511                         }
512
513                         if ( ( cursorAfter && key === VK.UP ) || ( cursorBefore && key === VK.BACKSPACE ) ) {
514                                 if ( view.previousSibling ) {
515                                         if ( getView( view.previousSibling ) ) {
516                                                 setViewCursor( false, view.previousSibling );
517                                         } else {
518                                                 if ( dom.isEmpty( view.previousSibling ) && key === VK.BACKSPACE ) {
519                                                         dom.remove( view.previousSibling );
520                                                 } else {
521                                                         selection.select( view.previousSibling, true );
522                                                         selection.collapse();
523                                                 }
524                                         }
525                                 } else {
526                                         setViewCursor( true, view );
527                                 }
528                                 event.preventDefault();
529                         } else if ( cursorAfter && ( key === VK.DOWN || key === VK.RIGHT ) ) {
530                                 if ( view.nextSibling ) {
531                                         if ( getView( view.nextSibling ) ) {
532                                                 setViewCursor( key === VK.RIGHT, view.nextSibling );
533                                         } else {
534                                                 selection.setCursorLocation( view.nextSibling, 0 );
535                                         }
536                                 }
537                                 event.preventDefault();
538                         } else if ( cursorBefore && ( key === VK.UP || key ===  VK.LEFT ) ) {
539                                 if ( view.previousSibling ) {
540                                         if ( getView( view.previousSibling ) ) {
541                                                 setViewCursor( key === VK.UP, view.previousSibling );
542                                         } else {
543                                                 selection.select( view.previousSibling, true );
544                                                 selection.collapse();
545                                         }
546                                 }
547                                 event.preventDefault();
548                         } else if ( cursorBefore && key === VK.DOWN ) {
549                                 if ( view.nextSibling ) {
550                                         if ( getView( view.nextSibling ) ) {
551                                                 setViewCursor( true, view.nextSibling );
552                                         } else {
553                                                 selection.setCursorLocation( view.nextSibling, 0 );
554                                         }
555                                 } else {
556                                         setViewCursor( false, view );
557                                 }
558                                 event.preventDefault();
559                         } else if ( ( cursorAfter && key === VK.LEFT ) || ( cursorBefore && key === VK.RIGHT ) ) {
560                                 select( view );
561                                 event.preventDefault();
562                         } else if ( cursorAfter && key === VK.BACKSPACE ) {
563                                 removeView( view );
564                                 event.preventDefault();
565                         } else if ( cursorAfter ) {
566                                 handleEnter( view );
567                         } else if ( cursorBefore ) {
568                                 handleEnter( view , true, key );
569                         }
570
571                         if ( key === VK.ENTER ) {
572                                 event.preventDefault();
573                         }
574                 }
575         });
576
577         editor.on( 'keyup', function() {
578                 if ( toRemove ) {
579                         removeView( toRemove );
580                         toRemove = false;
581                 }
582         });
583
584         editor.on( 'focus', function() {
585                 var view;
586
587                 focus = true;
588                 editor.dom.addClass( editor.getBody(), 'has-focus' );
589
590                 // Edge case: show the fake caret when the editor is focused for the first time
591                 // and the first element is a view.
592                 if ( firstFocus && ( view = getView( editor.getBody().firstChild ) ) ) {
593                         setViewCursor( true, view );
594                 }
595
596                 firstFocus = false;
597         } );
598
599         editor.on( 'blur', function() {
600                 focus = false;
601                 editor.dom.removeClass( editor.getBody(), 'has-focus' );
602         } );
603
604         editor.on( 'NodeChange', function( event ) {
605                 var dom = editor.dom,
606                         views = editor.dom.select( '.wpview-wrap' ),
607                         className = event.element.className,
608                         view = getView( event.element ),
609                         lKDN = lastKeyDownNode;
610
611                 lastKeyDownNode = false;
612
613                 clearInterval( cursorInterval );
614
615                 // This runs a lot and is faster than replacing each class separately
616                 tinymce.each( views, function ( view ) {
617                         if ( view.className ) {
618                                 view.className = view.className.replace( / ?\bwpview-(?:selection-before|selection-after|cursor-hide)\b/g, '' );
619                         }
620                 });
621
622                 if ( focus && view ) {
623                         if ( ( className === 'wpview-selection-before' || className === 'wpview-selection-after' ) &&
624                                 editor.selection.isCollapsed() ) {
625
626                                 setViewCursorTries = 0;
627
628                                 deselect();
629
630                                 // Make sure the cursor arrived in the right node.
631                                 // This is necessary for Firefox.
632                                 if ( lKDN === view.previousSibling ) {
633                                         setViewCursor( true, view );
634                                         return;
635                                 } else if ( lKDN === view.nextSibling ) {
636                                         setViewCursor( false, view );
637                                         return;
638                                 }
639
640                                 dom.addClass( view, className );
641
642                                 cursorInterval = setInterval( function() {
643                                         if ( dom.hasClass( view, 'wpview-cursor-hide' ) ) {
644                                                 dom.removeClass( view, 'wpview-cursor-hide' );
645                                         } else {
646                                                 dom.addClass( view, 'wpview-cursor-hide' );
647                                         }
648                                 }, 500 );
649                         // If the cursor lands anywhere else in the view, set the cursor before it.
650                         // Only try this once to prevent a loop. (You never know.)
651                         } else if ( ! getParent( event.element, 'wpview-clipboard' ) && ! setViewCursorTries ) {
652                                 deselect();
653                                 setViewCursorTries++;
654                                 setViewCursor( true, view );
655                         }
656                 }
657         });
658
659         editor.on( 'BeforeExecCommand', function() {
660                 var node = editor.selection.getNode(),
661                         view;
662
663                 if ( node && ( ( execCommandBefore = node.className === 'wpview-selection-before' ) || node.className === 'wpview-selection-after' ) && ( view = getView( node ) ) ) {
664                         handleEnter( view, execCommandBefore );
665                         execCommandView = view;
666                 }
667         });
668
669         editor.on( 'ExecCommand', function() {
670                 var toSelect, node;
671
672                 if ( selected ) {
673                         toSelect = selected;
674                         deselect();
675                         select( toSelect );
676                 }
677
678                 if ( execCommandView ) {
679                         node = execCommandView[ execCommandBefore ? 'previousSibling' : 'nextSibling' ];
680
681                         if ( node && node.nodeName === 'P' && editor.dom.isEmpty( node ) ) {
682                                 editor.dom.remove( node );
683                                 setViewCursor( execCommandBefore, execCommandView );
684                         }
685
686                         execCommandView = false;
687                 }
688         });
689
690         editor.on( 'ResolveName', function( event ) {
691                 if ( editor.dom.hasClass( event.target, 'wpview-wrap' ) ) {
692                         event.name = editor.dom.getAttrib( event.target, 'data-wpview-type' ) || 'wpview';
693                         event.stopPropagation();
694                 } else if ( getView( event.target ) ) {
695                         event.preventDefault();
696                         event.stopPropagation();
697                 }
698         });
699
700         editor.addButton( 'wp_view_edit', {
701                 tooltip: 'Edit ', // trailing space is needed, used for context
702                 icon: 'dashicon dashicons-edit',
703                 onclick: function() {
704                         selected && wp.mce.views.edit( editor, selected );
705                 }
706         } );
707
708         editor.addButton( 'wp_view_remove', {
709                 tooltip: 'Remove',
710                 icon: 'dashicon dashicons-no',
711                 onclick: function() {
712                         selected && removeView( selected );
713                 }
714         } );
715
716         editor.once( 'preinit', function() {
717                 toolbar = editor.wp._createToolbar( [
718                         'wp_view_edit',
719                         'wp_view_remove'
720                 ] );
721         } );
722
723         editor.on( 'wptoolbar', function( event ) {
724                 if ( selected ) {
725                         event.element = selected;
726                         event.toolbar = toolbar;
727                 }
728         } );
729
730         // Add to editor.wp
731         editor.wp = editor.wp || {};
732         editor.wp.getView = getView;
733
734         // Keep for back-compat.
735         return {
736                 getView: getView
737         };
738 });