##// END OF EJS Templates
Added autocomplete widget for pull request reviewers, in exchange of 90s style...
marcink -
r2612:9364776d beta
parent child Browse files
Show More
@@ -36,7 +36,8 b' from pylons.decorators import jsonify'
36
36
37 from rhodecode.lib.compat import json
37 from rhodecode.lib.compat import json
38 from rhodecode.lib.base import BaseRepoController, render
38 from rhodecode.lib.base import BaseRepoController, render
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
40 NotAnonymous
40 from rhodecode.lib import helpers as h
41 from rhodecode.lib import helpers as h
41 from rhodecode.lib import diffs
42 from rhodecode.lib import diffs
42 from rhodecode.lib.utils import action_logger
43 from rhodecode.lib.utils import action_logger
@@ -58,6 +59,9 b' class PullrequestsController(BaseRepoCon'
58 'repository.admin')
59 'repository.admin')
59 def __before__(self):
60 def __before__(self):
60 super(PullrequestsController, self).__before__()
61 super(PullrequestsController, self).__before__()
62 repo_model = RepoModel()
63 c.users_array = repo_model.get_users_js()
64 c.users_groups_array = repo_model.get_users_groups_js()
61
65
62 def _get_repo_refs(self, repo):
66 def _get_repo_refs(self, repo):
63 hist_l = []
67 hist_l = []
@@ -128,17 +132,10 b' class PullrequestsController(BaseRepoCon'
128 }
132 }
129
133
130 c.other_repos_info = json.dumps(other_repos_info)
134 c.other_repos_info = json.dumps(other_repos_info)
131 c.review_members = []
135 c.review_members = [org_repo.user]
132 c.available_members = []
133 for u in User.query().filter(User.username != 'default').all():
134 uname = u.username
135 if org_repo.user == u:
136 uname = _('%s (owner)') % u.username
137 # auto add owner to pull-request recipients
138 c.review_members.append([u.user_id, uname])
139 c.available_members.append([u.user_id, uname])
140 return render('/pullrequests/pullrequest.html')
136 return render('/pullrequests/pullrequest.html')
141
137
138 @NotAnonymous()
142 def create(self, repo_name):
139 def create(self, repo_name):
143 req_p = request.POST
140 req_p = request.POST
144 org_repo = req_p['org_repo']
141 org_repo = req_p['org_repo']
@@ -147,6 +144,7 b' class PullrequestsController(BaseRepoCon'
147 other_ref = req_p['other_ref']
144 other_ref = req_p['other_ref']
148 revisions = req_p.getall('revisions')
145 revisions = req_p.getall('revisions')
149 reviewers = req_p.getall('review_members')
146 reviewers = req_p.getall('review_members')
147
150 #TODO: wrap this into a FORM !!!
148 #TODO: wrap this into a FORM !!!
151
149
152 title = req_p['pullrequest_title']
150 title = req_p['pullrequest_title']
@@ -1429,7 +1429,8 b' tbody .yui-dt-editable { cursor: pointer'
1429 margin: 0 0 0 0px;
1429 margin: 0 0 0 0px;
1430 }
1430 }
1431
1431
1432 #content div.box div.form div.fields div.field div.input input {
1432 #content div.box div.form div.fields div.field div.input input,
1433 .reviewer_ac input {
1433 background: #FFF;
1434 background: #FFF;
1434 border-top: 1px solid #b3b3b3;
1435 border-top: 1px solid #b3b3b3;
1435 border-left: 1px solid #b3b3b3;
1436 border-left: 1px solid #b3b3b3;
@@ -1549,12 +1550,21 b' input.disabled {'
1549 padding: 5px 5px 5px 0;
1550 padding: 5px 5px 5px 0;
1550 }
1551 }
1551
1552
1552 #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
1553 #content div.box div.form div.fields div.field input[type=text]:focus,
1554 #content div.box div.form div.fields div.field input[type=password]:focus,
1555 #content div.box div.form div.fields div.field input[type=file]:focus,
1556 #content div.box div.form div.fields div.field textarea:focus,
1557 #content div.box div.form div.fields div.field select:focus,
1558 .reviewer_ac input:focus
1553 {
1559 {
1554 background: #f6f6f6;
1560 background: #f6f6f6;
1555 border-color: #666;
1561 border-color: #666;
1556 }
1562 }
1557
1563
1564 .reviewer_ac {
1565 padding:10px
1566 }
1567
1558 div.form div.fields div.field div.button {
1568 div.form div.fields div.field div.button {
1559 margin: 0;
1569 margin: 0;
1560 padding: 0 0 0 8px;
1570 padding: 0 0 0 8px;
@@ -3783,6 +3793,11 b' div#legend_container table td,div#legend'
3783 padding:0px 0px 0px 10px;
3793 padding:0px 0px 0px 10px;
3784 }
3794 }
3785
3795
3796 .reviewers_member{
3797 height: 15px;
3798 padding:0px 0px 0px 10px;
3799 }
3800
3786 .emails_wrap{
3801 .emails_wrap{
3787 padding: 0px 20px;
3802 padding: 0px 20px;
3788 }
3803 }
@@ -63,6 +63,18 b' String.prototype.rstrip = function(char)'
63 return this.replace(new RegExp(''+char+'+$'),'');
63 return this.replace(new RegExp(''+char+'+$'),'');
64 }
64 }
65
65
66
67 if(!Array.prototype.indexOf) {
68 Array.prototype.indexOf = function(needle) {
69 for(var i = 0; i < this.length; i++) {
70 if(this[i] === needle) {
71 return i;
72 }
73 }
74 return -1;
75 };
76 }
77
66 /**
78 /**
67 * SmartColorGenerator
79 * SmartColorGenerator
68 *
80 *
@@ -1204,7 +1216,8 b' var MentionsAutoComplete = function (div'
1204 return [unam, chunks];
1216 return [unam, chunks];
1205 }
1217 }
1206 return [null, null];
1218 return [null, null];
1207 };
1219 };
1220
1208 ownerAC.textboxKeyUpEvent.subscribe(function(type, args){
1221 ownerAC.textboxKeyUpEvent.subscribe(function(type, args){
1209
1222
1210 var ac_obj = args[0];
1223 var ac_obj = args[0];
@@ -1229,6 +1242,167 b' var MentionsAutoComplete = function (div'
1229 }
1242 }
1230
1243
1231
1244
1245 var PullRequestAutoComplete = function (divid, cont, users_list, groups_list) {
1246 var myUsers = users_list;
1247 var myGroups = groups_list;
1248
1249 // Define a custom search function for the DataSource of users
1250 var matchUsers = function (sQuery) {
1251 // Case insensitive matching
1252 var query = sQuery.toLowerCase();
1253 var i = 0;
1254 var l = myUsers.length;
1255 var matches = [];
1256
1257 // Match against each name of each contact
1258 for (; i < l; i++) {
1259 contact = myUsers[i];
1260 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
1261 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
1262 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
1263 matches[matches.length] = contact;
1264 }
1265 }
1266 return matches;
1267 };
1268
1269 // Define a custom search function for the DataSource of usersGroups
1270 var matchGroups = function (sQuery) {
1271 // Case insensitive matching
1272 var query = sQuery.toLowerCase();
1273 var i = 0;
1274 var l = myGroups.length;
1275 var matches = [];
1276
1277 // Match against each name of each contact
1278 for (; i < l; i++) {
1279 matched_group = myGroups[i];
1280 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
1281 matches[matches.length] = matched_group;
1282 }
1283 }
1284 return matches;
1285 };
1286
1287 //match all
1288 var matchAll = function (sQuery) {
1289 u = matchUsers(sQuery);
1290 return u
1291 };
1292
1293 // DataScheme for owner
1294 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
1295
1296 ownerDS.responseSchema = {
1297 fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
1298 };
1299
1300 // Instantiate AutoComplete for mentions
1301 var reviewerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS);
1302 reviewerAC.useShadow = false;
1303 reviewerAC.resultTypeList = false;
1304 reviewerAC.suppressInputUpdate = true;
1305 reviewerAC.animVert = false;
1306 reviewerAC.animHoriz = false;
1307 reviewerAC.animSpeed = 0.1;
1308
1309 // Helper highlight function for the formatter
1310 var highlightMatch = function (full, snippet, matchindex) {
1311 return full.substring(0, matchindex)
1312 + "<span class='match'>"
1313 + full.substr(matchindex, snippet.length)
1314 + "</span>" + full.substring(matchindex + snippet.length);
1315 };
1316
1317 // Custom formatter to highlight the matching letters
1318 reviewerAC.formatResult = function (oResultData, sQuery, sResultMatch) {
1319 var org_sQuery = sQuery;
1320 if(this.dataSource.mentionQuery != null){
1321 sQuery = this.dataSource.mentionQuery;
1322 }
1323
1324 var query = sQuery.toLowerCase();
1325 var _gravatar = function(res, em, group){
1326 if (group !== undefined){
1327 em = '/images/icons/group.png'
1328 }
1329 tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
1330 return tmpl.format(em,res)
1331 }
1332 if (oResultData.nname != undefined) {
1333 var fname = oResultData.fname || "";
1334 var lname = oResultData.lname || "";
1335 var nname = oResultData.nname;
1336
1337 // Guard against null value
1338 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1339 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1340 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1341 displayfname, displaylname, displaynname;
1342
1343 if (fnameMatchIndex > -1) {
1344 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1345 } else {
1346 displayfname = fname;
1347 }
1348
1349 if (lnameMatchIndex > -1) {
1350 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1351 } else {
1352 displaylname = lname;
1353 }
1354
1355 if (nnameMatchIndex > -1) {
1356 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1357 } else {
1358 displaynname = nname ? "(" + nname + ")" : "";
1359 }
1360
1361 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
1362 } else {
1363 return '';
1364 }
1365 };
1366
1367 //members cache to catch duplicates
1368 reviewerAC.dataSource.cache = [];
1369 // hack into select event
1370 if(reviewerAC.itemSelectEvent){
1371 reviewerAC.itemSelectEvent.subscribe(function (sType, aArgs) {
1372
1373 var myAC = aArgs[0]; // reference back to the AC instance
1374 var elLI = aArgs[1]; // reference to the selected LI element
1375 var oData = aArgs[2]; // object literal of selected item's result data
1376 var members = YUD.get('review_members');
1377 //fill the autocomplete with value
1378
1379 if (oData.nname != undefined) {
1380 if (myAC.dataSource.cache.indexOf(oData.id) != -1){
1381 return
1382 }
1383
1384 var tmpl = '<li>'+
1385 '<div class="reviewers_member">'+
1386 '<div class="gravatar"><img alt="gravatar" src="{0}"/> </div>'+
1387 '<div style="float:left">{1}</div>'+
1388 '<input type="hidden" value="{2}" name="review_members" />'+
1389 '</div>'+
1390 '</li>'
1391
1392 var displayname = "{0} {1} ({2})".format(oData.fname,oData.lname,oData.nname);
1393 var element = tmpl.format(oData.gravatar_lnk,displayname,oData.id);
1394 members.innerHTML += element;
1395 myAC.dataSource.cache.push(oData.id);
1396 }
1397 });
1398 }
1399 return {
1400 ownerDS: ownerDS,
1401 reviewerAC: reviewerAC,
1402 };
1403 }
1404
1405
1232 /**
1406 /**
1233 * QUICK REPO MENU
1407 * QUICK REPO MENU
1234 */
1408 */
@@ -69,40 +69,28 b''
69 <div style="float:left; border-left:1px dashed #eee">
69 <div style="float:left; border-left:1px dashed #eee">
70 <h4>${_('Pull request reviewers')}</h4>
70 <h4>${_('Pull request reviewers')}</h4>
71 <div id="reviewers" style="padding:0px 0px 0px 15px">
71 <div id="reviewers" style="padding:0px 0px 0px 15px">
72 ##TODO: make this nicer :)
72 ## members goes here !
73 <table class="table noborder">
73 <div class="group_members_wrap">
74 <tr>
74 <ul id="review_members" class="group_members">
75 <td>
75 %for member in c.review_members:
76 <div>
76 <li>
77 <div style="float:left">
77 <div class="reviewers_member">
78 <div class="text" style="padding: 0px 0px 6px;">${_('Chosen reviewers')}</div>
78 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
79 ${h.select('review_members',[x[0] for x in c.review_members],c.review_members,multiple=True,size=8,style="min-width:210px")}
79 <div style="float:left">${member.full_name} (${_('owner')})</div>
80 <div id="remove_all_elements" style="cursor:pointer;text-align:center">
80 <input type="hidden" value="${member.user_id}" name="review_members" />
81 ${_('Remove all elements')}
81 </div>
82 <img alt="remove" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_right.png')}"/>
82 </li>
83 </div>
83 %endfor
84 </div>
84 </ul>
85 <div style="float:left;width:20px;padding-top:50px">
85 </div>
86 <img alt="add" id="add_element"
86
87 style="padding:2px;cursor:pointer"
87 <div class='ac'>
88 src="${h.url('/images/icons/arrow_left.png')}"/>
88 <div class="reviewer_ac">
89 <br />
89 ${h.text('user', class_='yui-ac-input')}
90 <img alt="remove" id="remove_element"
90 <span class="help-block">${_('Add reviewer to this pull request.')}</span>
91 style="padding:2px;cursor:pointer"
91 <div id="reviewers_container"></div>
92 src="${h.url('/images/icons/arrow_right.png')}"/>
92 </div>
93 </div>
93 </div>
94 <div style="float:left">
95 <div class="text" style="padding: 0px 0px 6px;">${_('Available reviewers')}</div>
96 ${h.select('available_members',[],c.available_members,multiple=True,size=8,style="min-width:210px")}
97 <div id="add_all_elements" style="cursor:pointer;text-align:center">
98 <img alt="add" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_left.png')}"/>
99 ${_('Add all elements')}
100 </div>
101 </div>
102 </div>
103 </td>
104 </tr>
105 </table>
106 </div>
94 </div>
107 </div>
95 </div>
108 <h3>${_('Create new pull request')}</h3>
96 <h3>${_('Create new pull request')}</h3>
@@ -141,7 +129,10 b''
141 </div>
129 </div>
142
130
143 <script type="text/javascript">
131 <script type="text/javascript">
144 MultiSelectWidget('review_members','available_members','pull_request_form');
132 var _USERS_AC_DATA = ${c.users_array|n};
133 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
134 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
135
145 var other_repos_info = ${c.other_repos_info|n};
136 var other_repos_info = ${c.other_repos_info|n};
146 var loadPreview = function(){
137 var loadPreview = function(){
147 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','none');
138 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','none');
General Comments 0
You need to be logged in to leave comments. Login now