editor-expand.js 42 KB

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