var app = angular.module('plunker', ['ui.mention']);
app.run(function($rootScope) {
$rootScope.post = {
message: "hi there @[bob barker:11123] @[kenny logins:123ab-123]"
};
})
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
//$scope.$mentions.mentions = [{"first":"bob","last":"barker","id":11123}];
});
app.directive('mentionExample', function() {
return {
require: 'uiMention',
link: function link($scope, $element, $attrs, uiMention) {
/**
* $mention.findChoices()
*
* @param {regex.exec()} match The trigger-text regex match object
* @todo Try to avoid using a regex match object
* @return {array[choice]|Promise} The list of possible choices
*/
uiMention.findChoices = function(match, mentions) {
return choices
// Remove items that are already mentioned
.filter(function(choice) {
return !mentions.some(function(mention) {
return mention.id === choice.id;
});
})
// Matches items from search query
.filter(function(choice) {
return ~(choice.first + ' ' + choice.last).indexOf(match[1]);
});
};
uiMention.mentions.push(choices[0], choices[1]);
}
};
});
var choices = [{
first: 'bob',
last: 'barker',
id: 11123
}, {
first: 'kenny',
last: 'logins',
id: '123ab-123'
}, {
first: 'kyle',
last: 'corn',
id: '123'
}, {
first: 'steve',
last: 'rodriguez',
id: 'hi'
}, {
first: 'steve',
last: 'holt',
id: '0-9'
}, {
first: 'megan',
last: 'burgerpants',
id: 'ab-'
}];
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS - ui-mention </title>
<script>
document.write('<base href="' + document.location + '" />');
</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js@1.5.x" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.min.js" data-semver="1.5.11"></script>
<script src="mention.min.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<h1>UI Mention</h1>
<h4><a href="https://github.com/angular-ui/ui-mention">Source Code</a></h4>
<p>
Facebook-like <strong>@mentions</strong> in your text inputs. Designed with composability in mind.
</p>
<div class="mention-container">
<textarea ui-mention mention-example ng-model="post.message" placeholder="Use @mention to trigger" ng-trim="false"></textarea>
<div class="mention-highlight"></div>
<ul ng-if="$mention.choices.length" class="dropdown">
<li ng-repeat="choice in $mention.choices" ng-class="{active:$mention.activeChoice==choice}">
<a ng-click="$mention.select(choice)">
{{::choice.first}} {{::choice.last}}
</a>
</li>
</ul>
</div>
<input ng-model="newValue" placeholder="New Value">
<button ng-click="post.message=newValue">Set Value</button>
<br>
<p>
$mentions.mentions: {{$mention.mentions | json}}
</p>
<p>
ng-model (post.message): {{post.message | json}}
</p>
</body>
</html>
body {
font-size: 14px;
font-family: 'Helvetica'; }
textarea[ui-mention], textarea[ui-mention] + * {
line-height: 1em;
font-size: 1rem;
padding: 5px;
border: 1px;
font-family: helvetica;
font-weight: normal; }
textarea[ui-mention] {
min-height: 100px;
width: 100%;
display: block;
border: 1px solid;
z-index: 2;
position: relative; }
textarea[ui-mention][ui-mention] {
background: transparent; }
a {
color: blue;
text-decoration: underline;
cursor: pointer; }
a:hover {
color: green; }
.mention-container {
position: relative; }
.mention-highlight {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 1px;
padding: 2px;
z-index: 1;
color: transparent;
white-space: pre-wrap;
border: 1px solid transparent;
width: 100%; }
.mention-highlight span {
border-radius: 3px;
background: lightblue;
box-shadow: 0px 0px 0px 1px blue; }
.dropdown {
position: absolute;
top: 100%;
min-width: 150px;
right: 0;
background: lightblue;
list-style: none;
padding: 0;
margin: 0; }
.dropdown a {
display: block;
padding: 10px;
text-decoration: none; }
.dropdown .active {
background: lightgreen; }
"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"<span>"+this.label(choice)+"</span>"},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.choices.length-1?this.activeChoice=this.choices[index+1]:this.activeChoice=this.choices[0]},this.search=function(match){var _this3=this;return this.searching=match,$q.when(this.findChoices(match,this.mentions)).then(function(choices){return _this3.choices=choices,_this3.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(_this2.moved)return _this2.moved=!1;if($element[0].selectionStart==$element[0].selectionEnd){var text=$element.val(),match=_this2.searchPattern.exec(text.substr(0,$element[0].selectionStart));match?_this2.search(match):_this2.cancel(),$scope.$$phase||$scope.$apply()}}),$element.on("keydown",function(event){if(_this2.searching){switch(event.keyCode){case 13:_this2.select();break;case 38:_this2.up();break;case 40:_this2.down();break;default:return}_this2.moved=!0,event.preventDefault(),$scope.$$phase||$scope.$apply()}}),this.onMouseup=function(event){var _this4=this;event.target!=$element[0]&&($document.off("mouseup",this.onMouseup),this.searching&&$scope.$evalAsync(function(){_this4.cancel()}))}.bind(this),$element.on("focus",function(event){$document.on("mouseup",_this2.onMouseup)}),$element.on("input",this.autogrow),$timeout(this.autogrow,!0)}]);