*/
init: function(containerID, supportExtendedForm, quoteManager) {
require(['WoltLab/WCF/Ui/Message/InlineEditor'], (function(UiMessageInlineEditor) {
- UiMessageInlineEditor.init({
- extendedForm: (supportExtendedForm === true),
-
+ new UiMessageInlineEditor({
className: this._getClassName(),
containerId: containerID,
editorPrefix: this._messageEditorIDPrefix,
});
/**
- * Handles user mention suggestions in Redactor instances.
- *
- * Important: Objects of this class have to be created before Redactor
- * is initialized!
+ * @deprecated 2.2
*/
WCF.Message.UserMention = Class.extend({
- /**
- * current caret position
- * @var DOMRange
- */
- _caretPosition: null,
-
- /**
- * name of the class used to get the user suggestions
- * @var string
- */
- _className: 'wcf\\data\\user\\UserAction',
-
- /**
- * dropdown object
- * @var jQuery
- */
- _dropdown: null,
-
- /**
- * dropdown menu object
- * @var jQuery
- */
- _dropdownMenu: null,
-
- /**
- * suggestion item index, -1 if none is selected
- * @var integer
- */
- _itemIndex: -1,
-
- /**
- * line height
- * @var integer
- */
- _lineHeight: null,
-
- /**
- * current beginning of the mentioning
- * @var string
- */
- _mentionStart: '',
-
- /**
- * redactor instance object
- * @var $.Redactor
- */
- _redactor: null,
-
- /**
- * delay timer to only send requests after user paused typing
- * @var WCF.PeriodicalExecuter
- */
- _timer: null,
-
- /**
- * Initalizes user suggestions for Redactor with the given textarea id.
- *
- * @param string wysiwygSelector
- */
- init: function(wysiwygSelector) {
- if ($.browser.mobile && $.browser.mozilla) {
- // the desktop Firefox work-arounds do not work on Firefox for Android, in fact they crash it
- return;
- }
-
- this._textarea = $('#' + wysiwygSelector);
- this._redactor = this._textarea.redactor('core.getObject');
-
- this._dropdown = this._textarea.redactor('core.getEditor');
- this._dropdownMenu = $('<ul class="dropdownMenu userSuggestionList" />').appendTo(this._textarea.parent());
- WCF.Dropdown.initDropdownFragment(this._dropdown, this._dropdownMenu);
-
- this._proxy = new WCF.Action.Proxy({
- autoAbortPrevious: true,
- success: $.proxy(this._success, this)
- });
-
- WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'keydown_' + wysiwygSelector, $.proxy(this._keydown, this));
- WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'keyup_' + wysiwygSelector, $.proxy(this._keyup, this));
- },
-
- /**
- * Clears the suggestion list.
- */
- _clearList: function() {
- this._hideList();
-
- this._dropdownMenu.empty();
- },
-
- /**
- * Handles a click on a list item suggesting a username.
- *
- * This function is also called when seleting a suggested username by clicking
- * enter.
- *
- * @param object event
- */
- _click: function(event) {
- // in Firefox, this._caretPosition does not have the text node as
- // startContainer anymore when confirming a username suggestion by
- // clicking enter, thus we need to manually adjust it
- if ($.browser.mozilla && this._caretPosition.startContainer.nodeName == 'P') {
- var $textNode = this._caretPosition.startContainer.childNodes[this._caretPosition.startOffset - 1];
-
- this._caretPosition = document.createRange();
- this._caretPosition.selectNodeContents($textNode);
- this._caretPosition.collapse();
- }
-
- // restore caret position
- this._redactor.wutil.replaceRangesWith(this._caretPosition);
-
- this._setUsername($(event.currentTarget).data('username'));
- },
-
- /**
- * Creates an item in the suggestion list with the given data.
- *
- * @return object
- */
- _createListItem: function(listItemData) {
- var $listItem = $('<li />').data('username', listItemData.label).click($.proxy(this._click, this)).appendTo(this._dropdownMenu);
-
- var $box16 = $('<div />').addClass('box16').appendTo($listItem);
- $box16.append($(listItemData.icon));
- $box16.append($('<div />').append($('<span />').text(listItemData.label)));
- },
-
- /**
- * Returns the offsets used to set the position of the user suggestion
- * dropdown.
- *
- * @return object
- */
- _getDropdownMenuPosition: function() {
- 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.wutil.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()
- };
-
- if (this._lineHeight === null) {
- this._lineHeight = Math.round($rect.bottom - $rect.top);
- }
-
- // restore caret position
- this._redactor.wutil.replaceRangesWith($orgRange);
- this._caretPosition = $orgRange;
-
- return $offsets;
- },
-
- /**
- * Replaces the started mentioning with a chosen username.
- */
- _setUsername: function(username) {
- if (this._timer !== null) {
- this._timer.stop();
- this._timer = null;
- }
- this._proxy.abortPrevious();
-
- var $orgRange = getSelection().getRangeAt(0).cloneRange();
-
- // allow redactor to undo this
- this._redactor.buffer.set();
-
- var $startContainer = $orgRange.startContainer;
- var $startOffset = $orgRange.startOffset - (this._mentionStart.length + 1);
-
- // navigating with the keyboard before hitting enter will cause the text node to be split
- if ($startOffset < 0) {
- $startContainer = $startContainer.previousSibling;
- $startOffset = $startContainer.length - (this._mentionStart.length + 1) - ($orgRange.startOffset - 1);
- }
-
- var $newRange = document.createRange();
- $newRange.setStart($startContainer, $startOffset);
- $newRange.setEnd($orgRange.startContainer, $orgRange.startOffset);
-
- this._redactor.wutil.replaceRangesWith($newRange);
-
- var $range = getSelection().getRangeAt(0);
- $range.deleteContents();
- $range.collapse(true);
-
- // insert username
- if (username.indexOf("'") !== -1) {
- username = username.replace(/'/g, "''");
- }
- username = "'" + username + "'";
-
- // use native API to prevent issues in Internet Explorer
- var $text = document.createTextNode('@' + username);
- $range.insertNode($text);
-
- var $newRange = document.createRange();
- $newRange.setStart($text, username.length + 1);
- $newRange.setEnd($text, username.length + 1);
-
- this._redactor.wutil.replaceRangesWith($newRange);
-
- this._hideList();
- },
-
- /**
- * Returns the parameters for the AJAX request.
- *
- * @return object
- */
- _getParameters: function() {
- return {
- data: {
- includeUserGroups: false,
- searchString: this._mentionStart
- }
- };
- },
-
- /**
- * Returns the relevant text in front of the caret in the current line.
- *
- * @return string
- */
- _getTextLineInFrontOfCaret: function() {
- // if text is marked, user suggestions are disabled
- if (this._redactor.selection.getHtml().length) {
- return '';
- }
-
- var $range = getSelection().getRangeAt(0);
-
- // in Firefox, blurring and refocusing the browser creates separate
- // text nodes
- if ($.browser.mozilla && $range.startContainer.nodeType == 3) {
- $range.startContainer.parentNode.normalize();
- }
-
- var $text = $range.startContainer.textContent.substr(0, $range.startOffset);
-
- // remove unicode zero width space and non-breaking space
- var $textBackup = $text;
- $text = '';
- var $hadSpace = false;
- for (var $i = 0; $i < $textBackup.length; $i++) {
- var $byte = $textBackup.charCodeAt($i).toString(16);
- if ($byte != '200b' && (!/\s/.test($textBackup[$i]) || (($byte == 'a0' || $byte == '20') && !$hadSpace))) {
- if ($byte == 'a0' || $byte == '20') {
- $hadSpace = true;
- }
-
- if ($textBackup[$i] === '@' && $i && /\s/.test($textBackup[$i - 1])) {
- $hadSpace = false;
- $text = '';
- }
-
- $text += $textBackup[$i];
- }
- else {
- $hadSpace = false;
- $text = '';
- }
- }
-
- return $text;
- },
-
- /**
- * Hides the suggestion list.
- */
- _hideList: function() {
- this._dropdown.removeClass('dropdownOpen');
- this._dropdownMenu.removeClass('dropdownOpen');
-
- this._itemIndex = -1;
- },
-
- /**
- * Handles the keydown event to check if the user starts mentioning someone.
- *
- * @param object data
- */
- _keydown: function(data) {
- if (this._redactor.wutil.inPlainMode()) {
- return;
- }
-
- if (this._dropdownMenu.is(':visible')) {
- switch (data.event.which) {
- case $.ui.keyCode.ENTER:
- data.event.preventDefault();
- data.cancel = true;
-
- this._dropdownMenu.children('li').eq(this._itemIndex).trigger('click');
- break;
-
- case $.ui.keyCode.UP:
- data.cancel = true;
- data.event.preventDefault();
-
- this._selectItem(this._itemIndex - 1);
- break;
-
- case $.ui.keyCode.DOWN:
- data.cancel = true;
- data.event.preventDefault();
-
- this._selectItem(this._itemIndex + 1);
- break;
- }
- }
- },
-
- /**
- * Handles the keyup event to check if the user starts mentioning someone.
- *
- * @param object data
- */
- _keyup: function(data) {
- if (this._redactor.wutil.inPlainMode()) {
- return true;
- }
-
- // abort previous search requests
- if (this._timer !== null) {
- this._timer.stop();
- this._timer = null;
- }
- this._proxy.abortPrevious();
-
- // ignore enter key up event
- if (data.event.which === $.ui.keyCode.ENTER) {
- return;
- }
-
- // ignore event if suggestion list and user pressed enter, arrow up or arrow down
- if (this._dropdownMenu.is(':visible') && data.event.which in { 13:1, 38:1, 40:1 }) {
- return;
- }
-
- var $currentText = this._getTextLineInFrontOfCaret();
- if ($currentText) {
- var $match = $currentText.match(/@([^,]{3,})$/);
- if ($match) {
- // if mentioning is at text begin or there's a whitespace character
- // before the '@', everything is fine
- if (!$match.index || $currentText[$match.index - 1].match(/\s/)) {
- this._mentionStart = $match[1];
-
- if (this._timer !== null) {
- this._timer.stop();
- }
-
- this._timer = new WCF.PeriodicalExecuter($.proxy(function() {
- this._proxy.setOption('data', {
- actionName: 'getSearchResultList',
- className: this._className,
- interfaceName: 'wcf\\data\\ISearchAction',
- parameters: this._getParameters()
- });
- this._proxy.sendRequest();
-
- this._timer.stop();
- this._timer = null;
- }, this), 500);
- }
- }
- else {
- this._hideList();
- }
- }
- else {
- this._hideList();
- }
- },
-
- /**
- * Selects the suggestion with the given item index.
- *
- * @param integer itemIndex
- */
- _selectItem: function(itemIndex) {
- var $li = this._dropdownMenu.children('li');
-
- if (itemIndex < 0) {
- itemIndex = $li.length - 1;
- }
- else if (itemIndex + 1 > $li.length) {
- itemIndex = 0;
- }
-
- $li.removeClass('dropdownNavigationItem');
- $li.eq(itemIndex).addClass('dropdownNavigationItem');
-
- this._itemIndex = itemIndex;
- },
-
- /**
- * Shows the suggestion list.
- */
- _showList: function() {
- this._dropdown.addClass('dropdownOpen');
- this._dropdownMenu.addClass('dropdownOpen');
- },
-
- /**
- * Evalutes user suggestion-AJAX request results.
- *
- * @param object data
- * @param string textStatus
- * @param jQuery jqXHR
- */
- _success: function(data, textStatus, jqXHR) {
- this._clearList(false);
-
- if ($.getLength(data.returnValues)) {
- for (var $i in data.returnValues) {
- var $item = data.returnValues[$i];
- this._createListItem($item);
- }
-
- this._updateSuggestionListPosition();
- this._showList();
- }
- },
-
- /**
- * Updates the position of the suggestion list.
- */
- _updateSuggestionListPosition: function() {
- try {
- var $dropdownMenuPosition = this._getDropdownMenuPosition();
- $dropdownMenuPosition.top += 5; // add a little vertical gap
-
- this._dropdownMenu.css($dropdownMenuPosition);
- this._selectItem(0);
-
- if ($dropdownMenuPosition.top + this._dropdownMenu.outerHeight() + 10 > $(window).height() + $(document).scrollTop()) {
- this._dropdownMenu.addClass('dropdownArrowBottom');
-
- this._dropdownMenu.css({
- top: $dropdownMenuPosition.top - this._dropdownMenu.outerHeight() - 2 * this._lineHeight + 5
- });
- }
- else {
- this._dropdownMenu.removeClass('dropdownArrowBottom');
- }
- }
- catch (e) {
- // ignore errors that are caused by pressing enter to
- // often in a short period of time
- }
+ init: function() {
+ throw new Error("Support for mentions in Redactor are now enabled by adding the attribute 'data-support-mention=\"true\"' to the textarea element.");
}
});