Changeset - 7b73f54d84dc
[Not reviewed]
default
0 1 0
Mads Kiilerich - 8 years ago 2017-10-31 03:16:58
mads@kiilerich.com
js: refactor and simplify @mentions handling - no functional changes

The autocompleteCreate and get_mention functions are inlined.

The mentionQuery and tuple in datasource are replaced with before/search/after
data attributes on the $container element.

Variable use is simplified - less variables, better naming, and better content.

The @ is now in the before string, not in the search string.
1 file changed with 31 insertions and 73 deletions:
0 comments (0 inline, 0 general)
kallithea/public/js/base.js
Show inline comments
 
@@ -1073,245 +1073,203 @@ var gravatar = function(gravatar_lnk, si
 
    return ('<i class="icon-gravatar {2}"' +
 
            ' style="font-size: {0}px;background-image: url(\'{1}\'); background-size: {0}px"' +
 
            '></i>').format(size, gravatar_lnk, cssclass);
 
}
 

	
 
var autocompleteGravatar = function(res, gravatar_lnk, size, group) {
 
    var elem;
 
    if (group !== undefined) {
 
        elem = '<i class="perm-gravatar-ac icon-users"></i>';
 
    } else {
 
        elem = gravatar(gravatar_lnk, size, "perm-gravatar-ac");
 
    }
 
    return '<div class="ac-container-wrap">{0}{1}</div>'.format(elem, res);
 
}
 

	
 
// Custom formatter to highlight the matching letters
 
var autocompleteFormatter = function (oResultData, sQuery, sResultMatch) {
 
    var query;
 
    if (sQuery && sQuery.toLowerCase) // YAHOO AutoComplete
 
        query = sQuery.toLowerCase();
 
    else if (sResultMatch && sResultMatch.term) // select2 - parameter names doesn't match
 
        query = sResultMatch.term.toLowerCase();
 

	
 
    // group
 
    if (oResultData.grname) {
 
        return autocompleteGravatar(
 
            "{0}: {1}".format(
 
                _TM['Group'],
 
                autocompleteHighlightMatch(oResultData.grname, query)),
 
            null, null, true);
 
    }
 

	
 
    // users
 
    if (oResultData.nname) {
 
        var displayname = autocompleteHighlightMatch(oResultData.nname, query);
 
        if (oResultData.fname && oResultData.lname) {
 
            displayname = "{0} {1} ({2})".format(
 
                autocompleteHighlightMatch(oResultData.fname, query),
 
                autocompleteHighlightMatch(oResultData.lname, query),
 
                displayname);
 
        }
 

	
 
        return autocompleteGravatar(displayname, oResultData.gravatar_lnk, oResultData.gravatar_size);
 
    }
 

	
 
    return '';
 
};
 

	
 
// Generate a basic autocomplete instance that can be tweaked further by the caller
 
var autocompleteCreate = function ($inputElement, matchFunc) {
 
    var $container = $('<div/>').insertAfter($inputElement);
 
    var datasource = new YAHOO.util.FunctionDataSource(matchFunc);
 

	
 
    var autocomplete = new YAHOO.widget.AutoComplete($inputElement[0], $container[0], datasource);
 
    autocomplete.useShadow = false;
 
    autocomplete.resultTypeList = false;
 
    autocomplete.animVert = false;
 
    autocomplete.animHoriz = false;
 
    autocomplete.animSpeed = 0.1;
 
    autocomplete.formatResult = autocompleteFormatter;
 

	
 
    return autocomplete;
 
}
 

	
 
var SimpleUserAutoComplete = function ($inputElement, users_list) {
 
    $inputElement.select2(
 
    {
 
        formatInputTooShort: $inputElement.attr('placeholder'),
 
        initSelection : function (element, callback) {
 
            var val = $inputElement.val();
 
            $.each(users_list, function(i, user) {
 
                if (user.nname == val)
 
                    callback(user);
 
            });
 
        },
 
        minimumInputLength: 1,
 
        query: function (query) {
 
            query.callback({results: autocompleteMatchUsers(query.term, users_list)});
 
        },
 
        formatSelection: autocompleteFormatter,
 
        formatResult: autocompleteFormatter,
 
        escapeMarkup: function(m) { return m; },
 
        id: function(item) { return item.nname; },
 
    });
 
}
 

	
 
