From c92dd025f1d3d2a1e2a18e1c8f8aff85860c1b45 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Bartosz=20Dziewo=C5=84ski?= Date: Mon, 5 Feb 2018 15:08:52 +0100 Subject: [PATCH] jquery.textSelection: Rewrite 'scrollToCaretPosition' The previous version tried to assume some platform-dependent things about text rendering and guess the position based on that. It did not work very well. Change-Id: I9f4c465692cf128b7758de4ca75f977e8f8be83e --- resources/src/jquery/jquery.textSelection.js | 83 +++++++------------- 1 file changed, 28 insertions(+), 55 deletions(-) diff --git a/resources/src/jquery/jquery.textSelection.js b/resources/src/jquery/jquery.textSelection.js index 3891d6d0e3..993a11927b 100644 --- a/resources/src/jquery/jquery.textSelection.js +++ b/resources/src/jquery/jquery.textSelection.js @@ -293,66 +293,39 @@ * @chainable */ scrollToCaretPosition: function ( options ) { - function getLineLength( e ) { - return Math.floor( e.scrollWidth / ( $.client.profile().platform === 'linux' ? 7 : 8 ) ); - } - function getCaretScrollPosition( e ) { - // FIXME: This functions sucks and is off by a few lines most - // of the time. It should be replaced by something decent. - var i, j, - nextSpace, - text = e.value.replace( /\r/g, '' ), - caret = $( e ).textSelection( 'getCaretPosition' ), - lineLength = getLineLength( e ), - row = 0, - charInLine = 0, - lastSpaceInLine = 0; - - for ( i = 0; i < caret; i++ ) { - charInLine++; - if ( text.charAt( i ) === ' ' ) { - lastSpaceInLine = charInLine; - } else if ( text.charAt( i ) === '\n' ) { - lastSpaceInLine = 0; - charInLine = 0; - row++; - } - if ( charInLine > lineLength ) { - if ( lastSpaceInLine > 0 ) { - charInLine = charInLine - lastSpaceInLine; - lastSpaceInLine = 0; - row++; - } - } - } - nextSpace = 0; - for ( j = caret; j < caret + lineLength; j++ ) { - if ( - text.charAt( j ) === ' ' || - text.charAt( j ) === '\n' || - caret === text.length - ) { - nextSpace = j; - break; - } - } - if ( nextSpace > lineLength && caret <= lineLength ) { - charInLine = caret - lastSpaceInLine; - row++; - } - return ( $.client.profile().platform === 'mac' ? 13 : ( $.client.profile().platform === 'linux' ? 15 : 16 ) ) * row; - } return this.each( function () { - var scroll; + var + clientHeight = this.clientHeight, + origValue = this.value, + origSelectionStart = this.selectionStart, + origSelectionEnd = this.selectionEnd, + origScrollTop = this.scrollTop, + calcScrollTop; + // Do nothing if hidden if ( !$( this ).is( ':hidden' ) ) { - if ( this.selectionStart || this.selectionStart === 0 ) { - scroll = getCaretScrollPosition( this ); - if ( options.force || scroll < $( this ).scrollTop() || - scroll > $( this ).scrollTop() + $( this ).height() ) { - $( this ).scrollTop( scroll ); + // Delete all text after the selection and scroll the textarea to the end. + // This ensures the selection is visible (aligned to the bottom of the textarea). + // Then restore the text we deleted without changing scroll position. + this.value = this.value.slice( 0, this.selectionEnd ); + this.scrollTop = this.scrollHeight; + // Chrome likes to adjust scroll position when changing value, so save and re-set later. + // Note that this is not equal to scrollHeight, it's scrollHeight minus clientHeight. + calcScrollTop = this.scrollTop; + this.value = origValue; + this.selectionStart = origSelectionStart; + this.selectionEnd = origSelectionEnd; + + if ( !options.force ) { + // Check if all the scrolling was unnecessary and if so, restore previous position. + // If the current position is no more than a screenful above the original, + // the selection was previously visible on the screen. + if ( calcScrollTop < origScrollTop && origScrollTop - calcScrollTop < clientHeight ) { + calcScrollTop = origScrollTop; } } + + this.scrollTop = calcScrollTop; } $( this ).trigger( 'scrollToPosition' ); } ); -- 2.20.1