##// END OF EJS Templates
diffs: added scroll down/scroll up helper. Fixes #5643
marcink -
r4595:82d4afd7 stable
parent child Browse files
Show More
@@ -1,626 +1,630 b''
1 1
2 2
3 3 //BUTTONS
4 4 button,
5 5 .btn,
6 6 input[type="button"] {
7 7 -webkit-appearance: none;
8 8 display: inline-block;
9 9 margin: 0 @padding/3 0 0;
10 10 padding: @button-padding;
11 11 text-align: center;
12 12 font-size: @basefontsize;
13 13 line-height: 1em;
14 14 font-family: @text-light;
15 15 text-decoration: none;
16 16 text-shadow: none;
17 17 color: @grey2;
18 18 background-color: white;
19 19 background-image: none;
20 20 border: none;
21 21 .border ( @border-thickness-buttons, @grey5 );
22 22 .border-radius (@border-radius);
23 23 cursor: pointer;
24 24 white-space: nowrap;
25 25 -webkit-transition: background .3s,color .3s;
26 26 -moz-transition: background .3s,color .3s;
27 27 -o-transition: background .3s,color .3s;
28 28 transition: background .3s,color .3s;
29 29 box-shadow: @button-shadow;
30 30 -webkit-box-shadow: @button-shadow;
31 31
32 32
33 33
34 34 a {
35 35 display: block;
36 36 margin: 0;
37 37 padding: 0;
38 38 color: inherit;
39 39 text-decoration: none;
40 40
41 41 &:hover {
42 42 text-decoration: none;
43 43 }
44 44 }
45 45
46 46 &:focus,
47 47 &:active {
48 48 outline:none;
49 49 }
50 50
51 51 &:hover {
52 52 color: @rcdarkblue;
53 53 background-color: @grey6;
54 54
55 55 }
56 56
57 57 &.btn-active {
58 58 color: @rcdarkblue;
59 59 background-color: @grey6;
60 60 }
61 61
62 62 .icon-remove {
63 63 display: none;
64 64 }
65 65
66 66 //disabled buttons
67 67 //last; overrides any other styles
68 68 &:disabled {
69 69 opacity: .7;
70 70 cursor: auto;
71 71 background-color: white;
72 72 color: @grey4;
73 73 text-shadow: none;
74 74 }
75 75
76 76 &.no-margin {
77 77 margin: 0 0 0 0;
78 78 }
79 79
80 80
81 81
82 82 }
83 83
84 84
85 85 .btn-default {
86 86 border: @border-thickness solid @grey5;
87 87 background-image: none;
88 88 color: @grey2;
89 89
90 90 a {
91 91 color: @grey2;
92 92 }
93 93
94 94 &:hover,
95 95 &.active {
96 96 color: @rcdarkblue;
97 97 background-color: @white;
98 98 .border ( @border-thickness, @grey4 );
99 99
100 100 a {
101 101 color: @grey2;
102 102 }
103 103 }
104 104 &:disabled {
105 105 .border ( @border-thickness-buttons, @grey5 );
106 106 background-color: transparent;
107 107 }
108 108 &.btn-active {
109 109 color: @rcdarkblue;
110 110 background-color: @white;
111 111 .border ( @border-thickness, @rcdarkblue );
112 112 }
113 113 }
114 114
115 115 .btn-primary,
116 116 .btn-small, /* TODO: anderson: remove .btn-small to not mix with the new btn-sm */
117 117 .btn-success {
118 118 .border ( @border-thickness, @rcblue );
119 119 background-color: @rcblue;
120 120 color: white;
121 121
122 122 a {
123 123 color: white;
124 124 }
125 125
126 126 &:hover,
127 127 &.active {
128 128 .border ( @border-thickness, @rcdarkblue );
129 129 color: white;
130 130 background-color: @rcdarkblue;
131 131
132 132 a {
133 133 color: white;
134 134 }
135 135 }
136 136 &:disabled {
137 137 background-color: @rcblue;
138 138 }
139 139 }
140 140
141 141 .btn-secondary {
142 142 &:extend(.btn-default);
143 143
144 144 background-color: white;
145 145
146 146 &:focus {
147 147 outline: 0;
148 148 }
149 149
150 150 &:hover {
151 151 &:extend(.btn-default:hover);
152 152 }
153 153
154 154 &.btn-link {
155 155 &:extend(.btn-link);
156 156 color: @rcblue;
157 157 }
158 158
159 159 &:disabled {
160 160 color: @rcblue;
161 161 background-color: white;
162 162 }
163 163 }
164 164
165 165 .btn-danger,
166 166 .revoke_perm,
167 167 .btn-x,
168 168 .form .action_button.btn-x {
169 169 .border ( @border-thickness, @alert2 );
170 170 background-color: white;
171 171 color: @alert2;
172 172
173 173 a {
174 174 color: @alert2;
175 175 }
176 176
177 177 &:hover,
178 178 &.active {
179 179 .border ( @border-thickness, @alert2 );
180 180 color: white;
181 181 background-color: @alert2;
182 182
183 183 a {
184 184 color: white;
185 185 }
186 186 }
187 187
188 188 i {
189 189 display:none;
190 190 }
191 191
192 192 &:disabled {
193 193 background-color: white;
194 194 color: @alert2;
195 195 }
196 196 }
197 197
198 198 .btn-warning {
199 199 .border ( @border-thickness, @alert3 );
200 200 background-color: white;
201 201 color: @alert3;
202 202
203 203 a {
204 204 color: @alert3;
205 205 }
206 206
207 207 &:hover,
208 208 &.active {
209 209 .border ( @border-thickness, @alert3 );
210 210 color: white;
211 211 background-color: @alert3;
212 212
213 213 a {
214 214 color: white;
215 215 }
216 216 }
217 217
218 218 i {
219 219 display:none;
220 220 }
221 221
222 222 &:disabled {
223 223 background-color: white;
224 224 color: @alert3;
225 225 }
226 226 }
227 227
228 228
229 229 .btn-approved-status {
230 230 .border ( @border-thickness, @alert1 );
231 231 background-color: white;
232 232 color: @alert1;
233 233
234 234 }
235 235
236 236 .btn-rejected-status {
237 237 .border ( @border-thickness, @alert2 );
238 238 background-color: white;
239 239 color: @alert2;
240 240 }
241 241
242 242 .btn-sm,
243 243 .btn-mini,
244 244 .field-sm .btn {
245 245 padding: @padding/3;
246 246 }
247 247
248 248 .btn-xs {
249 249 padding: @padding/4;
250 250 }
251 251
252 252 .btn-lg {
253 253 padding: @padding * 1.2;
254 254 }
255 255
256 256 .btn-group {
257 257 display: inline-block;
258 258 .btn {
259 259 float: left;
260 260 margin: 0 0 0 0;
261 261 // first item
262 262 &:first-of-type:not(:last-of-type) {
263 263 border-radius: @border-radius 0 0 @border-radius;
264 264
265 265 }
266 // 2nd, if only 2 elements are there
267 &:nth-of-type(2) {
268 border-left-width: 0;
269 }
266 270 // middle elements
267 271 &:not(:first-of-type):not(:last-of-type) {
268 272 border-radius: 0;
269 273 border-left-width: 0;
270 274 border-right-width: 0;
271 275 }
272 276 // last item
273 277 &:last-of-type:not(:first-of-type) {
274 278 border-radius: 0 @border-radius @border-radius 0;
275 279 }
276 280
277 281 &:only-child {
278 282 border-radius: @border-radius;
279 283 }
280 284 }
281 285
282 286 }
283 287
284 288
285 289 .btn-group-actions {
286 290 position: relative;
287 291 z-index: 50;
288 292
289 293 &:not(.open) .btn-action-switcher-container {
290 294 display: none;
291 295 }
292 296
293 297 .btn-more-option {
294 298 margin-left: -1px;
295 299 padding-left: 2px;
296 300 padding-right: 2px;
297 301 }
298 302 }
299 303
300 304
301 305 .btn-action-switcher-container {
302 306 position: absolute;
303 307 top: 100%;
304 308
305 309 &.left-align {
306 310 left: 0;
307 311 }
308 312 &.right-align {
309 313 right: 0;
310 314 }
311 315
312 316 }
313 317
314 318 .btn-action-switcher {
315 319 display: block;
316 320 position: relative;
317 321 z-index: 300;
318 322 max-width: 600px;
319 323 margin-top: 4px;
320 324 margin-bottom: 24px;
321 325 font-size: 14px;
322 326 font-weight: 400;
323 327 padding: 8px 0;
324 328 background-color: #fff;
325 329 border: 1px solid @grey4;
326 330 border-radius: 3px;
327 331 box-shadow: @dropdown-shadow;
328 332 overflow: auto;
329 333
330 334 li {
331 335 display: block;
332 336 text-align: left;
333 337 list-style: none;
334 338 padding: 5px 10px;
335 339 }
336 340
337 341 li .action-help-block {
338 342 font-size: 10px;
339 343 line-height: normal;
340 344 color: @grey4;
341 345 }
342 346
343 347 }
344 348
345 349 .btn-link {
346 350 background: transparent;
347 351 border: none;
348 352 padding: 0;
349 353 color: @rcblue;
350 354
351 355 &:hover {
352 356 background: transparent;
353 357 border: none;
354 358 color: @rcdarkblue;
355 359 }
356 360
357 361 //disabled buttons
358 362 //last; overrides any other styles
359 363 &:disabled {
360 364 opacity: .7;
361 365 cursor: auto;
362 366 background-color: white;
363 367 color: @grey4;
364 368 text-shadow: none;
365 369 }
366 370
367 371 // TODO: johbo: Check if we can avoid this, indicates that the structure
368 372 // is not yet good.
369 373 // lisa: The button CSS reflects the button HTML; both need a cleanup.
370 374 &.btn-danger {
371 375 color: @alert2;
372 376
373 377 &:hover {
374 378 color: darken(@alert2, 30%);
375 379 }
376 380
377 381 &:disabled {
378 382 color: @alert2;
379 383 }
380 384 }
381 385 }
382 386
383 387 .btn-social {
384 388 &:extend(.btn-default);
385 389 margin: 5px 5px 5px 0px;
386 390 min-width: 160px;
387 391 }
388 392
389 393 // TODO: johbo: check these exceptions
390 394
391 395 .links {
392 396
393 397 .btn + .btn {
394 398 margin-top: @padding;
395 399 }
396 400 }
397 401
398 402
399 403 .action_button {
400 404 display:inline;
401 405 margin: 0;
402 406 padding: 0 1em 0 0;
403 407 font-size: inherit;
404 408 color: @rcblue;
405 409 border: none;
406 410 border-radius: 0;
407 411 background-color: transparent;
408 412
409 413 &.last-item {
410 414 border: none;
411 415 padding: 0 0 0 0;
412 416 }
413 417
414 418 &:last-child {
415 419 border: none;
416 420 padding: 0 0 0 0;
417 421 }
418 422
419 423 &:hover {
420 424 color: @rcdarkblue;
421 425 background-color: transparent;
422 426 border: none;
423 427 }
424 428 .noselect
425 429 }
426 430
427 431 .grid_delete {
428 432 .action_button {
429 433 border: none;
430 434 }
431 435 }
432 436
433 437 input[type="submit"].btn-draft {
434 438 .border ( @border-thickness, @rcblue );
435 439 background-color: white;
436 440 color: @rcblue;
437 441
438 442 a {
439 443 color: @rcblue;
440 444 }
441 445
442 446 &:hover,
443 447 &.active {
444 448 .border ( @border-thickness, @rcdarkblue );
445 449 background-color: white;
446 450 color: @rcdarkblue;
447 451
448 452 a {
449 453 color: @rcdarkblue;
450 454 }
451 455 }
452 456
453 457 &:disabled {
454 458 background-color: white;
455 459 color: @rcblue;
456 460 }
457 461 }
458 462
459 463 input[type="submit"].btn-warning {
460 464 &:extend(.btn-warning);
461 465
462 466 &:focus {
463 467 outline: 0;
464 468 }
465 469
466 470 &:hover {
467 471 &:extend(.btn-warning:hover);
468 472 }
469 473
470 474 &.btn-link {
471 475 &:extend(.btn-link);
472 476 color: @alert3;
473 477
474 478 &:disabled {
475 479 color: @alert3;
476 480 background-color: transparent;
477 481 }
478 482 }
479 483
480 484 &:disabled {
481 485 .border ( @border-thickness-buttons, @alert3 );
482 486 background-color: white;
483 487 color: @alert3;
484 488 opacity: 0.5;
485 489 }
486 490 }
487 491
488 492
489 493
490 494 // TODO: johbo: Form button tweaks, check if we can use the classes instead
491 495 input[type="submit"] {
492 496 &:extend(.btn-primary);
493 497
494 498 &:focus {
495 499 outline: 0;
496 500 }
497 501
498 502 &:hover {
499 503 &:extend(.btn-primary:hover);
500 504 }
501 505
502 506 &.btn-link {
503 507 &:extend(.btn-link);
504 508 color: @rcblue;
505 509
506 510 &:disabled {
507 511 color: @rcblue;
508 512 background-color: transparent;
509 513 }
510 514 }
511 515
512 516 &:disabled {
513 517 .border ( @border-thickness-buttons, @rcblue );
514 518 background-color: @rcblue;
515 519 color: white;
516 520 opacity: 0.5;
517 521 }
518 522 }
519 523
520 524 input[type="reset"] {
521 525 &:extend(.btn-default);
522 526
523 527 // TODO: johbo: Check if this tweak can be avoided.
524 528 background: transparent;
525 529
526 530 &:focus {
527 531 outline: 0;
528 532 }
529 533
530 534 &:hover {
531 535 &:extend(.btn-default:hover);
532 536 }
533 537
534 538 &.btn-link {
535 539 &:extend(.btn-link);
536 540 color: @rcblue;
537 541
538 542 &:disabled {
539 543 border: none;
540 544 }
541 545 }
542 546
543 547 &:disabled {
544 548 .border ( @border-thickness-buttons, @rcblue );
545 549 background-color: white;
546 550 color: @rcblue;
547 551 }
548 552 }
549 553
550 554 input[type="submit"],
551 555 input[type="reset"] {
552 556 &.btn-danger {
553 557 &:extend(.btn-danger);
554 558
555 559 &:focus {
556 560 outline: 0;
557 561 }
558 562
559 563 &:hover {
560 564 &:extend(.btn-danger:hover);
561 565 }
562 566
563 567 &.btn-link {
564 568 &:extend(.btn-link);
565 569 color: @alert2;
566 570
567 571 &:hover {
568 572 color: darken(@alert2,30%);
569 573 }
570 574 }
571 575
572 576 &:disabled {
573 577 color: @alert2;
574 578 background-color: white;
575 579 }
576 580 }
577 581 &.btn-danger-action {
578 582 .border ( @border-thickness, @alert2 );
579 583 background-color: @alert2;
580 584 color: white;
581 585
582 586 a {
583 587 color: white;
584 588 }
585 589
586 590 &:hover {
587 591 background-color: darken(@alert2,20%);
588 592 }
589 593
590 594 &.active {
591 595 .border ( @border-thickness, @alert2 );
592 596 color: white;
593 597 background-color: @alert2;
594 598
595 599 a {
596 600 color: white;
597 601 }
598 602 }
599 603
600 604 &:disabled {
601 605 background-color: white;
602 606 color: @alert2;
603 607 }
604 608 }
605 609 }
606 610
607 611
608 612 .button-links {
609 613 float: left;
610 614 display: inline;
611 615 margin: 0;
612 616 padding-left: 0;
613 617 list-style: none;
614 618 text-align: right;
615 619
616 620 li {
617 621
618 622
619 623 }
620 624
621 625 li.active {
622 626 background-color: @grey6;
623 627 .border ( @border-thickness, @grey4 );
624 628 }
625 629
626 630 }
@@ -1,293 +1,297 b''
1 1 @font-face {
2 2 font-family: 'rcicons';
3 3
4 4 src: url('../fonts/RCIcons/rcicons.eot?44705679');
5 5 src: url('../fonts/RCIcons/rcicons.eot?44705679#iefix') format('embedded-opentype'),
6 6 url('../fonts/RCIcons/rcicons.woff2?44705679') format('woff2'),
7 7 url('../fonts/RCIcons/rcicons.woff?44705679') format('woff'),
8 8 url('../fonts/RCIcons/rcicons.ttf?44705679') format('truetype'),
9 9 url('../fonts/RCIcons/rcicons.svg?44705679#rcicons') format('svg');
10 10
11 11 font-weight: normal;
12 12 font-style: normal;
13 13 }
14 14 /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
15 15 /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
16 16 /*
17 17 @media screen and (-webkit-min-device-pixel-ratio:0) {
18 18 @font-face {
19 19 font-family: 'rcicons';
20 20 src: url('../fonts/RCIcons/rcicons.svg?74666722#rcicons') format('svg');
21 21 }
22 22 }
23 23 */
24 24
25 25 [class^="icon-"]:before, [class*=" icon-"]:before {
26 26 font-family: "rcicons";
27 27 font-style: normal;
28 28 font-weight: normal;
29 29 speak: none;
30 30
31 31 display: inline-block;
32 32 text-decoration: inherit;
33 33 width: 1em;
34 34 margin-right: .2em;
35 35 text-align: center;
36 36 /* opacity: .8; */
37 37
38 38 /* For safety - reset parent styles, that can break glyph codes*/
39 39 font-variant: normal;
40 40 text-transform: none;
41 41
42 42 /* fix buttons height, for twitter bootstrap */
43 43 line-height: 1em;
44 44
45 45 /* Animation center compensation - margins should be symmetric */
46 46 /* remove if not needed */
47 47 margin-left: .2em;
48 48
49 49 /* you can be more comfortable with increased icons size */
50 50 /* font-size: 120%; */
51 51
52 52 /* Font smoothing. That was taken from TWBS */
53 53 -webkit-font-smoothing: antialiased;
54 54 -moz-osx-font-smoothing: grayscale;
55 55
56 56 /* Uncomment for 3D effect */
57 57 /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
58 58 }
59 59
60 60 .animate-spin {
61 61 -moz-animation: spin 2s infinite linear;
62 62 -o-animation: spin 2s infinite linear;
63 63 -webkit-animation: spin 2s infinite linear;
64 64 animation: spin 2s infinite linear;
65 65 display: inline-block;
66 66 }
67 67 @-moz-keyframes spin {
68 68 0% {
69 69 -moz-transform: rotate(0deg);
70 70 -o-transform: rotate(0deg);
71 71 -webkit-transform: rotate(0deg);
72 72 transform: rotate(0deg);
73 73 }
74 74
75 75 100% {
76 76 -moz-transform: rotate(359deg);
77 77 -o-transform: rotate(359deg);
78 78 -webkit-transform: rotate(359deg);
79 79 transform: rotate(359deg);
80 80 }
81 81 }
82 82 @-webkit-keyframes spin {
83 83 0% {
84 84 -moz-transform: rotate(0deg);
85 85 -o-transform: rotate(0deg);
86 86 -webkit-transform: rotate(0deg);
87 87 transform: rotate(0deg);
88 88 }
89 89
90 90 100% {
91 91 -moz-transform: rotate(359deg);
92 92 -o-transform: rotate(359deg);
93 93 -webkit-transform: rotate(359deg);
94 94 transform: rotate(359deg);
95 95 }
96 96 }
97 97 @-o-keyframes spin {
98 98 0% {
99 99 -moz-transform: rotate(0deg);
100 100 -o-transform: rotate(0deg);
101 101 -webkit-transform: rotate(0deg);
102 102 transform: rotate(0deg);
103 103 }
104 104
105 105 100% {
106 106 -moz-transform: rotate(359deg);
107 107 -o-transform: rotate(359deg);
108 108 -webkit-transform: rotate(359deg);
109 109 transform: rotate(359deg);
110 110 }
111 111 }
112 112 @-ms-keyframes spin {
113 113 0% {
114 114 -moz-transform: rotate(0deg);
115 115 -o-transform: rotate(0deg);
116 116 -webkit-transform: rotate(0deg);
117 117 transform: rotate(0deg);
118 118 }
119 119
120 120 100% {
121 121 -moz-transform: rotate(359deg);
122 122 -o-transform: rotate(359deg);
123 123 -webkit-transform: rotate(359deg);
124 124 transform: rotate(359deg);
125 125 }
126 126 }
127 127 @keyframes spin {
128 128 0% {
129 129 -moz-transform: rotate(0deg);
130 130 -o-transform: rotate(0deg);
131 131 -webkit-transform: rotate(0deg);
132 132 transform: rotate(0deg);
133 133 }
134 134
135 135 100% {
136 136 -moz-transform: rotate(359deg);
137 137 -o-transform: rotate(359deg);
138 138 -webkit-transform: rotate(359deg);
139 139 transform: rotate(359deg);
140 140 }
141 141 }
142 142
143 143
144 144
145 145 .icon-no-margin::before {
146 146 margin: 0;
147 147
148 148 }
149 149 // -- ICON CLASSES -- //
150 150 // sorter = lambda s: '\n'.join(sorted(s.splitlines()))
151 151
152 152 .icon-delete:before { content: '\e800'; } /* '' */
153 153 .icon-ok:before { content: '\e801'; } /* '' */
154 154 .icon-comment:before { content: '\e802'; } /* '' */
155 155 .icon-bookmark:before { content: '\e803'; } /* '' */
156 156 .icon-branch:before { content: '\e804'; } /* '' */
157 157 .icon-tag:before { content: '\e805'; } /* '' */
158 158 .icon-lock:before { content: '\e806'; } /* '' */
159 159 .icon-unlock:before { content: '\e807'; } /* '' */
160 160 .icon-feed:before { content: '\e808'; } /* '' */
161 161 .icon-left:before { content: '\e809'; } /* '' */
162 162 .icon-right:before { content: '\e80a'; } /* '' */
163 163 .icon-down:before { content: '\e80b'; } /* '' */
164 164 .icon-folder:before { content: '\e80c'; } /* '' */
165 165 .icon-folder-open:before { content: '\e80d'; } /* '' */
166 166 .icon-trash-empty:before { content: '\e80e'; } /* '' */
167 167 .icon-group:before { content: '\e80f'; } /* '' */
168 168 .icon-remove:before { content: '\e810'; } /* '' */
169 169 .icon-fork:before { content: '\e811'; } /* '' */
170 170 .icon-more:before { content: '\e812'; } /* '' */
171 171 .icon-options:before { content: '\e812'; } /* '' */
172 172 .icon-search:before { content: '\e813'; } /* '' */
173 173 .icon-scissors:before { content: '\e814'; } /* '' */
174 174 .icon-download:before { content: '\e815'; } /* '' */
175 175 .icon-doc:before { content: '\e816'; } /* '' */
176 176 .icon-cog:before { content: '\e817'; } /* '' */
177 177 .icon-cog-alt:before { content: '\e818'; } /* '' */
178 178 .icon-eye:before { content: '\e819'; } /* '' */
179 179 .icon-eye-off:before { content: '\e81a'; } /* '' */
180 180 .icon-cancel-circled2:before { content: '\e81b'; } /* '' */
181 181 .icon-cancel-circled:before { content: '\e81c'; } /* '' */
182 182 .icon-plus:before { content: '\e81d'; } /* '' */
183 183 .icon-plus-circled:before { content: '\e81e'; } /* '' */
184 184 .icon-minus-circled:before { content: '\e81f'; } /* '' */
185 185 .icon-minus:before { content: '\e820'; } /* '' */
186 186 .icon-info-circled:before { content: '\e821'; } /* '' */
187 187 .icon-upload:before { content: '\e822'; } /* '' */
188 188 .icon-home:before { content: '\e823'; } /* '' */
189 189 .icon-flag-filled:before { content: '\e824'; } /* '' */
190 190 .icon-git:before { content: '\e82a'; } /* '' */
191 191 .icon-hg:before { content: '\e82d'; } /* '' */
192 192 .icon-svn:before { content: '\e82e'; } /* '' */
193 193 .icon-comment-add:before { content: '\e82f'; } /* '' */
194 194 .icon-comment-toggle:before { content: '\e830'; } /* '' */
195 195 .icon-rhodecode:before { content: '\e831'; } /* '' */
196 196 .icon-up:before { content: '\e832'; } /* '' */
197 .icon-down:before { content: '\e832'; } /* '' */
197 198 .icon-merge:before { content: '\e833'; } /* '' */
198 199 .icon-spin-alt:before { content: '\e834'; } /* '' */
199 200 .icon-spin:before { content: '\e838'; } /* '' */
200 201 .icon-docs:before { content: '\f0c5'; } /* '' */
201 202 .icon-menu:before { content: '\f0c9'; } /* '' */
202 203 .icon-sort:before { content: '\f0dc'; } /* '' */
203 204 .icon-paste:before { content: '\f0ea'; } /* '' */
204 205 .icon-doc-text:before { content: '\f0f6'; } /* '' */
205 206 .icon-plus-squared:before { content: '\f0fe'; } /* '' */
206 207 .icon-angle-left:before { content: '\f104'; } /* '' */
207 208 .icon-angle-right:before { content: '\f105'; } /* '' */
208 209 .icon-angle-up:before { content: '\f106'; } /* '' */
209 210 .icon-angle-down:before { content: '\f107'; } /* '' */
210 211 .icon-circle-empty:before { content: '\f10c'; } /* '' */
211 212 .icon-circle:before { content: '\f111'; } /* '' */
212 213 .icon-folder-empty:before { content: '\f114'; } /* '' */
213 214 .icon-folder-open-empty:before { content: '\f115'; } /* '' */
214 215 .icon-code:before { content: '\f121'; } /* '' */
215 216 .icon-info:before { content: '\f129'; } /* '' */
216 217 .icon-minus-squared:before { content: '\f146'; } /* '' */
217 218 .icon-minus-squared-alt:before { content: '\f147'; } /* '' */
218 219 .icon-doc-inv:before { content: '\f15b'; } /* '' */
219 220 .icon-doc-text-inv:before { content: '\f15c'; } /* '' */
220 221 .icon-plus-squared-alt:before { content: '\f196'; } /* '' */
221 222 .icon-file-code:before { content: '\f1c9'; } /* '' */
222 223 .icon-history:before { content: '\f1da'; } /* '' */
223 224 .icon-circle-thin:before { content: '\f1db'; } /* '' */
224 225 .icon-sliders:before { content: '\f1de'; } /* '' */
225 226 .icon-trash:before { content: '\f1f8'; } /* '' */
226 227
227 228
228 229 // MERGED ICONS BASED ON CURRENT ONES
229 230 .icon-repo-group:before { &:extend(.icon-folder-open:before); }
230 231 .icon-repo-private:before { &:extend(.icon-lock:before); }
231 232 .icon-repo-lock:before { &:extend(.icon-lock:before); }
232 233 .icon-unlock-alt:before { &:extend(.icon-unlock:before); }
233 234 .icon-repo-unlock:before { &:extend(.icon-unlock:before); }
234 235 .icon-repo-public:before { &:extend(.icon-unlock:before); }
235 236 .icon-rss-sign:before { &:extend(.icon-feed:before); }
236 237 .icon-code-fork:before { &:extend(.icon-fork:before); }
237 238 .icon-arrow_up:before { &:extend(.icon-up:before); }
239 .icon-arrow_down:before { &:extend(.icon-down:before); }
238 240 .icon-file:before { &:extend(.icon-file-code:before); }
239 241 .icon-file-text:before { &:extend(.icon-file-code:before); }
240 242 .icon-directory:before { &:extend(.icon-folder:before); }
241 243 .icon-more-linked:before { &:extend(.icon-more:before); }
242 244 .icon-clipboard:before { &:extend(.icon-docs:before); }
243 245 .icon-copy:before { &:extend(.icon-docs:before); }
244 246 .icon-true:before { &:extend(.icon-ok:before); }
245 247 .icon-false:before { &:extend(.icon-delete:before); }
246 248 .icon-expand-linked:before { &:extend(.icon-down:before); }
247 249 .icon-pr-merge-fail:before { &:extend(.icon-delete:before); }
248 250 .icon-wide-mode:before { &:extend(.icon-sort:before); }
249 251 .icon-flag-filled-red:before { &:extend(.icon-flag-filled:before); }
250 252 .icon-user-group-alt:before { &:extend(.icon-group:before); }
251 253
252 254 // TRANSFORM
253 255 .icon-merge:before {transform: rotate(180deg);}
254 256 .icon-wide-mode:before {transform: rotate(90deg);}
255 257 .icon-options:before {transform: rotate(90deg);}
258 .icon-down:before {transform: rotate(180deg);}
259
256 260
257 261 // -- END ICON CLASSES -- //
258 262
259 263
260 264 //--- ICONS STYLING ------------------//
261 265
262 266 .icon-git { color: @color4 !important; }
263 267 .icon-hg { color: @color8 !important; }
264 268 .icon-svn { color: @color1 !important; }
265 269 .icon-git-inv { color: @color4 !important; }
266 270 .icon-hg-inv { color: @color8 !important; }
267 271 .icon-svn-inv { color: @color1 !important; }
268 272 .icon-repo-lock { color: #FF0000; }
269 273 .icon-repo-unlock { color: #FF0000; }
270 274 .icon-false { color: @grey5 }
271 275 .icon-expand-linked { cursor: pointer; color: @grey3; font-size: 14px }
272 276 .icon-more-linked { cursor: pointer; color: @grey3 }
273 277 .icon-flag-filled-red { color: @color5 !important; }
274 278 .icon-filled-red { color: @color5 !important; }
275 279
276 280 .repo-switcher-dropdown .select2-result-label {
277 281 .icon-git:before {
278 282 &:extend(.icon-git-transparent:before);
279 283 }
280 284 .icon-hg:before {
281 285 &:extend(.icon-hg-transparent:before);
282 286 color: @alert4;
283 287 }
284 288 .icon-svn:before {
285 289 &:extend(.icon-svn-transparent:before);
286 290 }
287 291 }
288 292
289 293 .icon-user-group:before {
290 294 &:extend(.icon-group:before);
291 295 margin: 0;
292 296 font-size: 16px;
293 297 }
@@ -1,716 +1,724 b''
1 1 // # Copyright (C) 2010-2020 RhodeCode GmbH
2 2 // #
3 3 // # This program is free software: you can redistribute it and/or modify
4 4 // # it under the terms of the GNU Affero General Public License, version 3
5 5 // # (only), as published by the Free Software Foundation.
6 6 // #
7 7 // # This program is distributed in the hope that it will be useful,
8 8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 // # GNU General Public License for more details.
11 11 // #
12 12 // # You should have received a copy of the GNU Affero General Public License
13 13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 // #
15 15 // # This program is dual-licensed. If you wish to learn more about the
16 16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 /**
20 20 RhodeCode JS Files
21 21 **/
22 22
23 23 if (typeof console == "undefined" || typeof console.log == "undefined"){
24 24 console = { log: function() {} }
25 25 }
26 26
27 27 // TODO: move the following function to submodules
28 28
29 29 /**
30 30 * show more
31 31 */
32 32 var show_more_event = function(){
33 33 $('table .show_more').click(function(e) {
34 34 var cid = e.target.id.substring(1);
35 35 var button = $(this);
36 36 if (button.hasClass('open')) {
37 37 $('#'+cid).hide();
38 38 button.removeClass('open');
39 39 } else {
40 40 $('#'+cid).show();
41 41 button.addClass('open one');
42 42 }
43 43 });
44 44 };
45 45
46 46 var compare_radio_buttons = function(repo_name, compare_ref_type){
47 47 $('#compare_action').on('click', function(e){
48 48 e.preventDefault();
49 49
50 50 var source = $('input[name=compare_source]:checked').val();
51 51 var target = $('input[name=compare_target]:checked').val();
52 52 if(source && target){
53 53 var url_data = {
54 54 repo_name: repo_name,
55 55 source_ref: source,
56 56 source_ref_type: compare_ref_type,
57 57 target_ref: target,
58 58 target_ref_type: compare_ref_type,
59 59 merge: 1
60 60 };
61 61 window.location = pyroutes.url('repo_compare', url_data);
62 62 }
63 63 });
64 64 $('.compare-radio-button').on('click', function(e){
65 65 var source = $('input[name=compare_source]:checked').val();
66 66 var target = $('input[name=compare_target]:checked').val();
67 67 if(source && target){
68 68 $('#compare_action').removeAttr("disabled");
69 69 $('#compare_action').removeClass("disabled");
70 70 }
71 71 })
72 72 };
73 73
74 74 var showRepoSize = function(target, repo_name, commit_id, callback) {
75 75 var container = $('#' + target);
76 76 var url = pyroutes.url('repo_stats',
77 77 {"repo_name": repo_name, "commit_id": commit_id});
78 78
79 79 container.show();
80 80 if (!container.hasClass('loaded')) {
81 81 $.ajax({url: url})
82 82 .complete(function (data) {
83 83 var responseJSON = data.responseJSON;
84 84 container.addClass('loaded');
85 85 container.html(responseJSON.size);
86 86 callback(responseJSON.code_stats)
87 87 })
88 88 .fail(function (data) {
89 89 console.log('failed to load repo stats');
90 90 });
91 91 }
92 92
93 93 };
94 94
95 95 var showRepoStats = function(target, data){
96 96 var container = $('#' + target);
97 97
98 98 if (container.hasClass('loaded')) {
99 99 return
100 100 }
101 101
102 102 var total = 0;
103 103 var no_data = true;
104 104 var tbl = document.createElement('table');
105 105 tbl.setAttribute('class', 'trending_language_tbl rctable');
106 106
107 107 $.each(data, function(key, val){
108 108 total += val.count;
109 109 });
110 110
111 111 var sortedStats = [];
112 112 for (var obj in data){
113 113 sortedStats.push([obj, data[obj]])
114 114 }
115 115 var sortedData = sortedStats.sort(function (a, b) {
116 116 return b[1].count - a[1].count
117 117 });
118 118 var cnt = 0;
119 119 $.each(sortedData, function(idx, val){
120 120 cnt += 1;
121 121 no_data = false;
122 122
123 123 var tr = document.createElement('tr');
124 124
125 125 var key = val[0];
126 126 var obj = {"desc": val[1].desc, "count": val[1].count};
127 127
128 128 // meta language names
129 129 var td1 = document.createElement('td');
130 130 var trending_language_label = document.createElement('div');
131 131 trending_language_label.innerHTML = obj.desc;
132 132 td1.appendChild(trending_language_label);
133 133
134 134 // extensions
135 135 var td2 = document.createElement('td');
136 136 var extension = document.createElement('div');
137 137 extension.innerHTML = ".{0}".format(key)
138 138 td2.appendChild(extension);
139 139
140 140 // number of files
141 141 var td3 = document.createElement('td');
142 142 var file_count = document.createElement('div');
143 143 var percentage_num = Math.round((obj.count / total * 100), 2);
144 144 var label = _ngettext('file', 'files', obj.count);
145 145 file_count.innerHTML = "{0} {1} ({2}%)".format(obj.count, label, percentage_num) ;
146 146 td3.appendChild(file_count);
147 147
148 148 // percentage
149 149 var td4 = document.createElement('td');
150 150 td4.setAttribute("class", 'trending_language');
151 151
152 152 var percentage = document.createElement('div');
153 153 percentage.setAttribute('class', 'lang-bar');
154 154 percentage.innerHTML = "&nbsp;";
155 155 percentage.style.width = percentage_num + '%';
156 156 td4.appendChild(percentage);
157 157
158 158 tr.appendChild(td1);
159 159 tr.appendChild(td2);
160 160 tr.appendChild(td3);
161 161 tr.appendChild(td4);
162 162 tbl.appendChild(tr);
163 163
164 164 });
165 165
166 166 $(container).html(tbl);
167 167 $(container).addClass('loaded');
168 168
169 169 $('#code_stats_show_more').on('click', function (e) {
170 170 e.preventDefault();
171 171 $('.stats_hidden').each(function (idx) {
172 172 $(this).css("display", "");
173 173 });
174 174 $('#code_stats_show_more').hide();
175 175 });
176 176
177 177 };
178 178
179 179 // returns a node from given html;
180 180 var fromHTML = function(html){
181 181 var _html = document.createElement('element');
182 182 _html.innerHTML = html;
183 183 return _html;
184 184 };
185 185
186 186 // Toggle Collapsable Content
187 187 function collapsableContent() {
188 188
189 189 $('.collapsable-content').not('.no-hide').hide();
190 190
191 191 $('.btn-collapse').unbind(); //in case we've been here before
192 192 $('.btn-collapse').click(function() {
193 193 var button = $(this);
194 194 var togglename = $(this).data("toggle");
195 195 $('.collapsable-content[data-toggle='+togglename+']').toggle();
196 196 if ($(this).html()=="Show Less")
197 197 $(this).html("Show More");
198 198 else
199 199 $(this).html("Show Less");
200 200 });
201 201 };
202 202
203 203 var timeagoActivate = function() {
204 204 $("time.timeago").timeago();
205 205 };
206 206
207 207
208 208 var clipboardActivate = function() {
209 209 /*
210 210 *
211 211 * <i class="tooltip icon-plus clipboard-action" data-clipboard-text="${commit.raw_id}" title="${_('Copy the full commit id')}"></i>
212 212 * */
213 213 var clipboard = new ClipboardJS('.clipboard-action');
214 214
215 215 clipboard.on('success', function(e) {
216 216 var callback = function () {
217 217 $(e.trigger).animate({'opacity': 1.00}, 200)
218 218 };
219 219 $(e.trigger).animate({'opacity': 0.15}, 200, callback);
220 220 e.clearSelection();
221 221 });
222 222 };
223 223
224 224 var tooltipActivate = function () {
225 225 var delay = 50;
226 226 var animation = 'fade';
227 227 var theme = 'tooltipster-shadow';
228 228 var debug = false;
229 229
230 230 $('.tooltip').tooltipster({
231 231 debug: debug,
232 232 theme: theme,
233 233 animation: animation,
234 234 delay: delay,
235 235 contentCloning: true,
236 236 contentAsHTML: true,
237 237
238 238 functionBefore: function (instance, helper) {
239 239 var $origin = $(helper.origin);
240 240 var data = '<div style="white-space: pre-wrap">{0}</div>'.format(instance.content());
241 241 instance.content(data);
242 242 }
243 243 });
244 244 var hovercardCache = {};
245 245
246 246 var loadHoverCard = function (url, altHovercard, callback) {
247 247 var id = url;
248 248
249 249 if (hovercardCache[id] !== undefined) {
250 250 callback(hovercardCache[id]);
251 251 return true;
252 252 }
253 253
254 254 hovercardCache[id] = undefined;
255 255 $.get(url, function (data) {
256 256 hovercardCache[id] = data;
257 257 callback(hovercardCache[id]);
258 258 return true;
259 259 }).fail(function (data, textStatus, errorThrown) {
260 260
261 261 if (parseInt(data.status) === 404) {
262 262 var msg = "<p>{0}</p>".format(altHovercard || "No Data exists for this hovercard");
263 263 } else {
264 264 var msg = "<p class='error-message'>Error while fetching hovercard.\nError code {0} ({1}).</p>".format(data.status,data.statusText);
265 265 }
266 266 callback(msg);
267 267 return false
268 268 });
269 269 };
270 270
271 271 $('.tooltip-hovercard').tooltipster({
272 272 debug: debug,
273 273 theme: theme,
274 274 animation: animation,
275 275 delay: delay,
276 276 interactive: true,
277 277 contentCloning: true,
278 278
279 279 trigger: 'custom',
280 280 triggerOpen: {
281 281 mouseenter: true,
282 282 },
283 283 triggerClose: {
284 284 mouseleave: true,
285 285 originClick: true,
286 286 touchleave: true
287 287 },
288 288 content: _gettext('Loading...'),
289 289 contentAsHTML: true,
290 290 updateAnimation: null,
291 291
292 292 functionBefore: function (instance, helper) {
293 293
294 294 var $origin = $(helper.origin);
295 295
296 296 // we set a variable so the data is only loaded once via Ajax, not every time the tooltip opens
297 297 if ($origin.data('loaded') !== true) {
298 298 var hovercardUrl = $origin.data('hovercardUrl');
299 299 var altHovercard = $origin.data('hovercardAlt');
300 300
301 301 if (hovercardUrl !== undefined && hovercardUrl !== "") {
302 302 var urlLoad = true;
303 303 if (hovercardUrl.substr(0, 12) === 'pyroutes.url') {
304 304 hovercardUrl = eval(hovercardUrl)
305 305 } else if (hovercardUrl.substr(0, 11) === 'javascript:') {
306 306 var jsFunc = hovercardUrl.substr(11);
307 307 urlLoad = false;
308 308 loaded = true;
309 309 instance.content(eval(jsFunc))
310 310 }
311 311
312 312 if (urlLoad) {
313 313 var loaded = loadHoverCard(hovercardUrl, altHovercard, function (data) {
314 314 instance.content(data);
315 315 })
316 316 }
317 317
318 318 } else {
319 319 if ($origin.data('hovercardAltHtml')) {
320 320 var data = atob($origin.data('hovercardAltHtml'));
321 321 } else {
322 322 var data = '<div style="white-space: pre-wrap">{0}</div>'.format(altHovercard)
323 323 }
324 324 var loaded = true;
325 325 instance.content(data);
326 326 }
327 327
328 328 // to remember that the data has been loaded
329 329 $origin.data('loaded', loaded);
330 330 }
331 331 }
332 332 })
333 333 };
334 334
335 335 // Formatting values in a Select2 dropdown of commit references
336 336 var formatSelect2SelectionRefs = function(commit_ref){
337 337 var tmpl = '';
338 338 if (!commit_ref.text || commit_ref.type === 'sha'){
339 339 return commit_ref.text;
340 340 }
341 341 if (commit_ref.type === 'branch'){
342 342 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
343 343 } else if (commit_ref.type === 'tag'){
344 344 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
345 345 } else if (commit_ref.type === 'book'){
346 346 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
347 347 }
348 348 return tmpl.concat(escapeHtml(commit_ref.text));
349 349 };
350 350
351 351 // takes a given html element and scrolls it down offset pixels
352 352 function offsetScroll(element, offset) {
353 353 setTimeout(function() {
354 354 var location = element.offset().top;
355 355 // some browsers use body, some use html
356 356 $('html, body').animate({ scrollTop: (location - offset) });
357 357 }, 100);
358 358 }
359 359
360 360 // scroll an element `percent`% from the top of page in `time` ms
361 361 function scrollToElement(element, percent, time) {
362 362 percent = (percent === undefined ? 25 : percent);
363 363 time = (time === undefined ? 100 : time);
364 364
365 365 var $element = $(element);
366 366 if ($element.length == 0) {
367 367 throw('Cannot scroll to {0}'.format(element))
368 368 }
369 369 var elOffset = $element.offset().top;
370 370 var elHeight = $element.height();
371 371 var windowHeight = $(window).height();
372 372 var offset = elOffset;
373 373 if (elHeight < windowHeight) {
374 374 offset = elOffset - ((windowHeight / (100 / percent)) - (elHeight / 2));
375 375 }
376 376 setTimeout(function() {
377 377 $('html, body').animate({ scrollTop: offset});
378 378 }, time);
379 379 }
380 380
381 381 /**
382 382 * global hooks after DOM is loaded
383 383 */
384 384 $(document).ready(function() {
385 385 firefoxAnchorFix();
386 386
387 387 $('.navigation a.menulink').on('click', function(e){
388 388 var menuitem = $(this).parent('li');
389 389 if (menuitem.hasClass('open')) {
390 390 menuitem.removeClass('open');
391 391 } else {
392 392 menuitem.addClass('open');
393 393 $(document).on('click', function(event) {
394 394 if (!$(event.target).closest(menuitem).length) {
395 395 menuitem.removeClass('open');
396 396 }
397 397 });
398 398 }
399 399 });
400 400
401 401 $('body').on('click', '.cb-lineno a', function(event) {
402 402 function sortNumber(a,b) {
403 403 return a - b;
404 404 }
405 405
406 406 var lineNo = $(this).data('lineNo');
407 407 var lineName = $(this).attr('name');
408 408
409 409 if (lineNo) {
410 410 var prevLine = $('.cb-line-selected a').data('lineNo');
411 411
412 412 // on shift, we do a range selection, if we got previous line
413 413 if (event.shiftKey && prevLine !== undefined) {
414 414 var prevLine = parseInt(prevLine);
415 415 var nextLine = parseInt(lineNo);
416 416 var pos = [prevLine, nextLine].sort(sortNumber);
417 417 var anchor = '#L{0}-{1}'.format(pos[0], pos[1]);
418 418
419 419 // single click
420 420 } else {
421 421 var nextLine = parseInt(lineNo);
422 422 var pos = [nextLine, nextLine];
423 423 var anchor = '#L{0}'.format(pos[0]);
424 424
425 425 }
426 426 // highlight
427 427 var range = [];
428 428 for (var i = pos[0]; i <= pos[1]; i++) {
429 429 range.push(i);
430 430 }
431 431 // clear old selected lines
432 432 $('.cb-line-selected').removeClass('cb-line-selected');
433 433
434 434 $.each(range, function (i, lineNo) {
435 435 var line_td = $('td.cb-lineno#L' + lineNo);
436 436
437 437 if (line_td.length) {
438 438 line_td.addClass('cb-line-selected'); // line number td
439 439 line_td.prev().addClass('cb-line-selected'); // line data
440 440 line_td.next().addClass('cb-line-selected'); // line content
441 441 }
442 442 });
443 443
444 444 } else if (lineName !== undefined) { // lineName only occurs in diffs
445 445 // clear old selected lines
446 446 $('td.cb-line-selected').removeClass('cb-line-selected');
447 447 var anchor = '#{0}'.format(lineName);
448 448 var diffmode = templateContext.session_attrs.diffmode || "sideside";
449 449
450 450 if (diffmode === "unified") {
451 451 $(this).closest('tr').find('td').addClass('cb-line-selected');
452 452 } else {
453 453 var activeTd = $(this).closest('td');
454 454 activeTd.addClass('cb-line-selected');
455 455 activeTd.next('td').addClass('cb-line-selected');
456 456 }
457 457
458 458 }
459 459
460 460 // Replace URL without jumping to it if browser supports.
461 461 // Default otherwise
462 462 if (history.pushState && anchor !== undefined) {
463 463 var new_location = location.href.rstrip('#');
464 464 if (location.hash) {
465 465 // location without hash
466 466 new_location = new_location.replace(location.hash, "");
467 467 }
468 468
469 469 // Make new anchor url
470 470 new_location = new_location + anchor;
471 471 history.pushState(true, document.title, new_location);
472 472
473 473 return false;
474 474 }
475 475
476 476 });
477 477
478 478 $('.collapse_file').on('click', function(e) {
479 479 e.stopPropagation();
480 480 if ($(e.target).is('a')) { return; }
481 481 var node = $(e.delegateTarget).first();
482 482 var icon = $($(node.children().first()).children().first());
483 483 var id = node.attr('fid');
484 484 var target = $('#'+id);
485 485 var tr = $('#tr_'+id);
486 486 var diff = $('#diff_'+id);
487 487 if(node.hasClass('expand_file')){
488 488 node.removeClass('expand_file');
489 489 icon.removeClass('expand_file_icon');
490 490 node.addClass('collapse_file');
491 491 icon.addClass('collapse_file_icon');
492 492 diff.show();
493 493 tr.show();
494 494 target.show();
495 495 } else {
496 496 node.removeClass('collapse_file');
497 497 icon.removeClass('collapse_file_icon');
498 498 node.addClass('expand_file');
499 499 icon.addClass('expand_file_icon');
500 500 diff.hide();
501 501 tr.hide();
502 502 target.hide();
503 503 }
504 504 });
505 505
506 506 $('#expand_all_files').click(function() {
507 507 $('.expand_file').each(function() {
508 508 var node = $(this);
509 509 var icon = $($(node.children().first()).children().first());
510 510 var id = $(this).attr('fid');
511 511 var target = $('#'+id);
512 512 var tr = $('#tr_'+id);
513 513 var diff = $('#diff_'+id);
514 514 node.removeClass('expand_file');
515 515 icon.removeClass('expand_file_icon');
516 516 node.addClass('collapse_file');
517 517 icon.addClass('collapse_file_icon');
518 518 diff.show();
519 519 tr.show();
520 520 target.show();
521 521 });
522 522 });
523 523
524 524 $('#collapse_all_files').click(function() {
525 525 $('.collapse_file').each(function() {
526 526 var node = $(this);
527 527 var icon = $($(node.children().first()).children().first());
528 528 var id = $(this).attr('fid');
529 529 var target = $('#'+id);
530 530 var tr = $('#tr_'+id);
531 531 var diff = $('#diff_'+id);
532 532 node.removeClass('collapse_file');
533 533 icon.removeClass('collapse_file_icon');
534 534 node.addClass('expand_file');
535 535 icon.addClass('expand_file_icon');
536 536 diff.hide();
537 537 tr.hide();
538 538 target.hide();
539 539 });
540 540 });
541 541
542 542 // Mouse over behavior for comments and line selection
543 543
544 544 // Select the line that comes from the url anchor
545 545 // At the time of development, Chrome didn't seem to support jquery's :target
546 546 // element, so I had to scroll manually
547 547
548 548 if (location.hash) {
549 549 var result = splitDelimitedHash(location.hash);
550 550
551 551 var loc = result.loc;
552 552
553 553 if (loc.length > 1) {
554 554
555 555 var highlightable_line_tds = [];
556 556
557 557 // source code line format
558 558 var page_highlights = loc.substring(loc.indexOf('#') + 1).split('L');
559 559
560 560 // multi-line HL, for files
561 561 if (page_highlights.length > 1) {
562 562 var highlight_ranges = page_highlights[1].split(",");
563 563 var h_lines = [];
564 564 for (var pos in highlight_ranges) {
565 565 var _range = highlight_ranges[pos].split('-');
566 566 if (_range.length === 2) {
567 567 var start = parseInt(_range[0]);
568 568 var end = parseInt(_range[1]);
569 569 if (start < end) {
570 570 for (var i = start; i <= end; i++) {
571 571 h_lines.push(i);
572 572 }
573 573 }
574 574 } else {
575 575 h_lines.push(parseInt(highlight_ranges[pos]));
576 576 }
577 577 }
578 578 for (pos in h_lines) {
579 579 var line_td = $('td.cb-lineno#L' + h_lines[pos]);
580 580 if (line_td.length) {
581 581 highlightable_line_tds.push(line_td);
582 582 }
583 583 }
584 584 }
585 585
586 586 // now check a direct id reference of line in diff / pull-request page)
587 587 if ($(loc).length > 0 && $(loc).hasClass('cb-lineno')) {
588 588 highlightable_line_tds.push($(loc));
589 589 }
590 590
591 591 // mark diff lines as selected
592 592 $.each(highlightable_line_tds, function (i, $td) {
593 593 $td.addClass('cb-line-selected'); // line number td
594 594 $td.prev().addClass('cb-line-selected'); // line data
595 595 $td.next().addClass('cb-line-selected'); // line content
596 596 });
597 597
598 598 if (highlightable_line_tds.length > 0) {
599 599 var $first_line_td = highlightable_line_tds[0];
600 600 scrollToElement($first_line_td);
601 601 $.Topic('/ui/plugins/code/anchor_focus').prepareOrPublish({
602 602 td: $first_line_td,
603 603 remainder: result.remainder
604 604 });
605 605 } else {
606 606 // case for direct anchor to comments
607 607 var $line = $(loc);
608 608
609 609 if ($line.hasClass('comment-general')) {
610 610 $line.show();
611 611 } else if ($line.hasClass('comment-inline')) {
612 612 $line.show();
613 613 var $cb = $line.closest('.cb');
614 614 $cb.removeClass('cb-collapsed')
615 615 }
616 616 if ($line.length > 0) {
617 617 $line.addClass('comment-selected-hl');
618 618 offsetScroll($line, 70);
619 619 }
620 620 if (!$line.hasClass('comment-outdated') && result.remainder === '/ReplyToComment') {
621 621 $line.parent().find('.cb-comment-add-button').trigger('click');
622 622 }
623 623 }
624 624
625 625 }
626 626 }
627 627 collapsableContent();
628 628 });
629 629
630 630 var feedLifetimeOptions = function(query, initialData){
631 631 var data = {results: []};
632 632 var isQuery = typeof query.term !== 'undefined';
633 633
634 634 var section = _gettext('Lifetime');
635 635 var children = [];
636 636
637 637 //filter results
638 638 $.each(initialData.results, function(idx, value) {
639 639
640 640 if (!isQuery || query.term.length === 0 || value.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
641 641 children.push({
642 642 'id': this.id,
643 643 'text': this.text
644 644 })
645 645 }
646 646
647 647 });
648 648 data.results.push({
649 649 'text': section,
650 650 'children': children
651 651 });
652 652
653 653 if (isQuery) {
654 654
655 655 var now = moment.utc();
656 656
657 657 var parseQuery = function(entry, now){
658 658 var fmt = 'DD/MM/YYYY H:mm';
659 659 var parsed = moment.utc(entry, fmt);
660 660 var diffInMin = parsed.diff(now, 'minutes');
661 661
662 662 if (diffInMin > 0){
663 663 return {
664 664 id: diffInMin,
665 665 text: parsed.format(fmt)
666 666 }
667 667 } else {
668 668 return {
669 669 id: undefined,
670 670 text: parsed.format('DD/MM/YYYY') + ' ' + _gettext('date not in future')
671 671 }
672 672 }
673 673
674 674
675 675 };
676 676
677 677 data.results.push({
678 678 'text': _gettext('Specified expiration date'),
679 679 'children': [{
680 680 'id': parseQuery(query.term, now).id,
681 681 'text': parseQuery(query.term, now).text
682 682 }]
683 683 });
684 684 }
685 685
686 686 query.callback(data);
687 687 };
688 688
689 689 /*
690 690 * Retrievew via templateContext.session_attrs.key
691 691 * */
692 692 var storeUserSessionAttr = function (key, val) {
693 693
694 694 var postData = {
695 695 'key': key,
696 696 'val': val,
697 697 'csrf_token': CSRF_TOKEN
698 698 };
699 699
700 700 var success = function(o) {
701 701 return true
702 702 };
703 703
704 704 ajaxPOST(pyroutes.url('store_user_session_value'), postData, success);
705 705 return false;
706 706 };
707 707
708 708
709 709 var getUserSessionAttr = function(key) {
710 710 var storeKey = templateContext.session_attrs;
711 711 var val = storeKey[key]
712 712 if (val !== undefined) {
713 713 return JSON.parse(val)
714 714 }
715 715 return null
716 716 }
717
718 window.scrollDown = function () {
719 $(document).scrollTop($(document).height());
720 }
721
722 window.scrollUp = function scrollUp() {
723 $(window).scrollTop(0);
724 }
@@ -1,1170 +1,1173 b''
1 1 // # Copyright (C) 2010-2020 RhodeCode GmbH
2 2 // #
3 3 // # This program is free software: you can redistribute it and/or modify
4 4 // # it under the terms of the GNU Affero General Public License, version 3
5 5 // # (only), as published by the Free Software Foundation.
6 6 // #
7 7 // # This program is distributed in the hope that it will be useful,
8 8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 // # GNU General Public License for more details.
11 11 // #
12 12 // # You should have received a copy of the GNU Affero General Public License
13 13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 // #
15 15 // # This program is dual-licensed. If you wish to learn more about the
16 16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19
20 20 var prButtonLockChecks = {
21 21 'compare': false,
22 22 'reviewers': false
23 23 };
24 24
25 25 /**
26 26 * lock button until all checks and loads are made. E.g reviewer calculation
27 27 * should prevent from submitting a PR
28 28 * @param lockEnabled
29 29 * @param msg
30 30 * @param scope
31 31 */
32 32 var prButtonLock = function(lockEnabled, msg, scope) {
33 33 scope = scope || 'all';
34 34 if (scope == 'all'){
35 35 prButtonLockChecks['compare'] = !lockEnabled;
36 36 prButtonLockChecks['reviewers'] = !lockEnabled;
37 37 } else if (scope == 'compare') {
38 38 prButtonLockChecks['compare'] = !lockEnabled;
39 39 } else if (scope == 'reviewers'){
40 40 prButtonLockChecks['reviewers'] = !lockEnabled;
41 41 }
42 42 var checksMeet = prButtonLockChecks.compare && prButtonLockChecks.reviewers;
43 43 if (lockEnabled) {
44 44 $('#pr_submit').attr('disabled', 'disabled');
45 45 }
46 46 else if (checksMeet) {
47 47 $('#pr_submit').removeAttr('disabled');
48 48 }
49 49
50 50 if (msg) {
51 51 $('#pr_open_message').html(msg);
52 52 }
53 53 };
54 54
55 55
56 56 /**
57 57 Generate Title and Description for a PullRequest.
58 58 In case of 1 commits, the title and description is that one commit
59 59 in case of multiple commits, we iterate on them with max N number of commits,
60 60 and build description in a form
61 61 - commitN
62 62 - commitN+1
63 63 ...
64 64
65 65 Title is then constructed from branch names, or other references,
66 66 replacing '-' and '_' into spaces
67 67
68 68 * @param sourceRef
69 69 * @param elements
70 70 * @param limit
71 71 * @returns {*[]}
72 72 */
73 73 var getTitleAndDescription = function(sourceRefType, sourceRef, elements, limit) {
74 74 var title = '';
75 75 var desc = '';
76 76
77 77 $.each($(elements).get().reverse().slice(0, limit), function(idx, value) {
78 78 var rawMessage = value['message'];
79 79 desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n';
80 80 });
81 81 // only 1 commit, use commit message as title
82 82 if (elements.length === 1) {
83 83 var rawMessage = elements[0]['message'];
84 84 title = rawMessage.split('\n')[0];
85 85 }
86 86 else {
87 87 // use reference name
88 88 var normalizedRef = sourceRef.replace(/-/g, ' ').replace(/_/g, ' ').capitalizeFirstLetter()
89 89 var refType = sourceRefType;
90 90 title = 'Changes from {0}: {1}'.format(refType, normalizedRef);
91 91 }
92 92
93 93 return [title, desc]
94 94 };
95 95
96 96
97 97 window.ReviewersController = function () {
98 98 var self = this;
99 99 this.$loadingIndicator = $('.calculate-reviewers');
100 100 this.$reviewRulesContainer = $('#review_rules');
101 101 this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules');
102 102 this.$userRule = $('.pr-user-rule-container');
103 103 this.$reviewMembers = $('#review_members');
104 104 this.$observerMembers = $('#observer_members');
105 105
106 106 this.currentRequest = null;
107 107 this.diffData = null;
108 108 this.enabledRules = [];
109 109 // sync with db.py entries
110 110 this.ROLE_REVIEWER = 'reviewer';
111 111 this.ROLE_OBSERVER = 'observer'
112 112
113 113 //dummy handler, we might register our own later
114 114 this.diffDataHandler = function (data) {};
115 115
116 116 this.defaultForbidUsers = function () {
117 117 return [
118 118 {
119 119 'username': 'default',
120 120 'user_id': templateContext.default_user.user_id
121 121 }
122 122 ];
123 123 };
124 124
125 125 // init default forbidden users
126 126 this.forbidUsers = this.defaultForbidUsers();
127 127
128 128 this.hideReviewRules = function () {
129 129 self.$reviewRulesContainer.hide();
130 130 $(self.$userRule.selector).hide();
131 131 };
132 132
133 133 this.showReviewRules = function () {
134 134 self.$reviewRulesContainer.show();
135 135 $(self.$userRule.selector).show();
136 136 };
137 137
138 138 this.addRule = function (ruleText) {
139 139 self.showReviewRules();
140 140 self.enabledRules.push(ruleText);
141 141 return '<div>- {0}</div>'.format(ruleText)
142 142 };
143 143
144 144 this.increaseCounter = function(role) {
145 145 if (role === self.ROLE_REVIEWER) {
146 146 var $elem = $('#reviewers-cnt')
147 147 var cnt = parseInt($elem.data('count') || 0)
148 148 cnt +=1
149 149 $elem.html(cnt);
150 150 $elem.data('count', cnt);
151 151 }
152 152 else if (role === self.ROLE_OBSERVER) {
153 153 var $elem = $('#observers-cnt');
154 154 var cnt = parseInt($elem.data('count') || 0)
155 155 cnt +=1
156 156 $elem.html(cnt);
157 157 $elem.data('count', cnt);
158 158 }
159 159 }
160 160
161 161 this.resetCounter = function () {
162 162 var $elem = $('#reviewers-cnt');
163 163
164 164 $elem.data('count', 0);
165 165 $elem.html(0);
166 166
167 167 var $elem = $('#observers-cnt');
168 168
169 169 $elem.data('count', 0);
170 170 $elem.html(0);
171 171 }
172 172
173 173 this.loadReviewRules = function (data) {
174 174 self.diffData = data;
175 175
176 176 // reset forbidden Users
177 177 this.forbidUsers = self.defaultForbidUsers();
178 178
179 179 // reset state of review rules
180 180 self.$rulesList.html('');
181 181
182 182 if (!data || data.rules === undefined || $.isEmptyObject(data.rules)) {
183 183 // default rule, case for older repo that don't have any rules stored
184 184 self.$rulesList.append(
185 185 self.addRule(_gettext('All reviewers must vote.'))
186 186 );
187 187 return self.forbidUsers
188 188 }
189 189
190 190 if (data.rules.forbid_adding_reviewers) {
191 191 $('#add_reviewer_input').remove();
192 192 }
193 193
194 194 if (data.rules_data !== undefined && data.rules_data.forbidden_users !== undefined) {
195 195 $.each(data.rules_data.forbidden_users, function(idx, val){
196 196 self.forbidUsers.push(val)
197 197 })
198 198 }
199 199
200 200 if (data.rules_humanized !== undefined && data.rules_humanized.length > 0) {
201 201 $.each(data.rules_humanized, function(idx, val) {
202 202 self.$rulesList.append(
203 203 self.addRule(val)
204 204 )
205 205 })
206 206 } else {
207 207 // we don't have any rules set, so we inform users about it
208 208 self.$rulesList.append(
209 209 self.addRule(_gettext('No additional review rules set.'))
210 210 )
211 211 }
212 212
213 213 return self.forbidUsers
214 214 };
215 215
216 216 this.emptyTables = function () {
217 217 self.emptyReviewersTable();
218 218 self.emptyObserversTable();
219 219
220 220 // Also reset counters.
221 221 self.resetCounter();
222 222 }
223 223
224 224 this.emptyReviewersTable = function (withText) {
225 225 self.$reviewMembers.empty();
226 226 if (withText !== undefined) {
227 227 self.$reviewMembers.html(withText)
228 228 }
229 229 };
230 230
231 231 this.emptyObserversTable = function (withText) {
232 232 self.$observerMembers.empty();
233 233 if (withText !== undefined) {
234 234 self.$observerMembers.html(withText)
235 235 }
236 236 }
237 237
238 238 this.loadDefaultReviewers = function (sourceRepo, sourceRef, targetRepo, targetRef) {
239 239
240 240 if (self.currentRequest) {
241 241 // make sure we cleanup old running requests before triggering this again
242 242 self.currentRequest.abort();
243 243 }
244 244
245 245 self.$loadingIndicator.show();
246 246
247 247 // reset reviewer/observe members
248 248 self.emptyTables();
249 249
250 250 prButtonLock(true, null, 'reviewers');
251 251 $('#user').hide(); // hide user autocomplete before load
252 252 $('#observer').hide(); //hide observer autocomplete before load
253 253
254 254 // lock PR button, so we cannot send PR before it's calculated
255 255 prButtonLock(true, _gettext('Loading diff ...'), 'compare');
256 256
257 257 if (sourceRef.length !== 3 || targetRef.length !== 3) {
258 258 // don't load defaults in case we're missing some refs...
259 259 self.$loadingIndicator.hide();
260 260 return
261 261 }
262 262
263 263 var url = pyroutes.url('repo_default_reviewers_data',
264 264 {
265 265 'repo_name': templateContext.repo_name,
266 266 'source_repo': sourceRepo,
267 267 'source_ref_type': sourceRef[0],
268 268 'source_ref_name': sourceRef[1],
269 269 'source_ref': sourceRef[2],
270 270 'target_repo': targetRepo,
271 271 'target_ref': targetRef[2],
272 272 'target_ref_type': targetRef[0],
273 273 'target_ref_name': targetRef[1]
274 274 });
275 275
276 276 self.currentRequest = $.ajax({
277 277 url: url,
278 278 headers: {'X-PARTIAL-XHR': true},
279 279 type: 'GET',
280 280 success: function (data) {
281 281
282 282 self.currentRequest = null;
283 283
284 284 // review rules
285 285 self.loadReviewRules(data);
286 286 var diffHandled = self.handleDiffData(data["diff_info"]);
287 287 if (diffHandled === false) {
288 288 return
289 289 }
290 290
291 291 for (var i = 0; i < data.reviewers.length; i++) {
292 292 var reviewer = data.reviewers[i];
293 293 // load reviewer rules from the repo data
294 294 self.addMember(reviewer, reviewer.reasons, reviewer.mandatory, reviewer.role);
295 295 }
296 296
297 297
298 298 self.$loadingIndicator.hide();
299 299 prButtonLock(false, null, 'reviewers');
300 300
301 301 $('#user').show(); // show user autocomplete before load
302 302 $('#observer').show(); // show observer autocomplete before load
303 303
304 304 var commitElements = data["diff_info"]['commits'];
305 305
306 306 if (commitElements.length === 0) {
307 307 var noCommitsMsg = '<span class="alert-text-warning">{0}</span>'.format(
308 308 _gettext('There are no commits to merge.'));
309 309 prButtonLock(true, noCommitsMsg, 'all');
310 310
311 311 } else {
312 312 // un-lock PR button, so we cannot send PR before it's calculated
313 313 prButtonLock(false, null, 'compare');
314 314 }
315 315
316 316 },
317 317 error: function (jqXHR, textStatus, errorThrown) {
318 318 var prefix = "Loading diff and reviewers/observers failed\n"
319 319 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
320 320 ajaxErrorSwal(message);
321 321 }
322 322 });
323 323
324 324 };
325 325
326 326 // check those, refactor
327 327 this.removeMember = function (reviewer_id, mark_delete) {
328 328 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
329 329
330 330 if (typeof (mark_delete) === undefined) {
331 331 mark_delete = false;
332 332 }
333 333
334 334 if (mark_delete === true) {
335 335 if (reviewer) {
336 336 // now delete the input
337 337 $('#reviewer_{0} input'.format(reviewer_id)).remove();
338 338 $('#reviewer_{0}_rules input'.format(reviewer_id)).remove();
339 339 // mark as to-delete
340 340 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
341 341 obj.addClass('to-delete');
342 342 obj.css({"text-decoration": "line-through", "opacity": 0.5});
343 343 }
344 344 } else {
345 345 $('#reviewer_{0}'.format(reviewer_id)).remove();
346 346 }
347 347 };
348 348
349 349 this.addMember = function (reviewer_obj, reasons, mandatory, role) {
350 350
351 351 var id = reviewer_obj.user_id;
352 352 var username = reviewer_obj.username;
353 353
354 354 reasons = reasons || [];
355 355 mandatory = mandatory || false;
356 356 role = role || self.ROLE_REVIEWER
357 357
358 358 // register current set IDS to check if we don't have this ID already in
359 359 // and prevent duplicates
360 360 var currentIds = [];
361 361
362 362 $.each($('.reviewer_entry'), function (index, value) {
363 363 currentIds.push($(value).data('reviewerUserId'))
364 364 })
365 365
366 366 var userAllowedReview = function (userId) {
367 367 var allowed = true;
368 368 $.each(self.forbidUsers, function (index, member_data) {
369 369 if (parseInt(userId) === member_data['user_id']) {
370 370 allowed = false;
371 371 return false // breaks the loop
372 372 }
373 373 });
374 374 return allowed
375 375 };
376 376
377 377 var userAllowed = userAllowedReview(id);
378 378
379 379 if (!userAllowed) {
380 380 alert(_gettext('User `{0}` not allowed to be a reviewer').format(username));
381 381 } else {
382 382 // only add if it's not there
383 383 var alreadyReviewer = currentIds.indexOf(id) != -1;
384 384
385 385 if (alreadyReviewer) {
386 386 alert(_gettext('User `{0}` already in reviewers/observers').format(username));
387 387 } else {
388 388
389 389 var reviewerEntry = renderTemplate('reviewMemberEntry', {
390 390 'member': reviewer_obj,
391 391 'mandatory': mandatory,
392 392 'role': role,
393 393 'reasons': reasons,
394 394 'allowed_to_update': true,
395 395 'review_status': 'not_reviewed',
396 396 'review_status_label': _gettext('Not Reviewed'),
397 397 'user_group': reviewer_obj.user_group,
398 398 'create': true,
399 399 'rule_show': true,
400 'rhodecode_user': templateContext.rhodecode_user
400 401 })
401 402
402 403 if (role === self.ROLE_REVIEWER) {
403 404 $(self.$reviewMembers.selector).append(reviewerEntry);
404 405 self.increaseCounter(self.ROLE_REVIEWER);
405 406 $('#reviewer-empty-msg').remove()
406 407 }
407 408 else if (role === self.ROLE_OBSERVER) {
408 409 $(self.$observerMembers.selector).append(reviewerEntry);
409 410 self.increaseCounter(self.ROLE_OBSERVER);
410 411 $('#observer-empty-msg').remove();
411 412 }
412 413
413 414 tooltipActivate();
414 415 }
415 416 }
416 417
417 418 };
418 419
419 420 this.updateReviewers = function (repo_name, pull_request_id, role) {
420 421 if (role === 'reviewer') {
421 422 var postData = $('#reviewers input').serialize();
422 423 _updatePullRequest(repo_name, pull_request_id, postData);
423 424 } else if (role === 'observer') {
424 425 var postData = $('#observers input').serialize();
425 426 _updatePullRequest(repo_name, pull_request_id, postData);
426 427 }
427 428 };
428 429
429 430 this.handleDiffData = function (data) {
430 431 return self.diffDataHandler(data)
431 432 }
432 433 };
433 434
434 435
435 436 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
436 437 var url = pyroutes.url(
437 438 'pullrequest_update',
438 439 {"repo_name": repo_name, "pull_request_id": pull_request_id});
439 440 if (typeof postData === 'string' ) {
440 441 postData += '&csrf_token=' + CSRF_TOKEN;
441 442 } else {
442 443 postData.csrf_token = CSRF_TOKEN;
443 444 }
444 445
445 446 var success = function(o) {
446 447 var redirectUrl = o['redirect_url'];
447 448 if (redirectUrl !== undefined && redirectUrl !== null && redirectUrl !== '') {
448 449 window.location = redirectUrl;
449 450 } else {
450 451 window.location.reload();
451 452 }
452 453 };
453 454
454 455 ajaxPOST(url, postData, success);
455 456 };
456 457
457 458 /**
458 459 * PULL REQUEST update commits
459 460 */
460 461 var updateCommits = function(repo_name, pull_request_id, force) {
461 462 var postData = {
462 463 'update_commits': true
463 464 };
464 465 if (force !== undefined && force === true) {
465 466 postData['force_refresh'] = true
466 467 }
467 468 _updatePullRequest(repo_name, pull_request_id, postData);
468 469 };
469 470
470 471
471 472 /**
472 473 * PULL REQUEST edit info
473 474 */
474 475 var editPullRequest = function(repo_name, pull_request_id, title, description, renderer) {
475 476 var url = pyroutes.url(
476 477 'pullrequest_update',
477 478 {"repo_name": repo_name, "pull_request_id": pull_request_id});
478 479
479 480 var postData = {
480 481 'title': title,
481 482 'description': description,
482 483 'description_renderer': renderer,
483 484 'edit_pull_request': true,
484 485 'csrf_token': CSRF_TOKEN
485 486 };
486 487 var success = function(o) {
487 488 window.location.reload();
488 489 };
489 490 ajaxPOST(url, postData, success);
490 491 };
491 492
492 493
493 494 /**
494 495 * autocomplete handler for reviewers/observers
495 496 */
496 497 var autoCompleteHandler = function (inputId, controller, role) {
497 498
498 499 return function (element, data) {
499 500 var mandatory = false;
500 501 var reasons = [_gettext('added manually by "{0}"').format(
501 502 templateContext.rhodecode_user.username)];
502 503
503 504 // add whole user groups
504 505 if (data.value_type == 'user_group') {
505 506 reasons.push(_gettext('member of "{0}"').format(data.value_display));
506 507
507 508 $.each(data.members, function (index, member_data) {
508 509 var reviewer = member_data;
509 510 reviewer['user_id'] = member_data['id'];
510 511 reviewer['gravatar_link'] = member_data['icon_link'];
511 512 reviewer['user_link'] = member_data['profile_link'];
512 513 reviewer['rules'] = [];
513 514 controller.addMember(reviewer, reasons, mandatory, role);
514 515 })
515 516 }
516 517 // add single user
517 518 else {
518 519 var reviewer = data;
519 520 reviewer['user_id'] = data['id'];
520 521 reviewer['gravatar_link'] = data['icon_link'];
521 522 reviewer['user_link'] = data['profile_link'];
522 523 reviewer['rules'] = [];
523 524 controller.addMember(reviewer, reasons, mandatory, role);
524 525 }
525 526
526 527 $(inputId).val('');
527 528 }
528 529 }
529 530
530 531 /**
531 532 * Reviewer autocomplete
532 533 */
533 534 var ReviewerAutoComplete = function (inputId, controller) {
534 535 var self = this;
535 536 self.controller = controller;
536 537 self.inputId = inputId;
537 538 var handler = autoCompleteHandler(inputId, controller, controller.ROLE_REVIEWER);
538 539
539 540 $(inputId).autocomplete({
540 541 serviceUrl: pyroutes.url('user_autocomplete_data'),
541 542 minChars: 2,
542 543 maxHeight: 400,
543 544 deferRequestBy: 300, //miliseconds
544 545 showNoSuggestionNotice: true,
545 546 tabDisabled: true,
546 547 autoSelectFirst: true,
547 548 params: {
548 549 user_id: templateContext.rhodecode_user.user_id,
549 550 user_groups: true,
550 551 user_groups_expand: true,
551 552 skip_default_user: true
552 553 },
553 554 formatResult: autocompleteFormatResult,
554 555 lookupFilter: autocompleteFilterResult,
555 556 onSelect: handler
556 557 });
557 558 };
558 559
559 560 /**
560 561 * Observers autocomplete
561 562 */
562 563 var ObserverAutoComplete = function(inputId, controller) {
563 564 var self = this;
564 565 self.controller = controller;
565 566 self.inputId = inputId;
566 567 var handler = autoCompleteHandler(inputId, controller, controller.ROLE_OBSERVER);
567 568
568 569 $(inputId).autocomplete({
569 570 serviceUrl: pyroutes.url('user_autocomplete_data'),
570 571 minChars: 2,
571 572 maxHeight: 400,
572 573 deferRequestBy: 300, //miliseconds
573 574 showNoSuggestionNotice: true,
574 575 tabDisabled: true,
575 576 autoSelectFirst: true,
576 577 params: {
577 578 user_id: templateContext.rhodecode_user.user_id,
578 579 user_groups: true,
579 580 user_groups_expand: true,
580 581 skip_default_user: true
581 582 },
582 583 formatResult: autocompleteFormatResult,
583 584 lookupFilter: autocompleteFilterResult,
584 585 onSelect: handler
585 586 });
586 587 }
587 588
588 589
589 590 window.VersionController = function () {
590 591 var self = this;
591 592 this.$verSource = $('input[name=ver_source]');
592 593 this.$verTarget = $('input[name=ver_target]');
593 594 this.$showVersionDiff = $('#show-version-diff');
594 595
595 596 this.adjustRadioSelectors = function (curNode) {
596 597 var getVal = function (item) {
597 598 if (item === 'latest') {
598 599 return Number.MAX_SAFE_INTEGER
599 600 }
600 601 else {
601 602 return parseInt(item)
602 603 }
603 604 };
604 605
605 606 var curVal = getVal($(curNode).val());
606 607 var cleared = false;
607 608
608 609 $.each(self.$verSource, function (index, value) {
609 610 var elVal = getVal($(value).val());
610 611
611 612 if (elVal > curVal) {
612 613 if ($(value).is(':checked')) {
613 614 cleared = true;
614 615 }
615 616 $(value).attr('disabled', 'disabled');
616 617 $(value).removeAttr('checked');
617 618 $(value).css({'opacity': 0.1});
618 619 }
619 620 else {
620 621 $(value).css({'opacity': 1});
621 622 $(value).removeAttr('disabled');
622 623 }
623 624 });
624 625
625 626 if (cleared) {
626 627 // if we unchecked an active, set the next one to same loc.
627 628 $(this.$verSource).filter('[value={0}]'.format(
628 629 curVal)).attr('checked', 'checked');
629 630 }
630 631
631 632 self.setLockAction(false,
632 633 $(curNode).data('verPos'),
633 634 $(this.$verSource).filter(':checked').data('verPos')
634 635 );
635 636 };
636 637
637 638
638 639 this.attachVersionListener = function () {
639 640 self.$verTarget.change(function (e) {
640 641 self.adjustRadioSelectors(this)
641 642 });
642 643 self.$verSource.change(function (e) {
643 644 self.adjustRadioSelectors(self.$verTarget.filter(':checked'))
644 645 });
645 646 };
646 647
647 648 this.init = function () {
648 649
649 650 var curNode = self.$verTarget.filter(':checked');
650 651 self.adjustRadioSelectors(curNode);
651 652 self.setLockAction(true);
652 653 self.attachVersionListener();
653 654
654 655 };
655 656
656 657 this.setLockAction = function (state, selectedVersion, otherVersion) {
657 658 var $showVersionDiff = this.$showVersionDiff;
658 659
659 660 if (state) {
660 661 $showVersionDiff.attr('disabled', 'disabled');
661 662 $showVersionDiff.addClass('disabled');
662 663 $showVersionDiff.html($showVersionDiff.data('labelTextLocked'));
663 664 }
664 665 else {
665 666 $showVersionDiff.removeAttr('disabled');
666 667 $showVersionDiff.removeClass('disabled');
667 668
668 669 if (selectedVersion == otherVersion) {
669 670 $showVersionDiff.html($showVersionDiff.data('labelTextShow'));
670 671 } else {
671 672 $showVersionDiff.html($showVersionDiff.data('labelTextDiff'));
672 673 }
673 674 }
674 675
675 676 };
676 677
677 678 this.showVersionDiff = function () {
678 679 var target = self.$verTarget.filter(':checked');
679 680 var source = self.$verSource.filter(':checked');
680 681
681 682 if (target.val() && source.val()) {
682 683 var params = {
683 684 'pull_request_id': templateContext.pull_request_data.pull_request_id,
684 685 'repo_name': templateContext.repo_name,
685 686 'version': target.val(),
686 687 'from_version': source.val()
687 688 };
688 689 window.location = pyroutes.url('pullrequest_show', params)
689 690 }
690 691
691 692 return false;
692 693 };
693 694
694 695 this.toggleVersionView = function (elem) {
695 696
696 697 if (this.$showVersionDiff.is(':visible')) {
697 698 $('.version-pr').hide();
698 699 this.$showVersionDiff.hide();
699 700 $(elem).html($(elem).data('toggleOn'))
700 701 } else {
701 702 $('.version-pr').show();
702 703 this.$showVersionDiff.show();
703 704 $(elem).html($(elem).data('toggleOff'))
704 705 }
705 706
706 707 return false
707 708 };
708 709
709 710 };
710 711
711 712
712 713 window.UpdatePrController = function () {
713 714 var self = this;
714 715 this.$updateCommits = $('#update_commits');
715 716 this.$updateCommitsSwitcher = $('#update_commits_switcher');
716 717
717 718 this.lockUpdateButton = function (label) {
718 719 self.$updateCommits.attr('disabled', 'disabled');
719 720 self.$updateCommitsSwitcher.attr('disabled', 'disabled');
720 721
721 722 self.$updateCommits.addClass('disabled');
722 723 self.$updateCommitsSwitcher.addClass('disabled');
723 724
724 725 self.$updateCommits.removeClass('btn-primary');
725 726 self.$updateCommitsSwitcher.removeClass('btn-primary');
726 727
727 728 self.$updateCommits.text(_gettext(label));
728 729 };
729 730
730 731 this.isUpdateLocked = function () {
731 732 return self.$updateCommits.attr('disabled') !== undefined;
732 733 };
733 734
734 735 this.updateCommits = function (curNode) {
735 736 if (self.isUpdateLocked()) {
736 737 return
737 738 }
738 739 self.lockUpdateButton(_gettext('Updating...'));
739 740 updateCommits(
740 741 templateContext.repo_name,
741 742 templateContext.pull_request_data.pull_request_id);
742 743 };
743 744
744 745 this.forceUpdateCommits = function () {
745 746 if (self.isUpdateLocked()) {
746 747 return
747 748 }
748 749 self.lockUpdateButton(_gettext('Force updating...'));
749 750 var force = true;
750 751 updateCommits(
751 752 templateContext.repo_name,
752 753 templateContext.pull_request_data.pull_request_id, force);
753 754 };
754 755 };
755 756
756 757
757 758 /**
758 759 * Reviewer display panel
759 760 */
760 761 window.ReviewersPanel = {
761 762 editButton: null,
762 763 closeButton: null,
763 764 addButton: null,
764 765 removeButtons: null,
765 766 reviewRules: null,
766 767 setReviewers: null,
767 768 controller: null,
768 769
769 770 setSelectors: function () {
770 771 var self = this;
771 772 self.editButton = $('#open_edit_reviewers');
772 773 self.closeButton =$('#close_edit_reviewers');
773 774 self.addButton = $('#add_reviewer');
774 775 self.removeButtons = $('.reviewer_member_remove,.reviewer_member_mandatory_remove');
775 776 },
776 777
777 778 init: function (controller, reviewRules, setReviewers) {
778 779 var self = this;
779 780 self.setSelectors();
780 781
781 782 self.controller = controller;
782 783 self.reviewRules = reviewRules;
783 784 self.setReviewers = setReviewers;
784 785
785 786 self.editButton.on('click', function (e) {
786 787 self.edit();
787 788 });
788 789 self.closeButton.on('click', function (e) {
789 790 self.close();
790 791 self.renderReviewers();
791 792 });
792 793
793 794 self.renderReviewers();
794 795
795 796 },
796 797
797 798 renderReviewers: function () {
798 799 var self = this;
799 800
800 801 if (self.setReviewers.reviewers === undefined) {
801 802 return
802 803 }
803 804 if (self.setReviewers.reviewers.length === 0) {
804 805 self.controller.emptyReviewersTable('<tr id="reviewer-empty-msg"><td colspan="6">No reviewers</td></tr>');
805 806 return
806 807 }
807 808
808 809 self.controller.emptyReviewersTable();
809 810
810 811 $.each(self.setReviewers.reviewers, function (key, val) {
811 812
812 813 var member = val;
813 814 if (member.role === self.controller.ROLE_REVIEWER) {
814 815 var entry = renderTemplate('reviewMemberEntry', {
815 816 'member': member,
816 817 'mandatory': member.mandatory,
817 818 'role': member.role,
818 819 'reasons': member.reasons,
819 820 'allowed_to_update': member.allowed_to_update,
820 821 'review_status': member.review_status,
821 822 'review_status_label': member.review_status_label,
822 823 'user_group': member.user_group,
823 'create': false
824 'create': false,
825 'rhodecode_user': templateContext.rhodecode_user
824 826 });
825 827
826 828 $(self.controller.$reviewMembers.selector).append(entry)
827 829 }
828 830 });
829 831
830 832 tooltipActivate();
831 833 },
832 834
833 835 edit: function (event) {
834 836 var self = this;
835 837 self.editButton.hide();
836 838 self.closeButton.show();
837 839 self.addButton.show();
838 840 $(self.removeButtons.selector).css('visibility', 'visible');
839 841 // review rules
840 842 self.controller.loadReviewRules(this.reviewRules);
841 843 },
842 844
843 845 close: function (event) {
844 846 var self = this;
845 847 this.editButton.show();
846 848 this.closeButton.hide();
847 849 this.addButton.hide();
848 850 $(this.removeButtons.selector).css('visibility', 'hidden');
849 851 // hide review rules
850 852 self.controller.hideReviewRules();
851 853 }
852 854 };
853 855
854 856 /**
855 857 * Reviewer display panel
856 858 */
857 859 window.ObserversPanel = {
858 860 editButton: null,
859 861 closeButton: null,
860 862 addButton: null,
861 863 removeButtons: null,
862 864 reviewRules: null,
863 865 setReviewers: null,
864 866 controller: null,
865 867
866 868 setSelectors: function () {
867 869 var self = this;
868 870 self.editButton = $('#open_edit_observers');
869 871 self.closeButton =$('#close_edit_observers');
870 872 self.addButton = $('#add_observer');
871 873 self.removeButtons = $('.observer_member_remove,.observer_member_mandatory_remove');
872 874 },
873 875
874 876 init: function (controller, reviewRules, setReviewers) {
875 877 var self = this;
876 878 self.setSelectors();
877 879
878 880 self.controller = controller;
879 881 self.reviewRules = reviewRules;
880 882 self.setReviewers = setReviewers;
881 883
882 884 self.editButton.on('click', function (e) {
883 885 self.edit();
884 886 });
885 887 self.closeButton.on('click', function (e) {
886 888 self.close();
887 889 self.renderObservers();
888 890 });
889 891
890 892 self.renderObservers();
891 893
892 894 },
893 895
894 896 renderObservers: function () {
895 897 var self = this;
896 898 if (self.setReviewers.observers === undefined) {
897 899 return
898 900 }
899 901 if (self.setReviewers.observers.length === 0) {
900 902 self.controller.emptyObserversTable('<tr id="observer-empty-msg"><td colspan="6">No observers</td></tr>');
901 903 return
902 904 }
903 905
904 906 self.controller.emptyObserversTable();
905 907
906 908 $.each(self.setReviewers.observers, function (key, val) {
907 909 var member = val;
908 910 if (member.role === self.controller.ROLE_OBSERVER) {
909 911 var entry = renderTemplate('reviewMemberEntry', {
910 912 'member': member,
911 913 'mandatory': member.mandatory,
912 914 'role': member.role,
913 915 'reasons': member.reasons,
914 916 'allowed_to_update': member.allowed_to_update,
915 917 'review_status': member.review_status,
916 918 'review_status_label': member.review_status_label,
917 919 'user_group': member.user_group,
918 'create': false
920 'create': false,
921 'rhodecode_user': templateContext.rhodecode_user
919 922 });
920 923
921 924 $(self.controller.$observerMembers.selector).append(entry)
922 925 }
923 926 });
924 927
925 928 tooltipActivate();
926 929 },
927 930
928 931 edit: function (event) {
929 932 this.editButton.hide();
930 933 this.closeButton.show();
931 934 this.addButton.show();
932 935 $(this.removeButtons.selector).css('visibility', 'visible');
933 936 },
934 937
935 938 close: function (event) {
936 939 this.editButton.show();
937 940 this.closeButton.hide();
938 941 this.addButton.hide();
939 942 $(this.removeButtons.selector).css('visibility', 'hidden');
940 943 }
941 944
942 945 };
943 946
944 947 window.PRDetails = {
945 948 editButton: null,
946 949 closeButton: null,
947 950 deleteButton: null,
948 951 viewFields: null,
949 952 editFields: null,
950 953
951 954 setSelectors: function () {
952 955 var self = this;
953 956 self.editButton = $('#open_edit_pullrequest')
954 957 self.closeButton = $('#close_edit_pullrequest')
955 958 self.deleteButton = $('#delete_pullrequest')
956 959 self.viewFields = $('#pr-desc, #pr-title')
957 960 self.editFields = $('#pr-desc-edit, #pr-title-edit, .pr-save')
958 961 },
959 962
960 963 init: function () {
961 964 var self = this;
962 965 self.setSelectors();
963 966 self.editButton.on('click', function (e) {
964 967 self.edit();
965 968 });
966 969 self.closeButton.on('click', function (e) {
967 970 self.view();
968 971 });
969 972 },
970 973
971 974 edit: function (event) {
972 975 var cmInstance = $('#pr-description-input').get(0).MarkupForm.cm;
973 976 this.viewFields.hide();
974 977 this.editButton.hide();
975 978 this.deleteButton.hide();
976 979 this.closeButton.show();
977 980 this.editFields.show();
978 981 cmInstance.refresh();
979 982 },
980 983
981 984 view: function (event) {
982 985 this.editButton.show();
983 986 this.deleteButton.show();
984 987 this.editFields.hide();
985 988 this.closeButton.hide();
986 989 this.viewFields.show();
987 990 }
988 991 };
989 992
990 993 /**
991 994 * OnLine presence using channelstream
992 995 */
993 996 window.ReviewerPresenceController = function (channel) {
994 997 var self = this;
995 998 this.channel = channel;
996 999 this.users = {};
997 1000
998 1001 this.storeUsers = function (users) {
999 1002 self.users = {}
1000 1003 $.each(users, function (index, value) {
1001 1004 var userId = value.state.id;
1002 1005 self.users[userId] = value.state;
1003 1006 })
1004 1007 }
1005 1008
1006 1009 this.render = function () {
1007 1010 $.each($('.reviewer_entry'), function (index, value) {
1008 1011 var userData = $(value).data();
1009 1012 if (self.users[userData.reviewerUserId] !== undefined) {
1010 1013 $(value).find('.presence-state').show();
1011 1014 } else {
1012 1015 $(value).find('.presence-state').hide();
1013 1016 }
1014 1017 })
1015 1018 };
1016 1019
1017 1020 this.handlePresence = function (data) {
1018 1021 if (data.type == 'presence' && data.channel === self.channel) {
1019 1022 this.storeUsers(data.users);
1020 1023 this.render();
1021 1024 }
1022 1025 };
1023 1026
1024 1027 this.handleChannelUpdate = function (data) {
1025 1028 if (data.channel === this.channel) {
1026 1029 this.storeUsers(data.state.users);
1027 1030 this.render();
1028 1031 }
1029 1032
1030 1033 };
1031 1034
1032 1035 /* subscribe to the current presence */
1033 1036 $.Topic('/connection_controller/presence').subscribe(this.handlePresence.bind(this));
1034 1037 /* subscribe to updates e.g connect/disconnect */
1035 1038 $.Topic('/connection_controller/channel_update').subscribe(this.handleChannelUpdate.bind(this));
1036 1039
1037 1040 };
1038 1041
1039 1042 window.refreshCommentsSuccess = function(targetNode, counterNode, extraCallback) {
1040 1043 var $targetElem = targetNode;
1041 1044 var $counterElem = counterNode;
1042 1045
1043 1046 return function (data) {
1044 1047 var newCount = $(data).data('counter');
1045 1048 if (newCount !== undefined) {
1046 1049 var callback = function () {
1047 1050 $counterElem.animate({'opacity': 1.00}, 200)
1048 1051 $counterElem.html(newCount);
1049 1052 };
1050 1053 $counterElem.animate({'opacity': 0.15}, 200, callback);
1051 1054 }
1052 1055
1053 1056 $targetElem.css('opacity', 1);
1054 1057 $targetElem.html(data);
1055 1058 tooltipActivate();
1056 1059
1057 1060 if (extraCallback !== undefined) {
1058 1061 extraCallback(data)
1059 1062 }
1060 1063 }
1061 1064 }
1062 1065
1063 1066 window.refreshComments = function (version) {
1064 1067 version = version || templateContext.pull_request_data.pull_request_version || '';
1065 1068
1066 1069 // Pull request case
1067 1070 if (templateContext.pull_request_data.pull_request_id !== null) {
1068 1071 var params = {
1069 1072 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1070 1073 'repo_name': templateContext.repo_name,
1071 1074 'version': version,
1072 1075 };
1073 1076 var loadUrl = pyroutes.url('pullrequest_comments', params);
1074 1077 } // commit case
1075 1078 else {
1076 1079 return
1077 1080 }
1078 1081
1079 1082 var currentIDs = []
1080 1083 $.each($('.comment'), function (idx, element) {
1081 1084 currentIDs.push($(element).data('commentId'));
1082 1085 });
1083 1086 var data = {"comments": currentIDs};
1084 1087
1085 1088 var $targetElem = $('.comments-content-table');
1086 1089 $targetElem.css('opacity', 0.3);
1087 1090 var $counterElem = $('#comments-count');
1088 1091 var success = refreshCommentsSuccess($targetElem, $counterElem);
1089 1092 ajaxPOST(loadUrl, data, success, null, {})
1090 1093
1091 1094 }
1092 1095
1093 1096 window.refreshTODOs = function (version) {
1094 1097 version = version || templateContext.pull_request_data.pull_request_version || '';
1095 1098 // Pull request case
1096 1099 if (templateContext.pull_request_data.pull_request_id !== null) {
1097 1100 var params = {
1098 1101 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1099 1102 'repo_name': templateContext.repo_name,
1100 1103 'version': version,
1101 1104 };
1102 1105 var loadUrl = pyroutes.url('pullrequest_todos', params);
1103 1106 } // commit case
1104 1107 else {
1105 1108 return
1106 1109 }
1107 1110
1108 1111 var currentIDs = []
1109 1112 $.each($('.comment'), function (idx, element) {
1110 1113 currentIDs.push($(element).data('commentId'));
1111 1114 });
1112 1115
1113 1116 var data = {"comments": currentIDs};
1114 1117 var $targetElem = $('.todos-content-table');
1115 1118 $targetElem.css('opacity', 0.3);
1116 1119 var $counterElem = $('#todos-count');
1117 1120 var success = refreshCommentsSuccess($targetElem, $counterElem);
1118 1121
1119 1122 ajaxPOST(loadUrl, data, success, null, {})
1120 1123
1121 1124 }
1122 1125
1123 1126 window.refreshDraftComments = function () {
1124 1127
1125 1128 // Pull request case
1126 1129 if (templateContext.pull_request_data.pull_request_id !== null) {
1127 1130 var params = {
1128 1131 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1129 1132 'repo_name': templateContext.repo_name,
1130 1133 };
1131 1134 var loadUrl = pyroutes.url('pullrequest_drafts', params);
1132 1135 } // commit case
1133 1136 else {
1134 1137 return
1135 1138 }
1136 1139
1137 1140 var data = {};
1138 1141
1139 1142 var $targetElem = $('.drafts-content-table');
1140 1143 $targetElem.css('opacity', 0.3);
1141 1144 var $counterElem = $('#drafts-count');
1142 1145 var extraCallback = function(data) {
1143 1146 if ($(data).data('counter') == 0){
1144 1147 $('#draftsTable').hide();
1145 1148 } else {
1146 1149 $('#draftsTable').show();
1147 1150 }
1148 1151 // uncheck on load the select all checkbox
1149 1152 $('[name=select_all_drafts]').prop('checked', 0);
1150 1153 }
1151 1154 var success = refreshCommentsSuccess($targetElem, $counterElem, extraCallback);
1152 1155
1153 1156 ajaxPOST(loadUrl, data, success, null, {})
1154 1157 };
1155 1158
1156 1159 window.refreshAllComments = function (version) {
1157 1160 version = version || templateContext.pull_request_data.pull_request_version || '';
1158 1161
1159 1162 refreshComments(version);
1160 1163 refreshTODOs(version);
1161 1164 };
1162 1165
1163 1166 window.sidebarComment = function (commentId) {
1164 1167 var jsonData = $('#commentHovercard{0}'.format(commentId)).data('commentJsonB64');
1165 1168 if (!jsonData) {
1166 1169 return 'Failed to load comment {0}'.format(commentId)
1167 1170 }
1168 1171 var funcData = JSON.parse(atob(jsonData));
1169 1172 return renderTemplate('sideBarCommentHovercard', funcData)
1170 1173 };
@@ -1,1395 +1,1404 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2 <%namespace name="commentblock" file="/changeset/changeset_file_comment.mako"/>
3 3
4 4 <%def name="diff_line_anchor(commit, filename, line, type)"><%
5 5 return '%s_%s_%i' % (h.md5_safe(commit+filename), type, line)
6 6 %></%def>
7 7
8 8 <%def name="action_class(action)">
9 9 <%
10 10 return {
11 11 '-': 'cb-deletion',
12 12 '+': 'cb-addition',
13 13 ' ': 'cb-context',
14 14 }.get(action, 'cb-empty')
15 15 %>
16 16 </%def>
17 17
18 18 <%def name="op_class(op_id)">
19 19 <%
20 20 return {
21 21 DEL_FILENODE: 'deletion', # file deleted
22 22 BIN_FILENODE: 'warning' # binary diff hidden
23 23 }.get(op_id, 'addition')
24 24 %>
25 25 </%def>
26 26
27 27
28 28
29 29 <%def name="render_diffset(diffset, commit=None,
30 30
31 31 # collapse all file diff entries when there are more than this amount of files in the diff
32 32 collapse_when_files_over=20,
33 33
34 34 # collapse lines in the diff when more than this amount of lines changed in the file diff
35 35 lines_changed_limit=500,
36 36
37 37 # add a ruler at to the output
38 38 ruler_at_chars=0,
39 39
40 40 # show inline comments
41 41 use_comments=False,
42 42
43 43 # disable new comments
44 44 disable_new_comments=False,
45 45
46 46 # special file-comments that were deleted in previous versions
47 47 # it's used for showing outdated comments for deleted files in a PR
48 48 deleted_files_comments=None,
49 49
50 50 # for cache purpose
51 51 inline_comments=None,
52 52
53 53 # additional menu for PRs
54 54 pull_request_menu=None,
55 55
56 56 # show/hide todo next to comments
57 57 show_todos=True,
58 58
59 59 )">
60 60
61 61 <%
62 62 diffset_container_id = h.md5(diffset.target_ref)
63 63 collapse_all = len(diffset.files) > collapse_when_files_over
64 64 active_pattern_entries = h.get_active_pattern_entries(getattr(c, 'repo_name', None))
65 65 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
66 66 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE
67 67 %>
68 68
69 69 %if use_comments:
70 70
71 71 ## Template for injecting comments
72 72 <div id="cb-comments-inline-container-template" class="js-template">
73 73 ${inline_comments_container([])}
74 74 </div>
75 75
76 76 <div class="js-template" id="cb-comment-inline-form-template">
77 77 <div class="comment-inline-form ac">
78 78 %if not c.rhodecode_user.is_default:
79 79 ## render template for inline comments
80 80 ${commentblock.comment_form(form_type='inline')}
81 81 %endif
82 82 </div>
83 83 </div>
84 84
85 85 %endif
86 86
87 87 %if c.user_session_attrs["diffmode"] == 'sideside':
88 88 <style>
89 89 .wrapper {
90 90 max-width: 1600px !important;
91 91 }
92 92 </style>
93 93 %endif
94 94
95 95 %if ruler_at_chars:
96 96 <style>
97 97 .diff table.cb .cb-content:after {
98 98 content: "";
99 99 border-left: 1px solid blue;
100 100 position: absolute;
101 101 top: 0;
102 102 height: 18px;
103 103 opacity: .2;
104 104 z-index: 10;
105 105 //## +5 to account for diff action (+/-)
106 106 left: ${ruler_at_chars + 5}ch;
107 107 </style>
108 108 %endif
109 109
110 110 <div class="diffset ${disable_new_comments and 'diffset-comments-disabled'}">
111 111
112 112 <div style="height: 20px; line-height: 20px">
113 113 ## expand/collapse action
114 114 <div class="pull-left">
115 115 <a class="${'collapsed' if collapse_all else ''}" href="#expand-files" onclick="toggleExpand(this, '${diffset_container_id}'); return false">
116 116 % if collapse_all:
117 117 <i class="icon-plus-squared-alt icon-no-margin"></i>${_('Expand all files')}
118 118 % else:
119 119 <i class="icon-minus-squared-alt icon-no-margin"></i>${_('Collapse all files')}
120 120 % endif
121 121 </a>
122 122
123 123 </div>
124 124
125 125 ## todos
126 126 % if show_todos and getattr(c, 'at_version', None):
127 127 <div class="pull-right">
128 128 <i class="icon-flag-filled" style="color: #949494">TODOs:</i>
129 129 ${_('not available in this view')}
130 130 </div>
131 131 % elif show_todos:
132 132 <div class="pull-right">
133 133 <div class="comments-number" style="padding-left: 10px">
134 134 % if hasattr(c, 'unresolved_comments') and hasattr(c, 'resolved_comments'):
135 135 <i class="icon-flag-filled" style="color: #949494">TODOs:</i>
136 136 % if c.unresolved_comments:
137 137 <a href="#show-todos" onclick="$('#todo-box').toggle(); return false">
138 138 ${_('{} unresolved').format(len(c.unresolved_comments))}
139 139 </a>
140 140 % else:
141 141 ${_('0 unresolved')}
142 142 % endif
143 143
144 144 ${_('{} Resolved').format(len(c.resolved_comments))}
145 145 % endif
146 146 </div>
147 147 </div>
148 148 % endif
149 149
150 150 ## ## comments
151 151 ## <div class="pull-right">
152 152 ## <div class="comments-number" style="padding-left: 10px">
153 153 ## % if hasattr(c, 'comments') and hasattr(c, 'inline_cnt'):
154 154 ## <i class="icon-comment" style="color: #949494">COMMENTS:</i>
155 155 ## % if c.comments:
156 156 ## <a href="#comments">${_ungettext("{} General", "{} General", len(c.comments)).format(len(c.comments))}</a>,
157 157 ## % else:
158 158 ## ${_('0 General')}
159 159 ## % endif
160 160 ##
161 161 ## % if c.inline_cnt:
162 162 ## <a href="#" onclick="return Rhodecode.comments.nextComment();"
163 163 ## id="inline-comments-counter">${_ungettext("{} Inline", "{} Inline", c.inline_cnt).format(c.inline_cnt)}
164 164 ## </a>
165 165 ## % else:
166 166 ## ${_('0 Inline')}
167 167 ## % endif
168 168 ## % endif
169 169 ##
170 170 ## % if pull_request_menu:
171 171 ## <%
172 172 ## outdated_comm_count_ver = pull_request_menu['outdated_comm_count_ver']
173 173 ## %>
174 174 ##
175 175 ## % if outdated_comm_count_ver:
176 176 ## <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">
177 177 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
178 178 ## </a>
179 179 ## <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated')}</a>
180 180 ## <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated')}</a>
181 181 ## % else:
182 182 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
183 183 ## % endif
184 184 ##
185 185 ## % endif
186 186 ##
187 187 ## </div>
188 188 ## </div>
189 189
190 190 </div>
191 191
192 192 % if diffset.limited_diff:
193 193 <div class="diffset-heading ${(diffset.limited_diff and 'diffset-heading-warning' or '')}">
194 194 <h2 class="clearinner">
195 195 ${_('The requested changes are too big and content was truncated.')}
196 196 <a href="${h.current_route_path(request, fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
197 197 </h2>
198 198 </div>
199 199 % endif
200 200
201 201 <div id="todo-box">
202 202 % if hasattr(c, 'unresolved_comments') and c.unresolved_comments:
203 203 % for co in c.unresolved_comments:
204 204 <a class="permalink" href="#comment-${co.comment_id}"
205 205 onclick="Rhodecode.comments.scrollToComment($('#comment-${co.comment_id}'))">
206 206 <i class="icon-flag-filled-red"></i>
207 207 ${co.comment_id}</a>${('' if loop.last else ',')}
208 208 % endfor
209 209 % endif
210 210 </div>
211 211 %if diffset.has_hidden_changes:
212 212 <p class="empty_data">${_('Some changes may be hidden')}</p>
213 213 %elif not diffset.files:
214 214 <p class="empty_data">${_('No files')}</p>
215 215 %endif
216 216
217 217 <div class="filediffs">
218 218
219 219 ## initial value could be marked as False later on
220 220 <% over_lines_changed_limit = False %>
221 221 %for i, filediff in enumerate(diffset.files):
222 222
223 223 %if filediff.source_file_path and filediff.target_file_path:
224 224 %if filediff.source_file_path != filediff.target_file_path:
225 225 ## file was renamed, or copied
226 226 %if RENAMED_FILENODE in filediff.patch['stats']['ops']:
227 227 <%
228 228 final_file_name = h.literal(u'{} <i class="icon-angle-left"></i> <del>{}</del>'.format(filediff.target_file_path, filediff.source_file_path))
229 229 final_path = filediff.target_file_path
230 230 %>
231 231 %elif COPIED_FILENODE in filediff.patch['stats']['ops']:
232 232 <%
233 233 final_file_name = h.literal(u'{} <i class="icon-angle-left"></i> {}'.format(filediff.target_file_path, filediff.source_file_path))
234 234 final_path = filediff.target_file_path
235 235 %>
236 236 %endif
237 237 %else:
238 238 ## file was modified
239 239 <%
240 240 final_file_name = filediff.source_file_path
241 241 final_path = final_file_name
242 242 %>
243 243 %endif
244 244 %else:
245 245 %if filediff.source_file_path:
246 246 ## file was deleted
247 247 <%
248 248 final_file_name = filediff.source_file_path
249 249 final_path = final_file_name
250 250 %>
251 251 %else:
252 252 ## file was added
253 253 <%
254 254 final_file_name = filediff.target_file_path
255 255 final_path = final_file_name
256 256 %>
257 257 %endif
258 258 %endif
259 259
260 260 <%
261 261 lines_changed = filediff.patch['stats']['added'] + filediff.patch['stats']['deleted']
262 262 over_lines_changed_limit = lines_changed > lines_changed_limit
263 263 %>
264 264 ## anchor with support of sticky header
265 265 <div class="anchor" id="a_${h.FID(filediff.raw_id, filediff.patch['filename'])}"></div>
266 266
267 267 <input ${(collapse_all and 'checked' or '')} class="filediff-collapse-state collapse-${diffset_container_id}" id="filediff-collapse-${id(filediff)}" type="checkbox" onchange="updateSticky();">
268 268 <div
269 269 class="filediff"
270 270 data-f-path="${filediff.patch['filename']}"
271 271 data-anchor-id="${h.FID(filediff.raw_id, filediff.patch['filename'])}"
272 272 >
273 273 <label for="filediff-collapse-${id(filediff)}" class="filediff-heading">
274 274 <%
275 275 file_comments = (get_inline_comments(inline_comments, filediff.patch['filename']) or {}).values()
276 276 total_file_comments = [_c for _c in h.itertools.chain.from_iterable(file_comments) if not (_c.outdated or _c.draft)]
277 277 %>
278 278 <div class="filediff-collapse-indicator icon-"></div>
279 279
280 280 ## Comments/Options PILL
281 281 <span class="pill-group pull-right">
282 282 <span class="pill" op="comments">
283 283 <i class="icon-comment"></i> ${len(total_file_comments)}
284 284 </span>
285 285
286 286 <details class="details-reset details-inline-block">
287 287 <summary class="noselect">
288 288 <i class="pill icon-options cursor-pointer" op="options"></i>
289 289 </summary>
290 290 <details-menu class="details-dropdown">
291 291
292 292 <div class="dropdown-item">
293 293 <span>${final_path}</span>
294 294 <span class="pull-right icon-clipboard clipboard-action" data-clipboard-text="${final_path}" title="Copy file path"></span>
295 295 </div>
296 296
297 297 <div class="dropdown-divider"></div>
298 298
299 299 <div class="dropdown-item">
300 300 <% permalink = request.current_route_url(_anchor='a_{}'.format(h.FID(filediff.raw_id, filediff.patch['filename']))) %>
301 301 <a href="${permalink}">¶ permalink</a>
302 302 <span class="pull-right icon-clipboard clipboard-action" data-clipboard-text="${permalink}" title="Copy permalink"></span>
303 303 </div>
304 304
305 305
306 306 </details-menu>
307 307 </details>
308 308
309 309 </span>
310 310
311 311 ${diff_ops(final_file_name, filediff)}
312 312
313 313 </label>
314 314
315 315 ${diff_menu(filediff, use_comments=use_comments)}
316 316 <table id="file-${h.safeid(h.safe_unicode(filediff.patch['filename']))}" data-f-path="${filediff.patch['filename']}" data-anchor-id="${h.FID(filediff.raw_id, filediff.patch['filename'])}" class="code-visible-block cb cb-diff-${c.user_session_attrs["diffmode"]} code-highlight ${(over_lines_changed_limit and 'cb-collapsed' or '')}">
317 317
318 318 ## new/deleted/empty content case
319 319 % if not filediff.hunks:
320 320 ## Comment container, on "fakes" hunk that contains all data to render comments
321 321 ${render_hunk_lines(filediff, c.user_session_attrs["diffmode"], filediff.hunk_ops, use_comments=use_comments, inline_comments=inline_comments, active_pattern_entries=active_pattern_entries)}
322 322 % endif
323 323
324 324 %if filediff.limited_diff:
325 325 <tr class="cb-warning cb-collapser">
326 326 <td class="cb-text" ${(c.user_session_attrs["diffmode"] == 'unified' and 'colspan=4' or 'colspan=6')}>
327 327 ${_('The requested commit or file is too big and content was truncated.')} <a href="${h.current_route_path(request, fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
328 328 </td>
329 329 </tr>
330 330 %else:
331 331 %if over_lines_changed_limit:
332 332 <tr class="cb-warning cb-collapser">
333 333 <td class="cb-text" ${(c.user_session_attrs["diffmode"] == 'unified' and 'colspan=4' or 'colspan=6')}>
334 334 ${_('This diff has been collapsed as it changes many lines, (%i lines changed)' % lines_changed)}
335 335 <a href="#" class="cb-expand"
336 336 onclick="$(this).closest('table').removeClass('cb-collapsed'); updateSticky(); return false;">${_('Show them')}
337 337 </a>
338 338 <a href="#" class="cb-collapse"
339 339 onclick="$(this).closest('table').addClass('cb-collapsed'); updateSticky(); return false;">${_('Hide them')}
340 340 </a>
341 341 </td>
342 342 </tr>
343 343 %endif
344 344 %endif
345 345
346 346 % for hunk in filediff.hunks:
347 347 <tr class="cb-hunk">
348 348 <td ${(c.user_session_attrs["diffmode"] == 'unified' and 'colspan=3' or '')}>
349 349 ## TODO: dan: add ajax loading of more context here
350 350 ## <a href="#">
351 351 <i class="icon-more"></i>
352 352 ## </a>
353 353 </td>
354 354 <td ${(c.user_session_attrs["diffmode"] == 'sideside' and 'colspan=5' or '')}>
355 355 @@
356 356 -${hunk.source_start},${hunk.source_length}
357 357 +${hunk.target_start},${hunk.target_length}
358 358 ${hunk.section_header}
359 359 </td>
360 360 </tr>
361 361
362 362 ${render_hunk_lines(filediff, c.user_session_attrs["diffmode"], hunk, use_comments=use_comments, inline_comments=inline_comments, active_pattern_entries=active_pattern_entries)}
363 363 % endfor
364 364
365 365 <% unmatched_comments = (inline_comments or {}).get(filediff.patch['filename'], {}) %>
366 366
367 367 ## outdated comments that do not fit into currently displayed lines
368 368 % for lineno, comments in unmatched_comments.items():
369 369
370 370 %if c.user_session_attrs["diffmode"] == 'unified':
371 371 % if loop.index == 0:
372 372 <tr class="cb-hunk">
373 373 <td colspan="3"></td>
374 374 <td>
375 375 <div>
376 376 ${_('Unmatched/outdated inline comments below')}
377 377 </div>
378 378 </td>
379 379 </tr>
380 380 % endif
381 381 <tr class="cb-line">
382 382 <td class="cb-data cb-context"></td>
383 383 <td class="cb-lineno cb-context"></td>
384 384 <td class="cb-lineno cb-context"></td>
385 385 <td class="cb-content cb-context">
386 386 ${inline_comments_container(comments, active_pattern_entries=active_pattern_entries)}
387 387 </td>
388 388 </tr>
389 389 %elif c.user_session_attrs["diffmode"] == 'sideside':
390 390 % if loop.index == 0:
391 391 <tr class="cb-comment-info">
392 392 <td colspan="2"></td>
393 393 <td class="cb-line">
394 394 <div>
395 395 ${_('Unmatched/outdated inline comments below')}
396 396 </div>
397 397 </td>
398 398 <td colspan="2"></td>
399 399 <td class="cb-line">
400 400 <div>
401 401 ${_('Unmatched/outdated comments below')}
402 402 </div>
403 403 </td>
404 404 </tr>
405 405 % endif
406 406 <tr class="cb-line">
407 407 <td class="cb-data cb-context"></td>
408 408 <td class="cb-lineno cb-context"></td>
409 409 <td class="cb-content cb-context">
410 410 % if lineno.startswith('o'):
411 411 ${inline_comments_container(comments, active_pattern_entries=active_pattern_entries)}
412 412 % endif
413 413 </td>
414 414
415 415 <td class="cb-data cb-context"></td>
416 416 <td class="cb-lineno cb-context"></td>
417 417 <td class="cb-content cb-context">
418 418 % if lineno.startswith('n'):
419 419 ${inline_comments_container(comments, active_pattern_entries=active_pattern_entries)}
420 420 % endif
421 421 </td>
422 422 </tr>
423 423 %endif
424 424
425 425 % endfor
426 426
427 427 </table>
428 428 </div>
429 429 %endfor
430 430
431 431 ## outdated comments that are made for a file that has been deleted
432 432 % for filename, comments_dict in (deleted_files_comments or {}).items():
433 433
434 434 <%
435 435 display_state = 'display: none'
436 436 open_comments_in_file = [x for x in comments_dict['comments'] if x.outdated is False]
437 437 if open_comments_in_file:
438 438 display_state = ''
439 439 fid = str(id(filename))
440 440 %>
441 441 <div class="filediffs filediff-outdated" style="${display_state}">
442 442 <input ${(collapse_all and 'checked' or '')} class="filediff-collapse-state collapse-${diffset_container_id}" id="filediff-collapse-${id(filename)}" type="checkbox" onchange="updateSticky();">
443 443 <div class="filediff" data-f-path="${filename}" id="a_${h.FID(fid, filename)}">
444 444 <label for="filediff-collapse-${id(filename)}" class="filediff-heading">
445 445 <div class="filediff-collapse-indicator icon-"></div>
446 446
447 447 <span class="pill">
448 448 ## file was deleted
449 449 ${filename}
450 450 </span>
451 451 <span class="pill-group pull-left" >
452 452 ## file op, doesn't need translation
453 453 <span class="pill" op="removed">unresolved comments</span>
454 454 </span>
455 455 <a class="pill filediff-anchor" href="#a_${h.FID(fid, filename)}"></a>
456 456 <span class="pill-group pull-right">
457 457 <span class="pill" op="deleted">
458 458 % if comments_dict['stats'] >0:
459 459 -${comments_dict['stats']}
460 460 % else:
461 461 ${comments_dict['stats']}
462 462 % endif
463 463 </span>
464 464 </span>
465 465 </label>
466 466
467 467 <table class="cb cb-diff-${c.user_session_attrs["diffmode"]} code-highlight ${(over_lines_changed_limit and 'cb-collapsed' or '')}">
468 468 <tr>
469 469 % if c.user_session_attrs["diffmode"] == 'unified':
470 470 <td></td>
471 471 %endif
472 472
473 473 <td></td>
474 474 <td class="cb-text cb-${op_class(BIN_FILENODE)}" ${(c.user_session_attrs["diffmode"] == 'unified' and 'colspan=4' or 'colspan=5')}>
475 475 <strong>${_('This file was removed from diff during updates to this pull-request.')}</strong><br/>
476 476 ${_('There are still outdated/unresolved comments attached to it.')}
477 477 </td>
478 478 </tr>
479 479 %if c.user_session_attrs["diffmode"] == 'unified':
480 480 <tr class="cb-line">
481 481 <td class="cb-data cb-context"></td>
482 482 <td class="cb-lineno cb-context"></td>
483 483 <td class="cb-lineno cb-context"></td>
484 484 <td class="cb-content cb-context">
485 485 ${inline_comments_container(comments_dict['comments'], active_pattern_entries=active_pattern_entries)}
486 486 </td>
487 487 </tr>
488 488 %elif c.user_session_attrs["diffmode"] == 'sideside':
489 489 <tr class="cb-line">
490 490 <td class="cb-data cb-context"></td>
491 491 <td class="cb-lineno cb-context"></td>
492 492 <td class="cb-content cb-context"></td>
493 493
494 494 <td class="cb-data cb-context"></td>
495 495 <td class="cb-lineno cb-context"></td>
496 496 <td class="cb-content cb-context">
497 497 ${inline_comments_container(comments_dict['comments'], active_pattern_entries=active_pattern_entries)}
498 498 </td>
499 499 </tr>
500 500 %endif
501 501 </table>
502 502 </div>
503 503 </div>
504 504 % endfor
505 505
506 506 </div>
507 507 </div>
508 508 </%def>
509 509
510 510 <%def name="diff_ops(file_name, filediff)">
511 511 <%
512 512 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
513 513 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE
514 514 %>
515 515 <span class="pill">
516 516 <i class="icon-file-text"></i>
517 517 ${file_name}
518 518 </span>
519 519
520 520 <span class="pill-group pull-right">
521 521
522 522 ## ops pills
523 523 %if filediff.limited_diff:
524 524 <span class="pill tooltip" op="limited" title="The stats for this diff are not complete">limited diff</span>
525 525 %endif
526 526
527 527 %if NEW_FILENODE in filediff.patch['stats']['ops']:
528 528 <span class="pill" op="created">created</span>
529 529 %if filediff['target_mode'].startswith('120'):
530 530 <span class="pill" op="symlink">symlink</span>
531 531 %else:
532 532 <span class="pill" op="mode">${nice_mode(filediff['target_mode'])}</span>
533 533 %endif
534 534 %endif
535 535
536 536 %if RENAMED_FILENODE in filediff.patch['stats']['ops']:
537 537 <span class="pill" op="renamed">renamed</span>
538 538 %endif
539 539
540 540 %if COPIED_FILENODE in filediff.patch['stats']['ops']:
541 541 <span class="pill" op="copied">copied</span>
542 542 %endif
543 543
544 544 %if DEL_FILENODE in filediff.patch['stats']['ops']:
545 545 <span class="pill" op="removed">removed</span>
546 546 %endif
547 547
548 548 %if CHMOD_FILENODE in filediff.patch['stats']['ops']:
549 549 <span class="pill" op="mode">
550 550 ${nice_mode(filediff['source_mode'])}${nice_mode(filediff['target_mode'])}
551 551 </span>
552 552 %endif
553 553
554 554 %if BIN_FILENODE in filediff.patch['stats']['ops']:
555 555 <span class="pill" op="binary">binary</span>
556 556 %if MOD_FILENODE in filediff.patch['stats']['ops']:
557 557 <span class="pill" op="modified">modified</span>
558 558 %endif
559 559 %endif
560 560
561 561 <span class="pill" op="added">${('+' if filediff.patch['stats']['added'] else '')}${filediff.patch['stats']['added']}</span>
562 562 <span class="pill" op="deleted">${((h.safe_int(filediff.patch['stats']['deleted']) or 0) * -1)}</span>
563 563
564 564 </span>
565 565
566 566 </%def>
567 567
568 568 <%def name="nice_mode(filemode)">
569 569 ${(filemode.startswith('100') and filemode[3:] or filemode)}
570 570 </%def>
571 571
572 572 <%def name="diff_menu(filediff, use_comments=False)">
573 573 <div class="filediff-menu">
574 574
575 575 %if filediff.diffset.source_ref:
576 576
577 577 ## FILE BEFORE CHANGES
578 578 %if filediff.operation in ['D', 'M']:
579 579 <a
580 580 class="tooltip"
581 581 href="${h.route_path('repo_files',repo_name=filediff.diffset.target_repo_name,commit_id=filediff.diffset.source_ref,f_path=filediff.source_file_path)}"
582 582 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
583 583 >
584 584 ${_('Show file before')}
585 585 </a> |
586 586 %else:
587 587 <span
588 588 class="tooltip"
589 589 title="${h.tooltip(_('File not present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
590 590 >
591 591 ${_('Show file before')}
592 592 </span> |
593 593 %endif
594 594
595 595 ## FILE AFTER CHANGES
596 596 %if filediff.operation in ['A', 'M']:
597 597 <a
598 598 class="tooltip"
599 599 href="${h.route_path('repo_files',repo_name=filediff.diffset.source_repo_name,commit_id=filediff.diffset.target_ref,f_path=filediff.target_file_path)}"
600 600 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
601 601 >
602 602 ${_('Show file after')}
603 603 </a>
604 604 %else:
605 605 <span
606 606 class="tooltip"
607 607 title="${h.tooltip(_('File not present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
608 608 >
609 609 ${_('Show file after')}
610 610 </span>
611 611 %endif
612 612
613 613 % if use_comments:
614 614 |
615 615 <a href="#" onclick="Rhodecode.comments.toggleDiffComments(this);return toggleElement(this)"
616 616 data-toggle-on="${_('Hide comments')}"
617 617 data-toggle-off="${_('Show comments')}">
618 618 <span class="hide-comment-button">${_('Hide comments')}</span>
619 619 </a>
620 620 % endif
621 621
622 622 %endif
623 623
624 624 </div>
625 625 </%def>
626 626
627 627
628 628 <%def name="inline_comments_container(comments, active_pattern_entries=None, line_no='', f_path='')">
629 629
630 630 <div class="inline-comments">
631 631 %for comment in comments:
632 632 ${commentblock.comment_block(comment, inline=True, active_pattern_entries=active_pattern_entries)}
633 633 %endfor
634 634
635 635 <%
636 636 extra_class = ''
637 637 extra_style = ''
638 638
639 639 if comments and comments[-1].outdated_at_version(c.at_version_num):
640 640 extra_class = ' comment-outdated'
641 641 extra_style = 'display: none;'
642 642
643 643 %>
644 644
645 645 <div class="reply-thread-container-wrapper${extra_class}" style="${extra_style}">
646 646 <div class="reply-thread-container${extra_class}">
647 647 <div class="reply-thread-gravatar">
648 648 % if c.rhodecode_user.username != h.DEFAULT_USER:
649 649 ${base.gravatar(c.rhodecode_user.email, 20, tooltip=True, user=c.rhodecode_user)}
650 650 % endif
651 651 </div>
652 652
653 653 <div class="reply-thread-reply-button">
654 654 % if c.rhodecode_user.username != h.DEFAULT_USER:
655 655 ## initial reply button, some JS logic can append here a FORM to leave a first comment.
656 656 <button class="cb-comment-add-button" onclick="return Rhodecode.comments.createComment(this, '${f_path}', '${line_no}', null)">Reply...</button>
657 657 % endif
658 658 </div>
659 659 ##% endif
660 660 <div class="reply-thread-last"></div>
661 661 </div>
662 662 </div>
663 663 </div>
664 664
665 665 </%def>
666 666
667 667 <%!
668 668
669 669 def get_inline_comments(comments, filename):
670 670 if hasattr(filename, 'unicode_path'):
671 671 filename = filename.unicode_path
672 672
673 673 if not isinstance(filename, (unicode, str)):
674 674 return None
675 675
676 676 if comments and filename in comments:
677 677 return comments[filename]
678 678
679 679 return None
680 680
681 681 def get_comments_for(diff_type, comments, filename, line_version, line_number):
682 682 if hasattr(filename, 'unicode_path'):
683 683 filename = filename.unicode_path
684 684
685 685 if not isinstance(filename, (unicode, str)):
686 686 return None
687 687
688 688 file_comments = get_inline_comments(comments, filename)
689 689 if file_comments is None:
690 690 return None
691 691
692 692 line_key = '{}{}'.format(line_version, line_number) ## e.g o37, n12
693 693 if line_key in file_comments:
694 694 data = file_comments.pop(line_key)
695 695 return data
696 696 %>
697 697
698 698 <%def name="render_hunk_lines_sideside(filediff, hunk, use_comments=False, inline_comments=None, active_pattern_entries=None)">
699 699
700 700 <% chunk_count = 1 %>
701 701 %for loop_obj, item in h.looper(hunk.sideside):
702 702 <%
703 703 line = item
704 704 i = loop_obj.index
705 705 prev_line = loop_obj.previous
706 706 old_line_anchor, new_line_anchor = None, None
707 707
708 708 if line.original.lineno:
709 709 old_line_anchor = diff_line_anchor(filediff.raw_id, hunk.source_file_path, line.original.lineno, 'o')
710 710 if line.modified.lineno:
711 711 new_line_anchor = diff_line_anchor(filediff.raw_id, hunk.target_file_path, line.modified.lineno, 'n')
712 712
713 713 line_action = line.modified.action or line.original.action
714 714 prev_line_action = prev_line and (prev_line.modified.action or prev_line.original.action)
715 715 %>
716 716
717 717 <tr class="cb-line">
718 718 <td class="cb-data ${action_class(line.original.action)}"
719 719 data-line-no="${line.original.lineno}"
720 720 >
721 721
722 722 <% line_old_comments, line_old_comments_no_drafts = None, None %>
723 723 %if line.original.get_comment_args:
724 724 <%
725 725 line_old_comments = get_comments_for('side-by-side', inline_comments, *line.original.get_comment_args)
726 726 line_old_comments_no_drafts = [c for c in line_old_comments if not c.draft] if line_old_comments else []
727 727 has_outdated = any([x.outdated for x in line_old_comments_no_drafts])
728 728 %>
729 729 %endif
730 730 %if line_old_comments_no_drafts:
731 731 % if has_outdated:
732 732 <i class="tooltip toggle-comment-action icon-comment-toggle" title="${_('Comments including outdated: {}. Click here to toggle them.').format(len(line_old_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
733 733 % else:
734 734 <i class="tooltip toggle-comment-action icon-comment" title="${_('Comments: {}. Click to toggle them.').format(len(line_old_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
735 735 % endif
736 736 %endif
737 737 </td>
738 738 <td class="cb-lineno ${action_class(line.original.action)}"
739 739 data-line-no="${line.original.lineno}"
740 740 %if old_line_anchor:
741 741 id="${old_line_anchor}"
742 742 %endif
743 743 >
744 744 %if line.original.lineno:
745 745 <a name="${old_line_anchor}" href="#${old_line_anchor}">${line.original.lineno}</a>
746 746 %endif
747 747 </td>
748 748
749 749 <% line_no = 'o{}'.format(line.original.lineno) %>
750 750 <td class="cb-content ${action_class(line.original.action)}"
751 751 data-line-no="${line_no}"
752 752 >
753 753 %if use_comments and line.original.lineno:
754 754 ${render_add_comment_button(line_no=line_no, f_path=filediff.patch['filename'])}
755 755 %endif
756 756 <span class="cb-code"><span class="cb-action ${action_class(line.original.action)}"></span>${line.original.content or '' | n}</span>
757 757
758 758 %if use_comments and line.original.lineno and line_old_comments:
759 759 ${inline_comments_container(line_old_comments, active_pattern_entries=active_pattern_entries, line_no=line_no, f_path=filediff.patch['filename'])}
760 760 %endif
761 761
762 762 </td>
763 763 <td class="cb-data ${action_class(line.modified.action)}"
764 764 data-line-no="${line.modified.lineno}"
765 765 >
766 766 <div>
767 767
768 768 <% line_new_comments, line_new_comments_no_drafts = None, None %>
769 769 %if line.modified.get_comment_args:
770 770 <%
771 771 line_new_comments = get_comments_for('side-by-side', inline_comments, *line.modified.get_comment_args)
772 772 line_new_comments_no_drafts = [c for c in line_new_comments if not c.draft] if line_new_comments else []
773 773 has_outdated = any([x.outdated for x in line_new_comments_no_drafts])
774 774 %>
775 775 %endif
776 776
777 777 %if line_new_comments_no_drafts:
778 778 % if has_outdated:
779 779 <i class="tooltip toggle-comment-action icon-comment-toggle" title="${_('Comments including outdated: {}. Click here to toggle them.').format(len(line_new_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
780 780 % else:
781 781 <i class="tooltip toggle-comment-action icon-comment" title="${_('Comments: {}. Click to toggle them.').format(len(line_new_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
782 782 % endif
783 783 %endif
784 784 </div>
785 785 </td>
786 786 <td class="cb-lineno ${action_class(line.modified.action)}"
787 787 data-line-no="${line.modified.lineno}"
788 788 %if new_line_anchor:
789 789 id="${new_line_anchor}"
790 790 %endif
791 791 >
792 792 %if line.modified.lineno:
793 793 <a name="${new_line_anchor}" href="#${new_line_anchor}">${line.modified.lineno}</a>
794 794 %endif
795 795 </td>
796 796
797 797 <% line_no = 'n{}'.format(line.modified.lineno) %>
798 798 <td class="cb-content ${action_class(line.modified.action)}"
799 799 data-line-no="${line_no}"
800 800 >
801 801 %if use_comments and line.modified.lineno:
802 802 ${render_add_comment_button(line_no=line_no, f_path=filediff.patch['filename'])}
803 803 %endif
804 804 <span class="cb-code"><span class="cb-action ${action_class(line.modified.action)}"></span>${line.modified.content or '' | n}</span>
805 805 % if line_action in ['+', '-'] and prev_line_action not in ['+', '-']:
806 806 <div class="nav-chunk" style="visibility: hidden">
807 807 <i class="icon-eye" title="viewing diff hunk-${hunk.index}-${chunk_count}"></i>
808 808 </div>
809 809 <% chunk_count +=1 %>
810 810 % endif
811 811 %if use_comments and line.modified.lineno and line_new_comments:
812 812 ${inline_comments_container(line_new_comments, active_pattern_entries=active_pattern_entries, line_no=line_no, f_path=filediff.patch['filename'])}
813 813 %endif
814 814
815 815 </td>
816 816 </tr>
817 817 %endfor
818 818 </%def>
819 819
820 820
821 821 <%def name="render_hunk_lines_unified(filediff, hunk, use_comments=False, inline_comments=None, active_pattern_entries=None)">
822 822 %for old_line_no, new_line_no, action, content, comments_args in hunk.unified:
823 823
824 824 <%
825 825 old_line_anchor, new_line_anchor = None, None
826 826 if old_line_no:
827 827 old_line_anchor = diff_line_anchor(filediff.raw_id, hunk.source_file_path, old_line_no, 'o')
828 828 if new_line_no:
829 829 new_line_anchor = diff_line_anchor(filediff.raw_id, hunk.target_file_path, new_line_no, 'n')
830 830 %>
831 831 <tr class="cb-line">
832 832 <td class="cb-data ${action_class(action)}">
833 833 <div>
834 834
835 835 <% comments, comments_no_drafts = None, None %>
836 836 %if comments_args:
837 837 <%
838 838 comments = get_comments_for('unified', inline_comments, *comments_args)
839 839 comments_no_drafts = [c for c in line_new_comments if not c.draft] if line_new_comments else []
840 840 has_outdated = any([x.outdated for x in comments_no_drafts])
841 841 %>
842 842 %endif
843 843
844 844 % if comments_no_drafts:
845 845 % if has_outdated:
846 846 <i class="tooltip toggle-comment-action icon-comment-toggle" title="${_('Comments including outdated: {}. Click here to toggle them.').format(len(comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
847 847 % else:
848 848 <i class="tooltip toggle-comment-action icon-comment" title="${_('Comments: {}. Click to toggle them.').format(len(comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
849 849 % endif
850 850 % endif
851 851 </div>
852 852 </td>
853 853 <td class="cb-lineno ${action_class(action)}"
854 854 data-line-no="${old_line_no}"
855 855 %if old_line_anchor:
856 856 id="${old_line_anchor}"
857 857 %endif
858 858 >
859 859 %if old_line_anchor:
860 860 <a name="${old_line_anchor}" href="#${old_line_anchor}">${old_line_no}</a>
861 861 %endif
862 862 </td>
863 863 <td class="cb-lineno ${action_class(action)}"
864 864 data-line-no="${new_line_no}"
865 865 %if new_line_anchor:
866 866 id="${new_line_anchor}"
867 867 %endif
868 868 >
869 869 %if new_line_anchor:
870 870 <a name="${new_line_anchor}" href="#${new_line_anchor}">${new_line_no}</a>
871 871 %endif
872 872 </td>
873 873 <% line_no = '{}{}'.format(new_line_no and 'n' or 'o', new_line_no or old_line_no) %>
874 874 <td class="cb-content ${action_class(action)}"
875 875 data-line-no="${line_no}"
876 876 >
877 877 %if use_comments:
878 878 ${render_add_comment_button(line_no=line_no, f_path=filediff.patch['filename'])}
879 879 %endif
880 880 <span class="cb-code"><span class="cb-action ${action_class(action)}"></span> ${content or '' | n}</span>
881 881 %if use_comments and comments:
882 882 ${inline_comments_container(comments, active_pattern_entries=active_pattern_entries, line_no=line_no, f_path=filediff.patch['filename'])}
883 883 %endif
884 884 </td>
885 885 </tr>
886 886 %endfor
887 887 </%def>
888 888
889 889
890 890 <%def name="render_hunk_lines(filediff, diff_mode, hunk, use_comments, inline_comments, active_pattern_entries)">
891 891 % if diff_mode == 'unified':
892 892 ${render_hunk_lines_unified(filediff, hunk, use_comments=use_comments, inline_comments=inline_comments, active_pattern_entries=active_pattern_entries)}
893 893 % elif diff_mode == 'sideside':
894 894 ${render_hunk_lines_sideside(filediff, hunk, use_comments=use_comments, inline_comments=inline_comments, active_pattern_entries=active_pattern_entries)}
895 895 % else:
896 896 <tr class="cb-line">
897 897 <td>unknown diff mode</td>
898 898 </tr>
899 899 % endif
900 900 </%def>file changes
901 901
902 902
903 903 <%def name="render_add_comment_button(line_no='', f_path='')">
904 904 % if not c.rhodecode_user.is_default:
905 905 <button class="btn btn-small btn-primary cb-comment-box-opener" onclick="return Rhodecode.comments.createComment(this, '${f_path}', '${line_no}', null)">
906 906 <span><i class="icon-comment"></i></span>
907 907 </button>
908 908 % endif
909 909 </%def>
910 910
911 911 <%def name="render_diffset_menu(diffset, range_diff_on=None, commit=None, pull_request_menu=None)">
912 912 <% diffset_container_id = h.md5(diffset.target_ref) %>
913 913
914 914 <div id="diff-file-sticky" class="diffset-menu clearinner">
915 915 ## auto adjustable
916 916 <div class="sidebar__inner">
917 917 <div class="sidebar__bar">
918 918 <div class="pull-right">
919
920 <div class="btn-group" style="margin-right: 5px;">
921 <a class="tooltip btn" onclick="scrollDown();return false" title="${_('Scroll to page bottom')}">
922 <i class="icon-arrow_down"></i>
923 </a>
924 <a class="tooltip btn" onclick="scrollUp();return false" title="${_('Scroll to page top')}">
925 <i class="icon-arrow_up"></i>
926 </a>
927 </div>
928
919 929 <div class="btn-group">
920 930 <a class="btn tooltip toggle-wide-diff" href="#toggle-wide-diff" onclick="toggleWideDiff(this); return false" title="${h.tooltip(_('Toggle wide diff'))}">
921 931 <i class="icon-wide-mode"></i>
922 932 </a>
923 933 </div>
924 934 <div class="btn-group">
925 935
926 936 <a
927 937 class="btn ${(c.user_session_attrs["diffmode"] == 'sideside' and 'btn-active')} tooltip"
928 938 title="${h.tooltip(_('View diff as side by side'))}"
929 939 href="${h.current_route_path(request, diffmode='sideside')}">
930 940 <span>${_('Side by Side')}</span>
931 941 </a>
932 942
933 943 <a
934 944 class="btn ${(c.user_session_attrs["diffmode"] == 'unified' and 'btn-active')} tooltip"
935 945 title="${h.tooltip(_('View diff as unified'))}" href="${h.current_route_path(request, diffmode='unified')}">
936 946 <span>${_('Unified')}</span>
937 947 </a>
938 948
939 949 % if range_diff_on is True:
940 950 <a
941 951 title="${_('Turn off: Show the diff as commit range')}"
942 952 class="btn btn-primary"
943 953 href="${h.current_route_path(request, **{"range-diff":"0"})}">
944 954 <span>${_('Range Diff')}</span>
945 955 </a>
946 956 % elif range_diff_on is False:
947 957 <a
948 958 title="${_('Show the diff as commit range')}"
949 959 class="btn"
950 960 href="${h.current_route_path(request, **{"range-diff":"1"})}">
951 961 <span>${_('Range Diff')}</span>
952 962 </a>
953 963 % endif
954 964 </div>
955 965 <div class="btn-group">
956 966
957 967 <details class="details-reset details-inline-block">
958 968 <summary class="noselect btn">
959 969 <i class="icon-options cursor-pointer" op="options"></i>
960 970 </summary>
961 971
962 972 <div>
963 973 <details-menu class="details-dropdown" style="top: 35px;">
964 974
965 975 <div class="dropdown-item">
966 976 <div style="padding: 2px 0px">
967 977 % if request.GET.get('ignorews', '') == '1':
968 978 <a href="${h.current_route_path(request, ignorews=0)}">${_('Show whitespace changes')}</a>
969 979 % else:
970 980 <a href="${h.current_route_path(request, ignorews=1)}">${_('Hide whitespace changes')}</a>
971 981 % endif
972 982 </div>
973 983 </div>
974 984
975 985 <div class="dropdown-item">
976 986 <div style="padding: 2px 0px">
977 987 % if request.GET.get('fullcontext', '') == '1':
978 988 <a href="${h.current_route_path(request, fullcontext=0)}">${_('Hide full context diff')}</a>
979 989 % else:
980 990 <a href="${h.current_route_path(request, fullcontext=1)}">${_('Show full context diff')}</a>
981 991 % endif
982 992 </div>
983 993 </div>
984 994
985 995 </details-menu>
986 996 </div>
987 997 </details>
988 998
989 999 </div>
990 1000 </div>
991 1001 <div class="pull-left">
992 1002 <div class="btn-group">
993 1003 <div class="pull-left">
994 1004 ${h.hidden('file_filter_{}'.format(diffset_container_id))}
995 1005 </div>
996 1006
997 1007 </div>
998 1008 </div>
999 1009 </div>
1000 1010 <div class="fpath-placeholder pull-left">
1001 1011 <i class="icon-file-text"></i>
1002 1012 <strong class="fpath-placeholder-text">
1003 1013 Context file:
1004 1014 </strong>
1005 1015 </div>
1006 1016 <div class="pull-right noselect">
1007
1008 1017 %if commit:
1009 1018 <span>
1010 1019 <code>${h.show_id(commit)}</code>
1011 1020 </span>
1012 1021 %elif pull_request_menu and pull_request_menu.get('pull_request'):
1013 1022 <span>
1014 1023 <code>!${pull_request_menu['pull_request'].pull_request_id}</code>
1015 1024 </span>
1016 1025 %endif
1017 1026 % if commit or pull_request_menu:
1018 1027 <span class="tooltip" title="Navigate to previous or next change inside files." id="diff_nav">Loading diff...:</span>
1019 1028 <span class="cursor-pointer" onclick="scrollToPrevChunk(); return false">
1020 1029 <i class="icon-angle-up"></i>
1021 1030 </span>
1022 1031 <span class="cursor-pointer" onclick="scrollToNextChunk(); return false">
1023 1032 <i class="icon-angle-down"></i>
1024 1033 </span>
1025 1034 % endif
1026 1035 </div>
1027 1036 <div class="sidebar_inner_shadow"></div>
1028 1037 </div>
1029 1038 </div>
1030 1039
1031 1040 % if diffset:
1032 1041 %if diffset.limited_diff:
1033 1042 <% file_placeholder = _ungettext('%(num)s file changed', '%(num)s files changed', diffset.changed_files) % {'num': diffset.changed_files} %>
1034 1043 %else:
1035 1044 <% file_placeholder = h.literal(_ungettext('%(num)s file changed: <span class="op-added">%(linesadd)s inserted</span>, <span class="op-deleted">%(linesdel)s deleted</span>', '%(num)s files changed: <span class="op-added">%(linesadd)s inserted</span>, <span class="op-deleted">%(linesdel)s deleted</span>',
1036 1045 diffset.changed_files) % {'num': diffset.changed_files, 'linesadd': diffset.lines_added, 'linesdel': diffset.lines_deleted}) %>
1037 1046
1038 1047 %endif
1039 1048 ## case on range-diff placeholder needs to be updated
1040 1049 % if range_diff_on is True:
1041 1050 <% file_placeholder = _('Disabled on range diff') %>
1042 1051 % endif
1043 1052
1044 1053 <script type="text/javascript">
1045 1054 var feedFilesOptions = function (query, initialData) {
1046 1055 var data = {results: []};
1047 1056 var isQuery = typeof query.term !== 'undefined';
1048 1057
1049 1058 var section = _gettext('Changed files');
1050 1059 var filteredData = [];
1051 1060
1052 1061 //filter results
1053 1062 $.each(initialData.results, function (idx, value) {
1054 1063
1055 1064 if (!isQuery || query.term.length === 0 || value.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
1056 1065 filteredData.push({
1057 1066 'id': this.id,
1058 1067 'text': this.text,
1059 1068 "ops": this.ops,
1060 1069 })
1061 1070 }
1062 1071
1063 1072 });
1064 1073
1065 1074 data.results = filteredData;
1066 1075
1067 1076 query.callback(data);
1068 1077 };
1069 1078
1070 1079 var selectionFormatter = function(data, escapeMarkup) {
1071 1080 var container = '<div class="filelist" style="padding-right:100px">{0}</div>';
1072 1081 var tmpl = '<div><strong>{0}</strong></div>'.format(escapeMarkup(data['text']));
1073 1082 var pill = '<div class="pill-group" style="position: absolute; top:7px; right: 0">' +
1074 1083 '<span class="pill" op="added">{0}</span>' +
1075 1084 '<span class="pill" op="deleted">{1}</span>' +
1076 1085 '</div>'
1077 1086 ;
1078 1087 var added = data['ops']['added'];
1079 1088 if (added === 0) {
1080 1089 // don't show +0
1081 1090 added = 0;
1082 1091 } else {
1083 1092 added = '+' + added;
1084 1093 }
1085 1094
1086 1095 var deleted = -1*data['ops']['deleted'];
1087 1096
1088 1097 tmpl += pill.format(added, deleted);
1089 1098 return container.format(tmpl);
1090 1099 };
1091 1100 var formatFileResult = function(result, container, query, escapeMarkup) {
1092 1101 return selectionFormatter(result, escapeMarkup);
1093 1102 };
1094 1103
1095 1104 var formatSelection = function (data, container) {
1096 1105 return '${file_placeholder}'
1097 1106 };
1098 1107
1099 1108 if (window.preloadFileFilterData === undefined) {
1100 1109 window.preloadFileFilterData = {}
1101 1110 }
1102 1111
1103 1112 preloadFileFilterData["${diffset_container_id}"] = {
1104 1113 results: [
1105 1114 % for filediff in diffset.files:
1106 1115 {id:"a_${h.FID(filediff.raw_id, filediff.patch['filename'])}",
1107 1116 text:"${filediff.patch['filename']}",
1108 1117 ops:${h.json.dumps(filediff.patch['stats'])|n}}${('' if loop.last else ',')}
1109 1118 % endfor
1110 1119 ]
1111 1120 };
1112 1121
1113 1122 var diffFileFilterId = "#file_filter_" + "${diffset_container_id}";
1114 1123 var diffFileFilter = $(diffFileFilterId).select2({
1115 1124 'dropdownAutoWidth': true,
1116 1125 'width': 'auto',
1117 1126
1118 1127 containerCssClass: "drop-menu",
1119 1128 dropdownCssClass: "drop-menu-dropdown",
1120 1129 data: preloadFileFilterData["${diffset_container_id}"],
1121 1130 query: function(query) {
1122 1131 feedFilesOptions(query, preloadFileFilterData["${diffset_container_id}"]);
1123 1132 },
1124 1133 initSelection: function(element, callback) {
1125 1134 callback({'init': true});
1126 1135 },
1127 1136 formatResult: formatFileResult,
1128 1137 formatSelection: formatSelection
1129 1138 });
1130 1139
1131 1140 % if range_diff_on is True:
1132 1141 diffFileFilter.select2("enable", false);
1133 1142 % endif
1134 1143
1135 1144 $(diffFileFilterId).on('select2-selecting', function (e) {
1136 1145 var idSelector = e.choice.id;
1137 1146
1138 1147 // expand the container if we quick-select the field
1139 1148 $('#'+idSelector).next().prop('checked', false);
1140 1149 // hide the mast as we later do preventDefault()
1141 1150 $("#select2-drop-mask").click();
1142 1151
1143 1152 window.location.hash = '#'+idSelector;
1144 1153 updateSticky();
1145 1154
1146 1155 e.preventDefault();
1147 1156 });
1148 1157
1149 1158 diffNavText = 'diff navigation:'
1150 1159
1151 1160 getCurrentChunk = function () {
1152 1161
1153 1162 var chunksAll = $('.nav-chunk').filter(function () {
1154 1163 return $(this).parents('.filediff').prev().get(0).checked !== true
1155 1164 })
1156 1165 var chunkSelected = $('.nav-chunk.selected');
1157 1166 var initial = false;
1158 1167
1159 1168 if (chunkSelected.length === 0) {
1160 1169 // no initial chunk selected, we pick first
1161 1170 chunkSelected = $(chunksAll.get(0));
1162 1171 var initial = true;
1163 1172 }
1164 1173
1165 1174 return {
1166 1175 'all': chunksAll,
1167 1176 'selected': chunkSelected,
1168 1177 'initial': initial,
1169 1178 }
1170 1179 }
1171 1180
1172 1181 animateDiffNavText = function () {
1173 1182 var $diffNav = $('#diff_nav')
1174 1183
1175 1184 var callback = function () {
1176 1185 $diffNav.animate({'opacity': 1.00}, 200)
1177 1186 };
1178 1187 $diffNav.animate({'opacity': 0.15}, 200, callback);
1179 1188 }
1180 1189
1181 1190 scrollToChunk = function (moveBy) {
1182 1191 var chunk = getCurrentChunk();
1183 1192 var all = chunk.all
1184 1193 var selected = chunk.selected
1185 1194
1186 1195 var curPos = all.index(selected);
1187 1196 var newPos = curPos;
1188 1197 if (!chunk.initial) {
1189 1198 var newPos = curPos + moveBy;
1190 1199 }
1191 1200
1192 1201 var curElem = all.get(newPos);
1193 1202
1194 1203 if (curElem === undefined) {
1195 1204 // end or back
1196 1205 $('#diff_nav').html('no next diff element:')
1197 1206 animateDiffNavText()
1198 1207 return
1199 1208 } else if (newPos < 0) {
1200 1209 $('#diff_nav').html('no previous diff element:')
1201 1210 animateDiffNavText()
1202 1211 return
1203 1212 } else {
1204 1213 $('#diff_nav').html(diffNavText)
1205 1214 }
1206 1215
1207 1216 curElem = $(curElem)
1208 1217 var offset = 100;
1209 1218 $(window).scrollTop(curElem.position().top - offset);
1210 1219
1211 1220 //clear selection
1212 1221 all.removeClass('selected')
1213 1222 curElem.addClass('selected')
1214 1223 }
1215 1224
1216 1225 scrollToPrevChunk = function () {
1217 1226 scrollToChunk(-1)
1218 1227 }
1219 1228 scrollToNextChunk = function () {
1220 1229 scrollToChunk(1)
1221 1230 }
1222 1231
1223 1232 </script>
1224 1233 % endif
1225 1234
1226 1235 <script type="text/javascript">
1227 1236 $('#diff_nav').html('loading diff...') // wait until whole page is loaded
1228 1237
1229 1238 $(document).ready(function () {
1230 1239
1231 1240 var contextPrefix = _gettext('Context file: ');
1232 1241 ## sticky sidebar
1233 1242 var sidebarElement = document.getElementById('diff-file-sticky');
1234 1243 sidebar = new StickySidebar(sidebarElement, {
1235 1244 topSpacing: 0,
1236 1245 bottomSpacing: 0,
1237 1246 innerWrapperSelector: '.sidebar__inner'
1238 1247 });
1239 1248 sidebarElement.addEventListener('affixed.static.stickySidebar', function () {
1240 1249 // reset our file so it's not holding new value
1241 1250 $('.fpath-placeholder-text').html(contextPrefix + ' - ')
1242 1251 });
1243 1252
1244 1253 updateSticky = function () {
1245 1254 sidebar.updateSticky();
1246 1255 Waypoint.refreshAll();
1247 1256 };
1248 1257
1249 1258 var animateText = function (fPath, anchorId) {
1250 1259 fPath = Select2.util.escapeMarkup(fPath);
1251 1260 $('.fpath-placeholder-text').html(contextPrefix + '<a href="#a_' + anchorId + '">' + fPath + '</a>')
1252 1261 };
1253 1262
1254 1263 ## dynamic file waypoints
1255 1264 var setFPathInfo = function(fPath, anchorId){
1256 1265 animateText(fPath, anchorId)
1257 1266 };
1258 1267
1259 1268 var codeBlock = $('.filediff');
1260 1269
1261 1270 // forward waypoint
1262 1271 codeBlock.waypoint(
1263 1272 function(direction) {
1264 1273 if (direction === "down"){
1265 1274 setFPathInfo($(this.element).data('fPath'), $(this.element).data('anchorId'))
1266 1275 }
1267 1276 }, {
1268 1277 offset: function () {
1269 1278 return 70;
1270 1279 },
1271 1280 context: '.fpath-placeholder'
1272 1281 }
1273 1282 );
1274 1283
1275 1284 // backward waypoint
1276 1285 codeBlock.waypoint(
1277 1286 function(direction) {
1278 1287 if (direction === "up"){
1279 1288 setFPathInfo($(this.element).data('fPath'), $(this.element).data('anchorId'))
1280 1289 }
1281 1290 }, {
1282 1291 offset: function () {
1283 1292 return -this.element.clientHeight + 90;
1284 1293 },
1285 1294 context: '.fpath-placeholder'
1286 1295 }
1287 1296 );
1288 1297
1289 1298 toggleWideDiff = function (el) {
1290 1299 updateSticky();
1291 1300 var wide = Rhodecode.comments.toggleWideMode(this);
1292 1301 storeUserSessionAttr('rc_user_session_attr.wide_diff_mode', wide);
1293 1302 if (wide === true) {
1294 1303 $(el).addClass('btn-active');
1295 1304 } else {
1296 1305 $(el).removeClass('btn-active');
1297 1306 }
1298 1307 return null;
1299 1308 };
1300 1309
1301 1310 toggleExpand = function (el, diffsetEl) {
1302 1311 var el = $(el);
1303 1312 if (el.hasClass('collapsed')) {
1304 1313 $('.filediff-collapse-state.collapse-{0}'.format(diffsetEl)).prop('checked', false);
1305 1314 el.removeClass('collapsed');
1306 1315 el.html(
1307 1316 '<i class="icon-minus-squared-alt icon-no-margin"></i>' +
1308 1317 _gettext('Collapse all files'));
1309 1318 }
1310 1319 else {
1311 1320 $('.filediff-collapse-state.collapse-{0}'.format(diffsetEl)).prop('checked', true);
1312 1321 el.addClass('collapsed');
1313 1322 el.html(
1314 1323 '<i class="icon-plus-squared-alt icon-no-margin"></i>' +
1315 1324 _gettext('Expand all files'));
1316 1325 }
1317 1326 updateSticky()
1318 1327 };
1319 1328
1320 1329 toggleCommitExpand = function (el) {
1321 1330 var $el = $(el);
1322 1331 var commits = $el.data('toggleCommitsCnt');
1323 1332 var collapseMsg = _ngettext('Collapse {0} commit', 'Collapse {0} commits', commits).format(commits);
1324 1333 var expandMsg = _ngettext('Expand {0} commit', 'Expand {0} commits', commits).format(commits);
1325 1334
1326 1335 if ($el.hasClass('collapsed')) {
1327 1336 $('.compare_select').show();
1328 1337 $('.compare_select_hidden').hide();
1329 1338
1330 1339 $el.removeClass('collapsed');
1331 1340 $el.html(
1332 1341 '<i class="icon-minus-squared-alt icon-no-margin"></i>' +
1333 1342 collapseMsg);
1334 1343 }
1335 1344 else {
1336 1345 $('.compare_select').hide();
1337 1346 $('.compare_select_hidden').show();
1338 1347 $el.addClass('collapsed');
1339 1348 $el.html(
1340 1349 '<i class="icon-plus-squared-alt icon-no-margin"></i>' +
1341 1350 expandMsg);
1342 1351 }
1343 1352 updateSticky();
1344 1353 };
1345 1354
1346 1355 // get stored diff mode and pre-enable it
1347 1356 if (templateContext.session_attrs.wide_diff_mode === "true") {
1348 1357 Rhodecode.comments.toggleWideMode(null);
1349 1358 $('.toggle-wide-diff').addClass('btn-active');
1350 1359 updateSticky();
1351 1360 }
1352 1361
1353 1362 // DIFF NAV //
1354 1363
1355 1364 // element to detect scroll direction of
1356 1365 var $window = $(window);
1357 1366
1358 1367 // initialize last scroll position
1359 1368 var lastScrollY = $window.scrollTop();
1360 1369
1361 1370 $window.on('resize scrollstop', {latency: 350}, function () {
1362 1371 var visibleChunks = $('.nav-chunk').withinviewport({top: 75});
1363 1372
1364 1373 // get current scroll position
1365 1374 var currentScrollY = $window.scrollTop();
1366 1375
1367 1376 // determine current scroll direction
1368 1377 if (currentScrollY > lastScrollY) {
1369 1378 var y = 'down'
1370 1379 } else if (currentScrollY !== lastScrollY) {
1371 1380 var y = 'up';
1372 1381 }
1373 1382
1374 1383 var pos = -1; // by default we use last element in viewport
1375 1384 if (y === 'down') {
1376 1385 pos = -1;
1377 1386 } else if (y === 'up') {
1378 1387 pos = 0;
1379 1388 }
1380 1389
1381 1390 if (visibleChunks.length > 0) {
1382 1391 $('.nav-chunk').removeClass('selected');
1383 1392 $(visibleChunks.get(pos)).addClass('selected');
1384 1393 }
1385 1394
1386 1395 // update last scroll position to current position
1387 1396 lastScrollY = currentScrollY;
1388 1397
1389 1398 });
1390 1399 $('#diff_nav').html(diffNavText);
1391 1400
1392 1401 });
1393 1402 </script>
1394 1403
1395 1404 </%def>
@@ -1,1051 +1,1052 b''
1 1 <%inherit file="/base/base.mako"/>
2 2 <%namespace name="base" file="/base/base.mako"/>
3 3 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 4 <%namespace name="sidebar" file="/base/sidebar.mako"/>
5 5
6 6
7 7 <%def name="title()">
8 8 ${_('{} Pull Request !{}').format(c.repo_name, c.pull_request.pull_request_id)}
9 9 %if c.rhodecode_name:
10 10 &middot; ${h.branding(c.rhodecode_name)}
11 11 %endif
12 12 </%def>
13 13
14 14 <%def name="breadcrumbs_links()">
15 15
16 16 </%def>
17 17
18 18 <%def name="menu_bar_nav()">
19 19 ${self.menu_items(active='repositories')}
20 20 </%def>
21 21
22 22 <%def name="menu_bar_subnav()">
23 23 ${self.repo_menu(active='showpullrequest')}
24 24 </%def>
25 25
26 26
27 27 <%def name="main()">
28 28 ## Container to gather extracted Tickets
29 29 <%
30 30 c.referenced_commit_issues = []
31 31 c.referenced_desc_issues = []
32 32 %>
33 33
34 34 <script type="text/javascript">
35 35 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
36 36 templateContext.pull_request_data.pull_request_version = '${request.GET.get('version', '')}';
37 37 </script>
38 38
39 39 <div class="box">
40 40
41 41 <div class="box pr-summary">
42 42
43 43 <div class="summary-details block-left">
44 44 <div id="pr-title">
45 45 % if c.pull_request.is_closed():
46 46 <span class="pr-title-closed-tag tag">${_('Closed')}</span>
47 47 % endif
48 48 <input class="pr-title-input large disabled" disabled="disabled" name="pullrequest_title" type="text" value="${c.pull_request.title}">
49 49 </div>
50 50 <div id="pr-title-edit" class="input" style="display: none;">
51 51 <input class="pr-title-input large" id="pr-title-input" name="pullrequest_title" type="text" value="${c.pull_request.title}">
52 52 </div>
53 53
54 54 <% summary = lambda n:{False:'summary-short'}.get(n) %>
55 55 <div class="pr-details-title">
56 56 <div class="pull-left">
57 57 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request !{}').format(c.pull_request.pull_request_id)}</a>
58 58 ${_('Created on')}
59 59 <span class="tooltip" title="${_('Last updated on')} ${h.format_date(c.pull_request.updated_on)}">${h.format_date(c.pull_request.created_on)},</span>
60 60 <span class="pr-details-title-author-pref">${_('by')}</span>
61 61 </div>
62 62
63 63 <div class="pull-left">
64 64 ${self.gravatar_with_user(c.pull_request.author.email, 16, tooltip=True)}
65 65 </div>
66 66
67 67 %if c.allowed_to_update:
68 68 <div class="pull-right">
69 69 <div id="edit_pull_request" class="action_button pr-save" style="display: none;">${_('Update title & description')}</div>
70 70 <div id="delete_pullrequest" class="action_button pr-save ${('' if c.allowed_to_delete else 'disabled' )}" style="display: none;">
71 71 % if c.allowed_to_delete:
72 72 ${h.secure_form(h.route_path('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id), request=request)}
73 73 <input class="btn btn-link btn-danger no-margin" id="remove_${c.pull_request.pull_request_id}" name="remove_${c.pull_request.pull_request_id}"
74 74 onclick="submitConfirm(event, this, _gettext('Confirm to delete this pull request'), _gettext('Delete'), '${'!{}'.format(c.pull_request.pull_request_id)}')"
75 75 type="submit" value="${_('Delete pull request')}">
76 76 ${h.end_form()}
77 77 % else:
78 78 <span class="tooltip" title="${_('Not allowed to delete this pull request')}">${_('Delete pull request')}</span>
79 79 % endif
80 80 </div>
81 81 <div id="open_edit_pullrequest" class="action_button">${_('Edit')}</div>
82 82 <div id="close_edit_pullrequest" class="action_button" style="display: none;">${_('Cancel')}</div>
83 83 </div>
84 84
85 85 %endif
86 86 </div>
87 87
88 88 <div id="pr-desc" class="input" title="${_('Rendered using {} renderer').format(c.renderer)}">
89 89 ${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name, issues_container=c.referenced_desc_issues)}
90 90 </div>
91 91
92 92 <div id="pr-desc-edit" class="input textarea" style="display: none;">
93 93 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
94 94 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
95 95 </div>
96 96
97 97 <div id="summary" class="fields pr-details-content">
98 98
99 99 ## source
100 100 <div class="field">
101 101 <div class="label-pr-detail">
102 102 <label>${_('Commit flow')}:</label>
103 103 </div>
104 104 <div class="input">
105 105 <div class="pr-commit-flow">
106 106 ## Source
107 107 %if c.pull_request.source_ref_parts.type == 'branch':
108 108 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}"><code class="pr-source-info">${c.pull_request.source_ref_parts.type}:${c.pull_request.source_ref_parts.name}</code></a>
109 109 %else:
110 110 <code class="pr-source-info">${'{}:{}'.format(c.pull_request.source_ref_parts.type, c.pull_request.source_ref_parts.name)}</code>
111 111 %endif
112 112 ${_('of')} <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.repo_name}</a>
113 113 &rarr;
114 114 ## Target
115 115 %if c.pull_request.target_ref_parts.type == 'branch':
116 116 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}"><code class="pr-target-info">${c.pull_request.target_ref_parts.type}:${c.pull_request.target_ref_parts.name}</code></a>
117 117 %else:
118 118 <code class="pr-target-info">${'{}:{}'.format(c.pull_request.target_ref_parts.type, c.pull_request.target_ref_parts.name)}</code>
119 119 %endif
120 120
121 121 ${_('of')} <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.repo_name}</a>
122 122
123 123 <a class="source-details-action" href="#expand-source-details" onclick="return toggleElement(this, '.source-details')" data-toggle-on='<i class="icon-angle-down">more details</i>' data-toggle-off='<i class="icon-angle-up">less details</i>'>
124 124 <i class="icon-angle-down">more details</i>
125 125 </a>
126 126
127 127 </div>
128 128
129 129 <div class="source-details" style="display: none">
130 130
131 131 <ul>
132 132
133 133 ## common ancestor
134 134 <li>
135 135 ${_('Common ancestor')}:
136 136 % if c.ancestor_commit:
137 137 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a>
138 138 % else:
139 139 ${_('not available')}
140 140 % endif
141 141 </li>
142 142
143 143 ## pull url
144 144 <li>
145 145 %if h.is_hg(c.pull_request.source_repo):
146 146 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
147 147 %elif h.is_git(c.pull_request.source_repo):
148 148 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
149 149 %endif
150 150
151 151 <span>${_('Pull changes from source')}</span>: <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
152 152 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
153 153 </li>
154 154
155 155 ## Shadow repo
156 156 <li>
157 157 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
158 158 %if h.is_hg(c.pull_request.target_repo):
159 159 <% clone_url = 'hg clone --update {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
160 160 %elif h.is_git(c.pull_request.target_repo):
161 161 <% clone_url = 'git clone --branch {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
162 162 %endif
163 163
164 164 <span class="tooltip" title="${_('Clone repository in its merged state using shadow repository')}">${_('Clone from shadow repository')}</span>: <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
165 165 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
166 166
167 167 % else:
168 168 <div class="">
169 169 ${_('Shadow repository data not available')}.
170 170 </div>
171 171 % endif
172 172 </li>
173 173
174 174 </ul>
175 175
176 176 </div>
177 177
178 178 </div>
179 179
180 180 </div>
181 181
182 182 ## versions
183 183 <div class="field">
184 184 <div class="label-pr-detail">
185 185 <label>${_('Versions')}:</label>
186 186 </div>
187 187
188 188 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
189 189 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
190 190
191 191 <div class="pr-versions">
192 192 % if c.show_version_changes:
193 193 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
194 194 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
195 195 ${_ungettext('{} version available for this pull request, ', '{} versions available for this pull request, ', len(c.versions)).format(len(c.versions))}
196 196 <a id="show-pr-versions" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
197 197 data-toggle-on="${_('show versions')}."
198 198 data-toggle-off="${_('hide versions')}.">
199 199 ${_('show versions')}.
200 200 </a>
201 201 <table>
202 202 ## SHOW ALL VERSIONS OF PR
203 203 <% ver_pr = None %>
204 204
205 205 % for data in reversed(list(enumerate(c.versions, 1))):
206 206 <% ver_pos = data[0] %>
207 207 <% ver = data[1] %>
208 208 <% ver_pr = ver.pull_request_version_id %>
209 209 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
210 210
211 211 <tr class="version-pr" style="display: ${display_row}">
212 212 <td>
213 213 <code>
214 214 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
215 215 </code>
216 216 </td>
217 217 <td>
218 218 <input ${('checked="checked"' if c.from_version_index == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
219 219 <input ${('checked="checked"' if c.at_version_num == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
220 220 </td>
221 221 <td>
222 222 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
223 223 <i class="tooltip icon-circle review-status-${review_status}" title="${_('Your review status at this version')}"></i>
224 224
225 225 </td>
226 226 <td>
227 227 % if c.at_version_num != ver_pr:
228 228 <i class="tooltip icon-comment" title="${_('Comments from pull request version v{0}').format(ver_pos)}"></i>
229 229 <code>
230 230 General:${len(c.comment_versions[ver_pr]['at'])} / Inline:${len(c.inline_versions[ver_pr]['at'])}
231 231 </code>
232 232 % endif
233 233 </td>
234 234 <td>
235 235 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
236 236 </td>
237 237 <td>
238 238 <code>${h.age_component(ver.updated_on, time_is_local=True, tooltip=False)}</code>
239 239 </td>
240 240 </tr>
241 241 % endfor
242 242
243 243 <tr>
244 244 <td colspan="6">
245 245 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
246 246 data-label-text-locked="${_('select versions to show changes')}"
247 247 data-label-text-diff="${_('show changes between versions')}"
248 248 data-label-text-show="${_('show pull request for this version')}"
249 249 >
250 250 ${_('select versions to show changes')}
251 251 </button>
252 252 </td>
253 253 </tr>
254 254 </table>
255 255 % else:
256 256 <div>
257 257 ${_('Pull request versions not available')}.
258 258 </div>
259 259 % endif
260 260 </div>
261 261 </div>
262 262
263 263 </div>
264 264
265 265 </div>
266 266
267 267
268 268 </div>
269 269
270 270 </div>
271 271
272 272 <div class="box">
273 273
274 274 % if c.state_progressing:
275 275
276 276 <h2 style="text-align: center">
277 277 ${_('Cannot show diff when pull request state is changing. Current progress state')}: <span class="tag tag-merge-state-${c.pull_request.state}">${c.pull_request.state}</span>
278 278
279 279 % if c.is_super_admin:
280 280 <br/>
281 281 If you think this is an error try <a href="${h.current_route_path(request, force_state='created')}">forced state reset</a> to <span class="tag tag-merge-state-created">created</span> state.
282 282 % endif
283 283 </h2>
284 284
285 285 % else:
286 286
287 287 ## Diffs rendered here
288 288 <div class="table" >
289 289 <div id="changeset_compare_view_content">
290 290 ##CS
291 291 % if c.missing_requirements:
292 292 <div class="box">
293 293 <div class="alert alert-warning">
294 294 <div>
295 295 <strong>${_('Missing requirements:')}</strong>
296 296 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
297 297 </div>
298 298 </div>
299 299 </div>
300 300 % elif c.missing_commits:
301 301 <div class="box">
302 302 <div class="alert alert-warning">
303 303 <div>
304 304 <strong>${_('Missing commits')}:</strong>
305 305 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}<br/>
306 306 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}<br/>
307 307 ${_('Consider doing a `force update commits` in case you think this is an error.')}
308 308 </div>
309 309 </div>
310 310 </div>
311 311 % elif c.pr_merge_source_commit.changed and not c.pull_request.is_closed():
312 312 <div class="box">
313 313 <div class="alert alert-info">
314 314 <div>
315 315 <strong>${_('There are new changes for `{}:{}` in source repository, please consider updating this pull request.').format(c.pr_merge_source_commit.ref_spec.type, c.pr_merge_source_commit.ref_spec.name)}</strong>
316 316 </div>
317 317 </div>
318 318 </div>
319 319 % endif
320 320
321 321 <div class="compare_view_commits_title">
322 322 % if not c.compare_mode:
323 323
324 324 % if c.at_version_index:
325 325 <h4>
326 326 ${_('Showing changes at v{}, commenting is disabled.').format(c.at_version_index)}
327 327 </h4>
328 328 % endif
329 329
330 330 <div class="pull-left">
331 331 <div class="btn-group">
332 332 <a class="${('collapsed' if c.collapse_all_commits else '')}" href="#expand-commits" onclick="toggleCommitExpand(this); return false" data-toggle-commits-cnt=${len(c.commit_ranges)} >
333 333 % if c.collapse_all_commits:
334 334 <i class="icon-plus-squared-alt icon-no-margin"></i>
335 335 ${_ungettext('Expand {} commit', 'Expand {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
336 336 % else:
337 337 <i class="icon-minus-squared-alt icon-no-margin"></i>
338 338 ${_ungettext('Collapse {} commit', 'Collapse {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
339 339 % endif
340 340 </a>
341 341 </div>
342 342 </div>
343 343
344 344 <div class="pull-right">
345 345 % if c.allowed_to_update and not c.pull_request.is_closed():
346 346
347 347 <div class="btn-group btn-group-actions">
348 348 <a id="update_commits" class="btn btn-primary no-margin" onclick="updateController.updateCommits(this); return false">
349 349 ${_('Update commits')}
350 350 </a>
351 351
352 352 <a id="update_commits_switcher" class="tooltip btn btn-primary btn-more-option" data-toggle="dropdown" aria-pressed="false" role="button" title="${_('more update options')}">
353 353 <i class="icon-down"></i>
354 354 </a>
355 355
356 356 <div class="btn-action-switcher-container right-align" id="update-commits-switcher">
357 357 <ul class="btn-action-switcher" role="menu" style="min-width: 300px;">
358 358 <li>
359 359 <a href="#forceUpdate" onclick="updateController.forceUpdateCommits(this); return false">
360 360 ${_('Force update commits')}
361 361 </a>
362 362 <div class="action-help-block">
363 363 ${_('Update commits and force refresh this pull request.')}
364 364 </div>
365 365 </li>
366 366 </ul>
367 367 </div>
368 368 </div>
369 369
370 370 % else:
371 371 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
372 372 % endif
373 373
374 374 </div>
375 375 % endif
376 376 </div>
377 377
378 378 % if not c.missing_commits:
379 379 ## COMPARE RANGE DIFF MODE
380 380 % if c.compare_mode:
381 381 % if c.at_version:
382 382 <h4>
383 383 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_index, ver_to=c.at_version_index if c.at_version_index else 'latest')}:
384 384 </h4>
385 385
386 386 <div class="subtitle-compare">
387 387 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
388 388 </div>
389 389
390 390 <div class="container">
391 391 <table class="rctable compare_view_commits">
392 392 <tr>
393 393 <th></th>
394 394 <th>${_('Time')}</th>
395 395 <th>${_('Author')}</th>
396 396 <th>${_('Commit')}</th>
397 397 <th></th>
398 398 <th>${_('Description')}</th>
399 399 </tr>
400 400
401 401 % for c_type, commit in c.commit_changes:
402 402 % if c_type in ['a', 'r']:
403 403 <%
404 404 if c_type == 'a':
405 405 cc_title = _('Commit added in displayed changes')
406 406 elif c_type == 'r':
407 407 cc_title = _('Commit removed in displayed changes')
408 408 else:
409 409 cc_title = ''
410 410 %>
411 411 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
412 412 <td>
413 413 <div class="commit-change-indicator color-${c_type}-border">
414 414 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
415 415 ${c_type.upper()}
416 416 </div>
417 417 </div>
418 418 </td>
419 419 <td class="td-time">
420 420 ${h.age_component(commit.date)}
421 421 </td>
422 422 <td class="td-user">
423 423 ${base.gravatar_with_user(commit.author, 16, tooltip=True)}
424 424 </td>
425 425 <td class="td-hash">
426 426 <code>
427 427 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
428 428 r${commit.idx}:${h.short_id(commit.raw_id)}
429 429 </a>
430 430 ${h.hidden('revisions', commit.raw_id)}
431 431 </code>
432 432 </td>
433 433 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
434 434 <i class="icon-expand-linked"></i>
435 435 </td>
436 436 <td class="mid td-description">
437 437 <div class="log-container truncate-wrap">
438 438 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name, issues_container=c.referenced_commit_issues)}</div>
439 439 </div>
440 440 </td>
441 441 </tr>
442 442 % endif
443 443 % endfor
444 444 </table>
445 445 </div>
446 446
447 447 % endif
448 448
449 449 ## Regular DIFF
450 450 % else:
451 451 <%include file="/compare/compare_commits.mako" />
452 452 % endif
453 453
454 454 <div class="cs_files">
455 455 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
456 456
457 457 <%
458 458 pr_menu_data = {
459 459 'outdated_comm_count_ver': outdated_comm_count_ver,
460 460 'pull_request': c.pull_request
461 461 }
462 462 %>
463 463
464 464 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on, pull_request_menu=pr_menu_data)}
465 465
466 466 % if c.range_diff_on:
467 467 % for commit in c.commit_ranges:
468 468 ${cbdiffs.render_diffset(
469 469 c.changes[commit.raw_id],
470 470 commit=commit, use_comments=True,
471 471 collapse_when_files_over=5,
472 472 disable_new_comments=True,
473 473 deleted_files_comments=c.deleted_files_comments,
474 474 inline_comments=c.inline_comments,
475 475 pull_request_menu=pr_menu_data, show_todos=False)}
476 476 % endfor
477 477 % else:
478 478 ${cbdiffs.render_diffset(
479 479 c.diffset, use_comments=True,
480 480 collapse_when_files_over=30,
481 481 disable_new_comments=not c.allowed_to_comment,
482 482 deleted_files_comments=c.deleted_files_comments,
483 483 inline_comments=c.inline_comments,
484 484 pull_request_menu=pr_menu_data, show_todos=False)}
485 485 % endif
486 486
487 487 </div>
488 488 % else:
489 489 ## skipping commits we need to clear the view for missing commits
490 490 <div style="clear:both;"></div>
491 491 % endif
492 492
493 493 </div>
494 494 </div>
495 495
496 496 ## template for inline comment form
497 497 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
498 498
499 499 ## comments heading with count
500 500 <div class="comments-heading">
501 501 <i class="icon-comment"></i>
502 502 ${_('General Comments')} ${len(c.comments)}
503 503 </div>
504 504
505 505 ## render general comments
506 506 <div id="comment-tr-show">
507 507 % if general_outdated_comm_count_ver:
508 508 <div class="info-box">
509 509 % if general_outdated_comm_count_ver == 1:
510 510 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
511 511 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
512 512 % else:
513 513 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
514 514 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
515 515 % endif
516 516 </div>
517 517 % endif
518 518 </div>
519 519
520 520 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
521 521
522 522 % if not c.pull_request.is_closed():
523 523 ## main comment form and it status
524 524 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
525 525 pull_request_id=c.pull_request.pull_request_id),
526 526 c.pull_request_review_status,
527 527 is_pull_request=True, change_status=c.allowed_to_change_status)}
528 528
529 529 ## merge status, and merge action
530 530 <div class="pull-request-merge">
531 531 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
532 532 </div>
533 533
534 534 %endif
535 535
536 536 % endif
537 537 </div>
538 538
539 539
540 540 ### NAV SIDEBAR
541 541 <aside class="right-sidebar right-sidebar-expanded" id="pr-nav-sticky" style="display: none">
542 542 <div class="sidenav navbar__inner" >
543 543 ## TOGGLE
544 544 <div class="sidebar-toggle" onclick="toggleSidebar(); return false">
545 545 <a href="#toggleSidebar" class="grey-link-action">
546 546
547 547 </a>
548 548 </div>
549 549
550 550 ## CONTENT
551 551 <div class="sidebar-content">
552 552
553 553 ## Drafts
554 554 % if c.rhodecode_edition_id == 'EE':
555 555 <div id="draftsTable" class="sidebar-element clear-both" style="display: ${'block' if c.draft_comments else 'none'}">
556 556 <div class="tooltip right-sidebar-collapsed-state" style="display: none;" onclick="toggleSidebar(); return false" title="${_('Drafts')}">
557 557 <i class="icon-comment icon-draft"></i>
558 558 <span id="drafts-count">${len(c.draft_comments)}</span>
559 559 </div>
560 560
561 561 <div class="right-sidebar-expanded-state pr-details-title">
562 562 <span style="padding-left: 2px">
563 563 <input name="select_all_drafts" type="checkbox" onclick="$('[name=submit_draft]').prop('checked', !$('[name=submit_draft]').prop('checked'))">
564 564 </span>
565 565 <span class="sidebar-heading noselect" onclick="refreshDraftComments(); return false">
566 566 <i class="icon-comment icon-draft"></i>
567 567 ${_('Drafts')}
568 568 </span>
569 569 <span class="block-right action_button last-item" onclick="submitDrafts(event)">${_('Submit')}</span>
570 570 </div>
571 571
572 572 <div id="drafts" class="right-sidebar-expanded-state pr-details-content reviewers">
573 573 % if c.draft_comments:
574 574 ${sidebar.comments_table(c.draft_comments, len(c.draft_comments), draft_comments=True)}
575 575 % else:
576 576 <table class="drafts-content-table">
577 577 <tr>
578 578 <td>
579 579 ${_('No TODOs yet')}
580 580 </td>
581 581 </tr>
582 582 </table>
583 583 % endif
584 584 </div>
585 585
586 586 </div>
587 587 % endif
588 588
589 589 ## RULES SUMMARY/RULES
590 590 <div class="sidebar-element clear-both">
591 591 <% vote_title = _ungettext(
592 592 'Status calculated based on votes from {} reviewer',
593 593 'Status calculated based on votes from {} reviewers', c.reviewers_count).format(c.reviewers_count)
594 594 %>
595 595
596 596 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
597 597 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
598 598 ${c.reviewers_count}
599 599 </div>
600 600
601 601 ## REVIEWERS
602 602 <div class="right-sidebar-expanded-state pr-details-title">
603 603 <span class="tooltip sidebar-heading" title="${vote_title}">
604 604 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
605 605 ${_('Reviewers')}
606 606 </span>
607
607 608 %if c.allowed_to_update:
608 609 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
609 610 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
610 611 %else:
611 612 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Show rules')}</span>
612 613 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
613 614 %endif
614 615 </div>
615 616
616 617 <div id="reviewers" class="right-sidebar-expanded-state pr-details-content reviewers">
617 618
618 619 <div id="review_rules" style="display: none" class="">
619 620
620 621 <strong>${_('Reviewer rules')}</strong>
621 622 <div class="pr-reviewer-rules">
622 623 ## review rules will be appended here, by default reviewers logic
623 624 </div>
624 625 <input id="review_data" type="hidden" name="review_data" value="">
625 626 </div>
626 627
627 628 ## members redering block
628 629 <input type="hidden" name="__start__" value="review_members:sequence">
629 630
630 631 <table id="review_members" class="group_members">
631 632 ## This content is loaded via JS and ReviewersPanel
632 633 </table>
633 634
634 635 <input type="hidden" name="__end__" value="review_members:sequence">
635 636 ## end members redering block
636 637
637 638 %if not c.pull_request.is_closed():
638 639 <div id="add_reviewer" class="ac" style="display: none;">
639 640 %if c.allowed_to_update:
640 641 % if not c.forbid_adding_reviewers:
641 642 <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px">
642 643 <input class="ac-input" id="user" name="user" placeholder="${_('Add reviewer or reviewer group')}" type="text" autocomplete="off">
643 644 <div id="reviewers_container"></div>
644 645 </div>
645 646 % endif
646 647 <div class="pull-right" style="margin-bottom: 15px">
647 <button data-role="reviewer" id="update_reviewers" class="btn btn-small no-margin">${_('Save Changes')}</button>
648 <button data-role="reviewer" id="update_reviewers" class="btn btn-sm no-margin">${_('Save Changes')}</button>
648 649 </div>
649 650 %endif
650 651 </div>
651 652 %endif
652 653 </div>
653 654 </div>
654 655
655 656 ## OBSERVERS
656 657 % if c.rhodecode_edition_id == 'EE':
657 658 <div class="sidebar-element clear-both">
658 659 <% vote_title = _ungettext(
659 660 '{} observer without voting right.',
660 661 '{} observers without voting right.', c.observers_count).format(c.observers_count)
661 662 %>
662 663
663 664 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
664 665 <i class="icon-circle-thin"></i>
665 666 ${c.observers_count}
666 667 </div>
667 668
668 669 <div class="right-sidebar-expanded-state pr-details-title">
669 670 <span class="tooltip sidebar-heading" title="${vote_title}">
670 671 <i class="icon-circle-thin"></i>
671 672 ${_('Observers')}
672 673 </span>
673 674 %if c.allowed_to_update:
674 675 <span id="open_edit_observers" class="block-right action_button last-item">${_('Edit')}</span>
675 676 <span id="close_edit_observers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
676 677 %endif
677 678 </div>
678 679
679 680 <div id="observers" class="right-sidebar-expanded-state pr-details-content reviewers">
680 681 ## members redering block
681 682 <input type="hidden" name="__start__" value="observer_members:sequence">
682 683
683 684 <table id="observer_members" class="group_members">
684 685 ## This content is loaded via JS and ReviewersPanel
685 686 </table>
686 687
687 688 <input type="hidden" name="__end__" value="observer_members:sequence">
688 689 ## end members redering block
689 690
690 691 %if not c.pull_request.is_closed():
691 692 <div id="add_observer" class="ac" style="display: none;">
692 693 %if c.allowed_to_update:
693 694 % if not c.forbid_adding_reviewers or 1:
694 695 <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px" >
695 696 <input class="ac-input" id="observer" name="observer" placeholder="${_('Add observer or observer group')}" type="text" autocomplete="off">
696 697 <div id="observers_container"></div>
697 698 </div>
698 699 % endif
699 700 <div class="pull-right" style="margin-bottom: 15px">
700 <button data-role="observer" id="update_observers" class="btn btn-small no-margin">${_('Save Changes')}</button>
701 <button data-role="observer" id="update_observers" class="btn btn-sm no-margin">${_('Save Changes')}</button>
701 702 </div>
702 703 %endif
703 704 </div>
704 705 %endif
705 706 </div>
706 707 </div>
707 708 % endif
708 709
709 710 ## TODOs
710 711 <div id="todosTable" class="sidebar-element clear-both">
711 712 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs">
712 713 <i class="icon-flag-filled"></i>
713 714 <span id="todos-count">${len(c.unresolved_comments)}</span>
714 715 </div>
715 716
716 717 <div class="right-sidebar-expanded-state pr-details-title">
717 718 ## Only show unresolved, that is only what matters
718 719 <span class="sidebar-heading noselect" onclick="refreshTODOs(); return false">
719 720 <i class="icon-flag-filled"></i>
720 721 TODOs
721 722 </span>
722 723
723 724 % if not c.at_version:
724 725 % if c.resolved_comments:
725 726 <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return toggleElement(this, '.resolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
726 727 % else:
727 728 <span class="block-right last-item noselect">Show resolved</span>
728 729 % endif
729 730 % endif
730 731 </div>
731 732
732 733 <div class="right-sidebar-expanded-state pr-details-content">
733 734
734 735 % if c.at_version:
735 736 <table>
736 737 <tr>
737 738 <td class="unresolved-todo-text">${_('TODOs unavailable when browsing versions')}.</td>
738 739 </tr>
739 740 </table>
740 741 % else:
741 742 % if c.unresolved_comments + c.resolved_comments:
742 743 ${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True)}
743 744 % else:
744 745 <table class="todos-content-table">
745 746 <tr>
746 747 <td>
747 748 ${_('No TODOs yet')}
748 749 </td>
749 750 </tr>
750 751 </table>
751 752 % endif
752 753 % endif
753 754 </div>
754 755 </div>
755 756
756 757 ## COMMENTS
757 758 <div id="commentsTable" class="sidebar-element clear-both">
758 759 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}">
759 760 <i class="icon-comment" style="color: #949494"></i>
760 761 <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span>
761 762 <span class="display-none" id="general-comments-count">${len(c.comments)}</span>
762 763 <span class="display-none" id="inline-comments-count">${len(c.inline_comments_flat)}</span>
763 764 </div>
764 765
765 766 <div class="right-sidebar-expanded-state pr-details-title">
766 767 <span class="sidebar-heading noselect" onclick="refreshComments(); return false">
767 768 <i class="icon-comment" style="color: #949494"></i>
768 769 ${_('Comments')}
769 770
770 771 ## % if outdated_comm_count_ver:
771 772 ## <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">
772 773 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
773 774 ## </a>
774 775 ## <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated')}</a>
775 776 ## <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated')}</a>
776 777
777 778 ## % else:
778 779 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
779 780 ## % endif
780 781
781 782 </span>
782 783
783 784 % if outdated_comm_count_ver:
784 785 <span class="block-right action_button last-item noselect" onclick="return toggleElement(this, '.hidden-comment');" data-toggle-on="Show outdated" data-toggle-off="Hide outdated">Show outdated</span>
785 786 % else:
786 787 <span class="block-right last-item noselect">Show hidden</span>
787 788 % endif
788 789
789 790 </div>
790 791
791 792 <div class="right-sidebar-expanded-state pr-details-content">
792 793 % if c.inline_comments_flat + c.comments:
793 794 ${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments))}
794 795 % else:
795 796 <table class="comments-content-table">
796 797 <tr>
797 798 <td>
798 799 ${_('No Comments yet')}
799 800 </td>
800 801 </tr>
801 802 </table>
802 803 % endif
803 804 </div>
804 805
805 806 </div>
806 807
807 808 ## Referenced Tickets
808 809 <div class="sidebar-element clear-both">
809 810 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Referenced Tickets')}">
810 811 <i class="icon-info-circled"></i>
811 812 ${(len(c.referenced_desc_issues) + len(c.referenced_commit_issues))}
812 813 </div>
813 814
814 815 <div class="right-sidebar-expanded-state pr-details-title">
815 816 <span class="sidebar-heading">
816 817 <i class="icon-info-circled"></i>
817 818 ${_('Referenced Tickets')}
818 819 </span>
819 820 </div>
820 821 <div class="right-sidebar-expanded-state pr-details-content">
821 822 <table>
822 823
823 824 <tr><td><code>${_('In pull request description')}:</code></td></tr>
824 825 % if c.referenced_desc_issues:
825 826 % for ticket_dict in sorted(c.referenced_desc_issues):
826 827 <tr>
827 828 <td>
828 829 <a href="${ticket_dict.get('url')}">
829 830 ${ticket_dict.get('id')}
830 831 </a>
831 832 </td>
832 833 </tr>
833 834 % endfor
834 835 % else:
835 836 <tr>
836 837 <td>
837 838 ${_('No Ticket data found.')}
838 839 </td>
839 840 </tr>
840 841 % endif
841 842
842 843 <tr><td style="padding-top: 10px"><code>${_('In commit messages')}:</code></td></tr>
843 844 % if c.referenced_commit_issues:
844 845 % for ticket_dict in sorted(c.referenced_commit_issues):
845 846 <tr>
846 847 <td>
847 848 <a href="${ticket_dict.get('url')}">
848 849 ${ticket_dict.get('id')}
849 850 </a>
850 851 </td>
851 852 </tr>
852 853 % endfor
853 854 % else:
854 855 <tr>
855 856 <td>
856 857 ${_('No Ticket data found.')}
857 858 </td>
858 859 </tr>
859 860 % endif
860 861 </table>
861 862
862 863 </div>
863 864 </div>
864 865
865 866 </div>
866 867
867 868 </div>
868 869 </aside>
869 870
870 871 ## This JS needs to be at the end
871 872 <script type="text/javascript">
872 873
873 874 versionController = new VersionController();
874 875 versionController.init();
875 876
876 877 reviewersController = new ReviewersController();
877 878 commitsController = new CommitsController();
878 879 commentsController = new CommentsController();
879 880
880 881 updateController = new UpdatePrController();
881 882
882 883 window.reviewerRulesData = ${c.pull_request_default_reviewers_data_json | n};
883 884 window.setReviewersData = ${c.pull_request_set_reviewers_data_json | n};
884 885 window.setObserversData = ${c.pull_request_set_observers_data_json | n};
885 886
886 887 (function () {
887 888 "use strict";
888 889
889 890 // custom code mirror
890 891 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
891 892
892 893 PRDetails.init();
893 894 ReviewersPanel.init(reviewersController, reviewerRulesData, setReviewersData);
894 895 ObserversPanel.init(reviewersController, reviewerRulesData, setObserversData);
895 896
896 897 window.showOutdated = function (self) {
897 898 $('.comment-inline.comment-outdated').show();
898 899 $('.filediff-outdated').show();
899 900 $('.showOutdatedComments').hide();
900 901 $('.hideOutdatedComments').show();
901 902 };
902 903
903 904 window.hideOutdated = function (self) {
904 905 $('.comment-inline.comment-outdated').hide();
905 906 $('.filediff-outdated').hide();
906 907 $('.hideOutdatedComments').hide();
907 908 $('.showOutdatedComments').show();
908 909 };
909 910
910 911 window.refreshMergeChecks = function () {
911 912 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
912 913 $('.pull-request-merge').css('opacity', 0.3);
913 914 $('.action-buttons-extra').css('opacity', 0.3);
914 915
915 916 $('.pull-request-merge').load(
916 917 loadUrl, function () {
917 918 $('.pull-request-merge').css('opacity', 1);
918 919
919 920 $('.action-buttons-extra').css('opacity', 1);
920 921 }
921 922 );
922 923 };
923 924
924 925 window.submitDrafts = function (event) {
925 926 var target = $(event.currentTarget);
926 927 var callback = function (result) {
927 928 target.removeAttr('onclick').html('saving...');
928 929 }
929 930 var draftIds = [];
930 931 $.each($('[name=submit_draft]:checked'), function (idx, val) {
931 932 draftIds.push(parseInt($(val).val()));
932 933 })
933 934 if (draftIds.length > 0) {
934 935 Rhodecode.comments.finalizeDrafts(draftIds, callback);
935 936 }
936 937 else {
937 938
938 939 }
939 940 }
940 941
941 942 window.closePullRequest = function (status) {
942 943 if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) {
943 944 return false;
944 945 }
945 946 // inject closing flag
946 947 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
947 948 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
948 949 $(generalCommentForm.submitForm).submit();
949 950 };
950 951
951 952 //TODO this functionality is now missing
952 953 $('#show-outdated-comments').on('click', function (e) {
953 954 var button = $(this);
954 955 var outdated = $('.comment-outdated');
955 956
956 957 if (button.html() === "(Show)") {
957 958 button.html("(Hide)");
958 959 outdated.show();
959 960 } else {
960 961 button.html("(Show)");
961 962 outdated.hide();
962 963 }
963 964 });
964 965
965 966 $('#merge_pull_request_form').submit(function () {
966 967 if (!$('#merge_pull_request').attr('disabled')) {
967 968 $('#merge_pull_request').attr('disabled', 'disabled');
968 969 }
969 970 return true;
970 971 });
971 972
972 973 $('#edit_pull_request').on('click', function (e) {
973 974 var title = $('#pr-title-input').val();
974 975 var description = codeMirrorInstance.getValue();
975 976 var renderer = $('#pr-renderer-input').val();
976 977 editPullRequest(
977 978 "${c.repo_name}", "${c.pull_request.pull_request_id}",
978 979 title, description, renderer);
979 980 });
980 981
981 982 var $updateButtons = $('#update_reviewers,#update_observers');
982 983 $updateButtons.on('click', function (e) {
983 984 var role = $(this).data('role');
984 985 $updateButtons.attr('disabled', 'disabled');
985 986 $updateButtons.addClass('disabled');
986 987 $updateButtons.html(_gettext('Saving...'));
987 988 reviewersController.updateReviewers(
988 989 templateContext.repo_name,
989 990 templateContext.pull_request_data.pull_request_id,
990 991 role
991 992 );
992 993 });
993 994
994 995 // fixing issue with caches on firefox
995 996 $('#update_commits').removeAttr("disabled");
996 997
997 998 $('.show-inline-comments').on('click', function (e) {
998 999 var boxid = $(this).attr('data-comment-id');
999 1000 var button = $(this);
1000 1001
1001 1002 if (button.hasClass("comments-visible")) {
1002 1003 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
1003 1004 $(this).hide();
1004 1005 });
1005 1006 button.removeClass("comments-visible");
1006 1007 } else {
1007 1008 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
1008 1009 $(this).show();
1009 1010 });
1010 1011 button.addClass("comments-visible");
1011 1012 }
1012 1013 });
1013 1014
1014 1015 $('.show-inline-comments').on('change', function (e) {
1015 1016 var show = 'none';
1016 1017 var target = e.currentTarget;
1017 1018 if (target.checked) {
1018 1019 show = ''
1019 1020 }
1020 1021 var boxid = $(target).attr('id_for');
1021 1022 var comments = $('#{0} .inline-comments'.format(boxid));
1022 1023 var fn_display = function (idx) {
1023 1024 $(this).css('display', show);
1024 1025 };
1025 1026 $(comments).each(fn_display);
1026 1027 var btns = $('#{0} .inline-comments-button'.format(boxid));
1027 1028 $(btns).each(fn_display);
1028 1029 });
1029 1030
1030 1031 // register submit callback on commentForm form to track TODOs, and refresh mergeChecks conditions
1031 1032 window.commentFormGlobalSubmitSuccessCallback = function (comment) {
1032 1033 if (!comment.draft) {
1033 1034 refreshMergeChecks();
1034 1035 }
1035 1036 };
1036 1037
1037 1038 ReviewerAutoComplete('#user', reviewersController);
1038 1039 ObserverAutoComplete('#observer', reviewersController);
1039 1040
1040 1041 })();
1041 1042
1042 1043 $(document).ready(function () {
1043 1044
1044 1045 var channel = '${c.pr_broadcast_channel}';
1045 1046 new ReviewerPresenceController(channel)
1046 1047 // register globally so inject comment logic can re-use it.
1047 1048 window.commentsController = commentsController;
1048 1049 })
1049 1050 </script>
1050 1051
1051 1052 </%def>
General Comments 0
You need to be logged in to leave comments. Login now