angelovcom.net

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

editor-expand.js (42607B)


      1 /**
      2  * @output wp-admin/js/editor-expand.js
      3  */
      4 
      5 ( function( window, $, undefined ) {
      6 	'use strict';
      7 
      8 	var $window = $( window ),
      9 		$document = $( document ),
     10 		$adminBar = $( '#wpadminbar' ),
     11 		$footer = $( '#wpfooter' );
     12 
     13 	/**
     14 	 * Handles the resizing of the editor.
     15 	 *
     16 	 * @since 4.0.0
     17 	 *
     18 	 * @return {void}
     19 	 */
     20 	$( function() {
     21 		var $wrap = $( '#postdivrich' ),
     22 			$contentWrap = $( '#wp-content-wrap' ),
     23 			$tools = $( '#wp-content-editor-tools' ),
     24 			$visualTop = $(),
     25 			$visualEditor = $(),
     26 			$textTop = $( '#ed_toolbar' ),
     27 			$textEditor = $( '#content' ),
     28 			textEditor = $textEditor[0],
     29 			oldTextLength = 0,
     30 			$bottom = $( '#post-status-info' ),
     31 			$menuBar = $(),
     32 			$statusBar = $(),
     33 			$sideSortables = $( '#side-sortables' ),
     34 			$postboxContainer = $( '#postbox-container-1' ),
     35 			$postBody = $('#post-body'),
     36 			fullscreen = window.wp.editor && window.wp.editor.fullscreen,
     37 			mceEditor,
     38 			mceBind = function(){},
     39 			mceUnbind = function(){},
     40 			fixedTop = false,
     41 			fixedBottom = false,
     42 			fixedSideTop = false,
     43 			fixedSideBottom = false,
     44 			scrollTimer,
     45 			lastScrollPosition = 0,
     46 			pageYOffsetAtTop = 130,
     47 			pinnedToolsTop = 56,
     48 			sidebarBottom = 20,
     49 			autoresizeMinHeight = 300,
     50 			initialMode = $contentWrap.hasClass( 'tmce-active' ) ? 'tinymce' : 'html',
     51 			advanced = !! parseInt( window.getUserSetting( 'hidetb' ), 10 ),
     52 			// These are corrected when adjust() runs, except on scrolling if already set.
     53 			heights = {
     54 				windowHeight: 0,
     55 				windowWidth: 0,
     56 				adminBarHeight: 0,
     57 				toolsHeight: 0,
     58 				menuBarHeight: 0,
     59 				visualTopHeight: 0,
     60 				textTopHeight: 0,
     61 				bottomHeight: 0,
     62 				statusBarHeight: 0,
     63 				sideSortablesHeight: 0
     64 			};
     65 
     66 		/**
     67 		 * Resizes textarea based on scroll height and width.
     68 		 *
     69 		 * Doesn't shrink the editor size below the 300px auto resize minimum height.
     70 		 *
     71 		 * @since 4.6.1
     72 		 *
     73 		 * @return {void}
     74 		 */
     75 		var shrinkTextarea = window._.throttle( function() {
     76 			var x = window.scrollX || document.documentElement.scrollLeft;
     77 			var y = window.scrollY || document.documentElement.scrollTop;
     78 			var height = parseInt( textEditor.style.height, 10 );
     79 
     80 			textEditor.style.height = autoresizeMinHeight + 'px';
     81 
     82 			if ( textEditor.scrollHeight > autoresizeMinHeight ) {
     83 				textEditor.style.height = textEditor.scrollHeight + 'px';
     84 			}
     85 
     86 			if ( typeof x !== 'undefined' ) {
     87 				window.scrollTo( x, y );
     88 			}
     89 
     90 			if ( textEditor.scrollHeight < height ) {
     91 				adjust();
     92 			}
     93 		}, 300 );
     94 
     95 		/**
     96 		 * Resizes the text editor depending on the old text length.
     97 		 *
     98 		 * If there is an mceEditor and it is hidden, it resizes the editor depending
     99 		 * on the old text length. If the current length of the text is smaller than
    100 		 * the old text length, it shrinks the text area. Otherwise it resizes the editor to
    101 		 * the scroll height.
    102 		 *
    103 		 * @since 4.6.1
    104 		 *
    105 		 * @return {void}
    106 		 */
    107 		function textEditorResize() {
    108 			var length = textEditor.value.length;
    109 
    110 			if ( mceEditor && ! mceEditor.isHidden() ) {
    111 				return;
    112 			}
    113 
    114 			if ( ! mceEditor && initialMode === 'tinymce' ) {
    115 				return;
    116 			}
    117 
    118 			if ( length < oldTextLength ) {
    119 				shrinkTextarea();
    120 			} else if ( parseInt( textEditor.style.height, 10 ) < textEditor.scrollHeight ) {
    121 				textEditor.style.height = Math.ceil( textEditor.scrollHeight ) + 'px';
    122 				adjust();
    123 			}
    124 
    125 			oldTextLength = length;
    126 		}
    127 
    128 		/**
    129 		 * Gets the height and widths of elements.
    130 		 *
    131 		 * Gets the heights of the window, the adminbar, the tools, the menu,
    132 		 * the visualTop, the textTop, the bottom, the statusbar and sideSortables
    133 		 * and stores these in the heights object. Defaults to 0.
    134 		 * Gets the width of the window and stores this in the heights object.
    135 		 *
    136 		 * @since 4.0.0
    137 		 *
    138 		 * @return {void}
    139 		 */
    140 		function getHeights() {
    141 			var windowWidth = $window.width();
    142 
    143 			heights = {
    144 				windowHeight: $window.height(),
    145 				windowWidth: windowWidth,
    146 				adminBarHeight: ( windowWidth > 600 ? $adminBar.outerHeight() : 0 ),
    147 				toolsHeight: $tools.outerHeight() || 0,
    148 				menuBarHeight: $menuBar.outerHeight() || 0,
    149 				visualTopHeight: $visualTop.outerHeight() || 0,
    150 				textTopHeight: $textTop.outerHeight() || 0,
    151 				bottomHeight: $bottom.outerHeight() || 0,
    152 				statusBarHeight: $statusBar.outerHeight() || 0,
    153 				sideSortablesHeight: $sideSortables.height() || 0
    154 			};
    155 
    156 			// Adjust for hidden menubar.
    157 			if ( heights.menuBarHeight < 3 ) {
    158 				heights.menuBarHeight = 0;
    159 			}
    160 		}
    161 
    162 		// We need to wait for TinyMCE to initialize.
    163 		/**
    164 		 * Binds all necessary functions for editor expand to the editor when the editor
    165 		 * is initialized.
    166 		 *
    167 		 * @since 4.0.0
    168 		 *
    169 		 * @param {event} event The TinyMCE editor init event.
    170 		 * @param {object} editor The editor to bind the vents on.
    171 		 *
    172 		 * @return {void}
    173 		 */
    174 		$document.on( 'tinymce-editor-init.editor-expand', function( event, editor ) {
    175 			// VK contains the type of key pressed. VK = virtual keyboard.
    176 			var VK = window.tinymce.util.VK,
    177 				/**
    178 				 * Hides any float panel with a hover state. Additionally hides tooltips.
    179 				 *
    180 				 * @return {void}
    181 				 */
    182 				hideFloatPanels = _.debounce( function() {
    183 					! $( '.mce-floatpanel:hover' ).length && window.tinymce.ui.FloatPanel.hideAll();
    184 					$( '.mce-tooltip' ).hide();
    185 				}, 1000, true );
    186 
    187 			// Make sure it's the main editor.
    188 			if ( editor.id !== 'content' ) {
    189 				return;
    190 			}
    191 
    192 			// Copy the editor instance.
    193 			mceEditor = editor;
    194 
    195 			// Set the minimum height to the initial viewport height.
    196 			editor.settings.autoresize_min_height = autoresizeMinHeight;
    197 
    198 			// Get the necessary UI elements.
    199 			$visualTop = $contentWrap.find( '.mce-toolbar-grp' );
    200 			$visualEditor = $contentWrap.find( '.mce-edit-area' );
    201 			$statusBar = $contentWrap.find( '.mce-statusbar' );
    202 			$menuBar = $contentWrap.find( '.mce-menubar' );
    203 
    204 			/**
    205 			 * Gets the offset of the editor.
    206 			 *
    207 			 * @return {number|boolean} Returns the offset of the editor
    208 			 * or false if there is no offset height.
    209 			 */
    210 			function mceGetCursorOffset() {
    211 				var node = editor.selection.getNode(),
    212 					range, view, offset;
    213 
    214 				/*
    215 				 * If editor.wp.getView and the selection node from the editor selection
    216 				 * are defined, use this as a view for the offset.
    217 				 */
    218 				if ( editor.wp && editor.wp.getView && ( view = editor.wp.getView( node ) ) ) {
    219 					offset = view.getBoundingClientRect();
    220 				} else {
    221 					range = editor.selection.getRng();
    222 
    223 					// Try to get the offset from a range.
    224 					try {
    225 						offset = range.getClientRects()[0];
    226 					} catch( er ) {}
    227 
    228 					// Get the offset from the bounding client rectangle of the node.
    229 					if ( ! offset ) {
    230 						offset = node.getBoundingClientRect();
    231 					}
    232 				}
    233 
    234 				return offset.height ? offset : false;
    235 			}
    236 
    237 			/**
    238 			 * Filters the special keys that should not be used for scrolling.
    239 			 *
    240 			 * @since 4.0.0
    241 			 *
    242 			 * @param {event} event The event to get the key code from.
    243 			 *
    244 			 * @return {void}
    245 			 */
    246 			function mceKeyup( event ) {
    247 				var key = event.keyCode;
    248 
    249 				// Bail on special keys. Key code 47 is a '/'.
    250 				if ( key <= 47 && ! ( key === VK.SPACEBAR || key === VK.ENTER || key === VK.DELETE || key === VK.BACKSPACE || key === VK.UP || key === VK.LEFT || key === VK.DOWN || key === VK.UP ) ) {
    251 					return;
    252 				// OS keys, function keys, num lock, scroll lock. Key code 91-93 are OS keys.
    253 				// Key code 112-123 are F1 to F12. Key code 144 is num lock. Key code 145 is scroll lock.
    254 				} else if ( ( key >= 91 && key <= 93 ) || ( key >= 112 && key <= 123 ) || key === 144 || key === 145 ) {
    255 					return;
    256 				}
    257 
    258 				mceScroll( key );
    259 			}
    260 
    261 			/**
    262 			 * Makes sure the cursor is always visible in the editor.
    263 			 *
    264 			 * Makes sure the cursor is kept between the toolbars of the editor and scrolls
    265 			 * the window when the cursor moves out of the viewport to a wpview.
    266 			 * Setting a buffer > 0 will prevent the browser default.
    267 			 * Some browsers will scroll to the middle,
    268 			 * others to the top/bottom of the *window* when moving the cursor out of the viewport.
    269 			 *
    270 			 * @since 4.1.0
    271 			 *
    272 			 * @param {string} key The key code of the pressed key.
    273 			 *
    274 			 * @return {void}
    275 			 */
    276 			function mceScroll( key ) {
    277 				var offset = mceGetCursorOffset(),
    278 					buffer = 50,
    279 					cursorTop, cursorBottom, editorTop, editorBottom;
    280 
    281 				// Don't scroll if there is no offset.
    282 				if ( ! offset ) {
    283 					return;
    284 				}
    285 
    286 				// Determine the cursorTop based on the offset and the top of the editor iframe.
    287 				cursorTop = offset.top + editor.iframeElement.getBoundingClientRect().top;
    288 
    289 				// Determine the cursorBottom based on the cursorTop and offset height.
    290 				cursorBottom = cursorTop + offset.height;
    291 
    292 				// Subtract the buffer from the cursorTop.
    293 				cursorTop = cursorTop - buffer;
    294 
    295 				// Add the buffer to the cursorBottom.
    296 				cursorBottom = cursorBottom + buffer;
    297 				editorTop = heights.adminBarHeight + heights.toolsHeight + heights.menuBarHeight + heights.visualTopHeight;
    298 
    299 				/*
    300 				 * Set the editorBottom based on the window Height, and add the bottomHeight and statusBarHeight if the
    301 				 * advanced editor is enabled.
    302 				 */
    303 				editorBottom = heights.windowHeight - ( advanced ? heights.bottomHeight + heights.statusBarHeight : 0 );
    304 
    305 				// Don't scroll if the node is taller than the visible part of the editor.
    306 				if ( editorBottom - editorTop < offset.height ) {
    307 					return;
    308 				}
    309 
    310 				/*
    311 				 * If the cursorTop is smaller than the editorTop and the up, left
    312 				 * or backspace key is pressed, scroll the editor to the position defined
    313 				 * by the cursorTop, pageYOffset and editorTop.
    314 				 */
    315 				if ( cursorTop < editorTop && ( key === VK.UP || key === VK.LEFT || key === VK.BACKSPACE ) ) {
    316 					window.scrollTo( window.pageXOffset, cursorTop + window.pageYOffset - editorTop );
    317 
    318 				/*
    319 				 * If any other key is pressed or the cursorTop is bigger than the editorTop,
    320 				 * scroll the editor to the position defined by the cursorBottom,
    321 				 * pageYOffset and editorBottom.
    322 				 */
    323 				} else if ( cursorBottom > editorBottom ) {
    324 					window.scrollTo( window.pageXOffset, cursorBottom + window.pageYOffset - editorBottom );
    325 				}
    326 			}
    327 
    328 			/**
    329 			 * If the editor is fullscreen, calls adjust.
    330 			 *
    331 			 * @since 4.1.0
    332 			 *
    333 			 * @param {event} event The FullscreenStateChanged event.
    334 			 *
    335 			 * @return {void}
    336 			 */
    337 			function mceFullscreenToggled( event ) {
    338 				// event.state is true if the editor is fullscreen.
    339 				if ( ! event.state ) {
    340 					adjust();
    341 				}
    342 			}
    343 
    344 			/**
    345 			 * Shows the editor when scrolled.
    346 			 *
    347 			 * Binds the hideFloatPanels function on the window scroll.mce-float-panels event.
    348 			 * Executes the wpAutoResize on the active editor.
    349 			 *
    350 			 * @since 4.0.0
    351 			 *
    352 			 * @return {void}
    353 			 */
    354 			function mceShow() {
    355 				$window.on( 'scroll.mce-float-panels', hideFloatPanels );
    356 
    357 				setTimeout( function() {
    358 					editor.execCommand( 'wpAutoResize' );
    359 					adjust();
    360 				}, 300 );
    361 			}
    362 
    363 			/**
    364 			 * Resizes the editor.
    365 			 *
    366 			 * Removes all functions from the window scroll.mce-float-panels event.
    367 			 * Resizes the text editor and scrolls to a position based on the pageXOffset and adminBarHeight.
    368 			 *
    369 			 * @since 4.0.0
    370 			 *
    371 			 * @return {void}
    372 			 */
    373 			function mceHide() {
    374 				$window.off( 'scroll.mce-float-panels' );
    375 
    376 				setTimeout( function() {
    377 					var top = $contentWrap.offset().top;
    378 
    379 					if ( window.pageYOffset > top ) {
    380 						window.scrollTo( window.pageXOffset, top - heights.adminBarHeight );
    381 					}
    382 
    383 					textEditorResize();
    384 					adjust();
    385 				}, 100 );
    386 
    387 				adjust();
    388 			}
    389 
    390 			/**
    391 			 * Toggles advanced states.
    392 			 *
    393 			 * @since 4.1.0
    394 			 *
    395 			 * @return {void}
    396 			 */
    397 			function toggleAdvanced() {
    398 				advanced = ! advanced;
    399 			}
    400 
    401 			/**
    402 			 * Binds events of the editor and window.
    403 			 *
    404 			 * @since 4.0.0
    405 			 *
    406 			 * @return {void}
    407 			 */
    408 			mceBind = function() {
    409 				editor.on( 'keyup', mceKeyup );
    410 				editor.on( 'show', mceShow );
    411 				editor.on( 'hide', mceHide );
    412 				editor.on( 'wp-toolbar-toggle', toggleAdvanced );
    413 
    414 				// Adjust when the editor resizes.
    415 				editor.on( 'setcontent wp-autoresize wp-toolbar-toggle', adjust );
    416 
    417 				// Don't hide the caret after undo/redo.
    418 				editor.on( 'undo redo', mceScroll );
    419 
    420 				// Adjust when exiting TinyMCE's fullscreen mode.
    421 				editor.on( 'FullscreenStateChanged', mceFullscreenToggled );
    422 
    423 				$window.off( 'scroll.mce-float-panels' ).on( 'scroll.mce-float-panels', hideFloatPanels );
    424 			};
    425 
    426 			/**
    427 			 * Unbinds the events of the editor and window.
    428 			 *
    429 			 * @since 4.0.0
    430 			 *
    431 			 * @return {void}
    432 			 */
    433 			mceUnbind = function() {
    434 				editor.off( 'keyup', mceKeyup );
    435 				editor.off( 'show', mceShow );
    436 				editor.off( 'hide', mceHide );
    437 				editor.off( 'wp-toolbar-toggle', toggleAdvanced );
    438 				editor.off( 'setcontent wp-autoresize wp-toolbar-toggle', adjust );
    439 				editor.off( 'undo redo', mceScroll );
    440 				editor.off( 'FullscreenStateChanged', mceFullscreenToggled );
    441 
    442 				$window.off( 'scroll.mce-float-panels' );
    443 			};
    444 
    445 			if ( $wrap.hasClass( 'wp-editor-expand' ) ) {
    446 
    447 				// Adjust "immediately".
    448 				mceBind();
    449 				initialResize( adjust );
    450 			}
    451 		} );
    452 
    453 		/**
    454 		 * Adjusts the toolbars heights and positions.
    455 		 *
    456 		 * Adjusts the toolbars heights and positions based on the scroll position on
    457 		 * the page, the active editor mode and the heights of the editor, admin bar and
    458 		 * side bar.
    459 		 *
    460 		 * @since 4.0.0
    461 		 *
    462 		 * @param {event} event The event that calls this function.
    463 		 *
    464 		 * @return {void}
    465 		 */
    466 		function adjust( event ) {
    467 
    468 			// Makes sure we're not in fullscreen mode.
    469 			if ( fullscreen && fullscreen.settings.visible ) {
    470 				return;
    471 			}
    472 
    473 			var windowPos = $window.scrollTop(),
    474 				type = event && event.type,
    475 				resize = type !== 'scroll',
    476 				visual = mceEditor && ! mceEditor.isHidden(),
    477 				buffer = autoresizeMinHeight,
    478 				postBodyTop = $postBody.offset().top,
    479 				borderWidth = 1,
    480 				contentWrapWidth = $contentWrap.width(),
    481 				$top, $editor, sidebarTop, footerTop, canPin,
    482 				topPos, topHeight, editorPos, editorHeight;
    483 
    484 			/*
    485 			 * Refresh the heights if type isn't 'scroll'
    486 			 * or heights.windowHeight isn't set.
    487 			 */
    488 			if ( resize || ! heights.windowHeight ) {
    489 				getHeights();
    490 			}
    491 
    492 			// Resize on resize event when the editor is in text mode.
    493 			if ( ! visual && type === 'resize' ) {
    494 				textEditorResize();
    495 			}
    496 
    497 			if ( visual ) {
    498 				$top = $visualTop;
    499 				$editor = $visualEditor;
    500 				topHeight = heights.visualTopHeight;
    501 			} else {
    502 				$top = $textTop;
    503 				$editor = $textEditor;
    504 				topHeight = heights.textTopHeight;
    505 			}
    506 
    507 			// Return if TinyMCE is still initializing.
    508 			if ( ! visual && ! $top.length ) {
    509 				return;
    510 			}
    511 
    512 			topPos = $top.parent().offset().top;
    513 			editorPos = $editor.offset().top;
    514 			editorHeight = $editor.outerHeight();
    515 
    516 			/*
    517 			 * If in visual mode, checks if the editorHeight is greater than the autoresizeMinHeight + topHeight.
    518 			 * If not in visual mode, checks if the editorHeight is greater than the autoresizeMinHeight + 20.
    519 			 */
    520 			canPin = visual ? autoresizeMinHeight + topHeight : autoresizeMinHeight + 20; // 20px from textarea padding.
    521 			canPin = editorHeight > ( canPin + 5 );
    522 
    523 			if ( ! canPin ) {
    524 				if ( resize ) {
    525 					$tools.css( {
    526 						position: 'absolute',
    527 						top: 0,
    528 						width: contentWrapWidth
    529 					} );
    530 
    531 					if ( visual && $menuBar.length ) {
    532 						$menuBar.css( {
    533 							position: 'absolute',
    534 							top: 0,
    535 							width: contentWrapWidth - ( borderWidth * 2 )
    536 						} );
    537 					}
    538 
    539 					$top.css( {
    540 						position: 'absolute',
    541 						top: heights.menuBarHeight,
    542 						width: contentWrapWidth - ( borderWidth * 2 ) - ( visual ? 0 : ( $top.outerWidth() - $top.width() ) )
    543 					} );
    544 
    545 					$statusBar.attr( 'style', advanced ? '' : 'visibility: hidden;' );
    546 					$bottom.attr( 'style', '' );
    547 				}
    548 			} else {
    549 				// Check if the top is not already in a fixed position.
    550 				if ( ( ! fixedTop || resize ) &&
    551 					( windowPos >= ( topPos - heights.toolsHeight - heights.adminBarHeight ) &&
    552 					windowPos <= ( topPos - heights.toolsHeight - heights.adminBarHeight + editorHeight - buffer ) ) ) {
    553 					fixedTop = true;
    554 
    555 					$tools.css( {
    556 						position: 'fixed',
    557 						top: heights.adminBarHeight,
    558 						width: contentWrapWidth
    559 					} );
    560 
    561 					if ( visual && $menuBar.length ) {
    562 						$menuBar.css( {
    563 							position: 'fixed',
    564 							top: heights.adminBarHeight + heights.toolsHeight,
    565 							width: contentWrapWidth - ( borderWidth * 2 ) - ( visual ? 0 : ( $top.outerWidth() - $top.width() ) )
    566 						} );
    567 					}
    568 
    569 					$top.css( {
    570 						position: 'fixed',
    571 						top: heights.adminBarHeight + heights.toolsHeight + heights.menuBarHeight,
    572 						width: contentWrapWidth - ( borderWidth * 2 ) - ( visual ? 0 : ( $top.outerWidth() - $top.width() ) )
    573 					} );
    574 					// Check if the top is already in a fixed position.
    575 				} else if ( fixedTop || resize ) {
    576 					if ( windowPos <= ( topPos - heights.toolsHeight - heights.adminBarHeight ) ) {
    577 						fixedTop = false;
    578 
    579 						$tools.css( {
    580 							position: 'absolute',
    581 							top: 0,
    582 							width: contentWrapWidth
    583 						} );
    584 
    585 						if ( visual && $menuBar.length ) {
    586 							$menuBar.css( {
    587 								position: 'absolute',
    588 								top: 0,
    589 								width: contentWrapWidth - ( borderWidth * 2 )
    590 							} );
    591 						}
    592 
    593 						$top.css( {
    594 							position: 'absolute',
    595 							top: heights.menuBarHeight,
    596 							width: contentWrapWidth - ( borderWidth * 2 ) - ( visual ? 0 : ( $top.outerWidth() - $top.width() ) )
    597 						} );
    598 					} else if ( windowPos >= ( topPos - heights.toolsHeight - heights.adminBarHeight + editorHeight - buffer ) ) {
    599 						fixedTop = false;
    600 
    601 						$tools.css( {
    602 							position: 'absolute',
    603 							top: editorHeight - buffer,
    604 							width: contentWrapWidth
    605 						} );
    606 
    607 						if ( visual && $menuBar.length ) {
    608 							$menuBar.css( {
    609 								position: 'absolute',
    610 								top: editorHeight - buffer,
    611 								width: contentWrapWidth - ( borderWidth * 2 )
    612 							} );
    613 						}
    614 
    615 						$top.css( {
    616 							position: 'absolute',
    617 							top: editorHeight - buffer + heights.menuBarHeight,
    618 							width: contentWrapWidth - ( borderWidth * 2 ) - ( visual ? 0 : ( $top.outerWidth() - $top.width() ) )
    619 						} );
    620 					}
    621 				}
    622 
    623 				// Check if the bottom is not already in a fixed position.
    624 				if ( ( ! fixedBottom || ( resize && advanced ) ) &&
    625 						// Add borderWidth for the border around the .wp-editor-container.
    626 						( windowPos + heights.windowHeight ) <= ( editorPos + editorHeight + heights.bottomHeight + heights.statusBarHeight + borderWidth ) ) {
    627 
    628 					if ( event && event.deltaHeight > 0 && event.deltaHeight < 100 ) {
    629 						window.scrollBy( 0, event.deltaHeight );
    630 					} else if ( visual && advanced ) {
    631 						fixedBottom = true;
    632 
    633 						$statusBar.css( {
    634 							position: 'fixed',
    635 							bottom: heights.bottomHeight,
    636 							visibility: '',
    637 							width: contentWrapWidth - ( borderWidth * 2 )
    638 						} );
    639 
    640 						$bottom.css( {
    641 							position: 'fixed',
    642 							bottom: 0,
    643 							width: contentWrapWidth
    644 						} );
    645 					}
    646 				} else if ( ( ! advanced && fixedBottom ) ||
    647 						( ( fixedBottom || resize ) &&
    648 						( windowPos + heights.windowHeight ) > ( editorPos + editorHeight + heights.bottomHeight + heights.statusBarHeight - borderWidth ) ) ) {
    649 					fixedBottom = false;
    650 
    651 					$statusBar.attr( 'style', advanced ? '' : 'visibility: hidden;' );
    652 					$bottom.attr( 'style', '' );
    653 				}
    654 			}
    655 
    656 			// The postbox container is positioned with @media from CSS. Ensure it is pinned on the side.
    657 			if ( $postboxContainer.width() < 300 && heights.windowWidth > 600 &&
    658 
    659 				// Check if the sidebar is not taller than the document height.
    660 				$document.height() > ( $sideSortables.height() + postBodyTop + 120 ) &&
    661 
    662 				// Check if the editor is taller than the viewport.
    663 				heights.windowHeight < editorHeight ) {
    664 
    665 				if ( ( heights.sideSortablesHeight + pinnedToolsTop + sidebarBottom ) > heights.windowHeight || fixedSideTop || fixedSideBottom ) {
    666 
    667 					// Reset the sideSortables style when scrolling to the top.
    668 					if ( windowPos + pinnedToolsTop <= postBodyTop ) {
    669 						$sideSortables.attr( 'style', '' );
    670 						fixedSideTop = fixedSideBottom = false;
    671 					} else {
    672 
    673 						// When scrolling down.
    674 						if ( windowPos > lastScrollPosition ) {
    675 							if ( fixedSideTop ) {
    676 
    677 								// Let it scroll.
    678 								fixedSideTop = false;
    679 								sidebarTop = $sideSortables.offset().top - heights.adminBarHeight;
    680 								footerTop = $footer.offset().top;
    681 
    682 								// Don't get over the footer.
    683 								if ( footerTop < sidebarTop + heights.sideSortablesHeight + sidebarBottom ) {
    684 									sidebarTop = footerTop - heights.sideSortablesHeight - 12;
    685 								}
    686 
    687 								$sideSortables.css({
    688 									position: 'absolute',
    689 									top: sidebarTop,
    690 									bottom: ''
    691 								});
    692 							} else if ( ! fixedSideBottom && heights.sideSortablesHeight + $sideSortables.offset().top + sidebarBottom < windowPos + heights.windowHeight ) {
    693 								// Pin the bottom.
    694 								fixedSideBottom = true;
    695 
    696 								$sideSortables.css({
    697 									position: 'fixed',
    698 									top: 'auto',
    699 									bottom: sidebarBottom
    700 								});
    701 							}
    702 
    703 						// When scrolling up.
    704 						} else if ( windowPos < lastScrollPosition ) {
    705 							if ( fixedSideBottom ) {
    706 								// Let it scroll.
    707 								fixedSideBottom = false;
    708 								sidebarTop = $sideSortables.offset().top - sidebarBottom;
    709 								footerTop = $footer.offset().top;
    710 
    711 								// Don't get over the footer.
    712 								if ( footerTop < sidebarTop + heights.sideSortablesHeight + sidebarBottom ) {
    713 									sidebarTop = footerTop - heights.sideSortablesHeight - 12;
    714 								}
    715 
    716 								$sideSortables.css({
    717 									position: 'absolute',
    718 									top: sidebarTop,
    719 									bottom: ''
    720 								});
    721 							} else if ( ! fixedSideTop && $sideSortables.offset().top >= windowPos + pinnedToolsTop ) {
    722 								// Pin the top.
    723 								fixedSideTop = true;
    724 
    725 								$sideSortables.css({
    726 									position: 'fixed',
    727 									top: pinnedToolsTop,
    728 									bottom: ''
    729 								});
    730 							}
    731 						}
    732 					}
    733 				} else {
    734 					// If the sidebar container is smaller than the viewport, then pin/unpin the top when scrolling.
    735 					if ( windowPos >= ( postBodyTop - pinnedToolsTop ) ) {
    736 
    737 						$sideSortables.css( {
    738 							position: 'fixed',
    739 							top: pinnedToolsTop
    740 						} );
    741 					} else {
    742 						$sideSortables.attr( 'style', '' );
    743 					}
    744 
    745 					fixedSideTop = fixedSideBottom = false;
    746 				}
    747 
    748 				lastScrollPosition = windowPos;
    749 			} else {
    750 				$sideSortables.attr( 'style', '' );
    751 				fixedSideTop = fixedSideBottom = false;
    752 			}
    753 
    754 			if ( resize ) {
    755 				$contentWrap.css( {
    756 					paddingTop: heights.toolsHeight
    757 				} );
    758 
    759 				if ( visual ) {
    760 					$visualEditor.css( {
    761 						paddingTop: heights.visualTopHeight + heights.menuBarHeight
    762 					} );
    763 				} else {
    764 					$textEditor.css( {
    765 						marginTop: heights.textTopHeight
    766 					} );
    767 				}
    768 			}
    769 		}
    770 
    771 		/**
    772 		 * Resizes the editor and adjusts the toolbars.
    773 		 *
    774 		 * @since 4.0.0
    775 		 *
    776 		 * @return {void}
    777 		 */
    778 		function fullscreenHide() {
    779 			textEditorResize();
    780 			adjust();
    781 		}
    782 
    783 		/**
    784 		 * Runs the passed function with 500ms intervals.
    785 		 *
    786 		 * @since 4.0.0
    787 		 *
    788 		 * @param {function} callback The function to run in the timeout.
    789 		 *
    790 		 * @return {void}
    791 		 */
    792 		function initialResize( callback ) {
    793 			for ( var i = 1; i < 6; i++ ) {
    794 				setTimeout( callback, 500 * i );
    795 			}
    796 		}
    797 
    798 		/**
    799 		 * Runs adjust after 100ms.
    800 		 *
    801 		 * @since 4.0.0
    802 		 *
    803 		 * @return {void}
    804 		 */
    805 		function afterScroll() {
    806 			clearTimeout( scrollTimer );
    807 			scrollTimer = setTimeout( adjust, 100 );
    808 		}
    809 
    810 		/**
    811 		 * Binds editor expand events on elements.
    812 		 *
    813 		 * @since 4.0.0
    814 		 *
    815 		 * @return {void}
    816 		 */
    817 		function on() {
    818 			/*
    819 			 * Scroll to the top when triggering this from JS.
    820 			 * Ensure the toolbars are pinned properly.
    821 			 */
    822 			if ( window.pageYOffset && window.pageYOffset > pageYOffsetAtTop ) {
    823 				window.scrollTo( window.pageXOffset, 0 );
    824 			}
    825 
    826 			$wrap.addClass( 'wp-editor-expand' );
    827 
    828 			// Adjust when the window is scrolled or resized.
    829 			$window.on( 'scroll.editor-expand resize.editor-expand', function( event ) {
    830 				adjust( event.type );
    831 				afterScroll();
    832 			} );
    833 
    834 			/*
    835 		 	 * Adjust when collapsing the menu, changing the columns
    836 		 	 * or changing the body class.
    837 			 */
    838 			$document.on( 'wp-collapse-menu.editor-expand postboxes-columnchange.editor-expand editor-classchange.editor-expand', adjust )
    839 				.on( 'postbox-toggled.editor-expand postbox-moved.editor-expand', function() {
    840 					if ( ! fixedSideTop && ! fixedSideBottom && window.pageYOffset > pinnedToolsTop ) {
    841 						fixedSideBottom = true;
    842 						window.scrollBy( 0, -1 );
    843 						adjust();
    844 						window.scrollBy( 0, 1 );
    845 					}
    846 
    847 					adjust();
    848 				}).on( 'wp-window-resized.editor-expand', function() {
    849 					if ( mceEditor && ! mceEditor.isHidden() ) {
    850 						mceEditor.execCommand( 'wpAutoResize' );
    851 					} else {
    852 						textEditorResize();
    853 					}
    854 				});
    855 
    856 			$textEditor.on( 'focus.editor-expand input.editor-expand propertychange.editor-expand', textEditorResize );
    857 			mceBind();
    858 
    859 			// Adjust when entering or exiting fullscreen mode.
    860 			fullscreen && fullscreen.pubsub.subscribe( 'hidden', fullscreenHide );
    861 
    862 			if ( mceEditor ) {
    863 				mceEditor.settings.wp_autoresize_on = true;
    864 				mceEditor.execCommand( 'wpAutoResizeOn' );
    865 
    866 				if ( ! mceEditor.isHidden() ) {
    867 					mceEditor.execCommand( 'wpAutoResize' );
    868 				}
    869 			}
    870 
    871 			if ( ! mceEditor || mceEditor.isHidden() ) {
    872 				textEditorResize();
    873 			}
    874 
    875 			adjust();
    876 
    877 			$document.trigger( 'editor-expand-on' );
    878 		}
    879 
    880 		/**
    881 		 * Unbinds editor expand events.
    882 		 *
    883 		 * @since 4.0.0
    884 		 *
    885 		 * @return {void}
    886 		 */
    887 		function off() {
    888 			var height = parseInt( window.getUserSetting( 'ed_size', 300 ), 10 );
    889 
    890 			if ( height < 50 ) {
    891 				height = 50;
    892 			} else if ( height > 5000 ) {
    893 				height = 5000;
    894 			}
    895 
    896 			/*
    897 			 * Scroll to the top when triggering this from JS.
    898 			 * Ensure the toolbars are reset properly.
    899 			 */
    900 			if ( window.pageYOffset && window.pageYOffset > pageYOffsetAtTop ) {
    901 				window.scrollTo( window.pageXOffset, 0 );
    902 			}
    903 
    904 			$wrap.removeClass( 'wp-editor-expand' );
    905 
    906 			$window.off( '.editor-expand' );
    907 			$document.off( '.editor-expand' );
    908 			$textEditor.off( '.editor-expand' );
    909 			mceUnbind();
    910 
    911 			// Adjust when entering or exiting fullscreen mode.
    912 			fullscreen && fullscreen.pubsub.unsubscribe( 'hidden', fullscreenHide );
    913 
    914 			// Reset all CSS.
    915 			$.each( [ $visualTop, $textTop, $tools, $menuBar, $bottom, $statusBar, $contentWrap, $visualEditor, $textEditor, $sideSortables ], function( i, element ) {
    916 				element && element.attr( 'style', '' );
    917 			});
    918 
    919 			fixedTop = fixedBottom = fixedSideTop = fixedSideBottom = false;
    920 
    921 			if ( mceEditor ) {
    922 				mceEditor.settings.wp_autoresize_on = false;
    923 				mceEditor.execCommand( 'wpAutoResizeOff' );
    924 
    925 				if ( ! mceEditor.isHidden() ) {
    926 					$textEditor.hide();
    927 
    928 					if ( height ) {
    929 						mceEditor.theme.resizeTo( null, height );
    930 					}
    931 				}
    932 			}
    933 
    934 			// If there is a height found in the user setting.
    935 			if ( height ) {
    936 				$textEditor.height( height );
    937 			}
    938 
    939 			$document.trigger( 'editor-expand-off' );
    940 		}
    941 
    942 		// Start on load.
    943 		if ( $wrap.hasClass( 'wp-editor-expand' ) ) {
    944 			on();
    945 
    946 			// Resize just after CSS has fully loaded and QuickTags is ready.
    947 			if ( $contentWrap.hasClass( 'html-active' ) ) {
    948 				initialResize( function() {
    949 					adjust();
    950 					textEditorResize();
    951 				} );
    952 			}
    953 		}
    954 
    955 		// Show the on/off checkbox.
    956 		$( '#adv-settings .editor-expand' ).show();
    957 		$( '#editor-expand-toggle' ).on( 'change.editor-expand', function() {
    958 			if ( $(this).prop( 'checked' ) ) {
    959 				on();
    960 				window.setUserSetting( 'editor_expand', 'on' );
    961 			} else {
    962 				off();
    963 				window.setUserSetting( 'editor_expand', 'off' );
    964 			}
    965 		});
    966 
    967 		// Expose on() and off().
    968 		window.editorExpand = {
    969 			on: on,
    970 			off: off
    971 		};
    972 	} );
    973 
    974 	/**
    975 	 * Handles the distraction free writing of TinyMCE.
    976 	 *
    977 	 * @since 4.1.0
    978 	 *
    979 	 * @return {void}
    980 	 */
    981 	$( function() {
    982 		var $body = $( document.body ),
    983 			$wrap = $( '#wpcontent' ),
    984 			$editor = $( '#post-body-content' ),
    985 			$title = $( '#title' ),
    986 			$content = $( '#content' ),
    987 			$overlay = $( document.createElement( 'DIV' ) ),
    988 			$slug = $( '#edit-slug-box' ),
    989 			$slugFocusEl = $slug.find( 'a' )
    990 				.add( $slug.find( 'button' ) )
    991 				.add( $slug.find( 'input' ) ),
    992 			$menuWrap = $( '#adminmenuwrap' ),
    993 			$editorWindow = $(),
    994 			$editorIframe = $(),
    995 			_isActive = window.getUserSetting( 'editor_expand', 'on' ) === 'on',
    996 			_isOn = _isActive ? window.getUserSetting( 'post_dfw' ) === 'on' : false,
    997 			traveledX = 0,
    998 			traveledY = 0,
    999 			buffer = 20,
   1000 			faded, fadedAdminBar, fadedSlug,
   1001 			editorRect, x, y, mouseY, scrollY,
   1002 			focusLostTimer, overlayTimer, editorHasFocus;
   1003 
   1004 		$body.append( $overlay );
   1005 
   1006 		$overlay.css( {
   1007 			display: 'none',
   1008 			position: 'fixed',
   1009 			top: $adminBar.height(),
   1010 			right: 0,
   1011 			bottom: 0,
   1012 			left: 0,
   1013 			'z-index': 9997
   1014 		} );
   1015 
   1016 		$editor.css( {
   1017 			position: 'relative'
   1018 		} );
   1019 
   1020 		$window.on( 'mousemove.focus', function( event ) {
   1021 			mouseY = event.pageY;
   1022 		} );
   1023 
   1024 		/**
   1025 		 * Recalculates the bottom and right position of the editor in the DOM.
   1026 		 *
   1027 		 * @since 4.1.0
   1028 		 *
   1029 		 * @return {void}
   1030 		 */
   1031 		function recalcEditorRect() {
   1032 			editorRect = $editor.offset();
   1033 			editorRect.right = editorRect.left + $editor.outerWidth();
   1034 			editorRect.bottom = editorRect.top + $editor.outerHeight();
   1035 		}
   1036 
   1037 		/**
   1038 		 * Activates the distraction free writing mode.
   1039 		 *
   1040 		 * @since 4.1.0
   1041 		 *
   1042 		 * @return {void}
   1043 		 */
   1044 		function activate() {
   1045 			if ( ! _isActive ) {
   1046 				_isActive = true;
   1047 
   1048 				$document.trigger( 'dfw-activate' );
   1049 				$content.on( 'keydown.focus-shortcut', toggleViaKeyboard );
   1050 			}
   1051 		}
   1052 
   1053 		/**
   1054 		 * Deactivates the distraction free writing mode.
   1055 		 *
   1056 		 * @since 4.1.0
   1057 		 *
   1058 		 * @return {void}
   1059 		 */
   1060 		function deactivate() {
   1061 			if ( _isActive ) {
   1062 				off();
   1063 
   1064 				_isActive = false;
   1065 
   1066 				$document.trigger( 'dfw-deactivate' );
   1067 				$content.off( 'keydown.focus-shortcut' );
   1068 			}
   1069 		}
   1070 
   1071 		/**
   1072 		 * Returns _isActive.
   1073 		 *
   1074 		 * @since 4.1.0
   1075 		 *
   1076 		 * @return {boolean} Returns true is _isActive is true.
   1077 		 */
   1078 		function isActive() {
   1079 			return _isActive;
   1080 		}
   1081 
   1082 		/**
   1083 		 * Binds events on the editor for distraction free writing.
   1084 		 *
   1085 		 * @since 4.1.0
   1086 		 *
   1087 		 * @return {void}
   1088 		 */
   1089 		function on() {
   1090 			if ( ! _isOn && _isActive ) {
   1091 				_isOn = true;
   1092 
   1093 				$content.on( 'keydown.focus', fadeOut );
   1094 
   1095 				$title.add( $content ).on( 'blur.focus', maybeFadeIn );
   1096 
   1097 				fadeOut();
   1098 
   1099 				window.setUserSetting( 'post_dfw', 'on' );
   1100 
   1101 				$document.trigger( 'dfw-on' );
   1102 			}
   1103 		}
   1104 
   1105 		/**
   1106 		 * Unbinds events on the editor for distraction free writing.
   1107 		 *
   1108 		 * @since 4.1.0
   1109 		 *
   1110 		 * @return {void}
   1111 		 */
   1112 		function off() {
   1113 			if ( _isOn ) {
   1114 				_isOn = false;
   1115 
   1116 				$title.add( $content ).off( '.focus' );
   1117 
   1118 				fadeIn();
   1119 
   1120 				$editor.off( '.focus' );
   1121 
   1122 				window.setUserSetting( 'post_dfw', 'off' );
   1123 
   1124 				$document.trigger( 'dfw-off' );
   1125 			}
   1126 		}
   1127 
   1128 		/**
   1129 		 * Binds or unbinds the editor expand events.
   1130 		 *
   1131 		 * @since 4.1.0
   1132 		 *
   1133 		 * @return {void}
   1134 		 */
   1135 		function toggle() {
   1136 			if ( _isOn ) {
   1137 				off();
   1138 			} else {
   1139 				on();
   1140 			}
   1141 		}
   1142 
   1143 		/**
   1144 		 * Returns the value of _isOn.
   1145 		 *
   1146 		 * @since 4.1.0
   1147 		 *
   1148 		 * @return {boolean} Returns true if _isOn is true.
   1149 		 */
   1150 		function isOn() {
   1151 			return _isOn;
   1152 		}
   1153 
   1154 		/**
   1155 		 * Fades out all elements except for the editor.
   1156 		 *
   1157 		 * The fading is done based on key presses and mouse movements.
   1158 		 * Also calls the fadeIn on certain key presses
   1159 		 * or if the mouse leaves the editor.
   1160 		 *
   1161 		 * @since 4.1.0
   1162 		 *
   1163 		 * @param event The event that triggers this function.
   1164 		 *
   1165 		 * @return {void}
   1166 		 */
   1167 		function fadeOut( event ) {
   1168 			var isMac,
   1169 				key = event && event.keyCode;
   1170 
   1171 			if ( window.navigator.platform ) {
   1172 				isMac = ( window.navigator.platform.indexOf( 'Mac' ) > -1 );
   1173 			}
   1174 
   1175 			// Fade in and returns on Escape and keyboard shortcut Alt+Shift+W and Ctrl+Opt+W.
   1176 			if ( key === 27 || ( key === 87 && event.altKey && ( ( ! isMac && event.shiftKey ) || ( isMac && event.ctrlKey ) ) ) ) {
   1177 				fadeIn( event );
   1178 				return;
   1179 			}
   1180 
   1181 			// Return if any of the following keys or combinations of keys is pressed.
   1182 			if ( event && ( event.metaKey || ( event.ctrlKey && ! event.altKey ) || ( event.altKey && event.shiftKey ) || ( key && (
   1183 				// Special keys ( tab, ctrl, alt, esc, arrow keys... ).
   1184 				( key <= 47 && key !== 8 && key !== 13 && key !== 32 && key !== 46 ) ||
   1185 				// Windows keys.
   1186 				( key >= 91 && key <= 93 ) ||
   1187 				// F keys.
   1188 				( key >= 112 && key <= 135 ) ||
   1189 				// Num Lock, Scroll Lock, OEM.
   1190 				( key >= 144 && key <= 150 ) ||
   1191 				// OEM or non-printable.
   1192 				key >= 224
   1193 			) ) ) ) {
   1194 				return;
   1195 			}
   1196 
   1197 			if ( ! faded ) {
   1198 				faded = true;
   1199 
   1200 				clearTimeout( overlayTimer );
   1201 
   1202 				overlayTimer = setTimeout( function() {
   1203 					$overlay.show();
   1204 				}, 600 );
   1205 
   1206 				$editor.css( 'z-index', 9998 );
   1207 
   1208 				$overlay
   1209 					// Always recalculate the editor area when entering the overlay with the mouse.
   1210 					.on( 'mouseenter.focus', function() {
   1211 						recalcEditorRect();
   1212 
   1213 						$window.on( 'scroll.focus', function() {
   1214 							var nScrollY = window.pageYOffset;
   1215 
   1216 							if ( (
   1217 								scrollY && mouseY &&
   1218 								scrollY !== nScrollY
   1219 							) && (
   1220 								mouseY < editorRect.top - buffer ||
   1221 								mouseY > editorRect.bottom + buffer
   1222 							) ) {
   1223 								fadeIn();
   1224 							}
   1225 
   1226 							scrollY = nScrollY;
   1227 						} );
   1228 					} )
   1229 					.on( 'mouseleave.focus', function() {
   1230 						x = y =  null;
   1231 						traveledX = traveledY = 0;
   1232 
   1233 						$window.off( 'scroll.focus' );
   1234 					} )
   1235 					// Fade in when the mouse moves away form the editor area.
   1236 					.on( 'mousemove.focus', function( event ) {
   1237 						var nx = event.clientX,
   1238 							ny = event.clientY,
   1239 							pageYOffset = window.pageYOffset,
   1240 							pageXOffset = window.pageXOffset;
   1241 
   1242 						if ( x && y && ( nx !== x || ny !== y ) ) {
   1243 							if (
   1244 								( ny <= y && ny < editorRect.top - pageYOffset ) ||
   1245 								( ny >= y && ny > editorRect.bottom - pageYOffset ) ||
   1246 								( nx <= x && nx < editorRect.left - pageXOffset ) ||
   1247 								( nx >= x && nx > editorRect.right - pageXOffset )
   1248 							) {
   1249 								traveledX += Math.abs( x - nx );
   1250 								traveledY += Math.abs( y - ny );
   1251 
   1252 								if ( (
   1253 									ny <= editorRect.top - buffer - pageYOffset ||
   1254 									ny >= editorRect.bottom + buffer - pageYOffset ||
   1255 									nx <= editorRect.left - buffer - pageXOffset ||
   1256 									nx >= editorRect.right + buffer - pageXOffset
   1257 								) && (
   1258 									traveledX > 10 ||
   1259 									traveledY > 10
   1260 								) ) {
   1261 									fadeIn();
   1262 
   1263 									x = y =  null;
   1264 									traveledX = traveledY = 0;
   1265 
   1266 									return;
   1267 								}
   1268 							} else {
   1269 								traveledX = traveledY = 0;
   1270 							}
   1271 						}
   1272 
   1273 						x = nx;
   1274 						y = ny;
   1275 					} )
   1276 
   1277 					// When the overlay is touched, fade in and cancel the event.
   1278 					.on( 'touchstart.focus', function( event ) {
   1279 						event.preventDefault();
   1280 						fadeIn();
   1281 					} );
   1282 
   1283 				$editor.off( 'mouseenter.focus' );
   1284 
   1285 				if ( focusLostTimer ) {
   1286 					clearTimeout( focusLostTimer );
   1287 					focusLostTimer = null;
   1288 				}
   1289 
   1290 				$body.addClass( 'focus-on' ).removeClass( 'focus-off' );
   1291 			}
   1292 
   1293 			fadeOutAdminBar();
   1294 			fadeOutSlug();
   1295 		}
   1296 
   1297 		/**
   1298 		 * Fades all elements back in.
   1299 		 *
   1300 		 * @since 4.1.0
   1301 		 *
   1302 		 * @param event The event that triggers this function.
   1303 		 *
   1304 		 * @return {void}
   1305 		 */
   1306 		function fadeIn( event ) {
   1307 			if ( faded ) {
   1308 				faded = false;
   1309 
   1310 				clearTimeout( overlayTimer );
   1311 
   1312 				overlayTimer = setTimeout( function() {
   1313 					$overlay.hide();
   1314 				}, 200 );
   1315 
   1316 				$editor.css( 'z-index', '' );
   1317 
   1318 				$overlay.off( 'mouseenter.focus mouseleave.focus mousemove.focus touchstart.focus' );
   1319 
   1320 				/*
   1321 				 * When fading in, temporarily watch for refocus and fade back out - helps
   1322 				 * with 'accidental' editor exits with the mouse. When fading in and the event
   1323 				 * is a key event (Escape or Alt+Shift+W) don't watch for refocus.
   1324 				 */
   1325 				if ( 'undefined' === typeof event ) {
   1326 					$editor.on( 'mouseenter.focus', function() {
   1327 						if ( $.contains( $editor.get( 0 ), document.activeElement ) || editorHasFocus ) {
   1328 							fadeOut();
   1329 						}
   1330 					} );
   1331 				}
   1332 
   1333 				focusLostTimer = setTimeout( function() {
   1334 					focusLostTimer = null;
   1335 					$editor.off( 'mouseenter.focus' );
   1336 				}, 1000 );
   1337 
   1338 				$body.addClass( 'focus-off' ).removeClass( 'focus-on' );
   1339 			}
   1340 
   1341 			fadeInAdminBar();
   1342 			fadeInSlug();
   1343 		}
   1344 
   1345 		/**
   1346 		 * Fades in if the focused element based on it position.
   1347 		 *
   1348 		 * @since 4.1.0
   1349 		 *
   1350 		 * @return {void}
   1351 		 */
   1352 		function maybeFadeIn() {
   1353 			setTimeout( function() {
   1354 				var position = document.activeElement.compareDocumentPosition( $editor.get( 0 ) );
   1355 
   1356 				function hasFocus( $el ) {
   1357 					return $.contains( $el.get( 0 ), document.activeElement );
   1358 				}
   1359 
   1360 				// The focused node is before or behind the editor area, and not outside the wrap.
   1361 				if ( ( position === 2 || position === 4 ) && ( hasFocus( $menuWrap ) || hasFocus( $wrap ) || hasFocus( $footer ) ) ) {
   1362 					fadeIn();
   1363 				}
   1364 			}, 0 );
   1365 		}
   1366 
   1367 		/**
   1368 		 * Fades out the admin bar based on focus on the admin bar.
   1369 		 *
   1370 		 * @since 4.1.0
   1371 		 *
   1372 		 * @return {void}
   1373 		 */
   1374 		function fadeOutAdminBar() {
   1375 			if ( ! fadedAdminBar && faded ) {
   1376 				fadedAdminBar = true;
   1377 
   1378 				$adminBar
   1379 					.on( 'mouseenter.focus', function() {
   1380 						$adminBar.addClass( 'focus-off' );
   1381 					} )
   1382 					.on( 'mouseleave.focus', function() {
   1383 						$adminBar.removeClass( 'focus-off' );
   1384 					} );
   1385 			}
   1386 		}
   1387 
   1388 		/**
   1389 		 * Fades in the admin bar.
   1390 		 *
   1391 		 * @since 4.1.0
   1392 		 *
   1393 		 * @return {void}
   1394 		 */
   1395 		function fadeInAdminBar() {
   1396 			if ( fadedAdminBar ) {
   1397 				fadedAdminBar = false;
   1398 
   1399 				$adminBar.off( '.focus' );
   1400 			}
   1401 		}
   1402 
   1403 		/**
   1404 		 * Fades out the edit slug box.
   1405 		 *
   1406 		 * @since 4.1.0
   1407 		 *
   1408 		 * @return {void}
   1409 		 */
   1410 		function fadeOutSlug() {
   1411 			if ( ! fadedSlug && faded && ! $slug.find( ':focus').length ) {
   1412 				fadedSlug = true;
   1413 
   1414 				$slug.stop().fadeTo( 'fast', 0.3 ).on( 'mouseenter.focus', fadeInSlug ).off( 'mouseleave.focus' );
   1415 
   1416 				$slugFocusEl.on( 'focus.focus', fadeInSlug ).off( 'blur.focus' );
   1417 			}
   1418 		}
   1419 
   1420 		/**
   1421 		 * Fades in the edit slug box.
   1422 		 *
   1423 		 * @since 4.1.0
   1424 		 *
   1425 		 * @return {void}
   1426 		 */
   1427 		function fadeInSlug() {
   1428 			if ( fadedSlug ) {
   1429 				fadedSlug = false;
   1430 
   1431 				$slug.stop().fadeTo( 'fast', 1 ).on( 'mouseleave.focus', fadeOutSlug ).off( 'mouseenter.focus' );
   1432 
   1433 				$slugFocusEl.on( 'blur.focus', fadeOutSlug ).off( 'focus.focus' );
   1434 			}
   1435 		}
   1436 
   1437 		/**
   1438 		 * Triggers the toggle on Alt + Shift + W.
   1439 		 *
   1440 		 * Keycode 87 = w.
   1441 		 *
   1442 		 * @since 4.1.0
   1443 		 *
   1444 		 * @param {event} event The event to trigger the toggle.
   1445 		 *
   1446 		 * @return {void}
   1447 		 */
   1448 		function toggleViaKeyboard( event ) {
   1449 			if ( event.altKey && event.shiftKey && 87 === event.keyCode ) {
   1450 				toggle();
   1451 			}
   1452 		}
   1453 
   1454 		if ( $( '#postdivrich' ).hasClass( 'wp-editor-expand' ) ) {
   1455 			$content.on( 'keydown.focus-shortcut', toggleViaKeyboard );
   1456 		}
   1457 
   1458 		/**
   1459 		 * Adds the distraction free writing button when setting up TinyMCE.
   1460 		 *
   1461 		 * @since 4.1.0
   1462 		 *
   1463 		 * @param {event} event The TinyMCE editor setup event.
   1464 		 * @param {object} editor The editor to add the button to.
   1465 		 *
   1466 		 * @return {void}
   1467 		 */
   1468 		$document.on( 'tinymce-editor-setup.focus', function( event, editor ) {
   1469 			editor.addButton( 'dfw', {
   1470 				active: _isOn,
   1471 				classes: 'wp-dfw btn widget',
   1472 				disabled: ! _isActive,
   1473 				onclick: toggle,
   1474 				onPostRender: function() {
   1475 					var button = this;
   1476 
   1477 					editor.on( 'init', function() {
   1478 						if ( button.disabled() ) {
   1479 							button.hide();
   1480 						}
   1481 					} );
   1482 
   1483 					$document
   1484 					.on( 'dfw-activate.focus', function() {
   1485 						button.disabled( false );
   1486 						button.show();
   1487 					} )
   1488 					.on( 'dfw-deactivate.focus', function() {
   1489 						button.disabled( true );
   1490 						button.hide();
   1491 					} )
   1492 					.on( 'dfw-on.focus', function() {
   1493 						button.active( true );
   1494 					} )
   1495 					.on( 'dfw-off.focus', function() {
   1496 						button.active( false );
   1497 					} );
   1498 				},
   1499 				tooltip: 'Distraction-free writing mode',
   1500 				shortcut: 'Alt+Shift+W'
   1501 			} );
   1502 
   1503 			editor.addCommand( 'wpToggleDFW', toggle );
   1504 			editor.addShortcut( 'access+w', '', 'wpToggleDFW' );
   1505 		} );
   1506 
   1507 		/**
   1508 		 * Binds and unbinds events on the editor.
   1509 		 *
   1510 		 * @since 4.1.0
   1511 		 *
   1512 		 * @param {event} event The TinyMCE editor init event.
   1513 		 * @param {object} editor The editor to bind events on.
   1514 		 *
   1515 		 * @return {void}
   1516 		 */
   1517 		$document.on( 'tinymce-editor-init.focus', function( event, editor ) {
   1518 			var mceBind, mceUnbind;
   1519 
   1520 			function focus() {
   1521 				editorHasFocus = true;
   1522 			}
   1523 
   1524 			function blur() {
   1525 				editorHasFocus = false;
   1526 			}
   1527 
   1528 			if ( editor.id === 'content' ) {
   1529 				$editorWindow = $( editor.getWin() );
   1530 				$editorIframe = $( editor.getContentAreaContainer() ).find( 'iframe' );
   1531 
   1532 				mceBind = function() {
   1533 					editor.on( 'keydown', fadeOut );
   1534 					editor.on( 'blur', maybeFadeIn );
   1535 					editor.on( 'focus', focus );
   1536 					editor.on( 'blur', blur );
   1537 					editor.on( 'wp-autoresize', recalcEditorRect );
   1538 				};
   1539 
   1540 				mceUnbind = function() {
   1541 					editor.off( 'keydown', fadeOut );
   1542 					editor.off( 'blur', maybeFadeIn );
   1543 					editor.off( 'focus', focus );
   1544 					editor.off( 'blur', blur );
   1545 					editor.off( 'wp-autoresize', recalcEditorRect );
   1546 				};
   1547 
   1548 				if ( _isOn ) {
   1549 					mceBind();
   1550 				}
   1551 
   1552 				// Bind and unbind based on the distraction free writing focus.
   1553 				$document.on( 'dfw-on.focus', mceBind ).on( 'dfw-off.focus', mceUnbind );
   1554 
   1555 				// Focuse the editor when it is the target of the click event.
   1556 				editor.on( 'click', function( event ) {
   1557 					if ( event.target === editor.getDoc().documentElement ) {
   1558 						editor.focus();
   1559 					}
   1560 				} );
   1561 			}
   1562 		} );
   1563 
   1564 		/**
   1565 		 *  Binds events on quicktags init.
   1566 		 *
   1567 		 * @since 4.1.0
   1568 		 *
   1569 		 * @param {event} event The quicktags init event.
   1570 		 * @param {object} editor The editor to bind events on.
   1571 		 *
   1572 		 * @return {void}
   1573 		 */
   1574 		$document.on( 'quicktags-init', function( event, editor ) {
   1575 			var $button;
   1576 
   1577 			// Bind the distraction free writing events if the distraction free writing button is available.
   1578 			if ( editor.settings.buttons && ( ',' + editor.settings.buttons + ',' ).indexOf( ',dfw,' ) !== -1 ) {
   1579 				$button = $( '#' + editor.name + '_dfw' );
   1580 
   1581 				$( document )
   1582 				.on( 'dfw-activate', function() {
   1583 					$button.prop( 'disabled', false );
   1584 				} )
   1585 				.on( 'dfw-deactivate', function() {
   1586 					$button.prop( 'disabled', true );
   1587 				} )
   1588 				.on( 'dfw-on', function() {
   1589 					$button.addClass( 'active' );
   1590 				} )
   1591 				.on( 'dfw-off', function() {
   1592 					$button.removeClass( 'active' );
   1593 				} );
   1594 			}
   1595 		} );
   1596 
   1597 		$document.on( 'editor-expand-on.focus', activate ).on( 'editor-expand-off.focus', deactivate );
   1598 
   1599 		if ( _isOn ) {
   1600 			$content.on( 'keydown.focus', fadeOut );
   1601 
   1602 			$title.add( $content ).on( 'blur.focus', maybeFadeIn );
   1603 		}
   1604 
   1605 		window.wp = window.wp || {};
   1606 		window.wp.editor = window.wp.editor || {};
   1607 		window.wp.editor.dfw = {
   1608 			activate: activate,
   1609 			deactivate: deactivate,
   1610 			isActive: isActive,
   1611 			on: on,
   1612 			off: off,
   1613 			toggle: toggle,
   1614 			isOn: isOn
   1615 		};
   1616 	} );
   1617 } )( window, window.jQuery );