diff --git a/rhodecode/public/js/rhodecode.js b/rhodecode/public/js/rhodecode.js
--- a/rhodecode/public/js/rhodecode.js
+++ b/rhodecode/public/js/rhodecode.js
@@ -988,6 +988,189 @@ var MembersAutoComplete = function (user
}
+var MentionsAutoComplete = function (divid, cont, users_list, groups_list, group_lbl, members_lbl) {
+ var myUsers = users_list;
+ var myGroups = groups_list;
+
+ // Define a custom search function for the DataSource of users
+ var matchUsers = function (sQuery) {
+ var org_sQuery = sQuery;
+ if(this.mentionQuery == null){
+ return []
+ }
+ sQuery = this.mentionQuery;
+ // Case insensitive matching
+ var query = sQuery.toLowerCase();
+ var i = 0;
+ var l = myUsers.length;
+ var matches = [];
+
+ // Match against each name of each contact
+ for (; i < l; i++) {
+ contact = myUsers[i];
+ if ((contact.fname.toLowerCase().indexOf(query) > -1) || (contact.lname.toLowerCase().indexOf(query) > -1) || (contact.nname && (contact.nname.toLowerCase().indexOf(query) > -1))) {
+ matches[matches.length] = contact;
+ }
+ }
+ return matches
+ };
+
+ //match all
+ var matchAll = function (sQuery) {
+ u = matchUsers(sQuery);
+ return u
+ };
+
+ // DataScheme for owner
+ var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
+ ownerDS.responseSchema = {
+ fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
+ };
+
+ // Instantiate AutoComplete for mentions
+ var ownerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS);
+ ownerAC.useShadow = false;
+ ownerAC.resultTypeList = false;
+ ownerAC.suppressInputUpdate = true;
+
+ // Helper highlight function for the formatter
+ var highlightMatch = function (full, snippet, matchindex) {
+ return full.substring(0, matchindex)
+ + ""
+ + full.substr(matchindex, snippet.length)
+ + "" + full.substring(matchindex + snippet.length);
+ };
+
+ // Custom formatter to highlight the matching letters
+ ownerAC.formatResult = function (oResultData, sQuery, sResultMatch) {
+ var org_sQuery = sQuery;
+ if(this.dataSource.mentionQuery != null){
+ sQuery = this.dataSource.mentionQuery;
+ }
+
+ var query = sQuery.toLowerCase();
+ var _gravatar = function(res, em, group){
+ if (group !== undefined){
+ em = '/images/icons/group.png'
+ }
+ tmpl = '

{1}
'
+ return tmpl.format(em,res)
+ }
+ if (oResultData.fname != undefined) {
+ var fname = oResultData.fname,
+ lname = oResultData.lname,
+ nname = oResultData.nname || "",
+ // Guard against null value
+ fnameMatchIndex = fname.toLowerCase().indexOf(query),
+ lnameMatchIndex = lname.toLowerCase().indexOf(query),
+ nnameMatchIndex = nname.toLowerCase().indexOf(query),
+ displayfname, displaylname, displaynname;
+
+ if (fnameMatchIndex > -1) {
+ displayfname = highlightMatch(fname, query, fnameMatchIndex);
+ } else {
+ displayfname = fname;
+ }
+
+ if (lnameMatchIndex > -1) {
+ displaylname = highlightMatch(lname, query, lnameMatchIndex);
+ } else {
+ displaylname = lname;
+ }
+
+ if (nnameMatchIndex > -1) {
+ displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
+ } else {
+ displaynname = nname ? "(" + nname + ")" : "";
+ }
+
+ return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
+ } else {
+ return '';
+ }
+ };
+
+ if(ownerAC.itemSelectEvent){
+ ownerAC.itemSelectEvent.subscribe(function (sType, aArgs) {
+
+ var myAC = aArgs[0]; // reference back to the AC instance
+ var elLI = aArgs[1]; // reference to the selected LI element
+ var oData = aArgs[2]; // object literal of selected item's result data
+ //fill the autocomplete with value
+ if (oData.nname != undefined) {
+ //users
+ //Replace the mention name with replaced
+ var re = new RegExp();
+ var org = myAC.getInputEl().value;
+ var chunks = myAC.dataSource.chunks
+ // replace middle chunk(the search term) with actuall match
+ chunks[1] = chunks[1].replace('@'+myAC.dataSource.mentionQuery,
+ '@'+oData.nname+' ');
+ myAC.getInputEl().value = chunks.join('')
+ YUD.get(myAC.getInputEl()).focus(); // Y U NO WORK !?
+ } else {
+ //groups
+ myAC.getInputEl().value = oData.grname;
+ YUD.get('perm_new_member_type').value = 'users_group';
+ }
+ });
+ }
+
+ // in this keybuffer we will gather current value of search !
+ // since we need to get this just when someone does `@` then we do the
+ // search
+ ownerAC.dataSource.chunks = [];
+ ownerAC.dataSource.mentionQuery = null;
+
+ ownerAC.get_mention = function(msg, max_pos) {
+ var org = msg;
+ var re = new RegExp('(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)$')
+ var chunks = [];
+
+
+ // cut first chunk until curret pos
+ var to_max = msg.substr(0, max_pos);
+ var at_pos = Math.max(0,to_max.lastIndexOf('@')-1);
+ var msg2 = to_max.substr(at_pos);
+
+ chunks.push(org.substr(0,at_pos))// prefix chunk
+ chunks.push(msg2) // search chunk
+ chunks.push(org.substr(max_pos)) // postfix chunk
+
+ // clean up msg2 for filtering and regex match
+ var msg2 = msg2.replace(' ','').replace('\n','');
+
+ if(re.test(msg2)){
+ var unam = re.exec(msg2)[1];
+ return [unam, chunks];
+ }
+ return [null, null];
+ };
+ ownerAC.textboxKeyUpEvent.subscribe(function(type, args){
+
+ var ac_obj = args[0];
+ var currentMessage = args[1];
+ var currentCaretPosition = args[0]._elTextbox.selectionStart;
+
+ var unam = ownerAC.get_mention(currentMessage, currentCaretPosition);
+ var curr_search = null;
+ if(unam[0]){
+ curr_search = unam[0];
+ }
+
+ ownerAC.dataSource.chunks = unam[1];
+ ownerAC.dataSource.mentionQuery = curr_search;
+
+ })
+
+ return {
+ ownerDS: ownerDS,
+ ownerAC: ownerAC,
+ };
+}
+
+
+
/**
* QUICK REPO MENU