##// END OF EJS Templates
html: clarify type of button elements - make it clear if we really want "submit"...
Mads Kiilerich -
r8713:5572eb8d stable
parent child Browse files
Show More
@@ -1,1381 +1,1383 b''
1 1 /**
2 2 Kallithea JS Files
3 3 **/
4 4 'use strict';
5 5
6 6 if (typeof console == "undefined" || typeof console.log == "undefined"){
7 7 console = { log: function() {} }
8 8 }
9 9
10 10 /**
11 11 * INJECT .html_escape function into String
12 12 * Usage: "unsafe string".html_escape()
13 13 *
14 14 * This is the Javascript equivalent of kallithea.lib.helpers.html_escape(). It
15 15 * will escape HTML characters to prevent XSS or other issues. It should be
16 16 * used in all cases where Javascript code is inserting potentially unsafe data
17 17 * into the document.
18 18 *
19 19 * For example:
20 20 * <script>confirm("boo")</script>
21 21 * is changed into:
22 22 * &lt;script&gt;confirm(&quot;boo&quot;)&lt;/script&gt;
23 23 *
24 24 */
25 25 String.prototype.html_escape = function() {
26 26 return this
27 27 .replace(/&/g,'&amp;')
28 28 .replace(/</g,'&lt;')
29 29 .replace(/>/g,'&gt;')
30 30 .replace(/"/g, '&quot;')
31 31 .replace(/'/g, '&#039;');
32 32 }
33 33
34 34 /**
35 35 * INJECT .format function into String
36 36 * Usage: "My name is {0} {1}".format("Johny","Bravo")
37 37 * Return "My name is Johny Bravo"
38 38 * Inspired by https://gist.github.com/1049426
39 39 */
40 40 String.prototype.format = function() {
41 41 function format() {
42 42 var str = this;
43 43 var len = arguments.length+1;
44 44 var safe = undefined;
45 45 var arg = undefined;
46 46
47 47 // For each {0} {1} {n...} replace with the argument in that position. If
48 48 // the argument is an object or an array it will be stringified to JSON.
49 49 for (var i=0; i < len; arg = arguments[i++]) {
50 50 safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
51 51 str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
52 52 }
53 53 return str;
54 54 }
55 55
56 56 // Save a reference of what may already exist under the property native.
57 57 // Allows for doing something like: if("".format.native) { /* use native */ }
58 58 format.native = String.prototype.format;
59 59
60 60 // Replace the prototype property
61 61 return format;
62 62
63 63 }();
64 64
65 65 String.prototype.strip = function(char) {
66 66 if(char === undefined){
67 67 char = '\\s';
68 68 }
69 69 return this.replace(new RegExp('^'+char+'+|'+char+'+$','g'), '');
70 70 }
71 71
72 72 String.prototype.lstrip = function(char) {
73 73 if(char === undefined){
74 74 char = '\\s';
75 75 }
76 76 return this.replace(new RegExp('^'+char+'+'),'');
77 77 }
78 78
79 79 String.prototype.rstrip = function(char) {
80 80 if(char === undefined){
81 81 char = '\\s';
82 82 }
83 83 return this.replace(new RegExp(''+char+'+$'),'');
84 84 }
85 85
86 86 /* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf#Polyfill
87 87 under MIT license / public domain, see
88 88 https://developer.mozilla.org/en-US/docs/MDN/About#Copyrights_and_licenses */
89 89 if(!Array.prototype.indexOf) {
90 90 Array.prototype.indexOf = function (searchElement, fromIndex) {
91 91 if ( this === undefined || this === null ) {
92 92 throw new TypeError( '"this" is null or not defined' );
93 93 }
94 94
95 95 var length = this.length >>> 0; // Hack to convert object.length to a UInt32
96 96
97 97 fromIndex = +fromIndex || 0;
98 98
99 99 if (Math.abs(fromIndex) === Infinity) {
100 100 fromIndex = 0;
101 101 }
102 102
103 103 if (fromIndex < 0) {
104 104 fromIndex += length;
105 105 if (fromIndex < 0) {
106 106 fromIndex = 0;
107 107 }
108 108 }
109 109
110 110 for (;fromIndex < length; fromIndex++) {
111 111 if (this[fromIndex] === searchElement) {
112 112 return fromIndex;
113 113 }
114 114 }
115 115
116 116 return -1;
117 117 };
118 118 }
119 119
120 120 /* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter#Compatibility
121 121 under MIT license / public domain, see
122 122 https://developer.mozilla.org/en-US/docs/MDN/About#Copyrights_and_licenses */
123 123 if (!Array.prototype.filter)
124 124 {
125 125 Array.prototype.filter = function(fun /*, thisArg */)
126 126 {
127 127 if (this === void 0 || this === null)
128 128 throw new TypeError();
129 129
130 130 var t = Object(this);
131 131 var len = t.length >>> 0;
132 132 if (typeof fun !== "function")
133 133 throw new TypeError();
134 134
135 135 var res = [];
136 136 var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
137 137 for (var i = 0; i < len; i++)
138 138 {
139 139 if (i in t)
140 140 {
141 141 var val = t[i];
142 142
143 143 // NOTE: Technically this should Object.defineProperty at
144 144 // the next index, as push can be affected by
145 145 // properties on Object.prototype and Array.prototype.
146 146 // But that method's new, and collisions should be
147 147 // rare, so use the more-compatible alternative.
148 148 if (fun.call(thisArg, val, i, t))
149 149 res.push(val);
150 150 }
151 151 }
152 152
153 153 return res;
154 154 };
155 155 }
156 156
157 157 /**
158 158 * A customized version of PyRoutes.JS from https://pypi.python.org/pypi/pyroutes.js/
159 159 * which is copyright Stephane Klein and was made available under the BSD License.
160 160 *
161 161 * Usage pyroutes.url('mark_error_fixed',{"error_id":error_id}) // /mark_error_fixed/<error_id>
162 162 */
163 163 var pyroutes = (function() {
164 164 var matchlist = {};
165 165 var sprintf = (function() {
166 166 function get_type(variable) {
167 167 return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
168 168 }
169 169 function str_repeat(input, multiplier) {
170 170 for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
171 171 return output.join('');
172 172 }
173 173
174 174 function str_format() {
175 175 if (!str_format.cache.hasOwnProperty(arguments[0])) {
176 176 str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
177 177 }
178 178 return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
179 179 }
180 180
181 181 str_format.format = function(parse_tree, argv) {
182 182 var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
183 183 for (i = 0; i < tree_length; i++) {
184 184 node_type = get_type(parse_tree[i]);
185 185 if (node_type === 'string') {
186 186 output.push(parse_tree[i]);
187 187 }
188 188 else if (node_type === 'array') {
189 189 match = parse_tree[i]; // convenience purposes only
190 190 if (match[2]) { // keyword argument
191 191 arg = argv[cursor];
192 192 for (k = 0; k < match[2].length; k++) {
193 193 if (!arg.hasOwnProperty(match[2][k])) {
194 194 throw(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
195 195 }
196 196 arg = arg[match[2][k]];
197 197 }
198 198 }
199 199 else if (match[1]) { // positional argument (explicit)
200 200 arg = argv[match[1]];
201 201 }
202 202 else { // positional argument (implicit)
203 203 arg = argv[cursor++];
204 204 }
205 205
206 206 if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
207 207 throw(sprintf('[sprintf] expecting number but found %s', get_type(arg)));
208 208 }
209 209 switch (match[8]) {
210 210 case 'b': arg = arg.toString(2); break;
211 211 case 'c': arg = String.fromCharCode(arg); break;
212 212 case 'd': arg = parseInt(arg, 10); break;
213 213 case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
214 214 case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
215 215 case 'o': arg = arg.toString(8); break;
216 216 case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
217 217 case 'u': arg = Math.abs(arg); break;
218 218 case 'x': arg = arg.toString(16); break;
219 219 case 'X': arg = arg.toString(16).toUpperCase(); break;
220 220 }
221 221 arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
222 222 pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
223 223 pad_length = match[6] - String(arg).length;
224 224 pad = match[6] ? str_repeat(pad_character, pad_length) : '';
225 225 output.push(match[5] ? arg + pad : pad + arg);
226 226 }
227 227 }
228 228 return output.join('');
229 229 };
230 230
231 231 str_format.cache = {};
232 232
233 233 str_format.parse = function(fmt) {
234 234 var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
235 235 while (_fmt) {
236 236 if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
237 237 parse_tree.push(match[0]);
238 238 }
239 239 else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
240 240 parse_tree.push('%');
241 241 }
242 242 else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
243 243 if (match[2]) {
244 244 arg_names |= 1;
245 245 var field_list = [], replacement_field = match[2], field_match = [];
246 246 if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
247 247 field_list.push(field_match[1]);
248 248 while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
249 249 if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
250 250 field_list.push(field_match[1]);
251 251 }
252 252 else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
253 253 field_list.push(field_match[1]);
254 254 }
255 255 else {
256 256 throw('[sprintf] huh?');
257 257 }
258 258 }
259 259 }
260 260 else {
261 261 throw('[sprintf] huh?');
262 262 }
263 263 match[2] = field_list;
264 264 }
265 265 else {
266 266 arg_names |= 2;
267 267 }
268 268 if (arg_names === 3) {
269 269 throw('[sprintf] mixing positional and named placeholders is not (yet) supported');
270 270 }
271 271 parse_tree.push(match);
272 272 }
273 273 else {
274 274 throw('[sprintf] huh?');
275 275 }
276 276 _fmt = _fmt.substring(match[0].length);
277 277 }
278 278 return parse_tree;
279 279 };
280 280
281 281 return str_format;
282 282 })();
283 283
284 284 return {
285 285 'url': function(route_name, params) {
286 286 var result = route_name;
287 287 if (typeof(params) != 'object'){
288 288 params = {};
289 289 }
290 290 if (matchlist.hasOwnProperty(route_name)) {
291 291 var route = matchlist[route_name];
292 292 // param substitution
293 293 for(var i=0; i < route[1].length; i++) {
294 294 if (!params.hasOwnProperty(route[1][i]))
295 295 throw new Error(route[1][i] + ' missing in "' + route_name + '" route generation');
296 296 }
297 297 result = sprintf(route[0], params);
298 298
299 299 var ret = [];
300 300 //extra params => GET
301 301 for(var param in params){
302 302 if (route[1].indexOf(param) == -1){
303 303 ret.push(encodeURIComponent(param) + "=" + encodeURIComponent(params[param]));
304 304 }
305 305 }
306 306 var _parts = ret.join("&");
307 307 if(_parts){
308 308 result = result +'?'+ _parts
309 309 }
310 310 }
311 311
312 312 return result;
313 313 },
314 314 'register': function(route_name, route_tmpl, req_params) {
315 315 if (typeof(req_params) != 'object') {
316 316 req_params = [];
317 317 }
318 318 var keys = [];
319 319 for (var i=0; i < req_params.length; i++) {
320 320 keys.push(req_params[i]);
321 321 }
322 322 matchlist[route_name] = [
323 323 unescape(route_tmpl),
324 324 keys
325 325 ]
326 326 },
327 327 '_routes': function(){
328 328 return matchlist;
329 329 }
330 330 }
331 331 })();
332 332
333 333
334 334 /**
335 335 * turns objects into GET query string
336 336 */
337 337 function _toQueryString(o) {
338 338 if(typeof o !== 'object') {
339 339 return false;
340 340 }
341 341 var _p, _qs = [];
342 342 for(_p in o) {
343 343 _qs.push(encodeURIComponent(_p) + '=' + encodeURIComponent(o[_p]));
344 344 }
345 345 return _qs.join('&');
346 346 }
347 347
348 348 /**
349 349 * Load HTML into DOM using Ajax
350 350 *
351 351 * @param $target: load html async and place it (or an error message) here
352 352 * @param success: success callback function
353 353 * @param args: query parameters to pass to url
354 354 */
355 355 function asynchtml(url, $target, success, args){
356 356 if(args===undefined){
357 357 args=null;
358 358 }
359 359 $target.html(_TM['Loading ...']).css('opacity','0.3');
360 360
361 361 return $.ajax({url: url, data: args, headers: {'X-PARTIAL-XHR': '1'}, cache: false, dataType: 'html'})
362 362 .done(function(html) {
363 363 $target.html(html);
364 364 $target.css('opacity','1.0');
365 365 //execute the given original callback
366 366 if (success !== undefined && success) {
367 367 success();
368 368 }
369 369 })
370 370 .fail(function(jqXHR, textStatus) {
371 371 if (textStatus == "abort")
372 372 return;
373 373 $target.html('<span class="bg-danger">ERROR: {0}</span>'.format(textStatus));
374 374 $target.css('opacity','1.0');
375 375 })
376 376 ;
377 377 }
378 378
379 379 function ajaxGET(url, success, failure) {
380 380 if(failure === undefined) {
381 381 failure = function(jqXHR, textStatus) {
382 382 if (textStatus != "abort")
383 383 alert("Ajax GET error: " + textStatus);
384 384 };
385 385 }
386 386 return $.ajax({url: url, headers: {'X-PARTIAL-XHR': '1'}, cache: false})
387 387 .done(success)
388 388 .fail(failure);
389 389 }
390 390
391 391 function ajaxPOST(url, postData, success, failure) {
392 392 postData['_session_csrf_secret_token'] = _session_csrf_secret_token;
393 393 if(failure === undefined) {
394 394 failure = function(jqXHR, textStatus) {
395 395 if (textStatus != "abort")
396 396 alert("Error posting to server: " + textStatus);
397 397 };
398 398 }
399 399 return $.ajax({url: url, data: _toQueryString(postData), type: 'POST', headers: {'X-PARTIAL-XHR': '1'}, cache: false})
400 400 .done(success)
401 401 .fail(failure);
402 402 }
403 403
404 404
405 405 /**
406 406 * activate .show_more links
407 407 * the .show_more must have an id that is the the id of an element to hide prefixed with _
408 408 * the parentnode will be displayed
409 409 */
410 410 function show_more_event(){
411 411 $('.show_more').click(function(e){
412 412 var el = e.currentTarget;
413 413 $('#' + el.id.substring(1)).hide();
414 414 $(el.parentNode).show();
415 415 });
416 416 }
417 417
418 418
419 419 function _onSuccessFollow(target){
420 420 var $target = $(target);
421 421 var $f_cnt = $('#current_followers_count');
422 422 if ($target.hasClass('follow')) {
423 423 $target.removeClass('follow').addClass('following');
424 424 $target.prop('title', _TM['Stop following this repository']);
425 425 if ($f_cnt.html()) {
426 426 const cnt = Number($f_cnt.html())+1;
427 427 $f_cnt.html(cnt);
428 428 }
429 429 } else {
430 430 $target.removeClass('following').addClass('follow');
431 431 $target.prop('title', _TM['Start following this repository']);
432 432 if ($f_cnt.html()) {
433 433 const cnt = Number($f_cnt.html())-1;
434 434 $f_cnt.html(cnt);
435 435 }
436 436 }
437 437 }
438 438
439 439 function toggleFollowingRepo(target, follows_repository_id){
440 440 var args = {
441 441 'follows_repository_id': follows_repository_id,
442 442 '_session_csrf_secret_token': _session_csrf_secret_token
443 443 }
444 444 $.post(TOGGLE_FOLLOW_URL, args, function(){
445 445 _onSuccessFollow(target);
446 446 });
447 447 return false;
448 448 }
449 449
450 450 function showRepoSize(target, repo_name){
451 451 var args = '_session_csrf_secret_token=' + _session_csrf_secret_token;
452 452
453 453 if(!$("#" + target).hasClass('loaded')){
454 454 $("#" + target).html(_TM['Loading ...']);
455 455 var url = pyroutes.url('repo_size', {"repo_name":repo_name});
456 456 $.post(url, args, function(data) {
457 457 $("#" + target).html(data);
458 458 $("#" + target).addClass('loaded');
459 459 });
460 460 }
461 461 return false;
462 462 }
463 463
464 464 /**
465 465 * load tooltips dynamically based on data attributes, used for .lazy-cs changeset links
466 466 */
467 467 function get_changeset_tooltip() {
468 468 var $target = $(this);
469 469 var tooltip = $target.data('tooltip');
470 470 if (!tooltip) {
471 471 var raw_id = $target.data('raw_id');
472 472 var repo_name = $target.data('repo_name');
473 473 var url = pyroutes.url('changeset_info', {"repo_name": repo_name, "revision": raw_id});
474 474
475 475 $.ajax(url, {
476 476 async: false,
477 477 success: function(data) {
478 478 tooltip = data["message"];
479 479 }
480 480 });
481 481 $target.data('tooltip', tooltip);
482 482 }
483 483 return tooltip;
484 484 }
485 485
486 486 /**
487 487 * activate tooltips and popups
488 488 */
489 489 function tooltip_activate(){
490 490 function placement(p, e){
491 491 if(e.getBoundingClientRect().top > 2*$(window).height()/3){
492 492 return 'top';
493 493 }else{
494 494 return 'bottom';
495 495 }
496 496 }
497 497 $(document).ready(function(){
498 498 $('[data-toggle="tooltip"]').tooltip({
499 499 container: 'body',
500 500 placement: placement
501 501 });
502 502 $('[data-toggle="popover"]').popover({
503 503 html: true,
504 504 container: 'body',
505 505 placement: placement,
506 506 trigger: 'hover',
507 507 template: '<div class="popover cs-popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
508 508 });
509 509 $('.lazy-cs').tooltip({
510 510 title: get_changeset_tooltip,
511 511 placement: placement
512 512 });
513 513 });
514 514 }
515 515
516 516
517 517 /**
518 518 * Comment handling
519 519 */
520 520
521 521 // move comments to their right location, inside new trs
522 522 function move_comments($anchorcomments) {
523 523 $anchorcomments.each(function(i, anchorcomment) {
524 524 var $anchorcomment = $(anchorcomment);
525 525 var target_id = $anchorcomment.data('target-id');
526 526 var $comment_div = _get_add_comment_div(target_id);
527 527 var f_path = $anchorcomment.data('f_path');
528 528 var line_no = $anchorcomment.data('line_no');
529 529 if ($comment_div[0]) {
530 530 $comment_div.append($anchorcomment.children());
531 531 if (f_path && line_no !== '') {
532 532 _comment_div_append_add($comment_div, f_path, line_no);
533 533 } else {
534 534 _comment_div_append_form($comment_div, f_path, line_no);
535 535 }
536 536 } else {
537 537 $anchorcomment.before("<span class='bg-warning'>Comment to {0} line {1} which is outside the diff context:</span>".format(f_path || '?', line_no || '?'));
538 538 }
539 539 });
540 540 linkInlineComments($('.firstlink'), $('.comment:first-child'));
541 541 }
542 542
543 543 // comment bubble was clicked - insert new tr and show form
544 544 function show_comment_form($bubble) {
545 545 var children = $bubble.closest('tr.line').children('[id]');
546 546 var line_td_id = children[children.length - 1].id;
547 547 var $comment_div = _get_add_comment_div(line_td_id);
548 548 var f_path = $bubble.closest('[data-f_path]').data('f_path');
549 549 var parts = line_td_id.split('_');
550 550 var line_no = parts[parts.length-1];
551 551 comment_div_state($comment_div, f_path, line_no, true);
552 552 }
553 553
554 554 // return comment div for target_id - add it if it doesn't exist yet
555 555 function _get_add_comment_div(target_id) {
556 556 var comments_box_id = 'comments-' + target_id;
557 557 var $comments_box = $('#' + comments_box_id);
558 558 if (!$comments_box.length) {
559 559 var html = '<tr><td id="{0}" colspan="3" class="inline-comments"></td></tr>'.format(comments_box_id);
560 560 $('#' + target_id).closest('tr').after(html);
561 561 $comments_box = $('#' + comments_box_id);
562 562 }
563 563 return $comments_box;
564 564 }
565 565
566 566 // Set $comment_div state - showing or not showing form and Add button.
567 567 // An Add button is shown on non-empty forms when no form is shown.
568 568 // The form is controlled by show_form_opt - if undefined, form is only shown for general comments.
569 569 function comment_div_state($comment_div, f_path, line_no, show_form_opt) {
570 570 var show_form = show_form_opt !== undefined ? show_form_opt : !f_path && !line_no;
571 571 var $forms = $comment_div.children('.comment-inline-form');
572 572 var $buttonrow = $comment_div.children('.add-button-row');
573 573 var $comments = $comment_div.children('.comment:not(.submitting)');
574 574 $forms.remove();
575 575 $buttonrow.remove();
576 576 if (show_form) {
577 577 _comment_div_append_form($comment_div, f_path, line_no);
578 578 } else if ($comments.length) {
579 579 _comment_div_append_add($comment_div, f_path, line_no);
580 580 } else {
581 581 $comment_div.parent('tr').remove();
582 582 }
583 583 }
584 584
585 585 // append an Add button to $comment_div and hook it up to show form
586 586 function _comment_div_append_add($comment_div, f_path, line_no) {
587 587 var addlabel = TRANSLATION_MAP['Add Another Comment'];
588 588 var $add = $('<div class="add-button-row"><span class="btn btn-default btn-xs add-button">{0}</span></div>'.format(addlabel));
589 589 $comment_div.append($add);
590 590 $add.children('.add-button').click(function() {
591 591 comment_div_state($comment_div, f_path, line_no, true);
592 592 });
593 593 }
594 594
595 595 // append a comment form to $comment_div
596 596 // Note: var AJAX_COMMENT_URL must have been defined before invoking this function
597 597 function _comment_div_append_form($comment_div, f_path, line_no) {
598 598 var $form_div = $('#comment-inline-form-template').children()
599 599 .clone()
600 600 .addClass('comment-inline-form');
601 601 $comment_div.append($form_div);
602 602 var $preview = $comment_div.find("div.comment-preview");
603 603 var $form = $comment_div.find("form");
604 604 var $textarea = $form.find('textarea');
605 605
606 606 $form.submit(function(e) {
607 607 e.preventDefault();
608 608
609 609 var text = $textarea.val();
610 610 var review_status = $form.find('input:radio[name=changeset_status]:checked').val();
611 611 var pr_close = $form.find('input:checkbox[name=save_close]:checked').length ? 'on' : '';
612 612 var pr_delete = $form.find('input:checkbox[name=save_delete]:checked').length ? 'delete' : '';
613 613
614 614 if (!text && !review_status && !pr_close && !pr_delete) {
615 615 alert("Please provide a comment");
616 616 return false;
617 617 }
618 618
619 619 if (pr_delete) {
620 620 if (text || review_status || pr_close) {
621 621 alert('Cannot delete pull request while making other changes');
622 622 return false;
623 623 }
624 624 if (!confirm('Confirm to delete this pull request')) {
625 625 return false;
626 626 }
627 627 var comments = $('.comment').length;
628 628 if (comments > 0 &&
629 629 !confirm('Confirm again to delete this pull request with {0} comments'.format(comments))) {
630 630 return false;
631 631 }
632 632 }
633 633
634 634 if (review_status) {
635 635 var $review_status = $preview.find('.automatic-comment');
636 636 var review_status_lbl = $("#comment-inline-form-template input.status_change_radio[value='" + review_status + "']").parent().text().strip();
637 637 $review_status.find('.comment-status-label').text(review_status_lbl);
638 638 $review_status.show();
639 639 }
640 640 $preview.find('.comment-text div').text(text);
641 641 $preview.show();
642 642 $textarea.val('');
643 643 if (f_path && line_no) {
644 644 $form.hide();
645 645 }
646 646
647 647 var postData = {
648 648 'text': text,
649 649 'f_path': f_path,
650 650 'line': line_no,
651 651 'changeset_status': review_status,
652 652 'save_close': pr_close,
653 653 'save_delete': pr_delete
654 654 };
655 655 function success(json_data) {
656 656 if (pr_delete) {
657 657 location = json_data['location'];
658 658 } else {
659 659 $comment_div.append(json_data['rendered_text']);
660 660 comment_div_state($comment_div, f_path, line_no);
661 661 linkInlineComments($('.firstlink'), $('.comment:first-child'));
662 662 if ((review_status || pr_close) && !f_path && !line_no) {
663 663 // Page changed a lot - reload it after closing the submitted form
664 664 comment_div_state($comment_div, f_path, line_no, false);
665 665 location.reload(true);
666 666 }
667 667 }
668 668 }
669 669 function failure(x, s, e) {
670 670 $preview.removeClass('submitting').children('.panel').addClass('panel-danger');
671 671 var $status = $preview.find('.comment-submission-status');
672 672 $('<span>', {
673 673 'title': e,
674 674 text: _TM['Unable to post']
675 675 }).replaceAll($status.contents());
676 676 $('<div>', {
677 677 'class': 'btn-group'
678 678 }).append(
679 679 $('<button>', {
680 'type': 'button',
680 681 'class': 'btn btn-default btn-xs',
681 682 text: _TM['Retry']
682 683 }).click(function() {
683 684 $status.text(_TM['Submitting ...']);
684 685 $preview.addClass('submitting').children('.panel').removeClass('panel-danger');
685 686 ajaxPOST(AJAX_COMMENT_URL, postData, success, failure);
686 687 }),
687 688 $('<button>', {
689 'type': 'button',
688 690 'class': 'btn btn-default btn-xs',
689 691 text: _TM['Cancel']
690 692 }).click(function() {
691 693 comment_div_state($comment_div, f_path, line_no);
692 694 })
693 695 ).appendTo($status);
694 696 }
695 697 ajaxPOST(AJAX_COMMENT_URL, postData, success, failure);
696 698 });
697 699
698 700 // add event handler for hide/cancel buttons
699 701 $form.find('.hide-inline-form').click(function() {
700 702 comment_div_state($comment_div, f_path, line_no);
701 703 });
702 704
703 705 tooltip_activate();
704 706 if ($textarea.length > 0) {
705 707 MentionsAutoComplete($textarea);
706 708 }
707 709 if (f_path) {
708 710 $textarea.focus();
709 711 }
710 712 }
711 713
712 714
713 715 // Note: var AJAX_COMMENT_URL must have been defined before invoking this function
714 716 function deleteComment(comment_id) {
715 717 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
716 718 var postData = {};
717 719 function success() {
718 720 $('#comment-'+comment_id).remove();
719 721 // Ignore that this might leave a stray Add button (or have a pending form with another comment) ...
720 722 }
721 723 ajaxPOST(url, postData, success);
722 724 }
723 725
724 726
725 727 /**
726 728 * Double link comments
727 729 */
728 730 function linkInlineComments($firstlinks, $comments){
729 731 if ($comments.length > 0) {
730 732 $firstlinks.html('<a href="#{0}">First comment</a>'.format($comments.prop('id')));
731 733 }
732 734 if ($comments.length <= 1) {
733 735 return;
734 736 }
735 737
736 738 $comments.each(function(i){
737 739 var prev = '';
738 740 if (i > 0){
739 741 var prev_anchor = $($comments.get(i-1)).prop('id');
740 742 prev = '<a href="#{0}">Previous comment</a>'.format(prev_anchor);
741 743 }
742 744 var next = '';
743 745 if (i+1 < $comments.length){
744 746 var next_anchor = $($comments.get(i+1)).prop('id');
745 747 next = '<a href="#{0}">Next comment</a>'.format(next_anchor);
746 748 }
747 749 $(this).find('.comment-prev-next-links').html(
748 750 '<div class="prev-comment">{0}</div>'.format(prev) +
749 751 '<div class="next-comment">{0}</div>'.format(next));
750 752 });
751 753 }
752 754
753 755 /* activate files.html stuff */
754 756 function fileBrowserListeners(node_list_url, url_base){
755 757 var $node_filter = $('#node_filter');
756 758
757 759 var filterTimeout = null;
758 760 var nodes = null;
759 761
760 762 function initFilter(){
761 763 $('#node_filter_box_loading').show();
762 764 $('#search_activate_id').hide();
763 765 $('#add_node_id').hide();
764 766 $.ajax({url: node_list_url, headers: {'X-PARTIAL-XHR': '1'}, cache: false})
765 767 .done(function(json) {
766 768 nodes = json.nodes;
767 769 $('#node_filter_box_loading').hide();
768 770 $('#node_filter_box').show();
769 771 $node_filter.focus();
770 772 if($node_filter.hasClass('init')){
771 773 $node_filter.val('');
772 774 $node_filter.removeClass('init');
773 775 }
774 776 })
775 777 .fail(function() {
776 778 console.log('fileBrowserListeners initFilter failed to load');
777 779 })
778 780 ;
779 781 }
780 782
781 783 function updateFilter(e) {
782 784 return function(){
783 785 // Reset timeout
784 786 filterTimeout = null;
785 787 var query = e.currentTarget.value.toLowerCase();
786 788 var match = [];
787 789 var matches = 0;
788 790 var matches_max = 20;
789 791 if (query != ""){
790 792 for(var i=0;i<nodes.length;i++){
791 793 var pos = nodes[i].name.toLowerCase().indexOf(query);
792 794 if(query && pos != -1){
793 795 matches++
794 796 //show only certain amount to not kill browser
795 797 if (matches > matches_max){
796 798 break;
797 799 }
798 800
799 801 var n = nodes[i].name;
800 802 var t = nodes[i].type;
801 803 var n_hl = n.substring(0,pos)
802 804 + "<b>{0}</b>".format(n.substring(pos,pos+query.length))
803 805 + n.substring(pos+query.length);
804 806 var new_url = url_base.replace('__FPATH__',n);
805 807 match.push('<tr><td><a class="browser-{0}" href="{1}">{2}</a></td><td colspan="5"></td></tr>'.format(t,new_url,n_hl));
806 808 }
807 809 if(match.length >= matches_max){
808 810 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['Search truncated']));
809 811 break;
810 812 }
811 813 }
812 814 }
813 815 if(query != ""){
814 816 $('#tbody').hide();
815 817 $('#tbody_filtered').show();
816 818
817 819 if (match.length==0){
818 820 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['No matching files']));
819 821 }
820 822
821 823 $('#tbody_filtered').html(match.join(""));
822 824 }
823 825 else{
824 826 $('#tbody').show();
825 827 $('#tbody_filtered').hide();
826 828 }
827 829 }
828 830 }
829 831
830 832 $('#filter_activate').click(function(){
831 833 initFilter();
832 834 });
833 835 $node_filter.click(function(){
834 836 if($node_filter.hasClass('init')){
835 837 $node_filter.val('');
836 838 $node_filter.removeClass('init');
837 839 }
838 840 });
839 841 $node_filter.keyup(function(e){
840 842 clearTimeout(filterTimeout);
841 843 filterTimeout = setTimeout(updateFilter(e),600);
842 844 });
843 845 }
844 846
845 847
846 848 function initCodeMirror(textarea_id, baseUrl, resetUrl){
847 849 var myCodeMirror = CodeMirror.fromTextArea($('#' + textarea_id)[0], {
848 850 mode: "null",
849 851 lineNumbers: true,
850 852 indentUnit: 4,
851 853 autofocus: true
852 854 });
853 855 CodeMirror.modeURL = baseUrl + "/codemirror/mode/%N/%N.js";
854 856
855 857 $('#reset').click(function(){
856 858 window.location=resetUrl;
857 859 });
858 860
859 861 $('#file_enable').click(function(){
860 862 $('#upload_file_container').hide();
861 863 $('#filename_container').show();
862 864 $('#body').show();
863 865 });
864 866
865 867 $('#upload_file_enable').click(function(){
866 868 $('#upload_file_container').show();
867 869 $('#filename_container').hide();
868 870 $('#body').hide();
869 871 });
870 872
871 873 return myCodeMirror
872 874 }
873 875
874 876 function setCodeMirrorMode(codeMirrorInstance, mode) {
875 877 CodeMirror.autoLoadMode(codeMirrorInstance, mode);
876 878 }
877 879
878 880
879 881 function _getIdentNode(n){
880 882 //iterate thrugh nodes until matching interesting node
881 883
882 884 if (typeof n == 'undefined'){
883 885 return -1
884 886 }
885 887
886 888 if(typeof n.id != "undefined" && n.id.match('L[0-9]+')){
887 889 return n
888 890 }
889 891 else{
890 892 return _getIdentNode(n.parentNode);
891 893 }
892 894 }
893 895
894 896 /* generate links for multi line selects that can be shown by files.html page_highlights.
895 897 * This is a mouseup handler for hlcode from CodeHtmlFormatter and pygmentize */
896 898 function getSelectionLink() {
897 899 //get selection from start/to nodes
898 900 if (typeof window.getSelection != "undefined") {
899 901 var s = window.getSelection();
900 902
901 903 var from = _getIdentNode(s.anchorNode);
902 904 var till = _getIdentNode(s.focusNode);
903 905
904 906 //var f_int = parseInt(from.id.replace('L',''));
905 907 //var t_int = parseInt(till.id.replace('L',''));
906 908
907 909 var yoffset = 35;
908 910 var ranges = [parseInt(from.id.replace('L','')), parseInt(till.id.replace('L',''))];
909 911 if (ranges[0] > ranges[1]){
910 912 //highlight from bottom
911 913 yoffset = -yoffset;
912 914 ranges = [ranges[1], ranges[0]];
913 915 }
914 916 var $hl_div = $('div#linktt');
915 917 // if we select more than 2 lines
916 918 if (ranges[0] != ranges[1]){
917 919 if ($hl_div.length) {
918 920 $hl_div.html('');
919 921 } else {
920 922 $hl_div = $('<div id="linktt" class="hl-tip-box">');
921 923 $('body').prepend($hl_div);
922 924 }
923 925
924 926 $hl_div.append($('<a>').html(_TM['Selection Link']).prop('href', location.href.substring(0, location.href.indexOf('#')) + '#L' + ranges[0] + '-'+ranges[1]));
925 927 var xy = $(till).offset();
926 928 $hl_div.css('top', (xy.top + yoffset) + 'px').css('left', xy.left + 'px');
927 929 $hl_div.show();
928 930 }
929 931 else{
930 932 $hl_div.hide();
931 933 }
932 934 }
933 935 }
934 936
935 937 /**
936 938 * Autocomplete functionality
937 939 */
938 940
939 941 // Highlight the snippet if it is found in the full text, while escaping any existing markup.
940 942 // Snippet must be lowercased already.
941 943 function autocompleteHighlightMatch(full, snippet) {
942 944 var matchindex = full.toLowerCase().indexOf(snippet);
943 945 if (matchindex <0)
944 946 return full.html_escape();
945 947 return full.substring(0, matchindex).html_escape()
946 948 + '<span class="select2-match">'
947 949 + full.substr(matchindex, snippet.length).html_escape()
948 950 + '</span>'
949 951 + full.substring(matchindex + snippet.length).html_escape();
950 952 }
951 953
952 954 // Return html snippet for showing the provided gravatar url
953 955 function gravatar(gravatar_lnk, size, cssclass) {
954 956 if (!gravatar_lnk) {
955 957 return '';
956 958 }
957 959 if (gravatar_lnk == 'default') {
958 960 return '<i class="icon-user {1}" style="font-size: {0}px;"></i>'.format(size, cssclass);
959 961 }
960 962 return ('<i class="icon-gravatar {2}"' +
961 963 ' style="font-size: {0}px;background-image: url(\'{1}\'); background-size: {0}px"' +
962 964 '></i>').format(size, gravatar_lnk, cssclass);
963 965 }
964 966
965 967 function autocompleteGravatar(res, gravatar_lnk, size, group) {
966 968 var elem;
967 969 if (group !== undefined) {
968 970 elem = '<i class="perm-gravatar-ac icon-users"></i>';
969 971 } else {
970 972 elem = gravatar(gravatar_lnk, size, "perm-gravatar-ac");
971 973 }
972 974 return '<div class="ac-container-wrap">{0}{1}</div>'.format(elem, res);
973 975 }
974 976
975 977 // Custom formatter to highlight the matching letters and do HTML escaping
976 978 function autocompleteFormatter(oResultData, sQuery, sResultMatch) {
977 979 var query;
978 980 if (sQuery && sQuery.toLowerCase) // YAHOO AutoComplete
979 981 query = sQuery.toLowerCase();
980 982 else if (sResultMatch && sResultMatch.term) // select2 - parameter names doesn't match
981 983 query = sResultMatch.term.toLowerCase();
982 984
983 985 // group
984 986 if (oResultData.type == "group") {
985 987 return autocompleteGravatar(
986 988 "{0}: {1}".format(
987 989 _TM['Group'],
988 990 autocompleteHighlightMatch(oResultData.grname, query)),
989 991 null, null, true);
990 992 }
991 993
992 994 // users
993 995 if (oResultData.nname) {
994 996 var displayname = autocompleteHighlightMatch(oResultData.nname, query);
995 997 if (oResultData.fname && oResultData.lname) {
996 998 displayname = "{0} {1} ({2})".format(
997 999 autocompleteHighlightMatch(oResultData.fname, query),
998 1000 autocompleteHighlightMatch(oResultData.lname, query),
999 1001 displayname);
1000 1002 }
1001 1003
1002 1004 return autocompleteGravatar(displayname, oResultData.gravatar_lnk, oResultData.gravatar_size);
1003 1005 }
1004 1006
1005 1007 return '';
1006 1008 }
1007 1009
1008 1010 function SimpleUserAutoComplete($inputElement) {
1009 1011 $inputElement.select2({
1010 1012 formatInputTooShort: $inputElement.attr('placeholder'),
1011 1013 initSelection : function (element, callback) {
1012 1014 $.ajax({
1013 1015 url: pyroutes.url('users_and_groups_data'),
1014 1016 dataType: 'json',
1015 1017 data: {
1016 1018 key: element.val()
1017 1019 },
1018 1020 success: function(data){
1019 1021 callback(data.results[0]);
1020 1022 }
1021 1023 });
1022 1024 },
1023 1025 minimumInputLength: 1,
1024 1026 ajax: {
1025 1027 url: pyroutes.url('users_and_groups_data'),
1026 1028 dataType: 'json',
1027 1029 data: function(term){
1028 1030 return {
1029 1031 query: term
1030 1032 };
1031 1033 },
1032 1034 results: function (data){
1033 1035 return data;
1034 1036 },
1035 1037 cache: true
1036 1038 },
1037 1039 formatSelection: autocompleteFormatter,
1038 1040 formatResult: autocompleteFormatter,
1039 1041 id: function(item) { return item.nname; },
1040 1042 });
1041 1043 }
1042 1044
1043 1045 function MembersAutoComplete($inputElement, $typeElement) {
1044 1046
1045 1047 $inputElement.select2({
1046 1048 placeholder: $inputElement.attr('placeholder'),
1047 1049 minimumInputLength: 1,
1048 1050 ajax: {
1049 1051 url: pyroutes.url('users_and_groups_data'),
1050 1052 dataType: 'json',
1051 1053 data: function(term){
1052 1054 return {
1053 1055 query: term,
1054 1056 types: 'users,groups'
1055 1057 };
1056 1058 },
1057 1059 results: function (data){
1058 1060 return data;
1059 1061 },
1060 1062 cache: true
1061 1063 },
1062 1064 formatSelection: autocompleteFormatter,
1063 1065 formatResult: autocompleteFormatter,
1064 1066 id: function(item) { return item.type == 'user' ? item.nname : item.grname },
1065 1067 }).on("select2-selecting", function(e) {
1066 1068 // e.choice.id is automatically used as selection value - just set the type of the selection
1067 1069 $typeElement.val(e.choice.type);
1068 1070 });
1069 1071 }
1070 1072
1071 1073 function MentionsAutoComplete($inputElement) {
1072 1074 $inputElement.atwho({
1073 1075 at: "@",
1074 1076 callbacks: {
1075 1077 remoteFilter: function(query, callback) {
1076 1078 $.getJSON(
1077 1079 pyroutes.url('users_and_groups_data'),
1078 1080 {
1079 1081 query: query,
1080 1082 types: 'users'
1081 1083 },
1082 1084 function(data) {
1083 1085 callback(data.results)
1084 1086 }
1085 1087 );
1086 1088 },
1087 1089 sorter: function(query, items) {
1088 1090 return items;
1089 1091 }
1090 1092 },
1091 1093 displayTpl: function(item) {
1092 1094 return "<li>" +
1093 1095 autocompleteGravatar(
1094 1096 "{0} {1} ({2})".format(item.fname, item.lname, item.nname).html_escape(),
1095 1097 '${gravatar_lnk}', 16) +
1096 1098 "</li>";
1097 1099 },
1098 1100 insertTpl: "${atwho-at}${nname}"
1099 1101 });
1100 1102 }
1101 1103
1102 1104
1103 1105 function addReviewMember(id,fname,lname,nname,gravatar_link,gravatar_size){
1104 1106 var displayname = nname;
1105 1107 if ((fname != "") && (lname != "")) {
1106 1108 displayname = "{0} {1} ({2})".format(fname, lname, nname);
1107 1109 }
1108 1110 var gravatarelm = gravatar(gravatar_link, gravatar_size, "");
1109 1111 // WARNING: the HTML below is duplicate with
1110 1112 // kallithea/templates/pullrequests/pullrequest_show.html
1111 1113 // If you change something here it should be reflected in the template too.
1112 1114 var element = (
1113 1115 ' <li id="reviewer_{2}">\n'+
1114 1116 ' <span class="reviewers_member">\n'+
1115 1117 ' <input type="hidden" value="{2}" name="review_members" />\n'+
1116 1118 ' <span class="reviewer_status" data-toggle="tooltip" title="not_reviewed">\n'+
1117 1119 ' <i class="icon-circle changeset-status-not_reviewed"></i>\n'+
1118 1120 ' </span>\n'+
1119 1121 (gravatarelm ?
1120 1122 ' {0}\n' :
1121 1123 '')+
1122 1124 ' <span>{1}</span>\n'+
1123 1125 ' <a href="#" class="reviewer_member_remove" onclick="removeReviewMember({2})">\n'+
1124 1126 ' <i class="icon-minus-circled"></i>\n'+
1125 1127 ' </a> (add not saved)\n'+
1126 1128 ' </span>\n'+
1127 1129 ' </li>\n'
1128 1130 ).format(gravatarelm, displayname.html_escape(), id);
1129 1131 // check if we don't have this ID already in
1130 1132 var ids = [];
1131 1133 $('#review_members').find('li').each(function() {
1132 1134 ids.push(this.id);
1133 1135 });
1134 1136 if(ids.indexOf('reviewer_'+id) == -1){
1135 1137 //only add if it's not there
1136 1138 $('#review_members').append(element);
1137 1139 }
1138 1140 }
1139 1141
1140 1142 function removeReviewMember(reviewer_id){
1141 1143 var $li = $('#reviewer_{0}'.format(reviewer_id));
1142 1144 $li.find('div div').css("text-decoration", "line-through");
1143 1145 $li.find('input').prop('name', 'review_members_removed');
1144 1146 $li.find('.reviewer_member_remove').replaceWith('&nbsp;(remove not saved)');
1145 1147 }
1146 1148
1147 1149 /* activate auto completion of users as PR reviewers */
1148 1150 function PullRequestAutoComplete($inputElement) {
1149 1151 $inputElement.select2(
1150 1152 {
1151 1153 placeholder: $inputElement.attr('placeholder'),
1152 1154 minimumInputLength: 1,
1153 1155 ajax: {
1154 1156 url: pyroutes.url('users_and_groups_data'),
1155 1157 dataType: 'json',
1156 1158 data: function(term){
1157 1159 return {
1158 1160 query: term
1159 1161 };
1160 1162 },
1161 1163 results: function (data){
1162 1164 return data;
1163 1165 },
1164 1166 cache: true
1165 1167 },
1166 1168 formatSelection: autocompleteFormatter,
1167 1169 formatResult: autocompleteFormatter,
1168 1170 }).on("select2-selecting", function(e) {
1169 1171 addReviewMember(e.choice.id, e.choice.fname, e.choice.lname, e.choice.nname,
1170 1172 e.choice.gravatar_lnk, e.choice.gravatar_size);
1171 1173 $inputElement.select2("close");
1172 1174 e.preventDefault();
1173 1175 });
1174 1176 }
1175 1177
1176 1178
1177 1179 function addPermAction(perm_type) {
1178 1180 var template =
1179 1181 '<td><input type="radio" value="{1}.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td>' +
1180 1182 '<td><input type="radio" value="{1}.read" checked="checked" name="perm_new_member_{0}" id="perm_new_member_{0}"></td>' +
1181 1183 '<td><input type="radio" value="{1}.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td>' +
1182 1184 '<td><input type="radio" value="{1}.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td>' +
1183 1185 '<td>' +
1184 1186 '<input class="form-control" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text" placeholder="{2}">' +
1185 1187 '<input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden">' +
1186 1188 '</td>' +
1187 1189 '<td></td>';
1188 1190 var $last_node = $('.last_new_member').last(); // empty tr between last and add
1189 1191 var next_id = $('.new_members').length;
1190 1192 $last_node.before($('<tr class="new_members">').append(template.format(next_id, perm_type, _TM['Type name of user or member to grant permission'])));
1191 1193 MembersAutoComplete($("#perm_new_member_name_"+next_id), $("#perm_new_member_type_"+next_id));
1192 1194 }
1193 1195
1194 1196 function ajaxActionRevokePermission(url, obj_id, obj_type, field_id, extra_data) {
1195 1197 function success() {
1196 1198 $('#' + field_id).remove();
1197 1199 }
1198 1200 function failure(o) {
1199 1201 alert(_TM['Failed to revoke permission'] + ": " + o.status);
1200 1202 }
1201 1203 var query_params = {};
1202 1204 // put extra data into POST
1203 1205 if (extra_data !== undefined && (typeof extra_data === 'object')){
1204 1206 for(var k in extra_data){
1205 1207 query_params[k] = extra_data[k];
1206 1208 }
1207 1209 }
1208 1210
1209 1211 if (obj_type=='user'){
1210 1212 query_params['user_id'] = obj_id;
1211 1213 query_params['obj_type'] = 'user';
1212 1214 }
1213 1215 else if (obj_type=='user_group'){
1214 1216 query_params['user_group_id'] = obj_id;
1215 1217 query_params['obj_type'] = 'user_group';
1216 1218 }
1217 1219
1218 1220 ajaxPOST(url, query_params, success, failure);
1219 1221 }
1220 1222
1221 1223 /* Multi selectors */
1222 1224
1223 1225 function MultiSelectWidget(selected_id, available_id, form_id){
1224 1226 var $availableselect = $('#' + available_id);
1225 1227 var $selectedselect = $('#' + selected_id);
1226 1228
1227 1229 //fill available only with those not in selected
1228 1230 var $selectedoptions = $selectedselect.children('option');
1229 1231 $availableselect.children('option').filter(function(i, e){
1230 1232 for(var j = 0, node; node = $selectedoptions[j]; j++){
1231 1233 if(node.value == e.value){
1232 1234 return true;
1233 1235 }
1234 1236 }
1235 1237 return false;
1236 1238 }).remove();
1237 1239
1238 1240 $('#add_element').click(function(){
1239 1241 $selectedselect.append($availableselect.children('option:selected'));
1240 1242 });
1241 1243 $('#remove_element').click(function(){
1242 1244 $availableselect.append($selectedselect.children('option:selected'));
1243 1245 });
1244 1246
1245 1247 $('#'+form_id).submit(function(){
1246 1248 $selectedselect.children('option').each(function(i, e){
1247 1249 e.selected = 'selected';
1248 1250 });
1249 1251 });
1250 1252 }
1251 1253
1252 1254
1253 1255 /**
1254 1256 Branch Sorting callback for select2, modifying the filtered result so prefix
1255 1257 matches come before matches in the line.
1256 1258 **/
1257 1259 function branchSort(results, container, query) {
1258 1260 if (query.term) {
1259 1261 return results.sort(function (a, b) {
1260 1262 // Put closed branches after open ones (a bit of a hack ...)
1261 1263 var aClosed = a.text.indexOf("(closed)") > -1,
1262 1264 bClosed = b.text.indexOf("(closed)") > -1;
1263 1265 if (aClosed && !bClosed) {
1264 1266 return 1;
1265 1267 }
1266 1268 if (bClosed && !aClosed) {
1267 1269 return -1;
1268 1270 }
1269 1271
1270 1272 // Put early (especially prefix) matches before later matches
1271 1273 var aPos = a.text.toLowerCase().indexOf(query.term.toLowerCase()),
1272 1274 bPos = b.text.toLowerCase().indexOf(query.term.toLowerCase());
1273 1275 if (aPos < bPos) {
1274 1276 return -1;
1275 1277 }
1276 1278 if (bPos < aPos) {
1277 1279 return 1;
1278 1280 }
1279 1281
1280 1282 // Default sorting
1281 1283 if (a.text > b.text) {
1282 1284 return 1;
1283 1285 }
1284 1286 if (a.text < b.text) {
1285 1287 return -1;
1286 1288 }
1287 1289 return 0;
1288 1290 });
1289 1291 }
1290 1292 return results;
1291 1293 }
1292 1294
1293 1295 function prefixFirstSort(results, container, query) {
1294 1296 if (query.term) {
1295 1297 return results.sort(function (a, b) {
1296 1298 // if parent node, no sorting
1297 1299 if (a.children != undefined || b.children != undefined) {
1298 1300 return 0;
1299 1301 }
1300 1302
1301 1303 // Put prefix matches before matches in the line
1302 1304 var aPos = a.text.toLowerCase().indexOf(query.term.toLowerCase()),
1303 1305 bPos = b.text.toLowerCase().indexOf(query.term.toLowerCase());
1304 1306 if (aPos === 0 && bPos !== 0) {
1305 1307 return -1;
1306 1308 }
1307 1309 if (bPos === 0 && aPos !== 0) {
1308 1310 return 1;
1309 1311 }
1310 1312
1311 1313 // Default sorting
1312 1314 if (a.text > b.text) {
1313 1315 return 1;
1314 1316 }
1315 1317 if (a.text < b.text) {
1316 1318 return -1;
1317 1319 }
1318 1320 return 0;
1319 1321 });
1320 1322 }
1321 1323 return results;
1322 1324 }
1323 1325
1324 1326 /* Helper for jQuery DataTables */
1325 1327
1326 1328 function updateRowCountCallback($elem, onlyDisplayed) {
1327 1329 return function drawCallback() {
1328 1330 var info = this.api().page.info(),
1329 1331 count = onlyDisplayed === true ? info.recordsDisplay : info.recordsTotal;
1330 1332 $elem.html(count);
1331 1333 }
1332 1334 }
1333 1335
1334 1336
1335 1337 /**
1336 1338 * activate changeset parent/child navigation links
1337 1339 */
1338 1340 function activate_parent_child_links(){
1339 1341
1340 1342 $('.parent-child-link').on('click', function(e){
1341 1343 var $this = $(this);
1342 1344 //fetch via ajax what is going to be the next link, if we have
1343 1345 //>1 links show them to user to choose
1344 1346 if(!$this.hasClass('disabled')){
1345 1347 $.ajax({
1346 1348 url: $this.data('ajax-url'),
1347 1349 success: function(data) {
1348 1350 var repo_name = $this.data('reponame');
1349 1351 if(data.results.length === 0){
1350 1352 $this.addClass('disabled');
1351 1353 $this.text(_TM['No revisions']);
1352 1354 }
1353 1355 if(data.results.length === 1){
1354 1356 var commit = data.results[0];
1355 1357 window.location = pyroutes.url('changeset_home', {'repo_name': repo_name, 'revision': commit.raw_id});
1356 1358 }
1357 1359 else if(data.results.length > 1){
1358 1360 $this.addClass('disabled');
1359 1361 $this.addClass('double');
1360 1362 var template =
1361 1363 ($this.data('linktype') == 'parent' ? '<i class="icon-left-open"/> ' : '') +
1362 1364 '<a title="__title__" href="__url__">__rev__</a>' +
1363 1365 ($this.data('linktype') == 'child' ? ' <i class="icon-right-open"/>' : '');
1364 1366 var _html = [];
1365 1367 for(var i = 0; i < data.results.length; i++){
1366 1368 _html.push(template
1367 1369 .replace('__rev__', 'r{0}:{1}'.format(data.results[i].revision, data.results[i].raw_id.substr(0, 6)))
1368 1370 .replace('__title__', data.results[i].message.html_escape())
1369 1371 .replace('__url__', pyroutes.url('changeset_home', {
1370 1372 'repo_name': repo_name,
1371 1373 'revision': data.results[i].raw_id}))
1372 1374 );
1373 1375 }
1374 1376 $this.html(_html.join('<br/>'));
1375 1377 }
1376 1378 }
1377 1379 });
1378 1380 e.preventDefault();
1379 1381 }
1380 1382 });
1381 1383 }
@@ -1,113 +1,113 b''
1 1 ${h.form(url('edit_user_group_perms_update', id=c.user_group.users_group_id))}
2 2 <div class="form">
3 3 <div>
4 4 <div>
5 5 <table id="permissions_manage" class="table">
6 6 <tr>
7 7 <td>${_('None')}</td>
8 8 <td>${_('Read')}</td>
9 9 <td>${_('Write')}</td>
10 10 <td>${_('Admin')}</td>
11 11 <td>${_('User/User Group')}</td>
12 12 <td></td>
13 13 </tr>
14 14 ## USERS
15 15 %for r2p in c.user_group.user_user_group_to_perm:
16 16 ##forbid revoking permission from yourself, except if you're an super admin
17 17 <tr id="id${id(r2p.user.username)}">
18 18 %if request.authuser.user_id != r2p.user.user_id or request.authuser.is_admin:
19 19 <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.none')}</td>
20 20 <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.read')}</td>
21 21 <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.write')}</td>
22 22 <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.admin')}</td>
23 23 <td>
24 24 ${h.gravatar(r2p.user.email, cls="perm-gravatar", size=14)}
25 25 %if h.HasPermissionAny('hg.admin')() and r2p.user.username != 'default':
26 26 <a href="${h.url('edit_user',id=r2p.user.user_id)}">${r2p.user.username}</a>
27 27 %else:
28 28 ${r2p.user.username if r2p.user.username != 'default' else _('Default')}
29 29 %endif
30 30 </td>
31 31 <td>
32 32 %if r2p.user.username !='default':
33 33 <button type="button" class="btn btn-default btn-xs" onclick="ajaxActionRevoke(${r2p.user.user_id}, 'user', '${'id%s'%id(r2p.user.username)}', '${r2p.user.username}')">
34 34 <i class="icon-minus-circled"></i>${_('Revoke')}
35 35 </button>
36 36 %endif
37 37 </td>
38 38 %else:
39 39 <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.none', disabled="disabled")}</td>
40 40 <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.read', disabled="disabled")}</td>
41 41 <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.write', disabled="disabled")}</td>
42 42 <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.admin', disabled="disabled")}</td>
43 43 <td>
44 44 ${h.gravatar(r2p.user.email, cls="perm-gravatar", size=14)}
45 45 ${r2p.user.username if r2p.user.username != 'default' else _('Default')}
46 46 </td>
47 47 <td><i class="icon-user"></i>${_('Admin')}</td>
48 48 %endif
49 49 </tr>
50 50 %endfor
51 51
52 52 ## USER GROUPS
53 53 %for g2p in c.user_group.user_group_user_group_to_perm:
54 54 <tr id="id${id(g2p.user_group.users_group_name)}">
55 55 <td>${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.none')}</td>
56 56 <td>${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.read')}</td>
57 57 <td>${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.write')}</td>
58 58 <td>${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.admin')}</td>
59 59 <td>
60 60 <i class="icon-users"></i>
61 61 %if h.HasPermissionAny('hg.admin')():
62 62 <a href="${h.url('edit_users_group',id=g2p.user_group.users_group_id)}">
63 63 ${g2p.user_group.users_group_name}
64 64 </a>
65 65 %else:
66 66 ${g2p.user_group.users_group_name}
67 67 %endif
68 68 </td>
69 69 <td>
70 <button class="btn btn-default btn-xs" onclick="ajaxActionRevoke(${g2p.user_group.users_group_id}, 'user_group', '${'id%s'%id(g2p.user_group.users_group_name)}', '${g2p.user_group.users_group_name}')">
70 <button type="button" class="btn btn-default btn-xs" onclick="ajaxActionRevoke(${g2p.user_group.users_group_id}, 'user_group', '${'id%s'%id(g2p.user_group.users_group_name)}', '${g2p.user_group.users_group_name}')">
71 71 <i class="icon-minus-circled"></i>${_('Revoke')}
72 72 </button>
73 73 </td>
74 74 </tr>
75 75 %endfor
76 76 ## New entries added by addPermAction here.
77 77 <tr class="new_members last_new_member" id="add_perm_input"><td colspan="6"></td></tr>
78 78 <tr>
79 79 <td colspan="6">
80 80 <button type="button" id="add_perm" class="btn btn-link btn-xs">
81 81 <i class="icon-plus"></i>${_('Add new')}
82 82 </button>
83 83 </td>
84 84 </tr>
85 85 </table>
86 86 </div>
87 87 <div class="buttons">
88 88 ${h.submit('save',_('Save'),class_="btn btn-default")}
89 89 ${h.reset('reset',_('Reset'),class_="btn btn-default")}
90 90 </div>
91 91 </div>
92 92 </div>
93 93 ${h.end_form()}
94 94
95 95 <script>
96 96 'use strict';
97 97 function ajaxActionRevoke(obj_id, obj_type, field_id, obj_name) {
98 98 let url = ${h.js(h.url('edit_user_group_perms_delete', id=c.user_group.users_group_id))};
99 99 var revoke_msg = _TM['Confirm to revoke permission for {0}: {1}?'].format(obj_type.replace('_', ' '), obj_name);
100 100 if (confirm(revoke_msg)){
101 101 ajaxActionRevokePermission(url, obj_id, obj_type, field_id);
102 102 }
103 103 }
104 104
105 105 $(document).ready(function () {
106 106 if (!$('#perm_new_member_name').hasClass('error')) {
107 107 $('#add_perm_input').hide();
108 108 }
109 109 $('#add_perm').click(function () {
110 110 addPermAction('usergroup');
111 111 });
112 112 });
113 113 </script>
@@ -1,150 +1,150 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 <%def name="diff_block(a_repo_name, a_ref_type, a_ref_name, a_rev,
4 4 cs_repo_name, cs_ref_name, cs_ref_type, cs_rev,
5 5 file_diff_data)">
6 6 <div class="diff-collapse">
7 <button data-target="${'diff-container-%s' % (id(file_diff_data))}" class="diff-collapse-button btn btn-link btn-sm">&uarr; ${_('Collapse Diff')} &uarr;</button>
7 <button type='button', data-target="${'diff-container-%s' % (id(file_diff_data))}" class="diff-collapse-button btn btn-link btn-sm">&uarr; ${_('Collapse Diff')} &uarr;</button>
8 8 </div>
9 9 %for id_fid, url_fid, op, a_filename, cs_filename, diff, stats in file_diff_data:
10 10 ${diff_block_diffblock(id_fid, url_fid, op, diff,
11 11 a_repo_name, a_rev, a_ref_type, a_ref_name, a_filename,
12 12 cs_repo_name, cs_rev, cs_ref_type, cs_ref_name, cs_filename,
13 13 'diff-container-%s' % id(file_diff_data))}
14 14 %endfor
15 15 </%def>
16 16
17 17 <%def name="diff_block_diffblock(id_fid, url_fid, op, diff,
18 18 a_repo_name, a_rev, a_ref_type, a_ref_name, a_filename,
19 19 cs_repo_name, cs_rev, cs_ref_type, cs_ref_name, cs_filename, cls)"
20 20 >
21 21 <div id="${id_fid}_target"></div>
22 22 <div id="${id_fid}" class="panel panel-default ${cls}">
23 23 <div class="panel-heading clearfix">
24 24 <div class="pull-left">
25 25 ${cs_filename}
26 26 </div>
27 27 <div class="pull-left diff-actions">
28 28 <span>
29 29 %if op == 'A':
30 30 <span class="no-file" data-toggle="tooltip" title="${_('No file before')}">
31 31 <i class="icon-minus-circled"></i></span>
32 32 %else:
33 33 <a href="${h.url('files_home', repo_name=a_repo_name, f_path=a_filename, revision=a_rev)}" data-toggle="tooltip" title="${_('File before')}">
34 34 <i class="icon-doc"></i></a>
35 35 %endif
36 36
37 37 %if op == 'A':
38 38 <span class="arrow" data-toggle="tooltip" title="${_('Added')}">&#10142;</span>
39 39 %elif op == 'M':
40 40 <span class="arrow" data-toggle="tooltip" title="${_('Modified')}">&#10142;</span>
41 41 %elif op == 'D':
42 42 <span class="arrow" data-toggle="tooltip" title="${_('Deleted')}">&#10142;</span>
43 43 %elif op == 'R':
44 44 <span class="arrow" data-toggle="tooltip" title="${_('Renamed')}">&#10142;</span>
45 45 %elif op is None:
46 46 <span class="arrow" data-toggle="tooltip" title="${_('No change')}">&#10142;</span>
47 47 %else:
48 48 <span class="arrow" data-toggle="tooltip" title="${_('Unknown operation: %r') % op}">&#10142;</span>
49 49 %endif
50 50
51 51 %if op == 'D':
52 52 <span class="no-file" data-toggle="tooltip" title="${_('No file after')}">
53 53 <i class="icon-minus-circled"></i></span>
54 54 %else:
55 55 <a href="${h.url('files_home', repo_name=cs_repo_name, f_path=cs_filename, revision=cs_rev)}" data-toggle="tooltip" title="${_('File after')}">
56 56 <i class="icon-doc"></i></a>
57 57 %endif
58 58 </span>
59 59
60 60 <a href="${h.url('files_diff_home',repo_name=cs_repo_name,f_path=cs_filename,diff2=cs_rev,diff1=a_rev,diff='diff',fulldiff=1)}" data-toggle="tooltip" title="${_('Show full diff for this file')}">
61 61 <i class="icon-file-code"></i></a>
62 62 <a href="${h.url('files_diff_2way_home',repo_name=cs_repo_name,f_path=cs_filename,diff2=cs_rev,diff1=a_rev,diff='diff',fulldiff=1)}" data-toggle="tooltip" title="${_('Show full side-by-side diff for this file')}">
63 63 <i class="icon-docs"></i></a>
64 64 <a href="${h.url('files_diff_home',repo_name=cs_repo_name,f_path=cs_filename,diff2=cs_rev,diff1=a_rev,diff='raw')}" data-toggle="tooltip" title="${_('Raw diff for this file')}">
65 65 <i class="icon-diff"></i></a>
66 66 <a href="${h.url('files_diff_home',repo_name=cs_repo_name,f_path=cs_filename,diff2=cs_rev,diff1=a_rev,diff='download')}" data-toggle="tooltip" title="${_('Download diff for this file')}">
67 67 <i class="icon-floppy"></i></a>
68 68 ${h.ignore_whitespace_link(request.GET, id_fid)}
69 69 ${h.increase_context_link(request.GET, id_fid)}
70 70 </div>
71 71 <div class="pull-right">
72 72 ${_('Show inline comments')}
73 73 ${h.checkbox('checkbox-show-inline-' + id_fid, checked="checked",class_="show-inline-comments",**{'data-for':id_fid})}
74 74 </div>
75 75 </div>
76 76 <div class="no-padding panel-body" data-f_path="${cs_filename}">
77 77 ${diff|n}
78 78 %if op and cs_filename.rsplit('.')[-1] in ['png', 'gif', 'jpg', 'bmp']:
79 79 <div class="btn btn-image-diff-show">Show images</div>
80 80 %if op == 'M':
81 81 <div id="${id_fid}_image-diff" class="btn btn-image-diff-swap" style="display:none">Press to swap images</div>
82 82 %endif
83 83 <div>
84 84 %if op in 'DM':
85 85 <img id="${id_fid}_image-diff-img-a" class="img-diff img-diff-swapable" style="display:none"
86 86 realsrc="${h.url('files_raw_home',repo_name=a_repo_name,revision=a_rev,f_path=a_filename)}" />
87 87 %endif
88 88 %if op in 'AM':
89 89 <img id="${id_fid}_image-diff-img-b" class="img-diff img-diff-swapable" style="display:none"
90 90 realsrc="${h.url('files_raw_home',repo_name=cs_repo_name,revision=cs_rev,f_path=cs_filename)}" />
91 91 %endif
92 92 </div>
93 93 %endif
94 94 </div>
95 95 </div>
96 96 </%def>
97 97
98 98 <%def name="diff_block_js()">
99 99 <script>
100 100 'use strict';
101 101 $(document).ready(function(){
102 102 $('.btn-image-diff-show').click(function(){
103 103 $('.btn-image-diff-show').hide();
104 104 $('.btn-image-diff-swap').show();
105 105 $('.img-diff-swapable')
106 106 .each(function(i,e){
107 107 $(e).prop('src', $(e).attr('realsrc'));
108 108 })
109 109 .show();
110 110 });
111 111
112 112 $('.btn-image-diff-swap').mousedown(function(e){
113 113 $('#'+e.currentTarget.id+'-img-a.img-diff-swapable')
114 114 .before($('#'+e.currentTarget.id+'-img-b.img-diff-swapable'));
115 115 });
116 116 function reset(e){
117 117 $('#'+e.currentTarget.id+'-img-a.img-diff-swapable')
118 118 .after($('#'+e.currentTarget.id+'-img-b.img-diff-swapable'));
119 119 }
120 120 $('.btn-image-diff-swap').mouseup(reset);
121 121 $('.btn-image-diff-swap').mouseleave(reset);
122 122
123 123 $('.diff-collapse-button').click(function(e) {
124 124 $('.diff_block').toggleClass('hidden');
125 125 var $button = $(e.currentTarget);
126 126 var $target = $('.' + $button.data('target'));
127 127 if($target.hasClass('hidden')){
128 128 $target.removeClass('hidden');
129 129 $button.html("&uarr; {0} &uarr;".format(_TM['Collapse Diff']));
130 130 }
131 131 else if(!$target.hasClass('hidden')){
132 132 $target.addClass('hidden');
133 133 $button.html("&darr; {0} &darr;".format(_TM['Expand Diff']));
134 134 }
135 135 });
136 136 $('.show-inline-comments').change(function(e){
137 137 var target = e.currentTarget;
138 138 if(target == null){
139 139 target = this;
140 140 }
141 141 var boxid = $(target).data('for');
142 142 if(target.checked){
143 143 $('#{0} .inline-comments'.format(boxid)).show();
144 144 }else{
145 145 $('#{0} .inline-comments'.format(boxid)).hide();
146 146 }
147 147 });
148 148 });
149 149 </script>
150 150 </%def>
@@ -1,149 +1,149 b''
1 1 ## DATA TABLE RE USABLE ELEMENTS
2 2 ## usage:
3 3 ## <%namespace name="dt" file="/data_table/_dt_elements.html"/>
4 4
5 5 <%namespace name="base" file="/base/base.html"/>
6 6
7 7 <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False)">
8 8 <%
9 9 def get_name(name,short_name=short_name):
10 10 if short_name:
11 11 return name.split('/')[-1]
12 12 else:
13 13 return name
14 14 %>
15 15 <div class="dt_repo ${'dt_repo_pending' if rstate == 'repo_state_pending' else ''}">
16 16 ${base.repolabel(rtype)}
17 17 <a href="${webutils.url('summary_home', repo_name=name)}">
18 18 ${get_name(name)}
19 19 </a>
20 20 %if private and c.visual.show_private_icon:
21 21 <i class="icon-lock" title="${_('Private repository')}"></i>
22 22 %elif not private and c.visual.show_public_icon:
23 23 <i class="icon-globe" title="${_('Public repository')}"></i>
24 24 %endif
25 25 %if fork_of:
26 26 <a href="${webutils.url('summary_home',repo_name=fork_of.repo_name)}"><i class="icon-fork"></i></a>
27 27 %endif
28 28 %if rstate == 'repo_state_pending':
29 29 <i class="icon-wrench" title="${_('Repository creation in progress...')}"></i>
30 30 %endif
31 31 </div>
32 32 </%def>
33 33
34 34 <%def name="following(repo_id, repo_following)">
35 35 %if request.authuser.username != 'default':
36 36 <a href="#" class="${'following' if repo_following else 'follow'}" onclick="return toggleFollowingRepo(this, ${repo_id});"><i class="list-extra icon-heart-empty show-follow" title="${_('Follow')}"></i><i class="list-extra icon-heart show-following" title="${_('Unfollow')}"></i></a>
37 37 %endif
38 38 </%def>
39 39
40 40 <%def name="last_change(last_change)">
41 41 <span data-toggle="tooltip" title="${webutils.fmt_date(last_change)}" date="${last_change}">${webutils.age(last_change)}</span>
42 42 </%def>
43 43
44 44 <%def name="revision(name,rev,tip,author,last_msg)">
45 45 %if rev >= 0:
46 46 <a data-toggle="popover" title="${author | entity}" data-content="${last_msg | entity}" class="changeset_hash" href="${webutils.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,webutils.short_id(tip))}</a>
47 47 %else:
48 48 ${_('No changesets yet')}
49 49 %endif
50 50 </%def>
51 51
52 52 <%def name="rss(name)">
53 53 %if request.authuser.username != 'default':
54 54 <a title="${_('Subscribe to %s rss feed')% name}" href="${webutils.url('rss_feed_home',repo_name=name,api_key=request.authuser.api_key)}"><i class="icon-rss-squared"></i></a>
55 55 %else:
56 56 <a title="${_('Subscribe to %s rss feed')% name}" href="${webutils.url('rss_feed_home',repo_name=name)}"><i class="icon-rss-squared"></i></a>
57 57 %endif
58 58 </%def>
59 59
60 60 <%def name="atom(name)">
61 61 %if request.authuser.username != 'default':
62 62 <a title="${_('Subscribe to %s atom feed')% name}" href="${webutils.url('atom_feed_home',repo_name=name,api_key=request.authuser.api_key)}"><i class="icon-rss-squared"></i></a>
63 63 %else:
64 64 <a title="${_('Subscribe to %s atom feed')% name}" href="${webutils.url('atom_feed_home',repo_name=name)}"><i class="icon-rss-squared"></i></a>
65 65 %endif
66 66 </%def>
67 67
68 68 <%def name="repo_actions(repo_name)">
69 69 <a href="${webutils.url('edit_repo',repo_name=repo_name)}" title="${_('Edit')}" class="btn btn-default btn-xs">
70 70 <i class="icon-pencil"></i>${_('Edit')}
71 71 </a>
72 72 ${webutils.form(webutils.url('delete_repo', repo_name=repo_name))}
73 <button name="${'remove_%s' % repo_name}" class="btn btn-default btn-xs"
73 <button type="submit" name="${'remove_%s' % repo_name}" class="btn btn-default btn-xs"
74 74 onclick="return confirm('${_('Confirm to delete this repository: %s') % repo_name}');">
75 75 <i class="icon-trashcan"></i>${_('Delete')}
76 76 </button>
77 77 ${webutils.end_form()}
78 78 </%def>
79 79
80 80 <%def name="repo_state(repo_state)">
81 81 %if repo_state == u'repo_state_pending':
82 82 <div class="label label-info">${_('Creating')}</div>
83 83 %elif repo_state == u'repo_state_created':
84 84 <div class="label label-success">${_('Created')}</div>
85 85 %else:
86 86 <div class="label label-danger" title="${repo_state}">invalid</div>
87 87 %endif
88 88 </%def>
89 89
90 90 <%def name="user_actions(user_id, username)">
91 91 <a href="${webutils.url('edit_user',id=user_id)}" title="${_('Edit')}" class="btn btn-default btn-xs">
92 92 <i class="icon-pencil"></i>${_('Edit')}
93 93 </a>
94 94 ${webutils.form(webutils.url('delete_user', id=user_id))}
95 <button id="${'remove_user_%s' % user_id}" name="${'remove_user_%s' % repo_name}" class="btn btn-default btn-xs" title="${_('Delete')}"
95 <button type="submit" id="${'remove_user_%s' % user_id}" name="${'remove_user_%s' % repo_name}" class="btn btn-default btn-xs" title="${_('Delete')}"
96 96 onclick="return confirm('${_('Confirm to delete this user: %s') % username}');">
97 97 <i class="icon-trashcan"></i>${_('Delete')}
98 98 </button>
99 99 ${webutils.end_form()}
100 100 </%def>
101 101
102 102 <%def name="user_group_actions(user_group_id, user_group_name)">
103 103 <a href="${webutils.url('edit_users_group', id=user_group_id)}" title="${_('Edit')}" class="btn btn-default btn-xs">
104 104 <i class="icon-pencil"></i>${_('Edit')}
105 105 </a>
106 106 ${webutils.form(webutils.url('delete_users_group', id=user_group_id))}
107 <button id="${'remove_group_%s' % user_group_id}" name="${'remove_user_%s' % repo_name}" class="btn btn-default btn-xs" title="${_('Delete')}"
107 <button type="submit" id="${'remove_group_%s' % user_group_id}" name="${'remove_user_%s' % repo_name}" class="btn btn-default btn-xs" title="${_('Delete')}"
108 108 onclick="return confirm('${_('Confirm to delete this user group: %s') % user_group_name}');">
109 109 <i class="icon-trashcan"></i>${_('Delete')}
110 110 </button>
111 111 ${webutils.end_form()}
112 112 </%def>
113 113
114 114 <%def name="group_name_html(group_name,name)">
115 115 <div class="dt_repo">
116 116 <i class="icon-folder"></i>
117 117 <a href="${webutils.url('repos_group_home',group_name=group_name)}">${name}</a>
118 118 </div>
119 119 </%def>
120 120
121 121 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
122 122 <a href="${webutils.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}" class="btn btn-default btn-xs">
123 123 <i class="icon-pencil"></i>${_('Edit')}
124 124 </a>
125 125 ${webutils.form(webutils.url('delete_repo_group', group_name=repo_group_name))}
126 <button id="${'remove_%s' % repo_group_name}" name="${'remove_%s' % repo_group_name}" class="btn btn-default btn-xs" title="${_('Delete')}"
126 <button type="submit" id="${'remove_%s' % repo_group_name}" name="${'remove_%s' % repo_group_name}" class="btn btn-default btn-xs" title="${_('Delete')}"
127 127 onclick="return confirm('${ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)}')">
128 128 <i class="icon-trashcan"></i>${_('Delete')}
129 129 </button>
130 130 ${webutils.end_form()}
131 131 </%def>
132 132
133 133 <%def name="user_name(user_id, username)">
134 134 ${webutils.link_to(username,webutils.url('edit_user', id=user_id))}
135 135 </%def>
136 136
137 137 <%def name="repo_group_name(repo_group_name, children_groups)">
138 138 <div class="text-nowrap">
139 139 <a href="${webutils.url('repos_group_home',group_name=repo_group_name)}">
140 140 <i class="icon-folder" title="${_('Repository group')}"></i>${webutils.literal(' &raquo; ').join(children_groups)}</a>
141 141 </div>
142 142 </%def>
143 143
144 144 <%def name="user_group_name(user_group_id, user_group_name)">
145 145 <div class="text-nowrap">
146 146 <a href="${webutils.url('edit_users_group', id=user_group_id)}">
147 147 <i class="icon-users" title="${_('User group')}"></i>${user_group_name}</a>
148 148 </div>
149 149 </%def>
General Comments 0
You need to be logged in to leave comments. Login now