editor-expand.js 42 KB

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