var MembersAutoComplete = function ($inputElement, $typeElement, users_list, groups_list) {
 

	
 
    var matchAll = function (sQuery) {
 
        var u = autocompleteMatchUsers(sQuery, users_list);
 
        var g = autocompleteMatchGroups(sQuery, groups_list);
 
        return u.concat(g);
 
    };
 

	
 
    $inputElement.select2(
 
    {
 
        placeholder: $inputElement.attr('placeholder'),
 
        minimumInputLength: 1,
 
        query: function (query) {
 
            query.callback({results: matchAll(query.term)});
 
        },
 
        formatSelection: autocompleteFormatter,
 
        formatResult: autocompleteFormatter,
 
        escapeMarkup: function(m) { return m; },
 
    }).on("select2-selecting", function(e) {
 
        // e.choice.id is automatically used as selection value - just set the type of the selection
 
        if (e.choice.nname != undefined) {
 
            $typeElement.val('user');
 
        } else {
 
            $typeElement.val('users_group');
 
        }
 
    });
 
}
 

	
 
var MentionsAutoComplete = function ($inputElement, users_list) {
 
    var $container = $('<div/>').insertAfter($inputElement);
 

	
 
    var matchUsers = function (sQuery) {
 
            var org_sQuery = sQuery;
 
            if(this.mentionQuery == null){
 
            // use the search string from $inputElement instead of sQuery
 
            if(!$container.data('search')){
 
                // return empty list so the input list isn't shown
 
                return []
 
            }
 
            sQuery = this.mentionQuery;
 
            return autocompleteMatchUsers(sQuery, users_list);
 
            return autocompleteMatchUsers($container.data('search'), users_list);
 
    }
 

	
 
    var mentionsAC = autocompleteCreate($inputElement, matchUsers);
 
    var datasource = new YAHOO.util.FunctionDataSource(matchUsers);
 
    var mentionsAC = new YAHOO.widget.AutoComplete($inputElement[0], $container[0], datasource);
 
    mentionsAC.useShadow = false;
 
    mentionsAC.resultTypeList = false;
 
    mentionsAC.animVert = false;
 
    mentionsAC.animHoriz = false;
 
    mentionsAC.animSpeed = 0.1;
 
    mentionsAC.suppressInputUpdate = true;
 
    // Overwrite formatResult to take into account mentionQuery
 
    mentionsAC.formatResult = function (oResultData, sQuery, sResultMatch) {
 
        var org_sQuery = sQuery;
 
        if (this.dataSource.mentionQuery != null) {
 
            sQuery = this.dataSource.mentionQuery;
 
        }
 
        return autocompleteFormatter(oResultData, sQuery, sResultMatch);
 
        // use the search string from $inputElement instead of sQuery
 
        return autocompleteFormatter(oResultData, $container.data('search'), sResultMatch);
 
    }
 

	
 
    // Handler for selection of an entry
 
    if(mentionsAC.itemSelectEvent){
 
        mentionsAC.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
 
            //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('');
 
            myAC.getInputEl().value = $container.data('before') + oData.nname + ' ' + $container.data('after');
 
            myAC.getInputEl().focus(); // Y U NO WORK !?
 
        });
 
    }
 

	
 
    // 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
 
    mentionsAC.dataSource.chunks = [];
 
    mentionsAC.dataSource.mentionQuery = null;
 

	
 
    mentionsAC.get_mention = function(msg, max_pos) {
 
        var org = msg;
 
        // Must match utils2.py MENTIONS_REGEX.
 
        // Only matching on string up to cursor, so it must end with $
 
        var re = new RegExp('(?:^|[^a-zA-Z0-9])@([a-zA-Z0-9][-_.a-zA-Z0-9]*[a-zA-Z0-9])$');
 
        var chunks  = [];
 

	
 
        // cut first chunk until current 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.lstrip(' ').lstrip('\n');
 

	
 
        if(re.test(msg2)){
 
            var unam = re.exec(msg2)[1];
 
            return [unam, chunks];
 
        }
 
        return [null, null];
 
    };
 
    // Must match utils2.py MENTIONS_REGEX.
 
    // Operates on a string from char before @ up to cursor.
 
    // Check that the char before @ doesn't look like an email address, and match to end of string.
 
    var mentionRe = new RegExp('(?:^|[^a-zA-Z0-9])@([a-zA-Z0-9][-_.a-zA-Z0-9]*[a-zA-Z0-9])$');
 

	
 
    $inputElement.keyup(function(e){
 
            var currentMessage = $inputElement.val();
 
            var currentCaretPosition = $inputElement[0].selectionStart;
 

	
 
            var unam = mentionsAC.get_mention(currentMessage, currentCaretPosition);
 
            var curr_search = null;
 
            if(unam[0]){
 
                curr_search = unam[0];
 
            $container.data('search', '');
 
            var messageBeforeCaret = currentMessage.substr(0, currentCaretPosition);
 
            var lastAtPos = messageBeforeCaret.lastIndexOf('@');
 
            if(lastAtPos >= 0){
 
                // Search from one char before last @ ... if possible
 
                var m = mentionRe.exec(messageBeforeCaret.substr(Math.max(0, lastAtPos - 1)));
 
                if(m){
 
                    $container.data('before', currentMessage.substr(0, lastAtPos + 1));
 
                    $container.data('search', currentMessage.substr(lastAtPos + 1, currentCaretPosition - lastAtPos - 1));
 
                    $container.data('after', currentMessage.substr(currentCaretPosition));
 
                }
 
            }
 

	
 
            mentionsAC.dataSource.chunks = unam[1];
 
            mentionsAC.dataSource.mentionQuery = curr_search;
 
        });
 
}
 

	
 
