diff --git a/dist/mention.js b/dist/mention.js
index 59ad15a..e580ec0 100644
--- a/dist/mention.js
+++ b/dist/mention.js
@@ -20,7 +20,7 @@ angular.module('ui.mention', []).directive('uiMention', function () {
'use strict';
angular.module('ui.mention').controller('uiMention', ["$element", "$scope", "$attrs", "$q", "$timeout", "$document", function ($element, $scope, $attrs, $q, $timeout, $document) {
- var _this2 = this;
+ var _this = this;
// Beginning of input or preceeded by spaces: @sometext
this.delimiter = '@';
@@ -43,8 +43,6 @@ angular.module('ui.mention').controller('uiMention', ["$element", "$scope", "$at
* @param {ngModelController} model
*/
this.init = function (model) {
- var _this = this;
-
// Leading whitespace shows up in the textarea but not the preview
$attrs.ngTrim = 'false';
@@ -53,7 +51,9 @@ angular.module('ui.mention').controller('uiMention', ["$element", "$scope", "$at
ngModel.$parsers.push(function (value) {
// Removes any mentions that aren't used
_this.mentions = _this.mentions.filter(function (mention) {
- if (~value.indexOf(_this.label(mention))) return value = value.replace(_this.label(mention), _this.encode(mention));
+ if (~value.indexOf(_this.label(mention))) {
+ return value = value.split(_this.label(mention)).join(_this.encode(mention));
+ }
});
_this.render(value);
@@ -70,7 +70,7 @@ angular.module('ui.mention').controller('uiMention', ["$element", "$scope", "$at
// Removes any mentions that aren't used
_this.mentions = _this.mentions.filter(function (mention) {
if (~value.indexOf(_this.encode(mention))) {
- value = value.replace(_this.encode(mention), _this.label(mention));
+ value = value.split(_this.encode(mention)).join(_this.label(mention));
return true;
} else {
return false;
@@ -111,10 +111,10 @@ angular.module('ui.mention').controller('uiMention', ["$element", "$scope", "$at
html = (html || '').toString();
// Convert input to text, to prevent script injection/rich text
html = parseContentAsText(html);
- _this2.mentions.forEach(function (mention) {
- html = html.replace(_this2.encode(mention), _this2.highlight(mention));
+ _this.mentions.forEach(function (mention) {
+ html = html.split(_this.encode(mention)).join(_this.highlight(mention));
});
- _this2.renderElement().html(html);
+ _this.renderElement().html(html);
return html;
};
@@ -138,7 +138,7 @@ angular.module('ui.mention').controller('uiMention', ["$element", "$scope", "$at
* @return {string} HTML highlighted version of the choice
*/
this.highlight = function (choice) {
- return '' + this.label(choice) + '';
+ return '' + _this.label(choice) + '';
};
/**
@@ -151,7 +151,7 @@ angular.module('ui.mention').controller('uiMention', ["$element", "$scope", "$at
this.decode = function () {
var value = arguments.length <= 0 || arguments[0] === undefined ? ngModel.$modelValue : arguments[0];
- return value ? value.replace(this.decodePattern, '$1') : '';
+ return value ? value.replace(_this.decodePattern, '$1') : '';
};
/**
@@ -175,7 +175,7 @@ angular.module('ui.mention').controller('uiMention', ["$element", "$scope", "$at
* @return {string} Syntax-encoded string version of choice
*/
this.encode = function (choice) {
- return this.delimiter + '[' + this.label(choice) + ':' + choice.id + ']';
+ return _this.delimiter + '[' + _this.label(choice) + ':' + choice.id + ']';
};
/**
@@ -189,12 +189,15 @@ angular.module('ui.mention').controller('uiMention', ["$element", "$scope", "$at
* @return {string} Human-readable string
*/
this.replace = function (mention) {
- var search = arguments.length <= 1 || arguments[1] === undefined ? this.searching : arguments[1];
+ var search = arguments.length <= 1 || arguments[1] === undefined ? _this.searching : arguments[1];
var text = arguments.length <= 2 || arguments[2] === undefined ? ngModel.$viewValue : arguments[2];
// TODO: come up with a better way to detect what to remove
// TODO: consider alternative to using regex match
- text = text.substr(0, search.index + search[0].indexOf(this.delimiter)) + this.label(mention) + ' ' + text.substr(search.index + search[0].length);
+ if (search === null) {
+ return text;
+ }
+ text = text.substr(0, search.index + search[0].indexOf(_this.delimiter)) + _this.label(mention) + ' ' + text.substr(search.index + search[0].length);
return text;
};
@@ -206,20 +209,26 @@ angular.module('ui.mention').controller('uiMention', ["$element", "$scope", "$at
* @param {mixed|object} [choice] The selected choice (default: activeChoice)
*/
this.select = function () {
- var choice = arguments.length <= 0 || arguments[0] === undefined ? this.activeChoice : arguments[0];
+ var choice = arguments.length <= 0 || arguments[0] === undefined ? _this.activeChoice : arguments[0];
if (!choice) {
return false;
}
- // Add the mention
- this.mentions.push(choice);
+ var mentionExists = ~_this.mentions.map(function (mention) {
+ return mention.id;
+ }).indexOf(choice.id);
+
+ // Add the mention, unless its already been mentioned
+ if (!mentionExists) {
+ _this.mentions.push(choice);
+ }
// Replace the search with the label
- ngModel.$setViewValue(this.replace(choice));
+ ngModel.$setViewValue(_this.replace(choice));
// Close choices panel
- this.cancel();
+ _this.cancel();
// Update the textarea
ngModel.$render();
@@ -231,11 +240,11 @@ angular.module('ui.mention').controller('uiMention', ["$element", "$scope", "$at
* Moves this.activeChoice up the this.choices collection
*/
this.up = function () {
- var index = this.choices.indexOf(this.activeChoice);
+ var index = _this.choices.indexOf(_this.activeChoice);
if (index > 0) {
- this.activeChoice = this.choices[index - 1];
+ _this.activeChoice = _this.choices[index - 1];
} else {
- this.activeChoice = this.choices[this.choices.length - 1];
+ _this.activeChoice = _this.choices[_this.choices.length - 1];
}
};
@@ -245,11 +254,11 @@ angular.module('ui.mention').controller('uiMention', ["$element", "$scope", "$at
* Moves this.activeChoice down the this.choices collection
*/
this.down = function () {
- var index = this.choices.indexOf(this.activeChoice);
- if (index < this.choices.length - 1) {
- this.activeChoice = this.choices[index + 1];
+ var index = _this.choices.indexOf(_this.activeChoice);
+ if (index < _this.choices.length - 1) {
+ _this.activeChoice = _this.choices[index + 1];
} else {
- this.activeChoice = this.choices[0];
+ _this.activeChoice = _this.choices[0];
}
};
@@ -263,13 +272,11 @@ angular.module('ui.mention').controller('uiMention', ["$element", "$scope", "$at
* @todo Try to avoid using a regex match object
*/
this.search = function (match) {
- var _this3 = this;
-
- this.searching = match;
+ _this.searching = match;
- return $q.when(this.findChoices(match, this.mentions)).then(function (choices) {
- _this3.choices = choices;
- _this3.activeChoice = choices[0];
+ return $q.when(_this.findChoices(match, _this.mentions)).then(function (choices) {
+ _this.choices = choices;
+ _this.activeChoice = choices[0];
return choices;
});
};
@@ -292,30 +299,36 @@ angular.module('ui.mention').controller('uiMention', ["$element", "$scope", "$at
* Clears the choices dropdown info and stops searching
*/
this.cancel = function () {
- this.choices = [];
- this.searching = null;
+ _this.choices = [];
+ _this.searching = null;
};
this.autogrow = function () {
$element[0].style.height = 0; // autoshrink - need accurate scrollHeight
var style = getComputedStyle($element[0]);
- if (style.boxSizing == 'border-box') $element[0].style.height = $element[0].scrollHeight + 'px';
+ if (style.boxSizing == 'border-box') {
+ $element[0].style.height = $element[0].scrollHeight + 'px';
+ }
};
// Interactions to trigger searching
$element.on('keyup click focus', function (event) {
// If event is fired AFTER activeChoice move is performed
- if (_this2.moved) return _this2.moved = false;
+ if (_this.moved) {
+ return _this.moved = false;
+ }
// Don't trigger on selection
- if ($element[0].selectionStart != $element[0].selectionEnd) return;
+ if ($element[0].selectionStart != $element[0].selectionEnd) {
+ return;
+ }
var text = $element.val();
// text to left of cursor ends with `@sometext`
- var match = _this2.searchPattern.exec(text.substr(0, $element[0].selectionStart));
+ var match = _this.searchPattern.exec(text.substr(0, $element[0].selectionStart));
if (match) {
- _this2.search(match);
+ _this.search(match);
} else {
- _this2.cancel();
+ _this.cancel();
}
if (!$scope.$$phase) {
@@ -324,27 +337,29 @@ angular.module('ui.mention').controller('uiMention', ["$element", "$scope", "$at
});
$element.on('keydown', function (event) {
- if (!_this2.searching) return;
+ if (!_this.searching) {
+ return;
+ }
switch (event.keyCode) {
case 13:
// return
- _this2.select();
+ _this.select();
break;
case 38:
// up
- _this2.up();
+ _this.up();
break;
case 40:
// down
- _this2.down();
+ _this.down();
break;
default:
// Exit function
return;
}
- _this2.moved = true;
+ _this.moved = true;
event.preventDefault();
if (!$scope.$$phase) {
@@ -353,22 +368,26 @@ angular.module('ui.mention').controller('uiMention', ["$element", "$scope", "$at
});
this.onMouseup = (function (event) {
- var _this4 = this;
+ var _this2 = this;
- if (event.target == $element[0]) return;
+ if (event.target == $element[0]) {
+ return;
+ }
$document.off('mouseup', this.onMouseup);
- if (!this.searching) return;
+ if (!this.searching) {
+ return;
+ }
// Let ngClick fire first
$scope.$evalAsync(function () {
- _this4.cancel();
+ _this2.cancel();
});
}).bind(this);
$element.on('focus', function (event) {
- $document.on('mouseup', _this2.onMouseup);
+ $document.on('mouseup', _this.onMouseup);
});
// Autogrow is mandatory beacuse the textarea scrolls away from highlights
diff --git a/dist/mention.min.js b/dist/mention.min.js
index 063605a..78c5f21 100644
--- a/dist/mention.min.js
+++ b/dist/mention.min.js
@@ -1 +1 @@
-"use strict";var _slicedToArray=function(){function sliceIterator(arr,i){var _arr=[],_n=!0,_d=!1,_e=void 0;try{for(var _s,_i=arr[Symbol.iterator]();!(_n=(_s=_i.next()).done)&&(_arr.push(_s.value),!i||_arr.length!==i);_n=!0);}catch(err){_d=!0,_e=err}finally{try{!_n&&_i["return"]&&_i["return"]()}finally{if(_d)throw _e}}return _arr}return function(arr,i){if(Array.isArray(arr))return arr;if(Symbol.iterator in Object(arr))return sliceIterator(arr,i);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}();angular.module("ui.mention",[]).directive("uiMention",function(){return{require:["ngModel","uiMention"],controller:"uiMention",controllerAs:"$mention",link:function($scope,$element,$attrs,_ref){var _ref2=_slicedToArray(_ref,2),ngModel=_ref2[0],uiMention=_ref2[1];uiMention.init(ngModel)}}}),angular.module("ui.mention").controller("uiMention",["$element","$scope","$attrs","$q","$timeout","$document",function($element,$scope,$attrs,$q,$timeout,$document){function parseContentAsText(content){try{return temp.textContent=content,temp.innerHTML}finally{temp.textContent=null}}var _this2=this;this.delimiter="@",this.searchPattern=this.pattern||new RegExp("(?:\\s+|^)"+this.delimiter+"(\\w+(?: \\w+)?)$"),this.decodePattern=new RegExp(this.delimiter+"[[\\s\\w]+:[0-9a-z-]+]","gi"),this.$element=$element,this.choices=[],this.mentions=[];var ngModel;this.init=function(model){var _this=this;$attrs.ngTrim="false",ngModel=model,ngModel.$parsers.push(function(value){return _this.mentions=_this.mentions.filter(function(mention){if(~value.indexOf(_this.label(mention)))return value=value.replace(_this.label(mention),_this.encode(mention))}),_this.render(value),value}),ngModel.$formatters.push(function(){var value=arguments.length<=0||void 0===arguments[0]?"":arguments[0];return value=value.toString(),_this.mentions=_this.mentions.filter(function(mention){return!!~value.indexOf(_this.encode(mention))&&(value=value.replace(_this.encode(mention),_this.label(mention)),!0)}),value}),ngModel.$render=function(){$element.val(ngModel.$viewValue||""),$timeout(_this.autogrow,!0),_this.render()}};var temp=document.createElement("span");this.render=function(){var html=arguments.length<=0||void 0===arguments[0]?ngModel.$modelValue:arguments[0];return html=(html||"").toString(),html=parseContentAsText(html),_this2.mentions.forEach(function(mention){html=html.replace(_this2.encode(mention),_this2.highlight(mention))}),_this2.renderElement().html(html),html},this.renderElement=function(){return $element.next()},this.highlight=function(choice){return""+this.label(choice)+""},this.decode=function(){var value=arguments.length<=0||void 0===arguments[0]?ngModel.$modelValue:arguments[0];return value?value.replace(this.decodePattern,"$1"):""},this.label=function(choice){return choice.first+" "+choice.last},this.encode=function(choice){return this.delimiter+"["+this.label(choice)+":"+choice.id+"]"},this.replace=function(mention){var search=arguments.length<=1||void 0===arguments[1]?this.searching:arguments[1],text=arguments.length<=2||void 0===arguments[2]?ngModel.$viewValue:arguments[2];return text=text.substr(0,search.index+search[0].indexOf(this.delimiter))+this.label(mention)+" "+text.substr(search.index+search[0].length)},this.select=function(){var choice=arguments.length<=0||void 0===arguments[0]?this.activeChoice:arguments[0];return!!choice&&(this.mentions.push(choice),ngModel.$setViewValue(this.replace(choice)),this.cancel(),void ngModel.$render())},this.up=function(){var index=this.choices.indexOf(this.activeChoice);index>0?this.activeChoice=this.choices[index-1]:this.activeChoice=this.choices[this.choices.length-1]},this.down=function(){var index=this.choices.indexOf(this.activeChoice);index"+_this.label(choice)+""},this.decode=function(){var value=arguments.length<=0||void 0===arguments[0]?ngModel.$modelValue:arguments[0];return value?value.replace(_this.decodePattern,"$1"):""},this.label=function(choice){return choice.first+" "+choice.last},this.encode=function(choice){return _this.delimiter+"["+_this.label(choice)+":"+choice.id+"]"},this.replace=function(mention){var search=arguments.length<=1||void 0===arguments[1]?_this.searching:arguments[1],text=arguments.length<=2||void 0===arguments[2]?ngModel.$viewValue:arguments[2];return null===search?text:text=text.substr(0,search.index+search[0].indexOf(_this.delimiter))+_this.label(mention)+" "+text.substr(search.index+search[0].length)},this.select=function(){var choice=arguments.length<=0||void 0===arguments[0]?_this.activeChoice:arguments[0];if(!choice)return!1;var mentionExists=~_this.mentions.map(function(mention){return mention.id}).indexOf(choice.id);mentionExists||_this.mentions.push(choice),ngModel.$setViewValue(_this.replace(choice)),_this.cancel(),ngModel.$render()},this.up=function(){var index=_this.choices.indexOf(_this.activeChoice);index>0?_this.activeChoice=_this.choices[index-1]:_this.activeChoice=_this.choices[_this.choices.length-1]},this.down=function(){var index=_this.choices.indexOf(_this.activeChoice);index<_this.choices.length-1?_this.activeChoice=_this.choices[index+1]:_this.activeChoice=_this.choices[0]},this.search=function(match){return _this.searching=match,$q.when(_this.findChoices(match,_this.mentions)).then(function(choices){return _this.choices=choices,_this.activeChoice=choices[0],choices})},this.findChoices=function(match,mentions){return[]},this.cancel=function(){_this.choices=[],_this.searching=null},this.autogrow=function(){$element[0].style.height=0;var style=getComputedStyle($element[0]);"border-box"==style.boxSizing&&($element[0].style.height=$element[0].scrollHeight+"px")},$element.on("keyup click focus",function(event){if(_this.moved)return _this.moved=!1;if($element[0].selectionStart==$element[0].selectionEnd){var text=$element.val(),match=_this.searchPattern.exec(text.substr(0,$element[0].selectionStart));match?_this.search(match):_this.cancel(),$scope.$$phase||$scope.$apply()}}),$element.on("keydown",function(event){if(_this.searching){switch(event.keyCode){case 13:_this.select();break;case 38:_this.up();break;case 40:_this.down();break;default:return}_this.moved=!0,event.preventDefault(),$scope.$$phase||$scope.$apply()}}),this.onMouseup=function(event){var _this2=this;event.target!=$element[0]&&($document.off("mouseup",this.onMouseup),this.searching&&$scope.$evalAsync(function(){_this2.cancel()}))}.bind(this),$element.on("focus",function(event){$document.on("mouseup",_this.onMouseup)}),$element.on("input",this.autogrow),$timeout(this.autogrow,!0)}]);
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index b91d646..243a261 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2781,14 +2781,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -2803,20 +2801,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"core-util-is": {
"version": "1.0.2",
@@ -2933,8 +2928,7 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"ini": {
"version": "1.3.5",
@@ -2946,7 +2940,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -2961,7 +2954,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -3073,8 +3065,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"object-assign": {
"version": "4.1.1",
@@ -3207,7 +3198,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
diff --git a/src/mention.es6.js b/src/mention.es6.js
index 079da72..1ed9ffe 100644
--- a/src/mention.es6.js
+++ b/src/mention.es6.js
@@ -1,10 +1,10 @@
angular.module('ui.mention', [])
-.directive('uiMention', function() {
+.directive('uiMention', function () {
return {
require: ['ngModel', 'uiMention'],
controller: 'uiMention',
controllerAs: '$mention',
- link: function($scope, $element, $attrs, [ngModel, uiMention]) {
+ link: function ($scope, $element, $attrs, [ngModel, uiMention]) {
uiMention.init(ngModel);
}
};
diff --git a/src/mentionController.es6.js b/src/mentionController.es6.js
index c684be0..ae2d138 100644
--- a/src/mentionController.es6.js
+++ b/src/mentionController.es6.js
@@ -1,6 +1,6 @@
angular.module('ui.mention')
.controller('uiMention', function (
- $element, $scope, $attrs, $q, $timeout, $document
+$element, $scope, $attrs, $q, $timeout, $document
) {
// Beginning of input or preceeded by spaces: @sometext
@@ -23,17 +23,18 @@ angular.module('ui.mention')
*
* @param {ngModelController} model
*/
- this.init = function(model) {
+ this.init = (model) => {
// Leading whitespace shows up in the textarea but not the preview
$attrs.ngTrim = 'false';
ngModel = model;
- ngModel.$parsers.push( value => {
+ ngModel.$parsers.push(value => {
// Removes any mentions that aren't used
- this.mentions = this.mentions.filter( mention => {
- if (~value.indexOf(this.label(mention)))
- return value = value.replace(this.label(mention), this.encode(mention));
+ this.mentions = this.mentions.filter((mention) => {
+ if (~value.indexOf(this.label(mention))) {
+ return value = value.split(this.label(mention)).join(this.encode(mention));
+ }
});
this.render(value);
@@ -41,14 +42,14 @@ angular.module('ui.mention')
return value;
});
- ngModel.$formatters.push( (value = '') => {
+ ngModel.$formatters.push((value = '') => {
// In case the value is a different primitive
value = value.toString();
// Removes any mentions that aren't used
- this.mentions = this.mentions.filter( mention => {
+ this.mentions = this.mentions.filter((mention) => {
if (~value.indexOf(this.encode(mention))) {
- value = value.replace(this.encode(mention), this.label(mention));
+ value = value.split(this.encode(mention)).join(this.label(mention));
return true;
} else {
return false;
@@ -87,8 +88,8 @@ angular.module('ui.mention')
html = (html || '').toString();
// Convert input to text, to prevent script injection/rich text
html = parseContentAsText(html);
- this.mentions.forEach( mention => {
- html = html.replace(this.encode(mention), this.highlight(mention));
+ this.mentions.forEach((mention) => {
+ html = html.split(this.encode(mention)).join(this.highlight(mention));
});
this.renderElement().html(html);
return html;
@@ -113,7 +114,7 @@ angular.module('ui.mention')
* @param {mixed|object} choice The choice to be highlighted
* @return {string} HTML highlighted version of the choice
*/
- this.highlight = function(choice) {
+ this.highlight = (choice) => {
return `${this.label(choice)}`;
};
@@ -124,7 +125,7 @@ angular.module('ui.mention')
* @param {string} [text] syntax encoded string (default: ngModel.$modelValue)
* @return {string} plaintext string with encoded labels used
*/
- this.decode = function(value = ngModel.$modelValue) {
+ this.decode = (value = ngModel.$modelValue) => {
return value ? value.replace(this.decodePattern, '$1') : '';
};
@@ -136,7 +137,7 @@ angular.module('ui.mention')
* @param {mixed|object} choice The choice to be rendered
* @return {string} Human-readable string version of choice
*/
- this.label = function(choice) {
+ this.label = (choice) => {
return `${choice.first} ${choice.last}`;
};
@@ -148,7 +149,7 @@ angular.module('ui.mention')
* @param {mixed|object} choice The choice to be encoded
* @return {string} Syntax-encoded string version of choice
*/
- this.encode = function(choice) {
+ this.encode = (choice) => {
return `${this.delimiter}[${this.label(choice)}:${choice.id}]`;
};
@@ -162,9 +163,12 @@ angular.module('ui.mention')
* @param {string} [text] String to perform the replacement on (default: ngModel.$viewValue)
* @return {string} Human-readable string
*/
- this.replace = function(mention, search = this.searching, text = ngModel.$viewValue) {
+ this.replace = (mention, search = this.searching, text = ngModel.$viewValue) => {
// TODO: come up with a better way to detect what to remove
// TODO: consider alternative to using regex match
+ if (search === null) {
+ return text;
+ }
text = text.substr(0, search.index + search[0].indexOf(this.delimiter)) +
this.label(mention) + ' ' +
text.substr(search.index + search[0].length);
@@ -178,13 +182,17 @@ angular.module('ui.mention')
*
* @param {mixed|object} [choice] The selected choice (default: activeChoice)
*/
- this.select = function(choice = this.activeChoice) {
+ this.select = (choice = this.activeChoice) => {
if (!choice) {
return false;
}
- // Add the mention
- this.mentions.push(choice);
+ const mentionExists = ~this.mentions.map(mention => mention.id).indexOf(choice.id);
+
+ // Add the mention, unless its already been mentioned
+ if (!mentionExists) {
+ this.mentions.push(choice);
+ }
// Replace the search with the label
ngModel.$setViewValue(this.replace(choice));
@@ -201,7 +209,7 @@ angular.module('ui.mention')
*
* Moves this.activeChoice up the this.choices collection
*/
- this.up = function() {
+ this.up = () => {
let index = this.choices.indexOf(this.activeChoice);
if (index > 0) {
this.activeChoice = this.choices[index - 1];
@@ -215,7 +223,7 @@ angular.module('ui.mention')
*
* Moves this.activeChoice down the this.choices collection
*/
- this.down = function() {
+ this.down = () => {
let index = this.choices.indexOf(this.activeChoice);
if (index < this.choices.length - 1) {
this.activeChoice = this.choices[index + 1];
@@ -233,11 +241,11 @@ angular.module('ui.mention')
* @param {regex.exec()} match The trigger-text regex match object
* @todo Try to avoid using a regex match object
*/
- this.search = function(match) {
+ this.search = (match) => {
this.searching = match;
- return $q.when( this.findChoices(match, this.mentions) )
- .then( choices => {
+ return $q.when(this.findChoices(match, this.mentions))
+ .then((choices) => {
this.choices = choices;
this.activeChoice = choices[0];
return choices;
@@ -252,7 +260,7 @@ angular.module('ui.mention')
* @todo Make it easier to override this
* @return {array[choice]|Promise} The list of possible choices
*/
- this.findChoices = function(match, mentions) {
+ this.findChoices = (match, mentions) => {
return [];
};
@@ -261,26 +269,29 @@ angular.module('ui.mention')
*
* Clears the choices dropdown info and stops searching
*/
- this.cancel = function() {
+ this.cancel = () => {
this.choices = [];
this.searching = null;
};
- this.autogrow = function() {
+ this.autogrow = () => {
$element[0].style.height = 0; // autoshrink - need accurate scrollHeight
let style = getComputedStyle($element[0]);
- if (style.boxSizing == 'border-box')
- $element[0].style.height = $element[0].scrollHeight + 'px';
+ if (style.boxSizing == 'border-box') {
+ $element[0].style.height = $element[0].scrollHeight + 'px';
+ }
};
// Interactions to trigger searching
- $element.on('keyup click focus', event => {
+ $element.on('keyup click focus', (event) => {
// If event is fired AFTER activeChoice move is performed
- if (this.moved)
+ if (this.moved) {
return this.moved = false;
+ }
// Don't trigger on selection
- if ($element[0].selectionStart != $element[0].selectionEnd)
+ if ($element[0].selectionStart != $element[0].selectionEnd) {
return;
+ }
let text = $element.val();
// text to left of cursor ends with `@sometext`
let match = this.searchPattern.exec(text.substr(0, $element[0].selectionStart));
@@ -296,9 +307,10 @@ angular.module('ui.mention')
}
});
- $element.on('keydown', event => {
- if (!this.searching)
+ $element.on('keydown', (event) => {
+ if (!this.searching) {
return;
+ }
switch (event.keyCode) {
case 13: // return
@@ -323,22 +335,24 @@ angular.module('ui.mention')
}
});
- this.onMouseup = (function(event) {
- if (event.target == $element[0])
+ this.onMouseup = (function (event) {
+ if (event.target == $element[0]) {
return
+ }
$document.off('mouseup', this.onMouseup);
- if (!this.searching)
+ if (!this.searching) {
return;
+ }
// Let ngClick fire first
- $scope.$evalAsync( () => {
+ $scope.$evalAsync(() => {
this.cancel();
});
}).bind(this);
- $element.on('focus', event => {
+ $element.on('focus', (event) => {
$document.on('mouseup', this.onMouseup);
});
diff --git a/test/uiMentionController.spec.js b/test/uiMentionController.spec.js
index 7cee4d4..899def2 100644
--- a/test/uiMentionController.spec.js
+++ b/test/uiMentionController.spec.js
@@ -170,9 +170,30 @@ describe('uiMention', () => {
});
it('updates the HTML content of the adjacent DOM element', () => {
- ngModel.$modelValue = '@[foo bar:1]';
- ngModel.$render();
- expect($element.next().html()).to.eq('foo bar');
+ let testCases = [
+ {
+ $modelValue: '@[foo bar:1]',
+ expected: 'foo bar'
+ },
+ {
+ $modelValue: '@[foo bar:1] @[foo bar:1]',
+ expected: 'foo bar foo bar'
+ },
+ {
+ $modelValue: '@[foo bar:1] @[k v:2] @[foo bar:1]',
+ expected: 'foo bar k v foo bar'
+ },
+ {
+ $modelValue: '@[foo bar:1] @[k v:2] @[foo bar:1] @[k v:2]',
+ expected: 'foo bar k v foo bar k v'
+ }
+ ];
+
+ testCases.forEach(testCase => {
+ ngModel.$modelValue = testCase.$modelValue;
+ ngModel.$render();
+ expect($element.next().html()).to.eq(testCase.expected);
+ });
});
});
});
@@ -202,8 +223,29 @@ describe('uiMention', () => {
});
it('converts a syntax encoded string to HTML', () => {
- ngModel.$modelValue = '@[foo bar:1] @[k v:2]';
- expect(ctrlInstance.render()).to.eq('foo bar k v');
+ let testCases = [
+ {
+ $modelValue: '@[foo bar:1] @[k v:2]',
+ expected: 'foo bar k v'
+ },
+ {
+ $modelValue: '@[foo bar:1] @[foo bar:1]',
+ expected: 'foo bar foo bar'
+ },
+ {
+ $modelValue: '@[foo bar:1] @[k v:2] @[foo bar:1]',
+ expected: 'foo bar k v foo bar'
+ },
+ {
+ $modelValue: '@[foo bar:1] @[k v:2] @[foo bar:1] @[k v:2]',
+ expected: 'foo bar k v foo bar k v'
+ }
+ ];
+
+ testCases.forEach(testCase => {
+ ngModel.$modelValue = testCase.$modelValue;
+ expect(ctrlInstance.render()).to.eq(testCase.expected);
+ });
});
it('does not convert non-mentions', () => {
@@ -216,9 +258,9 @@ describe('uiMention', () => {
});
it('replaces the html of $element.next with the converted value', () => {
- ngModel.$modelValue = '@[foo bar:1] @[k v:2]';
+ ngModel.$modelValue = '@[foo bar:1] @[k v:2] @[foo bar:1]';
ctrlInstance.render();
- expect(ctrlInstance.renderElement().html()).to.eq('foo bar k v')
+ expect(ctrlInstance.renderElement().html()).to.eq('foo bar k v foo bar')
});
});
@@ -267,8 +309,15 @@ describe('uiMention', () => {
it('adds a mention to the current mentions', () => {
expect(ctrlInstance.mentions.length).to.eq(0);
- ctrlInstance.select({ first: 'foo', last: 'bar' });
- expect(ctrlInstance.mentions[0]).to.eql({ first: 'foo', last: 'bar' });
+ ctrlInstance.select({ id: 1, first: 'foo', last: 'bar' });
+ expect(ctrlInstance.mentions[0]).to.eql({ id: 1, first: 'foo', last: 'bar' });
+
+ ctrlInstance.select({ id: 1, first: 'foo', last: 'bar' });
+ expect(ctrlInstance.mentions.length).to.eq(1);
+
+ ctrlInstance.select({ id: 2, first: 'k', last: 'v' });
+ expect(ctrlInstance.mentions.length).to.eq(2);
+ expect(ctrlInstance.mentions[1]).to.eql({ id: 2, first: 'k', last: 'v' });
});
it('clears the controller choices', () => {