Improved mentions, now works in IE, Firefox and Chrome
authorAlexander Ebert <ebert@woltlab.com>
Wed, 26 Mar 2014 02:04:21 +0000 (03:04 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Wed, 26 Mar 2014 02:04:21 +0000 (03:04 +0100)
 * _getDropdownMenuPosition() now uses the browser's getBoundingClientRect() to get the precise offsets and height
 * _setUsername() now entirely relies on native DOM functions, this prevents issues with Firefox and Internet Explorer. As a bonus, the editor is now able to properly undo all changes
 * moved _setUsername() since it shares a lot of code with the function above, makes it far easier to apply changes to both functions without massive scrolling

wcfsetup/install/files/js/3rdParty/redactor/plugins/wutil.js
wcfsetup/install/files/js/WCF.Message.js

index a3aface7a7ee7a08c76262ba3482af205fe60906..0915f24b517efc7a5346702a8c9aa590a2c85cd3 100644 (file)
@@ -56,5 +56,15 @@ RedactorPlugins.wutil = {
         */
        inWysiwygMode: function() {
                return (this.opts.visual);
+       },
+       
+       /**
+        * Replaces all ranges from the current selection with the provided one.
+        * 
+        * @param       DOMRange        range
+        */
+       replaceRangesWith: function(range) {
+               getSelection().removeAllRanges();
+               getSelection().addRange(range);
        }
 };
index 88c8555abeae02b7ea9dd6c1e0e06c1108fc9e66..67c4dd1081c3dee7bc01385436108e7f20e38c24 100644 (file)
@@ -3051,33 +3051,73 @@ WCF.Message.UserMention = Class.extend({
         * @return      object
         */
        _getDropdownMenuPosition: function() {
-               var $range = this._redactor.getSelection().getRangeAt(0);
-               var $startOffset = $range.startOffset;
+               var $orgRange = getSelection().getRangeAt(0).cloneRange();
+               
+               // mark the entire text, starting from the '@' to the current cursor position
+               var $newRange = document.createRange();
+               $newRange.setStart($orgRange.startContainer, $orgRange.startOffset - (this._mentionStart.length + 1));
+               $newRange.setEnd($orgRange.startContainer, $orgRange.startOffset);
+               
+               this._redactor.replaceRangesWith($newRange);
+               
+               // get the offsets of the bounding box of current text selection
+               var $range = getSelection().getRangeAt(0);
+               var $rect = $range.getBoundingClientRect();
+               var $window = $(window);
+               var $offsets = {
+                       top: Math.round($rect.bottom) + $window.scrollTop(),
+                       left: Math.round($rect.left) + $window.scrollLeft()
+               };
                
-               $range.setStart($range.startContainer, $range.startOffset - this._mentionStart.length);
-               $range.collapse(true);
+               if (this._lineHeight === null) {
+                       this._lineHeight = Math.round($rect.bottom - $rect.top);
+               }
                
-               var $element = document.createElement('span');
-               $range.insertNode($element);
+               // restore caret position
+               this._redactor.replaceRangesWith($orgRange);
                
-               var $jElement = $($element);
-               var $offsets = $jElement.offset();
-               if (this._lineHeight === null) {
-                       this._lineHeight = $jElement.height();
+               return $offsets;
+       },
+       
+       /**
+        * Replaces the started mentioning with a chosen username.
+        */
+       _setUsername: function(username) {
+               var $orgRange = getSelection().getRangeAt(0).cloneRange();
+               
+               // allow redactor to undo this
+               this._redactor.bufferSet();
+               
+               var $newRange = document.createRange();
+               $newRange.setStart($orgRange.startContainer, $orgRange.startOffset - (this._mentionStart.length + 1));
+               $newRange.setEnd($orgRange.startContainer, $orgRange.startOffset);
+               
+               this._redactor.replaceRangesWith($newRange);
+               
+               var $range = getSelection().getRangeAt(0);
+               $range.deleteContents();
+               $range.collapse(true);
+               
+               // insert username
+               if (username.indexOf("'") !== -1) {
+                       username = username.replace(/'/g, "''");
+                       username = "'" + username + "'";
+               }
+               else if (username.indexOf(' ') !== -1) {
+                       username = "'" + username + "'";
                }
                
-               $jElement.remove();
+               // use native API to prevent issues in Internet Explorer
+               var $text = document.createTextNode('@' + username);
+               $range.insertNode($text);
                
-               // inserting the node caused the text node to be split, merge them again
-               // using stupid DOM manipulation since .normalize() is broken in Internet Explorer 11
-               $range.startContainer.nodeValue += $range.startContainer.nextSibling.textContent;
-               $range.startContainer.parentNode.removeChild($range.startContainer.nextSibling);
+               var $newRange = document.createRange();
+               $newRange.setStart($text, username.length + 1);
+               $newRange.setEnd($text, username.length + 1);
                
-               // move to end of node
-               $range.setEnd($range.startContainer, $startOffset);
-               $range.collapse(false);
+               this._redactor.replaceRangesWith($newRange);
                
-               return $offsets;
+               this._hideList();
        },
        
        /**
@@ -3226,34 +3266,6 @@ WCF.Message.UserMention = Class.extend({
                }
        },
        
-       /**
-        * Replaces the started mentioning with a chosen username.
-        */
-       _setUsername: function(username) {
-               // allow redactor to undo this
-               this._redactor.bufferSet();
-               
-               var $range = this._redactor.getSelection().getRangeAt(0);
-               $range.setStart($range.startContainer, $range.startOffset - this._mentionStart.length);
-               $range.deleteContents();
-               $range.collapse(true);
-               
-               // insert username
-               if (username.indexOf("'") !== -1) {
-                       username = username.replace(/'/g, "''");
-                       username = "'" + username + "'";
-               }
-               else if (username.indexOf(' ') !== -1) {
-                       username = "'" + username + "'";
-               }
-               
-               // use native API to prevent issues in Internet Explorer
-               var $text = document.createTextNode(username);
-               $range.insertNode($text);
-               
-               this._hideList();
-       },
-       
        /**
         * Selects the suggestion with the given item index.
         * 
@@ -3308,10 +3320,10 @@ WCF.Message.UserMention = Class.extend({
         * Updates the position of the suggestion list.
         */
        _updateSuggestionListPosition: function() {
-               //try {
+               try {
                        var $dropdownMenuPosition = this._getDropdownMenuPosition();
-                       $dropdownMenuPosition.top += 5 + this._lineHeight; // add little vertical gap
-                       $dropdownMenuPosition.left -= 16;
+                       $dropdownMenuPosition.top += 5; // add a little vertical gap
+                       
                        this._dropdownMenu.css($dropdownMenuPosition);
                        this._selectItem(0);
                        
@@ -3325,10 +3337,10 @@ WCF.Message.UserMention = Class.extend({
                        else {
                                this._dropdownMenu.removeClass('dropdownArrowBottom');
                        }
-               /*}
+               }
                catch (e) {
                        // ignore errors that are caused by pressing enter to
                        // often in a short period of time
-               }*/
+               }
        }
 });