var addReviewMember = function(id,fname,lname,nname,gravatar_link,gravatar_size){
 
    var displayname = nname;
 
    if ((fname != "") && (lname != "")) {
 
        displayname = "{0} {1} ({2})".format(fname, lname, nname);
 
    }
 
    var gravatarelm = gravatar(gravatar_link, gravatar_size, "");
 
    // WARNING: the HTML below is duplicate with
 
    // kallithea/templates/pullrequests/pullrequest_show.html
 
    // If you change something here it should be reflected in the template too.
 
    var element = (
 
        '     <li id="reviewer_{2}">\n'+
 
        '       <span class="reviewers_member">\n'+
 
        '         <span class="reviewer_status" data-toggle="tooltip" title="not_reviewed">\n'+
 
        '             <i class="icon-circle changeset-status-not_reviewed"></i>\n'+
 
        '         </span>\n'+
 
        (gravatarelm ?
 
        '         {0}\n' :
 
        '')+
 
        '         <span>{1}</span>\n'+
 
        '         <input type="hidden" value="{2}" name="review_members" />\n'+
 
        '         <a href="#" class="reviewer_member_remove" onclick="removeReviewMember({2})">\n'+
 
        '             <i class="icon-minus-circled"></i>\n'+
 
        '         </a> (add not saved)\n'+
 
        '       </span>\n'+
 
        '     </li>\n'
 
        ).format(gravatarelm, displayname, id);
 
    // check if we don't have this ID already in
 
    var ids = [];
 
    $('#review_members').find('li').each(function() {
 
            ids.push(this.id);
 
        });
 
    if(ids.indexOf('reviewer_'+id) == -1){
 
        //only add if it's not there
 
        $('#review_members').append(element);
 
    }
 
}
 

	
 
var removeReviewMember = function(reviewer_id, repo_name, pull_request_id){
 
    var $li = $('#reviewer_{0}'.format(reviewer_id));
 
    $li.find('div div').css("text-decoration", "line-through");
 
    $li.find('input').prop('name', 'review_members_removed');
 
    $li.find('.reviewer_member_remove').replaceWith('&nbsp;(remove not saved)');
 
}
 

	
 
/* activate auto completion of users as PR reviewers */
0 comments (0 inline, 0 general)