X-Git-Url: https://scripts.mit.edu/gitweb/autoinstallsdev/mediawiki.git/blobdiff_plain/87219ebd28426c6d21cb545233ee52f5f7af7dfd..18a6620945d02687fbcfc4c27355d952fd748b41:/resources/jquery/jquery.textSelection.js diff --git a/resources/jquery/jquery.textSelection.js b/resources/jquery/jquery.textSelection.js new file mode 100644 index 00000000..35e224db --- /dev/null +++ b/resources/jquery/jquery.textSelection.js @@ -0,0 +1,404 @@ +/** + * These plugins provide extra functionality for interaction with textareas. + */ +( function( $ ) { +$.fn.textSelection = function( command, options ) { +var fn = { +/** + * Get the contents of the textarea + */ +getContents: function() { + return this.val(); +}, +/** + * Get the currently selected text in this textarea. Will focus the textarea + * in some browsers (IE/Opera) + */ +getSelection: function() { + var e = this.get( 0 ); + var retval = ''; + if ( $(e).is( ':hidden' ) ) { + // Do nothing + } else if ( document.selection && document.selection.createRange ) { + e.focus(); + var range = document.selection.createRange(); + retval = range.text; + } else if ( e.selectionStart || e.selectionStart == '0' ) { + retval = e.value.substring( e.selectionStart, e.selectionEnd ); + } + return retval; +}, +/** + * Ported from skins/common/edit.js by Trevor Parscal + * (c) 2009 Wikimedia Foundation (GPLv2) - http://www.wikimedia.org + * + * Inserts text at the begining and end of a text selection, optionally + * inserting text at the caret when selection is empty. + */ +encapsulateSelection: function( options ) { + return this.each( function() { + /** + * Check if the selected text is the same as the insert text + */ + function checkSelectedText() { + if ( !selText ) { + selText = options.peri; + isSample = true; + } else if ( options.replace ) { + selText = options.peri; + } else if ( selText.charAt( selText.length - 1 ) == ' ' ) { + // Exclude ending space char + selText = selText.substring(0, selText.length - 1); + options.post += ' '; + } + } + var isSample = false; + if ( this.style.display == 'none' ) { + // Do nothing + } else if ( this.selectionStart || this.selectionStart == '0' ) { + // Mozilla/Opera + $(this).focus(); + var selText = $(this).textSelection( 'getSelection' ); + var startPos = this.selectionStart; + var endPos = this.selectionEnd; + var scrollTop = this.scrollTop; + checkSelectedText(); + if ( options.ownline ) { + if ( startPos != 0 && this.value.charAt( startPos - 1 ) != "\n" ) { + options.pre = "\n" + options.pre; + } + if ( this.value.charAt( endPos ) != "\n" ) { + options.post += "\n"; + } + } + this.value = this.value.substring( 0, startPos ) + options.pre + selText + options.post + + this.value.substring( endPos, this.value.length ); + // Setting this.value scrolls the textarea to the top, restore the scroll position + this.scrollTop = scrollTop; + if ( window.opera ) { + options.pre = options.pre.replace( /\r?\n/g, "\r\n" ); + selText = selText.replace( /\r?\n/g, "\r\n" ); + options.post = options.post.replace( /\r?\n/g, "\r\n" ); + } + if ( isSample && options.selectPeri ) { + this.selectionStart = startPos + options.pre.length; + this.selectionEnd = startPos + options.pre.length + selText.length; + } else { + this.selectionStart = startPos + options.pre.length + selText.length + + options.post.length; + this.selectionEnd = this.selectionStart; + } + } else if ( document.selection && document.selection.createRange ) { + // IE + $(this).focus(); + if ( context ) { + context.fn.restoreCursorAndScrollTop(); + } + var selText = $(this).textSelection( 'getSelection' ); + var scrollTop = this.scrollTop; + var range = document.selection.createRange(); + if ( options.ownline && range.moveStart ) { + var range2 = document.selection.createRange(); + range2.collapse(); + range2.moveStart( 'character', -1 ); + // FIXME: Which check is correct? + if ( range2.text != "\r" && range2.text != "\n" && range2.text != "" ) { + options.pre = "\n" + options.pre; + } + var range3 = document.selection.createRange(); + range3.collapse( false ); + range3.moveEnd( 'character', 1 ); + if ( range3.text != "\r" && range3.text != "\n" && range3.text != "" ) { + options.post += "\n"; + } + } + checkSelectedText(); + range.text = options.pre + selText + options.post; + if ( isSample && options.selectPeri && range.moveStart ) { + range.moveStart( 'character', - options.post.length - selText.length ); + range.moveEnd( 'character', - options.post.length ); + } + range.select(); + // Restore the scroll position + this.scrollTop = scrollTop; + } + $(this).trigger( 'encapsulateSelection', [ options.pre, options.peri, options.post, options.ownline, + options.replace ] ); + }); +}, +/** + * Ported from Wikia's LinkSuggest extension + * https://svn.wikia-code.com/wikia/trunk/extensions/wikia/LinkSuggest + * Some code copied from + * http://www.dedestruct.com/2008/03/22/howto-cross-browser-cursor-position-in-textareas/ + * + * Get the position (in resolution of bytes not nessecarily characters) + * in a textarea + */ + getCaretPosition: function( options ) { + function getCaret( e ) { + var caretPos = 0, endPos = 0; + if ( $.browser.msie ) { + // IE Support + var preFinished = false; + var periFinished = false; + var postFinished = false; + var preText, rawPreText, periText; + var rawPeriText, postText, rawPostText; + // Create range containing text in the selection + var periRange = document.selection.createRange().duplicate(); + // Create range containing text before the selection + var preRange = document.body.createTextRange(); + // Select all the text + preRange.moveToElementText(e); + // Move the end where we need it + preRange.setEndPoint("EndToStart", periRange); + // Create range containing text after the selection + var postRange = document.body.createTextRange(); + // Select all the text + postRange.moveToElementText(e); + // Move the start where we need it + postRange.setEndPoint("StartToEnd", periRange); + // Load the text values we need to compare + preText = rawPreText = preRange.text; + periText = rawPeriText = periRange.text; + postText = rawPostText = postRange.text; + /* + * Check each range for trimmed newlines by shrinking the range by 1 + * character and seeing if the text property has changed. If it has + * not changed then we know that IE has trimmed a \r\n from the end. + */ + do { + if ( !preFinished ) { + if ( preRange.compareEndPoints( "StartToEnd", preRange ) == 0 ) { + preFinished = true; + } else { + preRange.moveEnd( "character", -1 ); + if ( preRange.text == preText ) { + rawPreText += "\r\n"; + } else { + preFinished = true; + } + } + } + if ( !periFinished ) { + if ( periRange.compareEndPoints( "StartToEnd", periRange ) == 0 ) { + periFinished = true; + } else { + periRange.moveEnd( "character", -1 ); + if ( periRange.text == periText ) { + rawPeriText += "\r\n"; + } else { + periFinished = true; + } + } + } + if ( !postFinished ) { + if ( postRange.compareEndPoints("StartToEnd", postRange) == 0 ) { + postFinished = true; + } else { + postRange.moveEnd( "character", -1 ); + if ( postRange.text == postText ) { + rawPostText += "\r\n"; + } else { + postFinished = true; + } + } + } + } while ( ( !preFinished || !periFinished || !postFinished ) ); + caretPos = rawPreText.replace( /\r\n/g, "\n" ).length; + endPos = caretPos + rawPeriText.replace( /\r\n/g, "\n" ).length; + } else if ( e.selectionStart || e.selectionStart == '0' ) { + // Firefox support + caretPos = e.selectionStart; + endPos = e.selectionEnd; + } + return options.startAndEnd ? [ caretPos, endPos ] : caretPos; + } + return getCaret( this.get( 0 ) ); +}, +setSelection: function( options ) { + return this.each( function() { + if ( $(this).is( ':hidden' ) ) { + // Do nothing + } else if ( this.selectionStart || this.selectionStart == '0' ) { + // Opera 9.0 doesn't allow setting selectionStart past + // selectionEnd; any attempts to do that will be ignored + // Make sure to set them in the right order + if ( options.start > this.selectionEnd ) { + this.selectionEnd = options.end; + this.selectionStart = options.start; + } else { + this.selectionStart = options.start; + this.selectionEnd = options.end; + } + } else if ( document.body.createTextRange ) { + var selection = document.body.createTextRange(); + selection.moveToElementText( this ); + var length = this.value.length; + // IE doesn't count \n when computing the offset, so we won't either + var newLines = this.value.match( /\n/g ); + if ( newLines) length = length - newLines.length; + selection.moveStart( 'character', options.start ); + selection.moveEnd( 'character', -length + options.end ); + + // This line can cause an error under certain circumstances (textarea empty, no selection) + // Silence that error + try { + selection.select(); + } catch( e ) { } + } + }); +}, +/** + * Ported from Wikia's LinkSuggest extension + * https://svn.wikia-code.com/wikia/trunk/extensions/wikia/LinkSuggest + * + * Scroll a textarea to the current cursor position. You can set the cursor + * position with setSelection() + * @param options boolean Whether to force a scroll even if the caret position + * is already visible. Defaults to false + */ +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 text = e.value.replace( /\r/g, "" ); + var caret = $( e ).textSelection( 'getCaretPosition' ); + var lineLength = getLineLength( e ); + var row = 0; + var charInLine = 0; + var 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++; + } + } + } + var 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() { + if ( $(this).is( ':hidden' ) ) { + // Do nothing + } else if ( this.selectionStart || this.selectionStart == '0' ) { + // Mozilla + var scroll = getCaretScrollPosition( this ); + if ( options.force || scroll < $(this).scrollTop() || + scroll > $(this).scrollTop() + $(this).height() ) + $(this).scrollTop( scroll ); + } else if ( document.selection && document.selection.createRange ) { + // IE / Opera + /* + * IE automatically scrolls the selected text to the + * bottom of the textarea at range.select() time, except + * if it was already in view and the cursor position + * wasn't changed, in which case it does nothing. To + * cover that case, we'll force it to act by moving one + * character back and forth. + */ + var range = document.body.createTextRange(); + var savedRange = document.selection.createRange(); + var pos = $(this).textSelection( 'getCaretPosition' ); + var oldScrollTop = this.scrollTop; + range.moveToElementText( this ); + range.collapse(); + range.move( 'character', pos + 1); + range.select(); + if ( this.scrollTop != oldScrollTop ) + this.scrollTop += range.offsetTop; + else if ( options.force ) { + range.move( 'character', -1 ); + range.select(); + } + savedRange.select(); + } + $(this).trigger( 'scrollToPosition' ); + } ); +} +}; + // Apply defaults + switch ( command ) { + //case 'getContents': // no params + //case 'setContents': // no params with defaults + //case 'getSelection': // no params + case 'encapsulateSelection': + options = $.extend( { + 'pre': '', // Text to insert before the cursor/selection + 'peri': '', // Text to insert between pre and post and select afterwards + 'post': '', // Text to insert after the cursor/selection + 'ownline': false, // Put the inserted text on a line of its own + 'replace': false, // If there is a selection, replace it with peri instead of leaving it alone + 'selectPeri': true // Select the peri text if it was inserted (but not if there was a selection and replace==false) + }, options ); + break; + case 'getCaretPosition': + options = $.extend( { + 'startAndEnd': false // Return [start, end] instead of just start + }, options ); + // FIXME: We may not need character position-based functions if we insert markers in the right places + break; + case 'setSelection': + options = $.extend( { + 'start': undefined, // Position to start selection at + 'end': undefined, // Position to end selection at. Defaults to start + 'startContainer': undefined, // Element to start selection in (iframe only) + 'endContainer': undefined // Element to end selection in (iframe only). Defaults to startContainer + }, options ); + if ( options.end === undefined ) + options.end = options.start; + if ( options.endContainer == undefined ) + options.endContainer = options.startContainer; + // FIXME: We may not need character position-based functions if we insert markers in the right places + break; + case 'scrollToCaretPosition': + options = $.extend( { + 'force': false // Force a scroll even if the caret position is already visible + }, options ); + break; + } + var context = $(this).data( 'wikiEditor-context' ); + var hasIframe = typeof context !== 'undefined' && context && typeof context.$iframe !== 'undefined'; + + // IE selection restore voodoo + var needSave = false; + if ( hasIframe && context.savedSelection !== null ) { + context.fn.restoreSelection(); + needSave = true; + } + retval = ( hasIframe ? context.fn : fn )[command].call( this, options ); + if ( hasIframe && needSave ) { + context.fn.saveSelection(); + } + return retval; +}; + +} )( jQuery );