Mercurial > kallithea
changeset 2612:9364776d1331 beta
Added autocomplete widget for pull request reviewers, in exchange of 90s style
multi select widget
author | Marcin Kuzminski <marcin@python-works.com> |
---|---|
date | Sun, 15 Jul 2012 18:49:11 +0200 |
parents | e83be26bb8d8 |
children | ad3573d744ef |
files | rhodecode/controllers/pullrequests.py rhodecode/public/css/style.css rhodecode/public/js/rhodecode.js rhodecode/templates/pullrequests/pullrequest.html |
diffstat | 4 files changed, 226 insertions(+), 48 deletions(-) [+] |
line wrap: on
line diff
--- a/rhodecode/controllers/pullrequests.py Sun Jul 15 17:01:31 2012 +0200 +++ b/rhodecode/controllers/pullrequests.py Sun Jul 15 18:49:11 2012 +0200 @@ -36,7 +36,8 @@ from rhodecode.lib.compat import json from rhodecode.lib.base import BaseRepoController, render -from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator +from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\ + NotAnonymous from rhodecode.lib import helpers as h from rhodecode.lib import diffs from rhodecode.lib.utils import action_logger @@ -58,6 +59,9 @@ 'repository.admin') def __before__(self): super(PullrequestsController, self).__before__() + repo_model = RepoModel() + c.users_array = repo_model.get_users_js() + c.users_groups_array = repo_model.get_users_groups_js() def _get_repo_refs(self, repo): hist_l = [] @@ -128,17 +132,10 @@ } c.other_repos_info = json.dumps(other_repos_info) - c.review_members = [] - c.available_members = [] - for u in User.query().filter(User.username != 'default').all(): - uname = u.username - if org_repo.user == u: - uname = _('%s (owner)') % u.username - # auto add owner to pull-request recipients - c.review_members.append([u.user_id, uname]) - c.available_members.append([u.user_id, uname]) + c.review_members = [org_repo.user] return render('/pullrequests/pullrequest.html') + @NotAnonymous() def create(self, repo_name): req_p = request.POST org_repo = req_p['org_repo'] @@ -147,6 +144,7 @@ other_ref = req_p['other_ref'] revisions = req_p.getall('revisions') reviewers = req_p.getall('review_members') + #TODO: wrap this into a FORM !!! title = req_p['pullrequest_title']
--- a/rhodecode/public/css/style.css Sun Jul 15 17:01:31 2012 +0200 +++ b/rhodecode/public/css/style.css Sun Jul 15 18:49:11 2012 +0200 @@ -1429,7 +1429,8 @@ margin: 0 0 0 0px; } -#content div.box div.form div.fields div.field div.input input { +#content div.box div.form div.fields div.field div.input input, +.reviewer_ac input { background: #FFF; border-top: 1px solid #b3b3b3; border-left: 1px solid #b3b3b3; @@ -1549,12 +1550,21 @@ padding: 5px 5px 5px 0; } -#content div.box div.form div.fields div.field input[type=text]:focus,#content div.box div.form div.fields div.field input[type=password]:focus,#content div.box div.form div.fields div.field input[type=file]:focus,#content div.box div.form div.fields div.field textarea:focus,#content div.box div.form div.fields div.field select:focus +#content div.box div.form div.fields div.field input[type=text]:focus, +#content div.box div.form div.fields div.field input[type=password]:focus, +#content div.box div.form div.fields div.field input[type=file]:focus, +#content div.box div.form div.fields div.field textarea:focus, +#content div.box div.form div.fields div.field select:focus, +.reviewer_ac input:focus { background: #f6f6f6; border-color: #666; } +.reviewer_ac { + padding:10px +} + div.form div.fields div.field div.button { margin: 0; padding: 0 0 0 8px; @@ -3783,6 +3793,11 @@ padding:0px 0px 0px 10px; } +.reviewers_member{ + height: 15px; + padding:0px 0px 0px 10px; +} + .emails_wrap{ padding: 0px 20px; }
--- a/rhodecode/public/js/rhodecode.js Sun Jul 15 17:01:31 2012 +0200 +++ b/rhodecode/public/js/rhodecode.js Sun Jul 15 18:49:11 2012 +0200 @@ -63,6 +63,18 @@ return this.replace(new RegExp(''+char+'+$'),''); } + +if(!Array.prototype.indexOf) { + Array.prototype.indexOf = function(needle) { + for(var i = 0; i < this.length; i++) { + if(this[i] === needle) { + return i; + } + } + return -1; + }; +} + /** * SmartColorGenerator * @@ -1204,7 +1216,8 @@ return [unam, chunks]; } return [null, null]; - }; + }; + ownerAC.textboxKeyUpEvent.subscribe(function(type, args){ var ac_obj = args[0]; @@ -1229,6 +1242,167 @@ } +var PullRequestAutoComplete = function (divid, cont, users_list, groups_list) { + var myUsers = users_list; + var myGroups = groups_list; + + // Define a custom search function for the DataSource of users + var matchUsers = function (sQuery) { + // 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; + }; + + // Define a custom search function for the DataSource of usersGroups + var matchGroups = function (sQuery) { + // Case insensitive matching + var query = sQuery.toLowerCase(); + var i = 0; + var l = myGroups.length; + var matches = []; + + // Match against each name of each contact + for (; i < l; i++) { + matched_group = myGroups[i]; + if (matched_group.grname.toLowerCase().indexOf(query) > -1) { + matches[matches.length] = matched_group; + } + } + 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 reviewerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS); + reviewerAC.useShadow = false; + reviewerAC.resultTypeList = false; + reviewerAC.suppressInputUpdate = true; + reviewerAC.animVert = false; + reviewerAC.animHoriz = false; + reviewerAC.animSpeed = 0.1; + + // Helper highlight function for the formatter + var highlightMatch = function (full, snippet, matchindex) { + return full.substring(0, matchindex) + + "<span class='match'>" + + full.substr(matchindex, snippet.length) + + "</span>" + full.substring(matchindex + snippet.length); + }; + + // Custom formatter to highlight the matching letters + reviewerAC.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 = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>' + return tmpl.format(em,res) + } + if (oResultData.nname != undefined) { + var fname = oResultData.fname || ""; + var lname = oResultData.lname || ""; + var nname = oResultData.nname; + + // Guard against null value + var 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 ''; + } + }; + + //members cache to catch duplicates + reviewerAC.dataSource.cache = []; + // hack into select event + if(reviewerAC.itemSelectEvent){ + reviewerAC.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 + var members = YUD.get('review_members'); + //fill the autocomplete with value + + if (oData.nname != undefined) { + if (myAC.dataSource.cache.indexOf(oData.id) != -1){ + return + } + + var tmpl = '<li>'+ + '<div class="reviewers_member">'+ + '<div class="gravatar"><img alt="gravatar" src="{0}"/> </div>'+ + '<div style="float:left">{1}</div>'+ + '<input type="hidden" value="{2}" name="review_members" />'+ + '</div>'+ + '</li>' + + var displayname = "{0} {1} ({2})".format(oData.fname,oData.lname,oData.nname); + var element = tmpl.format(oData.gravatar_lnk,displayname,oData.id); + members.innerHTML += element; + myAC.dataSource.cache.push(oData.id); + } + }); + } + return { + ownerDS: ownerDS, + reviewerAC: reviewerAC, + }; +} + + /** * QUICK REPO MENU */
--- a/rhodecode/templates/pullrequests/pullrequest.html Sun Jul 15 17:01:31 2012 +0200 +++ b/rhodecode/templates/pullrequests/pullrequest.html Sun Jul 15 18:49:11 2012 +0200 @@ -69,40 +69,28 @@ <div style="float:left; border-left:1px dashed #eee"> <h4>${_('Pull request reviewers')}</h4> <div id="reviewers" style="padding:0px 0px 0px 15px"> - ##TODO: make this nicer :) - <table class="table noborder"> - <tr> - <td> - <div> - <div style="float:left"> - <div class="text" style="padding: 0px 0px 6px;">${_('Chosen reviewers')}</div> - ${h.select('review_members',[x[0] for x in c.review_members],c.review_members,multiple=True,size=8,style="min-width:210px")} - <div id="remove_all_elements" style="cursor:pointer;text-align:center"> - ${_('Remove all elements')} - <img alt="remove" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_right.png')}"/> - </div> - </div> - <div style="float:left;width:20px;padding-top:50px"> - <img alt="add" id="add_element" - style="padding:2px;cursor:pointer" - src="${h.url('/images/icons/arrow_left.png')}"/> - <br /> - <img alt="remove" id="remove_element" - style="padding:2px;cursor:pointer" - src="${h.url('/images/icons/arrow_right.png')}"/> - </div> - <div style="float:left"> - <div class="text" style="padding: 0px 0px 6px;">${_('Available reviewers')}</div> - ${h.select('available_members',[],c.available_members,multiple=True,size=8,style="min-width:210px")} - <div id="add_all_elements" style="cursor:pointer;text-align:center"> - <img alt="add" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_left.png')}"/> - ${_('Add all elements')} - </div> - </div> - </div> - </td> - </tr> - </table> + ## members goes here ! + <div class="group_members_wrap"> + <ul id="review_members" class="group_members"> + %for member in c.review_members: + <li> + <div class="reviewers_member"> + <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div> + <div style="float:left">${member.full_name} (${_('owner')})</div> + <input type="hidden" value="${member.user_id}" name="review_members" /> + </div> + </li> + %endfor + </ul> + </div> + + <div class='ac'> + <div class="reviewer_ac"> + ${h.text('user', class_='yui-ac-input')} + <span class="help-block">${_('Add reviewer to this pull request.')}</span> + <div id="reviewers_container"></div> + </div> + </div> </div> </div> <h3>${_('Create new pull request')}</h3> @@ -141,7 +129,10 @@ </div> <script type="text/javascript"> - MultiSelectWidget('review_members','available_members','pull_request_form'); + var _USERS_AC_DATA = ${c.users_array|n}; + var _GROUPS_AC_DATA = ${c.users_groups_array|n}; + PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA); + var other_repos_info = ${c.other_repos_info|n}; var loadPreview = function(){ YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','none');