editor-expand.js 42 KB

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