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 // Make sure it's the main editor.
152 if ( editor.id !== 'content' ) {
156 // Copy the editor instance.
159 // Set the minimum height to the initial viewport height.
160 editor.settings.autoresize_min_height = autoresizeMinHeight;
162 // Get the necessary UI elements.
163 $visualTop = $contentWrap.find( '.mce-toolbar-grp' );
164 $visualEditor = $contentWrap.find( '.mce-edit-area' );
165 $statusBar = $contentWrap.find( '.mce-statusbar' );
166 $menuBar = $contentWrap.find( '.mce-menubar' );
168 function mceGetCursorOffset() {
169 var node = editor.selection.getNode(),
172 if ( editor.plugins.wpview && ( view = editor.plugins.wpview.getView( node ) ) ) {
173 offset = view.getBoundingClientRect();
175 offset = node.getBoundingClientRect();
178 return offset.height ? offset : false;
181 // Make sure the cursor is always visible.
182 // This is not only necessary to keep the cursor between the toolbars,
183 // but also to scroll the window when the cursor moves out of the viewport to a wpview.
184 // Setting a buffer > 0 will prevent the browser default.
185 // Some browsers will scroll to the middle,
186 // others to the top/bottom of the *window* when moving the cursor out of the viewport.
187 function mceKeyup( event ) {
188 var VK = tinymce.util.VK,
190 offset = mceGetCursorOffset(),
192 cursorTop, cursorBottom, editorTop, editorBottom;
198 // Bail on special keys.
199 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 ) ) {
201 // OS keys, function keys, num lock, scroll lock
202 } else if ( ( key >= 91 && key <= 93 ) || ( key >= 112 && key <= 123 ) || key === 144 || key === 145 ) {
206 cursorTop = offset.top + editor.iframeElement.getBoundingClientRect().top;
207 cursorBottom = cursorTop + offset.height;
208 cursorTop = cursorTop - buffer;
209 cursorBottom = cursorBottom + buffer;
210 editorTop = heights.adminBarHeight + heights.toolsHeight + heights.menuBarHeight + heights.visualTopHeight;
211 editorBottom = heights.windowHeight - heights.bottomHeight - heights.statusBarHeight;
213 // Don't scroll if the node is taller than the visible part of the editor
214 if ( editorBottom - editorTop < offset.height ) {
218 if ( cursorTop < editorTop && ( key === VK.UP || key === VK.LEFT || key === VK.BACKSPACE ) ) {
219 window.scrollTo( window.pageXOffset, cursorTop + window.pageYOffset - editorTop );
220 } else if ( cursorBottom > editorBottom ) {
221 window.scrollTo( window.pageXOffset, cursorBottom + window.pageYOffset - editorBottom );
225 // Adjust when switching editor modes.
227 setTimeout( function() {
228 editor.execCommand( 'wpAutoResize' );
234 setTimeout( function() {
235 var top = $contentWrap.offset().top;
237 if ( window.pageYOffset > top ) {
238 window.scrollTo( window.pageXOffset, top - heights.adminBarHeight );
248 mceBind = function() {
249 editor.on( 'keyup', mceKeyup );
250 editor.on( 'show', mceShow );
251 editor.on( 'hide', mceHide );
252 // Adjust when the editor resizes.
253 editor.on( 'setcontent wp-autoresize wp-toolbar-toggle', adjust );
256 mceUnbind = function() {
257 editor.off( 'keyup', mceKeyup );
258 editor.off( 'show', mceShow );
259 editor.off( 'hide', mceHide );
260 editor.off( 'setcontent wp-autoresize wp-toolbar-toggle', adjust );
263 if ( $wrap.hasClass( 'wp-editor-expand' ) ) {
264 // Adjust "immediately"
266 initialResize( adjust );
270 // Adjust the toolbars based on the active editor mode.
271 function adjust( type ) {
272 // Make sure we're not in fullscreen mode.
273 if ( fullscreen && fullscreen.settings.visible ) {
277 var windowPos = $window.scrollTop(),
278 resize = type !== 'scroll',
279 visual = ( mceEditor && ! mceEditor.isHidden() ),
280 buffer = autoresizeMinHeight,
281 postBodyTop = $postBody.offset().top,
283 contentWrapWidth = $contentWrap.width(),
284 $top, $editor, sidebarTop, footerTop, canPin,
285 topPos, topHeight, editorPos, editorHeight;
287 // Refresh the heights
288 if ( resize || ! heights.windowHeight ) {
292 if ( ! visual && type === 'resize' ) {
298 $editor = $visualEditor;
299 topHeight = heights.visualTopHeight;
302 $editor = $textEditor;
303 topHeight = heights.textTopHeight;
306 topPos = $top.parent().offset().top;
307 editorPos = $editor.offset().top;
308 editorHeight = $editor.outerHeight();
311 canPin = visual ? autoresizeMinHeight + topHeight : autoresizeMinHeight + 20; // 20px from textarea padding
312 canPin = editorHeight > ( canPin + 5 );
317 position: 'absolute',
319 width: contentWrapWidth
322 if ( visual && $menuBar.length ) {
324 position: 'absolute',
326 width: contentWrapWidth - ( borderWidth * 2 )
331 position: 'absolute',
332 top: heights.menuBarHeight,
333 width: contentWrapWidth - ( borderWidth * 2 ) - ( visual ? 0 : ( $top.outerWidth() - $top.width() ) )
336 $statusBar.add( $bottom ).attr( 'style', '' );
339 // Maybe pin the top.
340 if ( ( ! fixedTop || resize ) &&
341 // Handle scrolling down.
342 ( windowPos >= ( topPos - heights.toolsHeight - heights.adminBarHeight ) &&
343 // Handle scrolling up.
344 windowPos <= ( topPos - heights.toolsHeight - heights.adminBarHeight + editorHeight - buffer ) ) ) {
349 top: heights.adminBarHeight,
350 width: contentWrapWidth
353 if ( visual && $menuBar.length ) {
356 top: heights.adminBarHeight + heights.toolsHeight,
357 width: contentWrapWidth - ( borderWidth * 2 ) - ( visual ? 0 : ( $top.outerWidth() - $top.width() ) )
363 top: heights.adminBarHeight + heights.toolsHeight + heights.menuBarHeight,
364 width: contentWrapWidth - ( borderWidth * 2 ) - ( visual ? 0 : ( $top.outerWidth() - $top.width() ) )
366 // Maybe unpin the top.
367 } else if ( fixedTop || resize ) {
368 // Handle scrolling up.
369 if ( windowPos <= ( topPos - heights.toolsHeight - heights.adminBarHeight ) ) {
373 position: 'absolute',
375 width: contentWrapWidth
378 if ( visual && $menuBar.length ) {
380 position: 'absolute',
382 width: contentWrapWidth - ( borderWidth * 2 )
387 position: 'absolute',
388 top: heights.menuBarHeight,
389 width: contentWrapWidth - ( borderWidth * 2 ) - ( visual ? 0 : ( $top.outerWidth() - $top.width() ) )
391 // Handle scrolling down.
392 } else if ( windowPos >= ( topPos - heights.toolsHeight - heights.adminBarHeight + editorHeight - buffer ) ) {
396 position: 'absolute',
397 top: editorHeight - buffer,
398 width: contentWrapWidth
401 if ( visual && $menuBar.length ) {
403 position: 'absolute',
404 top: editorHeight - buffer,
405 width: contentWrapWidth - ( borderWidth * 2 )
410 position: 'absolute',
411 top: editorHeight - buffer + heights.menuBarHeight,
412 width: contentWrapWidth - ( borderWidth * 2 ) - ( visual ? 0 : ( $top.outerWidth() - $top.width() ) )
417 // Maybe adjust the bottom bar.
418 if ( ( ! fixedBottom || resize ) &&
419 // +[n] for the border around the .wp-editor-container.
420 ( windowPos + heights.windowHeight ) <= ( editorPos + editorHeight + heights.bottomHeight + heights.statusBarHeight + borderWidth ) ) {
425 bottom: heights.bottomHeight,
426 width: contentWrapWidth - ( borderWidth * 2 )
432 width: contentWrapWidth
434 } else if ( ( fixedBottom || resize ) &&
435 ( windowPos + heights.windowHeight ) > ( editorPos + editorHeight + heights.bottomHeight + heights.statusBarHeight - borderWidth ) ) {
438 $statusBar.add( $bottom ).attr( 'style', '' );
443 if ( $postboxContainer.width() < 300 && heights.windowWidth > 600 && // sidebar position is changed with @media from CSS, make sure it is on the side
444 $document.height() > ( $sideSortables.height() + postBodyTop + 120 ) && // the sidebar is not the tallest element
445 heights.windowHeight < editorHeight ) { // the editor is taller than the viewport
447 if ( ( heights.sideSortablesHeight + pinnedToolsTop + sidebarBottom ) > heights.windowHeight || fixedSideTop || fixedSideBottom ) {
448 // Reset when scrolling to the top
449 if ( windowPos + pinnedToolsTop <= postBodyTop ) {
450 $sideSortables.attr( 'style', '' );
451 fixedSideTop = fixedSideBottom = false;
453 if ( windowPos > lastScrollPosition ) {
455 if ( fixedSideTop ) {
457 fixedSideTop = false;
458 sidebarTop = $sideSortables.offset().top - heights.adminBarHeight;
459 footerTop = $footer.offset().top;
461 // don't get over the footer
462 if ( footerTop < sidebarTop + heights.sideSortablesHeight + sidebarBottom ) {
463 sidebarTop = footerTop - heights.sideSortablesHeight - 12;
467 position: 'absolute',
471 } else if ( ! fixedSideBottom && heights.sideSortablesHeight + $sideSortables.offset().top + sidebarBottom < windowPos + heights.windowHeight ) {
473 fixedSideBottom = true;
478 bottom: sidebarBottom
481 } else if ( windowPos < lastScrollPosition ) {
483 if ( fixedSideBottom ) {
485 fixedSideBottom = false;
486 sidebarTop = $sideSortables.offset().top - sidebarBottom;
487 footerTop = $footer.offset().top;
489 // don't get over the footer
490 if ( footerTop < sidebarTop + heights.sideSortablesHeight + sidebarBottom ) {
491 sidebarTop = footerTop - heights.sideSortablesHeight - 12;
495 position: 'absolute',
499 } else if ( ! fixedSideTop && $sideSortables.offset().top >= windowPos + pinnedToolsTop ) {
512 // if the sidebar container is smaller than the viewport, then pin/unpin the top when scrolling
513 if ( windowPos >= ( postBodyTop - pinnedToolsTop ) ) {
515 $sideSortables.css( {
520 $sideSortables.attr( 'style', '' );
523 fixedSideTop = fixedSideBottom = false;
526 lastScrollPosition = windowPos;
528 $sideSortables.attr( 'style', '' );
529 fixedSideTop = fixedSideBottom = false;
534 paddingTop: heights.toolsHeight
539 paddingTop: heights.visualTopHeight + heights.menuBarHeight
543 marginTop: heights.textTopHeight
546 $textEditorClone.width( contentWrapWidth - 20 - ( borderWidth * 2 ) );
551 function fullscreenHide() {
556 function initialResize( callback ) {
557 for ( var i = 1; i < 6; i++ ) {
558 setTimeout( callback, 500 * i );
562 function afterScroll() {
563 clearTimeout( scrollTimer );
564 scrollTimer = setTimeout( adjust, 100 );
568 // Scroll to the top when triggering this from JS.
569 // Ensures toolbars are pinned properly.
570 if ( window.pageYOffset && window.pageYOffset > pageYOffsetAtTop ) {
571 window.scrollTo( window.pageXOffset, 0 );
574 $wrap.addClass( 'wp-editor-expand' );
576 // Adjust when the window is scrolled or resized.
577 $window.on( 'scroll.editor-expand resize.editor-expand', function( event ) {
578 adjust( event.type );
582 // Adjust when collapsing the menu, changing the columns, changing the body class.
583 $document.on( 'wp-collapse-menu.editor-expand postboxes-columnchange.editor-expand editor-classchange.editor-expand', adjust )
584 .on( 'postbox-toggled.editor-expand', function() {
585 if ( ! fixedSideTop && ! fixedSideBottom && window.pageYOffset > pinnedToolsTop ) {
586 fixedSideBottom = true;
587 window.scrollBy( 0, -1 );
589 window.scrollBy( 0, 1 );
595 $textEditor.on( 'focus.editor-expand input.editor-expand propertychange.editor-expand', textEditorResize );
596 $textEditor.on( 'keyup.editor-expand', textEditorKeyup );
599 // Adjust when entering/exiting fullscreen mode.
600 fullscreen && fullscreen.pubsub.subscribe( 'hidden', fullscreenHide );
603 mceEditor.settings.wp_autoresize_on = true;
604 mceEditor.execCommand( 'wpAutoResizeOn' );
606 if ( ! mceEditor.isHidden() ) {
607 mceEditor.execCommand( 'wpAutoResize' );
611 if ( ! mceEditor || mceEditor.isHidden() ) {
619 var height = window.getUserSetting('ed_size');
621 // Scroll to the top when triggering this from JS.
622 // Ensures toolbars are reset properly.
623 if ( window.pageYOffset && window.pageYOffset > pageYOffsetAtTop ) {
624 window.scrollTo( window.pageXOffset, 0 );
627 $wrap.removeClass( 'wp-editor-expand' );
629 $window.off( '.editor-expand' );
630 $document.off( '.editor-expand' );
631 $textEditor.off( '.editor-expand' );
634 // Adjust when entering/exiting fullscreen mode.
635 fullscreen && fullscreen.pubsub.unsubscribe( 'hidden', fullscreenHide );
638 $.each( [ $visualTop, $textTop, $tools, $menuBar, $bottom, $statusBar, $contentWrap, $visualEditor, $textEditor, $sideSortables ], function( i, element ) {
639 element && element.attr( 'style', '' );
642 fixedTop = fixedBottom = fixedSideTop = fixedSideBottom = false;
645 mceEditor.settings.wp_autoresize_on = false;
646 mceEditor.execCommand( 'wpAutoResizeOff' );
648 if ( ! mceEditor.isHidden() ) {
652 mceEditor.theme.resizeTo( null, height );
658 $textEditor.height( height );
663 if ( $wrap.hasClass( 'wp-editor-expand' ) ) {
666 // Ideally we need to resize just after CSS has fully loaded and QuickTags is ready.
667 if ( $contentWrap.hasClass( 'html-active' ) ) {
668 initialResize( function() {
675 // Show the on/off checkbox
676 $( '#adv-settings .editor-expand' ).show();
677 $( '#editor-expand-toggle' ).on( 'change.editor-expand', function() {
678 if ( $(this).prop( 'checked' ) ) {
680 window.setUserSetting( 'editor_expand', 'on' );
683 window.setUserSetting( 'editor_expand', 'off' );
687 // Expose on() and off()
688 window.editorExpand = {