3 window.wp = window.wp || {};
5 jQuery( document ).ready( function( $ ) {
6 var $window = $( window ),
7 $document = $( document ),
8 $adminBar = $( '#wpadminbar' ),
9 $footer = $( '#wpfooter' ),
10 $wrap = $( '#postdivrich' ),
11 $contentWrap = $( '#wp-content-wrap' ),
12 $tools = $( '#wp-content-editor-tools' ),
15 $textTop = $( '#ed_toolbar' ),
16 $textEditor = $( '#content' ),
17 $textEditorClone = $( '<div id="content-textarea-clone"></div>' ),
18 $bottom = $( '#post-status-info' ),
21 $sideSortables = $( '#side-sortables' ),
22 $postboxContainer = $( '#postbox-container-1' ),
23 $postBody = $('#post-body'),
24 fullscreen = window.wp.editor && window.wp.editor.fullscreen,
26 mceBind = function(){},
27 mceUnbind = function(){},
31 fixedSideBottom = false,
33 lastScrollPosition = 0,
34 pageYOffsetAtTop = 130,
37 autoresizeMinHeight = 300,
38 initialMode = window.getUserSetting( 'editor' ),
39 // These are corrected when adjust() runs, except on scrolling if already set.
50 sideSortablesHeight: 0
53 $textEditorClone.insertAfter( $textEditor );
55 $textEditorClone.css( {
56 'font-family': $textEditor.css( 'font-family' ),
57 'font-size': $textEditor.css( 'font-size' ),
58 'line-height': $textEditor.css( 'line-height' ),
59 'white-space': 'pre-wrap',
60 'word-wrap': 'break-word'
63 function getHeights() {
64 var windowWidth = $window.width();
67 windowHeight: $window.height(),
68 windowWidth: windowWidth,
69 adminBarHeight: ( windowWidth > 600 ? $adminBar.outerHeight() : 0 ),
70 toolsHeight: $tools.outerHeight() || 0,
71 menuBarHeight: $menuBar.outerHeight() || 0,
72 visualTopHeight: $visualTop.outerHeight() || 0,
73 textTopHeight: $textTop.outerHeight() || 0,
74 bottomHeight: $bottom.outerHeight() || 0,
75 statusBarHeight: $statusBar.outerHeight() || 0,
76 sideSortablesHeight: $sideSortables.height() || 0
80 if ( heights.menuBarHeight < 3 ) {
81 heights.menuBarHeight = 0;
85 function textEditorKeyup( event ) {
86 var VK = jQuery.ui.keyCode,
88 range = document.createRange(),
89 selStart = $textEditor[0].selectionStart,
90 selEnd = $textEditor[0].selectionEnd,
91 textNode = $textEditorClone[0].firstChild,
93 offset, cursorTop, cursorBottom, editorTop, editorBottom;
95 if ( selStart && selEnd && selStart !== selEnd ) {
99 // These are not TinyMCE ranges.
101 range.setStart( textNode, selStart );
102 range.setEnd( textNode, selEnd + 1 );
105 offset = range.getBoundingClientRect();
107 if ( ! offset.height ) {
111 cursorTop = offset.top - buffer;
112 cursorBottom = cursorTop + offset.height + buffer;
113 editorTop = heights.adminBarHeight + heights.toolsHeight + heights.textTopHeight;
114 editorBottom = heights.windowHeight - heights.bottomHeight;
116 if ( cursorTop < editorTop && ( key === VK.UP || key === VK.LEFT || key === VK.BACKSPACE ) ) {
117 window.scrollTo( window.pageXOffset, cursorTop + window.pageYOffset - editorTop );
118 } else if ( cursorBottom > editorBottom ) {
119 window.scrollTo( window.pageXOffset, cursorBottom + window.pageYOffset - editorBottom );
123 function textEditorResize() {
124 if ( ( mceEditor && ! mceEditor.isHidden() ) || ( ! mceEditor && initialMode === 'tinymce' ) ) {
128 var textEditorHeight = $textEditor.height(),
131 $textEditorClone.width( $textEditor.width() - 22 );
132 $textEditorClone.text( $textEditor.val() + ' ' );
134 hiddenHeight = $textEditorClone.height();
136 if ( hiddenHeight < autoresizeMinHeight ) {
137 hiddenHeight = autoresizeMinHeight;
140 if ( hiddenHeight === textEditorHeight ) {
144 $textEditor.height( hiddenHeight );
149 // We need to wait for TinyMCE to initialize.
150 $document.on( 'tinymce-editor-init.editor-expand', function( event, editor ) {
151 var hideFloatPanels = _.debounce( function() {
152 ! $( '.mce-floatpanel:hover' ).length && tinymce.ui.FloatPanel.hideAll();
153 $( '.mce-tooltip' ).hide();
156 // Make sure it's the main editor.
157 if ( editor.id !== 'content' ) {
161 // Copy the editor instance.
164 // Set the minimum height to the initial viewport height.
165 editor.settings.autoresize_min_height = autoresizeMinHeight;
167 // Get the necessary UI elements.
168 $visualTop = $contentWrap.find( '.mce-toolbar-grp' );
169 $visualEditor = $contentWrap.find( '.mce-edit-area' );
170 $statusBar = $contentWrap.find( '.mce-statusbar' );
171 $menuBar = $contentWrap.find( '.mce-menubar' );
173 function mceGetCursorOffset() {
174 var node = editor.selection.getNode(),
177 if ( editor.plugins.wpview && ( view = editor.plugins.wpview.getView( node ) ) ) {
178 offset = view.getBoundingClientRect();
180 offset = node.getBoundingClientRect();
183 return offset.height ? offset : false;
186 // Make sure the cursor is always visible.
187 // This is not only necessary to keep the cursor between the toolbars,
188 // but also to scroll the window when the cursor moves out of the viewport to a wpview.
189 // Setting a buffer > 0 will prevent the browser default.
190 // Some browsers will scroll to the middle,
191 // others to the top/bottom of the *window* when moving the cursor out of the viewport.
192 function mceKeyup( event ) {
193 var VK = tinymce.util.VK,
195 offset = mceGetCursorOffset(),
197 cursorTop, cursorBottom, editorTop, editorBottom;
203 // Bail on special keys.
204 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 ) ) {
206 // OS keys, function keys, num lock, scroll lock
207 } else if ( ( key >= 91 && key <= 93 ) || ( key >= 112 && key <= 123 ) || key === 144 || key === 145 ) {
211 cursorTop = offset.top + editor.iframeElement.getBoundingClientRect().top;
212 cursorBottom = cursorTop + offset.height;
213 cursorTop = cursorTop - buffer;
214 cursorBottom = cursorBottom + buffer;
215 editorTop = heights.adminBarHeight + heights.toolsHeight + heights.menuBarHeight + heights.visualTopHeight;
216 editorBottom = heights.windowHeight - heights.bottomHeight - heights.statusBarHeight;
218 // Don't scroll if the node is taller than the visible part of the editor
219 if ( editorBottom - editorTop < offset.height ) {
223 if ( cursorTop < editorTop && ( key === VK.UP || key === VK.LEFT || key === VK.BACKSPACE ) ) {
224 window.scrollTo( window.pageXOffset, cursorTop + window.pageYOffset - editorTop );
225 } else if ( cursorBottom > editorBottom ) {
226 window.scrollTo( window.pageXOffset, cursorBottom + window.pageYOffset - editorBottom );
230 // Adjust when switching editor modes.
232 $window.on( 'scroll.mce-float-panels', hideFloatPanels );
234 setTimeout( function() {
235 editor.execCommand( 'wpAutoResize' );
241 $window.off( 'scroll.mce-float-panels' );
243 setTimeout( function() {
244 var top = $contentWrap.offset().top;
246 if ( window.pageYOffset > top ) {
247 window.scrollTo( window.pageXOffset, top - heights.adminBarHeight );
257 mceBind = function() {
258 editor.on( 'keyup', mceKeyup );
259 editor.on( 'show', mceShow );
260 editor.on( 'hide', mceHide );
261 // Adjust when the editor resizes.
262 editor.on( 'setcontent wp-autoresize wp-toolbar-toggle', adjust );
264 $window.off( 'scroll.mce-float-panels' ).on( 'scroll.mce-float-panels', hideFloatPanels );
267 mceUnbind = function() {
268 editor.off( 'keyup', mceKeyup );
269 editor.off( 'show', mceShow );
270 editor.off( 'hide', mceHide );
271 editor.off( 'setcontent wp-autoresize wp-toolbar-toggle', adjust );
273 $window.off( 'scroll.mce-float-panels' );
276 if ( $wrap.hasClass( 'wp-editor-expand' ) ) {
277 // Adjust "immediately"
279 initialResize( adjust );
283 // Adjust the toolbars based on the active editor mode.
284 function adjust( type ) {
285 // Make sure we're not in fullscreen mode.
286 if ( fullscreen && fullscreen.settings.visible ) {
290 var windowPos = $window.scrollTop(),
291 resize = type !== 'scroll',
292 visual = ( mceEditor && ! mceEditor.isHidden() ),
293 buffer = autoresizeMinHeight,
294 postBodyTop = $postBody.offset().top,
296 contentWrapWidth = $contentWrap.width(),
297 $top, $editor, sidebarTop, footerTop, canPin,
298 topPos, topHeight, editorPos, editorHeight;
300 // Refresh the heights
301 if ( resize || ! heights.windowHeight ) {
305 if ( ! visual && type === 'resize' ) {
311 $editor = $visualEditor;
312 topHeight = heights.visualTopHeight;
315 $editor = $textEditor;
316 topHeight = heights.textTopHeight;
319 topPos = $top.parent().offset().top;
320 editorPos = $editor.offset().top;
321 editorHeight = $editor.outerHeight();
324 canPin = visual ? autoresizeMinHeight + topHeight : autoresizeMinHeight + 20; // 20px from textarea padding
325 canPin = editorHeight > ( canPin + 5 );
330 position: 'absolute',
332 width: contentWrapWidth
335 if ( visual && $menuBar.length ) {
337 position: 'absolute',
339 width: contentWrapWidth - ( borderWidth * 2 )
344 position: 'absolute',
345 top: heights.menuBarHeight,
346 width: contentWrapWidth - ( borderWidth * 2 ) - ( visual ? 0 : ( $top.outerWidth() - $top.width() ) )
349 $statusBar.add( $bottom ).attr( 'style', '' );
352 // Maybe pin the top.
353 if ( ( ! fixedTop || resize ) &&
354 // Handle scrolling down.
355 ( windowPos >= ( topPos - heights.toolsHeight - heights.adminBarHeight ) &&
356 // Handle scrolling up.
357 windowPos <= ( topPos - heights.toolsHeight - heights.adminBarHeight + editorHeight - buffer ) ) ) {
362 top: heights.adminBarHeight,
363 width: contentWrapWidth
366 if ( visual && $menuBar.length ) {
369 top: heights.adminBarHeight + heights.toolsHeight,
370 width: contentWrapWidth - ( borderWidth * 2 ) - ( visual ? 0 : ( $top.outerWidth() - $top.width() ) )
376 top: heights.adminBarHeight + heights.toolsHeight + heights.menuBarHeight,
377 width: contentWrapWidth - ( borderWidth * 2 ) - ( visual ? 0 : ( $top.outerWidth() - $top.width() ) )
379 // Maybe unpin the top.
380 } else if ( fixedTop || resize ) {
381 // Handle scrolling up.
382 if ( windowPos <= ( topPos - heights.toolsHeight - heights.adminBarHeight ) ) {
386 position: 'absolute',
388 width: contentWrapWidth
391 if ( visual && $menuBar.length ) {
393 position: 'absolute',
395 width: contentWrapWidth - ( borderWidth * 2 )
400 position: 'absolute',
401 top: heights.menuBarHeight,
402 width: contentWrapWidth - ( borderWidth * 2 ) - ( visual ? 0 : ( $top.outerWidth() - $top.width() ) )
404 // Handle scrolling down.
405 } else if ( windowPos >= ( topPos - heights.toolsHeight - heights.adminBarHeight + editorHeight - buffer ) ) {
409 position: 'absolute',
410 top: editorHeight - buffer,
411 width: contentWrapWidth
414 if ( visual && $menuBar.length ) {
416 position: 'absolute',
417 top: editorHeight - buffer,
418 width: contentWrapWidth - ( borderWidth * 2 )
423 position: 'absolute',
424 top: editorHeight - buffer + heights.menuBarHeight,
425 width: contentWrapWidth - ( borderWidth * 2 ) - ( visual ? 0 : ( $top.outerWidth() - $top.width() ) )
430 // Maybe adjust the bottom bar.
431 if ( ( ! fixedBottom || resize ) &&
432 // +[n] for the border around the .wp-editor-container.
433 ( windowPos + heights.windowHeight ) <= ( editorPos + editorHeight + heights.bottomHeight + heights.statusBarHeight + borderWidth ) ) {
438 bottom: heights.bottomHeight,
439 width: contentWrapWidth - ( borderWidth * 2 )
445 width: contentWrapWidth
447 } else if ( ( fixedBottom || resize ) &&
448 ( windowPos + heights.windowHeight ) > ( editorPos + editorHeight + heights.bottomHeight + heights.statusBarHeight - borderWidth ) ) {
451 $statusBar.add( $bottom ).attr( 'style', '' );
456 if ( $postboxContainer.width() < 300 && heights.windowWidth > 600 && // sidebar position is changed with @media from CSS, make sure it is on the side
457 $document.height() > ( $sideSortables.height() + postBodyTop + 120 ) && // the sidebar is not the tallest element
458 heights.windowHeight < editorHeight ) { // the editor is taller than the viewport
460 if ( ( heights.sideSortablesHeight + pinnedToolsTop + sidebarBottom ) > heights.windowHeight || fixedSideTop || fixedSideBottom ) {
461 // Reset when scrolling to the top
462 if ( windowPos + pinnedToolsTop <= postBodyTop ) {
463 $sideSortables.attr( 'style', '' );
464 fixedSideTop = fixedSideBottom = false;
466 if ( windowPos > lastScrollPosition ) {
468 if ( fixedSideTop ) {
470 fixedSideTop = false;
471 sidebarTop = $sideSortables.offset().top - heights.adminBarHeight;
472 footerTop = $footer.offset().top;
474 // don't get over the footer
475 if ( footerTop < sidebarTop + heights.sideSortablesHeight + sidebarBottom ) {
476 sidebarTop = footerTop - heights.sideSortablesHeight - 12;
480 position: 'absolute',
484 } else if ( ! fixedSideBottom && heights.sideSortablesHeight + $sideSortables.offset().top + sidebarBottom < windowPos + heights.windowHeight ) {
486 fixedSideBottom = true;
491 bottom: sidebarBottom
494 } else if ( windowPos < lastScrollPosition ) {
496 if ( fixedSideBottom ) {
498 fixedSideBottom = false;
499 sidebarTop = $sideSortables.offset().top - sidebarBottom;
500 footerTop = $footer.offset().top;
502 // don't get over the footer
503 if ( footerTop < sidebarTop + heights.sideSortablesHeight + sidebarBottom ) {
504 sidebarTop = footerTop - heights.sideSortablesHeight - 12;
508 position: 'absolute',
512 } else if ( ! fixedSideTop && $sideSortables.offset().top >= windowPos + pinnedToolsTop ) {
525 // if the sidebar container is smaller than the viewport, then pin/unpin the top when scrolling
526 if ( windowPos >= ( postBodyTop - pinnedToolsTop ) ) {
528 $sideSortables.css( {
533 $sideSortables.attr( 'style', '' );
536 fixedSideTop = fixedSideBottom = false;
539 lastScrollPosition = windowPos;
541 $sideSortables.attr( 'style', '' );
542 fixedSideTop = fixedSideBottom = false;
547 paddingTop: heights.toolsHeight
552 paddingTop: heights.visualTopHeight + heights.menuBarHeight
556 marginTop: heights.textTopHeight
559 $textEditorClone.width( contentWrapWidth - 20 - ( borderWidth * 2 ) );
564 function fullscreenHide() {
569 function initialResize( callback ) {
570 for ( var i = 1; i < 6; i++ ) {
571 setTimeout( callback, 500 * i );
575 function afterScroll() {
576 clearTimeout( scrollTimer );
577 scrollTimer = setTimeout( adjust, 100 );
581 // Scroll to the top when triggering this from JS.
582 // Ensures toolbars are pinned properly.
583 if ( window.pageYOffset && window.pageYOffset > pageYOffsetAtTop ) {
584 window.scrollTo( window.pageXOffset, 0 );
587 $wrap.addClass( 'wp-editor-expand' );
589 // Adjust when the window is scrolled or resized.
590 $window.on( 'scroll.editor-expand resize.editor-expand', function( event ) {
591 adjust( event.type );
595 // Adjust when collapsing the menu, changing the columns, changing the body class.
596 $document.on( 'wp-collapse-menu.editor-expand postboxes-columnchange.editor-expand editor-classchange.editor-expand', adjust )
597 .on( 'postbox-toggled.editor-expand', function() {
598 if ( ! fixedSideTop && ! fixedSideBottom && window.pageYOffset > pinnedToolsTop ) {
599 fixedSideBottom = true;
600 window.scrollBy( 0, -1 );
602 window.scrollBy( 0, 1 );
606 }).on( 'wp-window-resized.editor-expand', function() {
607 if ( mceEditor && ! mceEditor.isHidden() ) {
608 mceEditor.execCommand( 'wpAutoResize' );
614 $textEditor.on( 'focus.editor-expand input.editor-expand propertychange.editor-expand', textEditorResize );
615 $textEditor.on( 'keyup.editor-expand', textEditorKeyup );
618 // Adjust when entering/exiting fullscreen mode.
619 fullscreen && fullscreen.pubsub.subscribe( 'hidden', fullscreenHide );
622 mceEditor.settings.wp_autoresize_on = true;
623 mceEditor.execCommand( 'wpAutoResizeOn' );
625 if ( ! mceEditor.isHidden() ) {
626 mceEditor.execCommand( 'wpAutoResize' );
630 if ( ! mceEditor || mceEditor.isHidden() ) {
638 var height = window.getUserSetting('ed_size');
640 // Scroll to the top when triggering this from JS.
641 // Ensures toolbars are reset properly.
642 if ( window.pageYOffset && window.pageYOffset > pageYOffsetAtTop ) {
643 window.scrollTo( window.pageXOffset, 0 );
646 $wrap.removeClass( 'wp-editor-expand' );
648 $window.off( '.editor-expand' );
649 $document.off( '.editor-expand' );
650 $textEditor.off( '.editor-expand' );
653 // Adjust when entering/exiting fullscreen mode.
654 fullscreen && fullscreen.pubsub.unsubscribe( 'hidden', fullscreenHide );
657 $.each( [ $visualTop, $textTop, $tools, $menuBar, $bottom, $statusBar, $contentWrap, $visualEditor, $textEditor, $sideSortables ], function( i, element ) {
658 element && element.attr( 'style', '' );
661 fixedTop = fixedBottom = fixedSideTop = fixedSideBottom = false;
664 mceEditor.settings.wp_autoresize_on = false;
665 mceEditor.execCommand( 'wpAutoResizeOff' );
667 if ( ! mceEditor.isHidden() ) {
671 mceEditor.theme.resizeTo( null, height );
677 $textEditor.height( height );
682 if ( $wrap.hasClass( 'wp-editor-expand' ) ) {
685 // Ideally we need to resize just after CSS has fully loaded and QuickTags is ready.
686 if ( $contentWrap.hasClass( 'html-active' ) ) {
687 initialResize( function() {
694 // Show the on/off checkbox
695 $( '#adv-settings .editor-expand' ).show();
696 $( '#editor-expand-toggle' ).on( 'change.editor-expand', function() {
697 if ( $(this).prop( 'checked' ) ) {
699 window.setUserSetting( 'editor_expand', 'on' );
702 window.setUserSetting( 'editor_expand', 'off' );
706 // Expose on() and off()
707 window.editorExpand = {