~canonical-sysadmins/wordpress/4.7.2

« back to all changes in this revision

Viewing changes to wp-includes/js/tinymce/plugins/wpview/plugin.js

  • Committer: Jacek Nykis
  • Date: 2015-01-05 16:17:05 UTC
  • Revision ID: jacek.nykis@canonical.com-20150105161705-w544l1h5mcg7u4w9
Initial commit

Show diffs side-by-side

added added

removed removed

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