From 838ad24448a025bc579d8220f2407ae03c9e36ad Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Wed, 26 Mar 2014 03:04:21 +0100 Subject: [PATCH] Improved mentions, now works in IE, Firefox and Chrome * _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 --- .../js/3rdParty/redactor/plugins/wutil.js | 10 ++ wcfsetup/install/files/js/WCF.Message.js | 116 ++++++++++-------- 2 files changed, 74 insertions(+), 52 deletions(-) diff --git a/wcfsetup/install/files/js/3rdParty/redactor/plugins/wutil.js b/wcfsetup/install/files/js/3rdParty/redactor/plugins/wutil.js index a3aface7a7..0915f24b51 100644 --- a/wcfsetup/install/files/js/3rdParty/redactor/plugins/wutil.js +++ b/wcfsetup/install/files/js/3rdParty/redactor/plugins/wutil.js @@ -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); } }; diff --git a/wcfsetup/install/files/js/WCF.Message.js b/wcfsetup/install/files/js/WCF.Message.js index 88c8555abe..67c4dd1081 100644 --- a/wcfsetup/install/files/js/WCF.Message.js +++ b/wcfsetup/install/files/js/WCF.Message.js @@ -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 - }*/ + } } }); -- 2.20.1