jquery.textSelection: Add 'replaceSelection' method
[lhc/web/wiklou.git] / resources / src / jquery / jquery.textSelection.js
index 27d5fc7..da88270 100644 (file)
@@ -34,6 +34,7 @@
         *  - {@link jQuery.plugin.textSelection#getContents getContents}
         *  - {@link jQuery.plugin.textSelection#setContents setContents}
         *  - {@link jQuery.plugin.textSelection#getSelection getSelection}
+        *  - {@link jQuery.plugin.textSelection#replaceSelection replaceSelection}
         *  - {@link jQuery.plugin.textSelection#encapsulateSelection encapsulateSelection}
         *  - {@link jQuery.plugin.textSelection#getCaretPosition getCaretPosition}
         *  - {@link jQuery.plugin.textSelection#setSelection setSelection}
                         *
                         * @private
                         * @param {string} content
+                        * @return {jQuery}
+                        * @chainable
                         */
                        setContents: function ( content ) {
-                               this.val( content );
+                               return this.each( function () {
+                                       var scrollTop = this.scrollTop;
+                                       $( this ).val( content );
+                                       // Setting this.value may scroll the textarea, restore the scroll position
+                                       this.scrollTop = scrollTop;
+                               } );
                        },
 
                        /**
                                return retval;
                        },
 
+                       /**
+                        * Replace the selected text in the textarea with the given text, or insert it at the cursor.
+                        *
+                        * @private
+                        * @param {string} value
+                        * @return {jQuery}
+                        * @chainable
+                        */
+                       replaceSelection: function ( value ) {
+                               return this.each( function () {
+                                       var allText, currSelection, startPos, endPos;
+
+                                       allText = $( this ).textSelection( 'getContents' );
+                                       currSelection = $( this ).textSelection( 'getCaretPosition', { startAndEnd: true } );
+                                       startPos = currSelection[ 0 ];
+                                       endPos = currSelection[ 1 ];
+
+                                       $( this ).textSelection( 'setContents', allText.slice( 0, startPos ) + value +
+                                               allText.slice( endPos ) );
+                                       $( this ).textSelection( 'setSelection', {
+                                               start: startPos,
+                                               end: startPos + value.length
+                                       } );
+                               } );
+                       },
+
                        /**
                         * Insert text at the beginning and end of a text selection, optionally
                         * inserting text at the caret when selection is empty.
                         */
                        encapsulateSelection: function ( options ) {
                                return this.each( function () {
-                                       var selText, scrollTop, insertText,
+                                       var selText, allText, currSelection, insertText,
+                                               combiningCharSelectionBug = false,
                                                isSample, startPos, endPos,
                                                pre = options.pre,
                                                post = options.post;
                                        }
 
                                        selText = $( this ).textSelection( 'getSelection' );
-                                       startPos = this.selectionStart;
-                                       endPos = this.selectionEnd;
-                                       scrollTop = this.scrollTop;
+                                       allText = $( this ).textSelection( 'getContents' );
+                                       currSelection = $( this ).textSelection( 'getCaretPosition', { startAndEnd: true } );
+                                       startPos = currSelection[ 0 ];
+                                       endPos = currSelection[ 1 ];
                                        checkSelectedText();
                                        if (
                                                options.selectionStart !== undefined &&
                                                endPos - startPos !== options.selectionEnd - options.selectionStart
                                        ) {
                                                // This means there is a difference in the selection range returned by browser and what we passed.
-                                               // This happens for Chrome in the case of composite characters. Ref bug #30130
+                                               // This happens for Safari 5.1, Chrome 12 in the case of composite characters. Ref T32130
                                                // Set the startPos to the correct position.
                                                startPos = options.selectionStart;
+                                               combiningCharSelectionBug = true;
+                                               // TODO: The comment above is from 2011. Is this still a problem for browsers we support today?
+                                               // Minimal test case: https://jsfiddle.net/z4q7a2ko/
                                        }
 
                                        insertText = pre + selText + post;
                                                insertText = doSplitLines( selText, pre, post );
                                        }
                                        if ( options.ownline ) {
-                                               if ( startPos !== 0 && this.value.charAt( startPos - 1 ) !== '\n' && this.value.charAt( startPos - 1 ) !== '\r' ) {
+                                               if ( startPos !== 0 && allText.charAt( startPos - 1 ) !== '\n' && allText.charAt( startPos - 1 ) !== '\r' ) {
                                                        insertText = '\n' + insertText;
                                                        pre += '\n';
                                                }
-                                               if ( this.value.charAt( endPos ) !== '\n' && this.value.charAt( endPos ) !== '\r' ) {
+                                               if ( allText.charAt( endPos ) !== '\n' && allText.charAt( endPos ) !== '\r' ) {
                                                        insertText += '\n';
                                                        post += '\n';
                                                }
                                        }
-                                       this.value = this.value.slice( 0, startPos ) + insertText +
-                                               this.value.slice( endPos );
-                                       // Setting this.value scrolls the textarea to the top, restore the scroll position
-                                       this.scrollTop = scrollTop;
+                                       if ( combiningCharSelectionBug ) {
+                                               $( this ).textSelection( 'setContents', allText.slice( 0, startPos ) + insertText +
+                                                       allText.slice( endPos ) );
+                                       } else {
+                                               $( this ).textSelection( 'replaceSelection', insertText );
+                                       }
                                        if ( isSample && options.selectPeri && ( !options.splitlines || ( options.splitlines && selText.indexOf( '\n' ) === -1 ) ) ) {
-                                               this.selectionStart = startPos + pre.length;
-                                               this.selectionEnd = startPos + pre.length + selText.length;
+                                               $( this ).textSelection( 'setSelection', {
+                                                       start: startPos + pre.length,
+                                                       end: startPos + pre.length + selText.length
+                                               } );
                                        } else {
-                                               this.selectionStart = startPos + insertText.length;
-                                               this.selectionEnd = this.selectionStart;
+                                               $( this ).textSelection( 'setSelection', {
+                                                       start: startPos + insertText.length
+                                               } );
                                        }
                                        $( this ).trigger( 'encapsulateSelection', [ options.pre, options.peri, options.post, options.ownline,
                                                options.replace, options.splitlines ] );
                        // case 'getContents': // no params
                        // case 'setContents': // no params with defaults
                        // case 'getSelection': // no params
+                       // case 'replaceSelection': // no params with defaults
                        case 'encapsulateSelection':
                                options = $.extend( {
                                        pre: '',