##// END OF EJS Templates
drafts: draft finize boilerplate
milka -
r4549:883fcc39 default
parent child Browse files
Show More
@@ -1,746 +1,750 b''
1 1 // comments.less
2 2 // For use in RhodeCode applications;
3 3 // see style guide documentation for guidelines.
4 4
5 5
6 6 // Comments
7 7 @comment-outdated-opacity: 1.0;
8 8
9 9 .comments {
10 10 width: 100%;
11 11 }
12 12
13 13 .comments-heading {
14 14 margin-bottom: -1px;
15 15 background: @grey6;
16 16 display: block;
17 17 padding: 10px 0px;
18 18 font-size: 18px
19 19 }
20 20
21 21 #comment-tr-show {
22 22 padding: 5px 0;
23 23 }
24 24
25 25 tr.inline-comments div {
26 26 max-width: 100%;
27 27
28 28 p {
29 29 white-space: normal;
30 30 }
31 31
32 32 code, pre, .code, dd {
33 33 overflow-x: auto;
34 34 width: 1062px;
35 35 }
36 36
37 37 dd {
38 38 width: auto;
39 39 }
40 40 }
41 41
42 42 #injected_page_comments {
43 43 .comment-previous-link,
44 44 .comment-next-link,
45 45 .comment-links-divider {
46 46 display: none;
47 47 }
48 48 }
49 49
50 50 .add-comment {
51 51 margin-bottom: 10px;
52 52 }
53 53 .hide-comment-button .add-comment {
54 54 display: none;
55 55 }
56 56
57 57 .comment-bubble {
58 58 color: @grey4;
59 59 margin-top: 4px;
60 60 margin-right: 30px;
61 61 visibility: hidden;
62 62 }
63 63
64 64 .comment-draft {
65 65 float: left;
66 66 margin-right: 10px;
67 67 font-weight: 400;
68 68 color: @color-draft;
69 69 }
70 70
71 71 .comment-new {
72 72 float: left;
73 73 margin-right: 10px;
74 74 font-weight: 400;
75 75 color: @color-new;
76 76 }
77 77
78 78 .comment-label {
79 79 float: left;
80 80
81 81 padding: 0 8px 0 0;
82 82 min-height: 0;
83 83
84 84 text-align: center;
85 85 font-size: 10px;
86 86
87 87 font-family: @text-italic;
88 88 font-style: italic;
89 89 background: #fff none;
90 90 color: @grey3;
91 91 white-space: nowrap;
92 92
93 93 text-transform: uppercase;
94 94 min-width: 50px;
95 95
96 96 &.todo {
97 97 color: @color5;
98 98 font-style: italic;
99 99 font-weight: @text-bold-italic-weight;
100 100 font-family: @text-bold-italic;
101 101 }
102 102
103 103 .resolve {
104 104 cursor: pointer;
105 105 text-decoration: underline;
106 106 }
107 107
108 108 .resolved {
109 109 text-decoration: line-through;
110 110 color: @color1;
111 111 }
112 112 .resolved a {
113 113 text-decoration: line-through;
114 114 color: @color1;
115 115 }
116 116 .resolve-text {
117 117 color: @color1;
118 118 margin: 2px 8px;
119 119 font-family: @text-italic;
120 120 font-style: italic;
121 121 }
122 122 }
123 123
124 124 .has-spacer-after {
125 125 &:after {
126 126 content: ' | ';
127 127 color: @grey5;
128 128 }
129 129 }
130 130
131 131 .has-spacer-before {
132 132 &:before {
133 133 content: ' | ';
134 134 color: @grey5;
135 135 }
136 136 }
137 137
138 138 .comment {
139 139
140 140 &.comment-general {
141 141 border: 1px solid @grey5;
142 142 padding: 5px 5px 5px 5px;
143 143 }
144 144
145 145 margin: @padding 0;
146 146 padding: 4px 0 0 0;
147 147 line-height: 1em;
148 148
149 149 .rc-user {
150 150 min-width: 0;
151 151 margin: 0px .5em 0 0;
152 152
153 153 .user {
154 154 display: inline;
155 155 }
156 156 }
157 157
158 158 .meta {
159 159 position: relative;
160 160 width: 100%;
161 161 border-bottom: 1px solid @grey5;
162 162 margin: -5px 0px;
163 163 line-height: 24px;
164 164
165 165 &:hover .permalink {
166 166 visibility: visible;
167 167 color: @rcblue;
168 168 }
169 169 }
170 170
171 171 .author,
172 172 .date {
173 173 display: inline;
174 174
175 175 &:after {
176 176 content: ' | ';
177 177 color: @grey5;
178 178 }
179 179 }
180 180
181 181 .author-general img {
182 182 top: 3px;
183 183 }
184 184 .author-inline img {
185 185 top: 3px;
186 186 }
187 187
188 188 .status-change,
189 189 .permalink,
190 190 .changeset-status-lbl {
191 191 display: inline;
192 192 }
193 193
194 194 .permalink {
195 195 visibility: hidden;
196 196 }
197 197
198 198 .comment-links-divider {
199 199 display: inline;
200 200 }
201 201
202 202 .comment-links-block {
203 203 float:right;
204 204 text-align: right;
205 205 min-width: 85px;
206 206
207 207 [class^="icon-"]:before,
208 208 [class*=" icon-"]:before {
209 209 margin-left: 0;
210 210 margin-right: 0;
211 211 }
212 212 }
213 213
214 214 .comment-previous-link {
215 215 display: inline-block;
216 216
217 217 .arrow_comment_link{
218 218 cursor: pointer;
219 219 i {
220 220 font-size:10px;
221 221 }
222 222 }
223 223 .arrow_comment_link.disabled {
224 224 cursor: default;
225 225 color: @grey5;
226 226 }
227 227 }
228 228
229 229 .comment-next-link {
230 230 display: inline-block;
231 231
232 232 .arrow_comment_link{
233 233 cursor: pointer;
234 234 i {
235 235 font-size:10px;
236 236 }
237 237 }
238 238 .arrow_comment_link.disabled {
239 239 cursor: default;
240 240 color: @grey5;
241 241 }
242 242 }
243 243
244 244 .delete-comment {
245 245 display: inline-block;
246 246 color: @rcblue;
247 247
248 248 &:hover {
249 249 cursor: pointer;
250 250 }
251 251 }
252 252
253 253 .text {
254 254 clear: both;
255 255 .border-radius(@border-radius);
256 256 .box-sizing(border-box);
257 257
258 258 .markdown-block p,
259 259 .rst-block p {
260 260 margin: .5em 0 !important;
261 261 // TODO: lisa: This is needed because of other rst !important rules :[
262 262 }
263 263 }
264 264
265 265 .pr-version {
266 266 display: inline-block;
267 267 }
268 268 .pr-version-inline {
269 269 display: inline-block;
270 270 }
271 271 .pr-version-num {
272 272 font-size: 10px;
273 273 }
274 274 }
275 275
276 276 @comment-padding: 5px;
277 277
278 278 .general-comments {
279 279 .comment-outdated {
280 280 opacity: @comment-outdated-opacity;
281 281 }
282 282
283 283 .comment-outdated-label {
284 284 color: @grey3;
285 285 padding-right: 4px;
286 286 }
287 287 }
288 288
289 289 .inline-comments {
290 290
291 291 .comment {
292 292 margin: 0;
293 293 }
294 294
295 295 .comment-outdated {
296 296 opacity: @comment-outdated-opacity;
297 297 }
298 298
299 299 .comment-outdated-label {
300 300 color: @grey3;
301 301 padding-right: 4px;
302 302 }
303 303
304 304 .comment-inline {
305 305
306 306 &:first-child {
307 307 margin: 4px 4px 0 4px;
308 308 border-top: 1px solid @grey5;
309 309 border-bottom: 0 solid @grey5;
310 310 border-left: 1px solid @grey5;
311 311 border-right: 1px solid @grey5;
312 312 .border-radius-top(4px);
313 313 }
314 314
315 315 &:only-child {
316 316 margin: 4px 4px 0 4px;
317 317 border-top: 1px solid @grey5;
318 318 border-bottom: 0 solid @grey5;
319 319 border-left: 1px solid @grey5;
320 320 border-right: 1px solid @grey5;
321 321 .border-radius-top(4px);
322 322 }
323 323
324 324 background: white;
325 325 padding: @comment-padding @comment-padding;
326 326 margin: 0 4px 0 4px;
327 327 border-top: 0 solid @grey5;
328 328 border-bottom: 0 solid @grey5;
329 329 border-left: 1px solid @grey5;
330 330 border-right: 1px solid @grey5;
331 331
332 332 .text {
333 333 border: none;
334 334 }
335 335
336 336 .meta {
337 337 border-bottom: 1px solid @grey6;
338 338 margin: -5px 0px;
339 339 line-height: 24px;
340 340 }
341 341
342 342 }
343 343 .comment-selected {
344 344 border-left: 6px solid @comment-highlight-color;
345 345 }
346 346
347 347 .comment-inline-form-open {
348 348 display: block !important;
349 349 }
350 350
351 351 .comment-inline-form {
352 352 display: none;
353 353 }
354 354
355 355 .comment-inline-form-edit {
356 356 padding: 0;
357 357 margin: 0px 4px 2px 4px;
358 358 }
359 359
360 360 .reply-thread-container {
361 361 display: table;
362 362 width: 100%;
363 363 padding: 0px 4px 4px 4px;
364 364 }
365 365
366 366 .reply-thread-container-wrapper {
367 367 margin: 0 4px 4px 4px;
368 368 border-top: 0 solid @grey5;
369 369 border-bottom: 1px solid @grey5;
370 370 border-left: 1px solid @grey5;
371 371 border-right: 1px solid @grey5;
372 372 .border-radius-bottom(4px);
373 373 }
374 374
375 375 .reply-thread-gravatar {
376 376 display: table-cell;
377 377 width: 24px;
378 378 height: 24px;
379 379 padding-top: 10px;
380 380 padding-left: 10px;
381 381 background-color: #eeeeee;
382 382 vertical-align: top;
383 383 }
384 384
385 385 .reply-thread-reply-button {
386 386 display: table-cell;
387 387 width: 100%;
388 388 height: 33px;
389 389 padding: 3px 8px;
390 390 margin-left: 8px;
391 391 background-color: #eeeeee;
392 392 }
393 393
394 394 .reply-thread-reply-button .cb-comment-add-button {
395 395 border-radius: 4px;
396 396 width: 100%;
397 397 padding: 6px 2px;
398 398 text-align: left;
399 399 cursor: text;
400 400 color: @grey3;
401 401 }
402 402 .reply-thread-reply-button .cb-comment-add-button:hover {
403 403 background-color: white;
404 404 color: @grey2;
405 405 }
406 406
407 407 .reply-thread-last {
408 408 display: table-cell;
409 409 width: 10px;
410 410 }
411 411
412 412 /* Hide reply box when it's a first element,
413 413 can happen when drafts are saved but not shown to specific user,
414 414 or there are outdated comments hidden
415 415 */
416 416 .reply-thread-container-wrapper:first-child:not(.comment-form-active) {
417 417 display: none;
418 418 }
419 419
420 420 .reply-thread-container-wrapper.comment-outdated {
421 421 display: none
422 422 }
423 423
424 424 /* hide add comment button when form is open */
425 425 .comment-inline-form-open ~ .cb-comment-add-button {
426 426 display: none;
427 427 }
428 428
429 429 /* hide add comment button when only comment is being deleted */
430 430 .comment-deleting:first-child + .cb-comment-add-button {
431 431 display: none;
432 432 }
433 433
434 434 /* hide add comment button when form but no comments */
435 435 .comment-inline-form:first-child + .cb-comment-add-button {
436 436 display: none;
437 437 }
438 438
439 439 }
440 440
441 441 .show-outdated-comments {
442 442 display: inline;
443 443 color: @rcblue;
444 444 }
445 445
446 446 // Comment Form
447 447 div.comment-form {
448 448 margin-top: 20px;
449 449 }
450 450
451 451 .comment-form strong {
452 452 display: block;
453 453 margin-bottom: 15px;
454 454 }
455 455
456 456 .comment-form textarea {
457 457 width: 100%;
458 458 height: 100px;
459 459 font-family: @text-monospace;
460 460 }
461 461
462 462 form.comment-form {
463 463 margin-top: 10px;
464 464 margin-left: 10px;
465 465 }
466 466
467 467 .comment-inline-form .comment-block-ta,
468 468 .comment-form .comment-block-ta,
469 469 .comment-form .preview-box {
470 470 .border-radius(@border-radius);
471 471 .box-sizing(border-box);
472 472 background-color: white;
473 473 }
474 474
475 475 .comment-form-submit {
476 476 margin-top: 5px;
477 477 margin-left: 525px;
478 478 }
479 479
480 480 .file-comments {
481 481 display: none;
482 482 }
483 483
484 484 .comment-form .preview-box.unloaded,
485 485 .comment-inline-form .preview-box.unloaded {
486 486 height: 50px;
487 487 text-align: center;
488 488 padding: 20px;
489 489 background-color: white;
490 490 }
491 491
492 492 .comment-footer {
493 493 display: table;
494 494 width: 100%;
495 495 height: 42px;
496 496
497 497 .comment-status-box,
498 498 .cancel-button {
499 499 display: inline-block;
500 500 }
501 501
502 502 .comment-status-box {
503 503 margin-left: 10px;
504 504 }
505 505
506 506 .action-buttons {
507 507 display: table-cell;
508 508 padding: 5px 0 5px 2px;
509 509 }
510 510
511 511 .toolbar-text {
512 512 height: 42px;
513 513 display: table-cell;
514 514 vertical-align: bottom;
515 515 font-size: 11px;
516 516 color: @grey4;
517 517 text-align: right;
518 518
519 519 a {
520 520 color: @grey4;
521 521 }
522 522
523 523 p {
524 524 padding: 0;
525 525 margin: 0;
526 526 }
527 527 }
528 528
529 529 .action-buttons-extra {
530 530 display: inline-block;
531 531 }
532 532 }
533 533
534 534 .comment-form {
535 535
536 536 .comment {
537 537 margin-left: 10px;
538 538 }
539 539
540 540 .comment-help {
541 541 color: @grey4;
542 542 padding: 5px 0 5px 0;
543 543 }
544 544
545 545 .comment-title {
546 546 padding: 5px 0 5px 0;
547 547 }
548 548
549 549 .comment-button {
550 550 display: inline-block;
551 551 }
552 552
553 553 .comment-button-input {
554 554 margin-right: 0;
555 555 }
556 556
557 #save_general {
558 margin-left: -6px;
559 }
560
557 561 }
558 562
559 563
560 564 .comment-form-login {
561 565 .comment-help {
562 566 padding: 0.7em; //same as the button
563 567 }
564 568
565 569 div.clearfix {
566 570 clear: both;
567 571 width: 100%;
568 572 display: block;
569 573 }
570 574 }
571 575
572 576 .comment-version-select {
573 577 margin: 0px;
574 578 border-radius: inherit;
575 579 border-color: @grey6;
576 580 height: 20px;
577 581 }
578 582
579 583 .comment-type {
580 584 margin: 0px;
581 585 border-radius: inherit;
582 586 border-color: @grey6;
583 587 }
584 588
585 589 .preview-box {
586 590 min-height: 105px;
587 591 margin-bottom: 15px;
588 592 background-color: white;
589 593 .border-radius(@border-radius);
590 594 .box-sizing(border-box);
591 595 }
592 596
593 597 .add-another-button {
594 598 margin-left: 10px;
595 599 margin-top: 10px;
596 600 margin-bottom: 10px;
597 601 }
598 602
599 603 .comment .buttons {
600 604 float: right;
601 605 margin: -1px 0px 0px 0px;
602 606 }
603 607
604 608 // Inline Comment Form
605 609 .injected_diff .comment-inline-form,
606 610 .comment-inline-form {
607 611 background-color: white;
608 612 margin-top: 4px;
609 613 margin-bottom: 10px;
610 614 }
611 615
612 616 .inline-form {
613 617 padding: 10px 7px;
614 618 }
615 619
616 620 .inline-form div {
617 621 max-width: 100%;
618 622 }
619 623
620 624 .overlay {
621 625 display: none;
622 626 position: absolute;
623 627 width: 100%;
624 628 text-align: center;
625 629 vertical-align: middle;
626 630 font-size: 16px;
627 631 background: none repeat scroll 0 0 white;
628 632
629 633 &.submitting {
630 634 display: block;
631 635 opacity: 0.5;
632 636 z-index: 100;
633 637 }
634 638 }
635 639 .comment-inline-form .overlay.submitting .overlay-text {
636 640 margin-top: 5%;
637 641 }
638 642
639 643 .comment-inline-form .clearfix,
640 644 .comment-form .clearfix {
641 645 .border-radius(@border-radius);
642 646 margin: 0px;
643 647 }
644 648
645 649
646 650 .hide-inline-form-button {
647 651 margin-left: 5px;
648 652 }
649 653 .comment-button .hide-inline-form {
650 654 background: white;
651 655 }
652 656
653 657 .comment-area {
654 658 padding: 6px 8px;
655 659 border: 1px solid @grey5;
656 660 .border-radius(@border-radius);
657 661
658 662 .resolve-action {
659 663 padding: 1px 0px 0px 6px;
660 664 }
661 665
662 666 }
663 667
664 668 comment-area-text {
665 669 color: @grey3;
666 670 }
667 671
668 672 .comment-area-header {
669 673 height: 35px;
670 674 border-bottom: 1px solid @grey5;
671 675 }
672 676
673 677 .comment-area-header .nav-links {
674 678 display: flex;
675 679 flex-flow: row wrap;
676 680 -webkit-flex-flow: row wrap;
677 681 width: 100%;
678 682 border: none;
679 683 }
680 684
681 685 .comment-area-footer {
682 686 min-height: 30px;
683 687 }
684 688
685 689 .comment-footer .toolbar {
686 690
687 691 }
688 692
689 693 .comment-attachment-uploader {
690 694 border: 1px dashed white;
691 695 border-radius: @border-radius;
692 696 margin-top: -10px;
693 697 line-height: 30px;
694 698 &.dz-drag-hover {
695 699 border-color: @grey3;
696 700 }
697 701
698 702 .dz-error-message {
699 703 padding-top: 0;
700 704 }
701 705 }
702 706
703 707 .comment-attachment-text {
704 708 clear: both;
705 709 font-size: 11px;
706 710 color: #8F8F8F;
707 711 width: 100%;
708 712 .pick-attachment {
709 713 color: #8F8F8F;
710 714 }
711 715 .pick-attachment:hover {
712 716 color: @rcblue;
713 717 }
714 718 }
715 719
716 720 .nav-links {
717 721 padding: 0;
718 722 margin: 0;
719 723 list-style: none;
720 724 height: auto;
721 725 border-bottom: 1px solid @grey5;
722 726 }
723 727 .nav-links li {
724 728 display: inline-block;
725 729 list-style-type: none;
726 730 }
727 731
728 732 .nav-links li a.disabled {
729 733 cursor: not-allowed;
730 734 }
731 735
732 736 .nav-links li.active a {
733 737 border-bottom: 2px solid @rcblue;
734 738 color: #000;
735 739 font-weight: 600;
736 740 }
737 741 .nav-links li a {
738 742 display: inline-block;
739 743 padding: 0px 10px 5px 10px;
740 744 margin-bottom: -1px;
741 745 font-size: 14px;
742 746 line-height: 28px;
743 747 color: #8f8f8f;
744 748 border-bottom: 2px solid transparent;
745 749 }
746 750
@@ -1,402 +1,403 b''
1 1
2 2 /******************************************************************************
3 3 * *
4 4 * DO NOT CHANGE THIS FILE MANUALLY *
5 5 * *
6 6 * *
7 7 * This file is automatically generated when the app starts up with *
8 8 * generate_js_files = true *
9 9 * *
10 10 * To add a route here pass jsroute=True to the route definition in the app *
11 11 * *
12 12 ******************************************************************************/
13 13 function registerRCRoutes() {
14 14 // routes registration
15 15 pyroutes.register('favicon', '/favicon.ico', []);
16 16 pyroutes.register('robots', '/robots.txt', []);
17 17 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
18 18 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
19 19 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
20 20 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
21 21 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
22 22 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
23 23 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
24 24 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
25 25 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
26 26 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
27 27 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
28 28 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
29 29 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
30 30 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
31 31 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
32 32 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
33 33 pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']);
34 34 pyroutes.register('hovercard_username', '/_hovercard/username/%(username)s', ['username']);
35 35 pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']);
36 36 pyroutes.register('hovercard_pull_request', '/_hovercard/pull_request/%(pull_request_id)s', ['pull_request_id']);
37 37 pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']);
38 38 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
39 39 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
40 40 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
41 41 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
42 42 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
43 43 pyroutes.register('admin_home', '/_admin', []);
44 44 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
45 45 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
46 46 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
47 47 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
48 48 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
49 49 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
50 50 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
51 51 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
52 52 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
53 53 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
54 54 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions/delete', []);
55 55 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
56 56 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
57 57 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
58 58 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
59 59 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
60 60 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
61 61 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
62 62 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
63 63 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
64 64 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
65 65 pyroutes.register('admin_settings', '/_admin/settings', []);
66 66 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
67 67 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
68 68 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
69 69 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
70 70 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
71 71 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
72 72 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
73 73 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
74 74 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
75 75 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
76 76 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
77 77 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
78 78 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
79 79 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
80 80 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
81 81 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
82 82 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
83 83 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
84 84 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
85 85 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
86 86 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
87 87 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
88 88 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
89 89 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
90 90 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
91 91 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
92 92 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
93 93 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
94 94 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
95 95 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
96 96 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
97 97 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
98 98 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
99 99 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
100 100 pyroutes.register('users', '/_admin/users', []);
101 101 pyroutes.register('users_data', '/_admin/users_data', []);
102 102 pyroutes.register('users_create', '/_admin/users/create', []);
103 103 pyroutes.register('users_new', '/_admin/users/new', []);
104 104 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
105 105 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
106 106 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
107 107 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
108 108 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
109 109 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
110 110 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
111 111 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
112 112 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
113 113 pyroutes.register('user_notice_dismiss', '/_admin/users/%(user_id)s/notice_dismiss', ['user_id']);
114 114 pyroutes.register('edit_user_auth_tokens_view', '/_admin/users/%(user_id)s/edit/auth_tokens/view', ['user_id']);
115 115 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
116 116 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
117 117 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
118 118 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
119 119 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
120 120 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
121 121 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
122 122 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
123 123 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
124 124 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
125 125 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
126 126 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
127 127 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
128 128 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
129 129 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
130 130 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
131 131 pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']);
132 132 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
133 133 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
134 134 pyroutes.register('user_groups', '/_admin/user_groups', []);
135 135 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
136 136 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
137 137 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
138 138 pyroutes.register('repos', '/_admin/repos', []);
139 139 pyroutes.register('repos_data', '/_admin/repos_data', []);
140 140 pyroutes.register('repo_new', '/_admin/repos/new', []);
141 141 pyroutes.register('repo_create', '/_admin/repos/create', []);
142 142 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
143 143 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
144 144 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
145 145 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
146 146 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
147 147 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
148 148 pyroutes.register('channelstream_proxy', '/_channelstream', []);
149 149 pyroutes.register('upload_file', '/_file_store/upload', []);
150 150 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
151 151 pyroutes.register('download_file_by_token', '/_file_store/token-download/%(_auth_token)s/%(fid)s', ['_auth_token', 'fid']);
152 152 pyroutes.register('logout', '/_admin/logout', []);
153 153 pyroutes.register('reset_password', '/_admin/password_reset', []);
154 154 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
155 155 pyroutes.register('home', '/', []);
156 156 pyroutes.register('main_page_repos_data', '/_home_repos', []);
157 157 pyroutes.register('main_page_repo_groups_data', '/_home_repo_groups', []);
158 158 pyroutes.register('user_autocomplete_data', '/_users', []);
159 159 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
160 160 pyroutes.register('repo_list_data', '/_repos', []);
161 161 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
162 162 pyroutes.register('goto_switcher_data', '/_goto_data', []);
163 163 pyroutes.register('markup_preview', '/_markup_preview', []);
164 164 pyroutes.register('file_preview', '/_file_preview', []);
165 165 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
166 166 pyroutes.register('journal', '/_admin/journal', []);
167 167 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
168 168 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
169 169 pyroutes.register('journal_public', '/_admin/public_journal', []);
170 170 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
171 171 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
172 172 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
173 173 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
174 174 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
175 175 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
176 176 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
177 177 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
178 178 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
179 179 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
180 180 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
181 181 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
182 182 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
183 183 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
184 184 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
185 185 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
186 186 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
187 187 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
188 188 pyroutes.register('repo_commit_comment_history_view', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_history_id)s/history_view', ['repo_name', 'commit_id', 'comment_history_id']);
189 189 pyroutes.register('repo_commit_comment_attachment_upload', '/%(repo_name)s/changeset/%(commit_id)s/comment/attachment_upload', ['repo_name', 'commit_id']);
190 190 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
191 191 pyroutes.register('repo_commit_comment_edit', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/edit', ['repo_name', 'commit_id', 'comment_id']);
192 192 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
193 193 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
194 194 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
195 195 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
196 196 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
197 197 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
198 198 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
199 199 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
200 200 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
201 201 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
202 202 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
203 203 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
204 204 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
205 205 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
206 206 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
207 207 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
208 208 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
209 209 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
210 210 pyroutes.register('repo_files_check_head', '/%(repo_name)s/check_head/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
211 211 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
212 212 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
213 213 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
214 214 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
215 215 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
216 216 pyroutes.register('repo_files_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
217 217 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
218 218 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
219 219 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
220 220 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
221 221 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
222 222 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
223 223 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
224 224 pyroutes.register('repo_commits_elements_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
225 225 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
226 226 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
227 227 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
228 228 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
229 229 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
230 230 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
231 231 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
232 232 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
233 233 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
234 234 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
235 235 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
236 236 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
237 237 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
238 238 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
239 239 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
240 240 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
241 241 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
242 242 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
243 243 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
244 244 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
245 245 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
246 246 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
247 247 pyroutes.register('pullrequest_comment_edit', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/edit', ['repo_name', 'pull_request_id', 'comment_id']);
248 248 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
249 249 pyroutes.register('pullrequest_comments', '/%(repo_name)s/pull-request/%(pull_request_id)s/comments', ['repo_name', 'pull_request_id']);
250 250 pyroutes.register('pullrequest_todos', '/%(repo_name)s/pull-request/%(pull_request_id)s/todos', ['repo_name', 'pull_request_id']);
251 251 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
252 252 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
253 253 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
254 254 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
255 255 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
256 256 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
257 257 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
258 258 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
259 259 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
260 260 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
261 261 pyroutes.register('edit_repo_perms_set_private', '/%(repo_name)s/settings/permissions/set_private', ['repo_name']);
262 262 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
263 263 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
264 264 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
265 265 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
266 266 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
267 267 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
268 268 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
269 269 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
270 270 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
271 271 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
272 272 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
273 273 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
274 274 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
275 275 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
276 276 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
277 277 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
278 278 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
279 279 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
280 280 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
281 281 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
282 282 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
283 283 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
284 284 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
285 285 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed-rss', ['repo_name']);
286 286 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']);
287 287 pyroutes.register('rss_feed_home_old', '/%(repo_name)s/feed/rss', ['repo_name']);
288 288 pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']);
289 289 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
290 290 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
291 291 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
292 292 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
293 293 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
294 294 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
295 295 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
296 296 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
297 297 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
298 298 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
299 299 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
300 300 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
301 301 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
302 302 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
303 303 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
304 304 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
305 305 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
306 306 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
307 307 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
308 308 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
309 309 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
310 310 pyroutes.register('search', '/_admin/search', []);
311 311 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
312 312 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
313 313 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
314 314 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
315 315 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
316 316 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
317 317 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
318 318 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
319 319 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
320 320 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
321 321 pyroutes.register('my_account_auth_tokens_view', '/_admin/my_account/auth_tokens/view', []);
322 322 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
323 323 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
324 324 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
325 325 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
326 326 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
327 327 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
328 328 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
329 329 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
330 330 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
331 331 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
332 332 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
333 333 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
334 334 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
335 335 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
336 336 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
337 337 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
338 338 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
339 339 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
340 340 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
341 341 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
342 342 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
343 343 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
344 344 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
345 345 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
346 346 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
347 347 pyroutes.register('gists_show', '/_admin/gists', []);
348 348 pyroutes.register('gists_new', '/_admin/gists/new', []);
349 349 pyroutes.register('gists_create', '/_admin/gists/create', []);
350 350 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
351 351 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
352 352 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
353 353 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
354 354 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
355 355 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
356 356 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
357 357 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
358 358 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
359 359 pyroutes.register('debug_style_email', '/_admin/debug_style/email/%(email_id)s', ['email_id']);
360 360 pyroutes.register('debug_style_email_plain_rendered', '/_admin/debug_style/email-rendered/%(email_id)s', ['email_id']);
361 361 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
362 362 pyroutes.register('apiv2', '/_admin/api', []);
363 363 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
364 364 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
365 365 pyroutes.register('login', '/_admin/login', []);
366 366 pyroutes.register('register', '/_admin/register', []);
367 367 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
368 368 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
369 369 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
370 370 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
371 371 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
372 372 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
373 373 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
374 374 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
375 375 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
376 376 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
377 377 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
378 378 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
379 379 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
380 380 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
381 381 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
382 382 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
383 383 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
384 384 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
385 385 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
386 386 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
387 387 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
388 388 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
389 pyroutes.register('pullrequest_draft_comments_submit', '/%(repo_name)s/pull-request/%(pull_request_id)s/draft_comments_submit', ['repo_name', 'pull_request_id']);
389 390 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
390 391 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
391 392 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
392 393 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
393 394 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
394 395 pyroutes.register('repo_artifacts_info', '/%(repo_name)s/artifacts/info/%(uid)s', ['repo_name', 'uid']);
395 396 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
396 397 pyroutes.register('repo_artifacts_update', '/%(repo_name)s/artifacts/update/%(uid)s', ['repo_name', 'uid']);
397 398 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
398 399 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
399 400 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
400 401 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
401 402 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
402 403 }
@@ -1,1450 +1,1473 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 var firefoxAnchorFix = function() {
20 20 // hack to make anchor links behave properly on firefox, in our inline
21 21 // comments generation when comments are injected firefox is misbehaving
22 22 // when jumping to anchor links
23 23 if (location.href.indexOf('#') > -1) {
24 24 location.href += '';
25 25 }
26 26 };
27 27
28 28 var linkifyComments = function(comments) {
29 29 var firstCommentId = null;
30 30 if (comments) {
31 31 firstCommentId = $(comments[0]).data('comment-id');
32 32 }
33 33
34 34 if (firstCommentId){
35 35 $('#inline-comments-counter').attr('href', '#comment-' + firstCommentId);
36 36 }
37 37 };
38 38
39 39 var bindToggleButtons = function() {
40 40 $('.comment-toggle').on('click', function() {
41 41 $(this).parent().nextUntil('tr.line').toggle('inline-comments');
42 42 });
43 43 };
44 44
45 45
46 46
47 47 var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
48 48 failHandler = failHandler || function() {};
49 49 postData = toQueryString(postData);
50 50 var request = $.ajax({
51 51 url: url,
52 52 type: 'POST',
53 53 data: postData,
54 54 headers: {'X-PARTIAL-XHR': true}
55 55 })
56 56 .done(function (data) {
57 57 successHandler(data);
58 58 })
59 59 .fail(function (data, textStatus, errorThrown) {
60 60 failHandler(data, textStatus, errorThrown)
61 61 });
62 62 return request;
63 63 };
64 64
65 65
66 66
67 67
68 68 /* Comment form for main and inline comments */
69 69 (function(mod) {
70 70
71 71 if (typeof exports == "object" && typeof module == "object") {
72 72 // CommonJS
73 73 module.exports = mod();
74 74 }
75 75 else {
76 76 // Plain browser env
77 77 (this || window).CommentForm = mod();
78 78 }
79 79
80 80 })(function() {
81 81 "use strict";
82 82
83 83 function CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId, edit, comment_id) {
84 84
85 85 if (!(this instanceof CommentForm)) {
86 86 return new CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId, edit, comment_id);
87 87 }
88 88
89 89 // bind the element instance to our Form
90 90 $(formElement).get(0).CommentForm = this;
91 91
92 92 this.withLineNo = function(selector) {
93 93 var lineNo = this.lineNo;
94 94 if (lineNo === undefined) {
95 95 return selector
96 96 } else {
97 97 return selector + '_' + lineNo;
98 98 }
99 99 };
100 100
101 101 this.commitId = commitId;
102 102 this.pullRequestId = pullRequestId;
103 103 this.lineNo = lineNo;
104 104 this.initAutocompleteActions = initAutocompleteActions;
105 105
106 106 this.previewButton = this.withLineNo('#preview-btn');
107 107 this.previewContainer = this.withLineNo('#preview-container');
108 108
109 109 this.previewBoxSelector = this.withLineNo('#preview-box');
110 110
111 111 this.editButton = this.withLineNo('#edit-btn');
112 112 this.editContainer = this.withLineNo('#edit-container');
113 113 this.cancelButton = this.withLineNo('#cancel-btn');
114 114 this.commentType = this.withLineNo('#comment_type');
115 115
116 116 this.resolvesId = null;
117 117 this.resolvesActionId = null;
118 118
119 119 this.closesPr = '#close_pull_request';
120 120
121 121 this.cmBox = this.withLineNo('#text');
122 122 this.cm = initCommentBoxCodeMirror(this, this.cmBox, this.initAutocompleteActions);
123 123
124 124 this.statusChange = this.withLineNo('#change_status');
125 125
126 126 this.submitForm = formElement;
127 127
128 128 this.submitButton = $(this.submitForm).find('.submit-comment-action');
129 129 this.submitButtonText = this.submitButton.val();
130 130
131 131 this.submitDraftButton = $(this.submitForm).find('.submit-draft-action');
132 132 this.submitDraftButtonText = this.submitDraftButton.val();
133 133
134 134 this.previewUrl = pyroutes.url('repo_commit_comment_preview',
135 135 {'repo_name': templateContext.repo_name,
136 136 'commit_id': templateContext.commit_data.commit_id});
137 137
138 138 if (edit){
139 139 this.submitDraftButton.hide();
140 140 this.submitButtonText = _gettext('Update Comment');
141 141 $(this.commentType).prop('disabled', true);
142 142 $(this.commentType).addClass('disabled');
143 143 var editInfo =
144 144 '';
145 145 $(editInfo).insertBefore($(this.editButton).parent());
146 146 }
147 147
148 148 if (resolvesCommentId){
149 149 this.resolvesId = '#resolve_comment_{0}'.format(resolvesCommentId);
150 150 this.resolvesActionId = '#resolve_comment_action_{0}'.format(resolvesCommentId);
151 151 $(this.commentType).prop('disabled', true);
152 152 $(this.commentType).addClass('disabled');
153 153
154 154 // disable select
155 155 setTimeout(function() {
156 156 $(self.statusChange).select2('readonly', true);
157 157 }, 10);
158 158
159 159 var resolvedInfo = (
160 160 '<li class="resolve-action">' +
161 161 '<input type="hidden" id="resolve_comment_{0}" name="resolve_comment_{0}" value="{0}">' +
162 162 '<button id="resolve_comment_action_{0}" class="resolve-text btn btn-sm" onclick="return Rhodecode.comments.submitResolution({0})">{1} #{0}</button>' +
163 163 '</li>'
164 164 ).format(resolvesCommentId, _gettext('resolve comment'));
165 165 $(resolvedInfo).insertAfter($(this.commentType).parent());
166 166 }
167 167
168 168 // based on commitId, or pullRequestId decide where do we submit
169 169 // out data
170 170 if (this.commitId){
171 171 var pyurl = 'repo_commit_comment_create';
172 172 if(edit){
173 173 pyurl = 'repo_commit_comment_edit';
174 174 }
175 175 this.submitUrl = pyroutes.url(pyurl,
176 176 {'repo_name': templateContext.repo_name,
177 177 'commit_id': this.commitId,
178 178 'comment_id': comment_id});
179 179 this.selfUrl = pyroutes.url('repo_commit',
180 180 {'repo_name': templateContext.repo_name,
181 181 'commit_id': this.commitId});
182 182
183 183 } else if (this.pullRequestId) {
184 184 var pyurl = 'pullrequest_comment_create';
185 185 if(edit){
186 186 pyurl = 'pullrequest_comment_edit';
187 187 }
188 188 this.submitUrl = pyroutes.url(pyurl,
189 189 {'repo_name': templateContext.repo_name,
190 190 'pull_request_id': this.pullRequestId,
191 191 'comment_id': comment_id});
192 192 this.selfUrl = pyroutes.url('pullrequest_show',
193 193 {'repo_name': templateContext.repo_name,
194 194 'pull_request_id': this.pullRequestId});
195 195
196 196 } else {
197 197 throw new Error(
198 198 'CommentForm requires pullRequestId, or commitId to be specified.')
199 199 }
200 200
201 201 // FUNCTIONS and helpers
202 202 var self = this;
203 203
204 204 this.isInline = function(){
205 205 return this.lineNo && this.lineNo != 'general';
206 206 };
207 207
208 208 this.getCmInstance = function(){
209 209 return this.cm
210 210 };
211 211
212 212 this.setPlaceholder = function(placeholder) {
213 213 var cm = this.getCmInstance();
214 214 if (cm){
215 215 cm.setOption('placeholder', placeholder);
216 216 }
217 217 };
218 218
219 219 this.getCommentStatus = function() {
220 220 return $(this.submitForm).find(this.statusChange).val();
221 221 };
222 222
223 223 this.getCommentType = function() {
224 224 return $(this.submitForm).find(this.commentType).val();
225 225 };
226 226
227 227 this.getDraftState = function () {
228 228 var submitterElem = $(this.submitForm).find('input[type="submit"].submitter');
229 229 var data = $(submitterElem).data('isDraft');
230 230 return data
231 231 }
232 232
233 233 this.getResolvesId = function() {
234 234 return $(this.submitForm).find(this.resolvesId).val() || null;
235 235 };
236 236
237 237 this.getClosePr = function() {
238 238 return $(this.submitForm).find(this.closesPr).val() || null;
239 239 };
240 240
241 241 this.markCommentResolved = function(resolvedCommentId){
242 242 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolved').show();
243 243 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolve').hide();
244 244 };
245 245
246 246 this.isAllowedToSubmit = function() {
247 247 var commentDisabled = $(this.submitButton).prop('disabled');
248 248 var draftDisabled = $(this.submitDraftButton).prop('disabled');
249 249 return !commentDisabled && !draftDisabled;
250 250 };
251 251
252 252 this.initStatusChangeSelector = function(){
253 253 var formatChangeStatus = function(state, escapeMarkup) {
254 254 var originalOption = state.element;
255 255 var tmpl = '<i class="icon-circle review-status-{0}"></i><span>{1}</span>'.format($(originalOption).data('status'), escapeMarkup(state.text));
256 256 return tmpl
257 257 };
258 258 var formatResult = function(result, container, query, escapeMarkup) {
259 259 return formatChangeStatus(result, escapeMarkup);
260 260 };
261 261
262 262 var formatSelection = function(data, container, escapeMarkup) {
263 263 return formatChangeStatus(data, escapeMarkup);
264 264 };
265 265
266 266 $(this.submitForm).find(this.statusChange).select2({
267 267 placeholder: _gettext('Status Review'),
268 268 formatResult: formatResult,
269 269 formatSelection: formatSelection,
270 270 containerCssClass: "drop-menu status_box_menu",
271 271 dropdownCssClass: "drop-menu-dropdown",
272 272 dropdownAutoWidth: true,
273 273 minimumResultsForSearch: -1
274 274 });
275 275
276 276 $(this.submitForm).find(this.statusChange).on('change', function() {
277 277 var status = self.getCommentStatus();
278 278
279 279 if (status && !self.isInline()) {
280 280 $(self.submitButton).prop('disabled', false);
281 281 $(self.submitDraftButton).prop('disabled', false);
282 282 }
283 283
284 284 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
285 285 self.setPlaceholder(placeholderText)
286 286 })
287 287 };
288 288
289 289 // reset the comment form into it's original state
290 290 this.resetCommentFormState = function(content) {
291 291 content = content || '';
292 292
293 293 $(this.editContainer).show();
294 294 $(this.editButton).parent().addClass('active');
295 295
296 296 $(this.previewContainer).hide();
297 297 $(this.previewButton).parent().removeClass('active');
298 298
299 299 this.setActionButtonsDisabled(true);
300 300 self.cm.setValue(content);
301 301 self.cm.setOption("readOnly", false);
302 302
303 303 if (this.resolvesId) {
304 304 // destroy the resolve action
305 305 $(this.resolvesId).parent().remove();
306 306 }
307 307 // reset closingPR flag
308 308 $('.close-pr-input').remove();
309 309
310 310 $(this.statusChange).select2('readonly', false);
311 311 };
312 312
313 313 this.globalSubmitSuccessCallback = function(comment){
314 314 // default behaviour is to call GLOBAL hook, if it's registered.
315 315 if (window.commentFormGlobalSubmitSuccessCallback !== undefined){
316 316 commentFormGlobalSubmitSuccessCallback(comment);
317 317 }
318 318 };
319 319
320 320 this.submitAjaxPOST = function(url, postData, successHandler, failHandler) {
321 321 return _submitAjaxPOST(url, postData, successHandler, failHandler);
322 322 };
323 323
324 324 // overwrite a submitHandler, we need to do it for inline comments
325 325 this.setHandleFormSubmit = function(callback) {
326 326 this.handleFormSubmit = callback;
327 327 };
328 328
329 329 // overwrite a submitSuccessHandler
330 330 this.setGlobalSubmitSuccessCallback = function(callback) {
331 331 this.globalSubmitSuccessCallback = callback;
332 332 };
333 333
334 334 // default handler for for submit for main comments
335 335 this.handleFormSubmit = function() {
336 336 var text = self.cm.getValue();
337 337 var status = self.getCommentStatus();
338 338 var commentType = self.getCommentType();
339 339 var isDraft = self.getDraftState();
340 340 var resolvesCommentId = self.getResolvesId();
341 341 var closePullRequest = self.getClosePr();
342 342
343 343 if (text === "" && !status) {
344 344 return;
345 345 }
346 346
347 347 var excludeCancelBtn = false;
348 348 var submitEvent = true;
349 349 self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
350 350 self.cm.setOption("readOnly", true);
351 351
352 352 var postData = {
353 353 'text': text,
354 354 'changeset_status': status,
355 355 'comment_type': commentType,
356 356 'csrf_token': CSRF_TOKEN
357 357 };
358 358
359 359 if (resolvesCommentId) {
360 360 postData['resolves_comment_id'] = resolvesCommentId;
361 361 }
362 362
363 363 if (closePullRequest) {
364 364 postData['close_pull_request'] = true;
365 365 }
366 366
367 367 // submitSuccess for general comments
368 368 var submitSuccessCallback = function(json_data) {
369 369 // reload page if we change status for single commit.
370 370 if (status && self.commitId) {
371 371 location.reload(true);
372 372 } else {
373 373 // inject newly created comments, json_data is {<comment_id>: {}}
374 374 self.attachGeneralComment(json_data)
375 375
376 376 self.resetCommentFormState();
377 377 timeagoActivate();
378 378 tooltipActivate();
379 379
380 380 // mark visually which comment was resolved
381 381 if (resolvesCommentId) {
382 382 self.markCommentResolved(resolvesCommentId);
383 383 }
384 384 }
385 385
386 386 // run global callback on submit
387 387 self.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
388 388
389 389 };
390 390 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
391 391 var prefix = "Error while submitting comment.\n"
392 392 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
393 393 ajaxErrorSwal(message);
394 394 self.resetCommentFormState(text);
395 395 };
396 396 self.submitAjaxPOST(
397 397 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
398 398 };
399 399
400 400 this.previewSuccessCallback = function(o) {
401 401 $(self.previewBoxSelector).html(o);
402 402 $(self.previewBoxSelector).removeClass('unloaded');
403 403
404 404 // swap buttons, making preview active
405 405 $(self.previewButton).parent().addClass('active');
406 406 $(self.editButton).parent().removeClass('active');
407 407
408 408 // unlock buttons
409 409 self.setActionButtonsDisabled(false);
410 410 };
411 411
412 412 this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) {
413 413 excludeCancelBtn = excludeCancelBtn || false;
414 414 submitEvent = submitEvent || false;
415 415
416 416 $(this.editButton).prop('disabled', state);
417 417 $(this.previewButton).prop('disabled', state);
418 418
419 419 if (!excludeCancelBtn) {
420 420 $(this.cancelButton).prop('disabled', state);
421 421 }
422 422
423 423 var submitState = state;
424 424 if (!submitEvent && this.getCommentStatus() && !self.isInline()) {
425 425 // if the value of commit review status is set, we allow
426 426 // submit button, but only on Main form, isInline means inline
427 427 submitState = false
428 428 }
429 429
430 430 $(this.submitButton).prop('disabled', submitState);
431 431 $(this.submitDraftButton).prop('disabled', submitState);
432 432
433 433 if (submitEvent) {
434 434 var isDraft = self.getDraftState();
435 435
436 436 if (isDraft) {
437 437 $(this.submitDraftButton).val(_gettext('Saving Draft...'));
438 438 } else {
439 439 $(this.submitButton).val(_gettext('Submitting...'));
440 440 }
441 441
442 442 } else {
443 443 $(this.submitButton).val(this.submitButtonText);
444 444 $(this.submitDraftButton).val(this.submitDraftButtonText);
445 445 }
446 446
447 447 };
448 448
449 449 // lock preview/edit/submit buttons on load, but exclude cancel button
450 450 var excludeCancelBtn = true;
451 451 this.setActionButtonsDisabled(true, excludeCancelBtn);
452 452
453 453 // anonymous users don't have access to initialized CM instance
454 454 if (this.cm !== undefined){
455 455 this.cm.on('change', function(cMirror) {
456 456 if (cMirror.getValue() === "") {
457 457 self.setActionButtonsDisabled(true, excludeCancelBtn)
458 458 } else {
459 459 self.setActionButtonsDisabled(false, excludeCancelBtn)
460 460 }
461 461 });
462 462 }
463 463
464 464 $(this.editButton).on('click', function(e) {
465 465 e.preventDefault();
466 466
467 467 $(self.previewButton).parent().removeClass('active');
468 468 $(self.previewContainer).hide();
469 469
470 470 $(self.editButton).parent().addClass('active');
471 471 $(self.editContainer).show();
472 472
473 473 });
474 474
475 475 $(this.previewButton).on('click', function(e) {
476 476 e.preventDefault();
477 477 var text = self.cm.getValue();
478 478
479 479 if (text === "") {
480 480 return;
481 481 }
482 482
483 483 var postData = {
484 484 'text': text,
485 485 'renderer': templateContext.visual.default_renderer,
486 486 'csrf_token': CSRF_TOKEN
487 487 };
488 488
489 489 // lock ALL buttons on preview
490 490 self.setActionButtonsDisabled(true);
491 491
492 492 $(self.previewBoxSelector).addClass('unloaded');
493 493 $(self.previewBoxSelector).html(_gettext('Loading ...'));
494 494
495 495 $(self.editContainer).hide();
496 496 $(self.previewContainer).show();
497 497
498 498 // by default we reset state of comment preserving the text
499 499 var previewFailCallback = function(jqXHR, textStatus, errorThrown) {
500 500 var prefix = "Error while preview of comment.\n"
501 501 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
502 502 ajaxErrorSwal(message);
503 503
504 504 self.resetCommentFormState(text)
505 505 };
506 506 self.submitAjaxPOST(
507 507 self.previewUrl, postData, self.previewSuccessCallback,
508 508 previewFailCallback);
509 509
510 510 $(self.previewButton).parent().addClass('active');
511 511 $(self.editButton).parent().removeClass('active');
512 512 });
513 513
514 514 $(this.submitForm).submit(function(e) {
515 515 e.preventDefault();
516 516 var allowedToSubmit = self.isAllowedToSubmit();
517 517 if (!allowedToSubmit){
518 518 return false;
519 519 }
520 520
521 521 self.handleFormSubmit();
522 522 });
523 523
524 524 }
525 525
526 526 return CommentForm;
527 527 });
528 528
529 529 /* selector for comment versions */
530 530 var initVersionSelector = function(selector, initialData) {
531 531
532 532 var formatResult = function(result, container, query, escapeMarkup) {
533 533
534 534 return renderTemplate('commentVersion', {
535 535 show_disabled: true,
536 536 version: result.comment_version,
537 537 user_name: result.comment_author_username,
538 538 gravatar_url: result.comment_author_gravatar,
539 539 size: 16,
540 540 timeago_component: result.comment_created_on,
541 541 })
542 542 };
543 543
544 544 $(selector).select2({
545 545 placeholder: "Edited",
546 546 containerCssClass: "drop-menu-comment-history",
547 547 dropdownCssClass: "drop-menu-dropdown",
548 548 dropdownAutoWidth: true,
549 549 minimumResultsForSearch: -1,
550 550 data: initialData,
551 551 formatResult: formatResult,
552 552 });
553 553
554 554 $(selector).on('select2-selecting', function (e) {
555 555 // hide the mast as we later do preventDefault()
556 556 $("#select2-drop-mask").click();
557 557 e.preventDefault();
558 558 e.choice.action();
559 559 });
560 560
561 561 $(selector).on("select2-open", function() {
562 562 timeagoActivate();
563 563 });
564 564 };
565 565
566 566 /* comments controller */
567 567 var CommentsController = function() {
568 568 var mainComment = '#text';
569 569 var self = this;
570 570
571 571 this.showVersion = function (comment_id, comment_history_id) {
572 572
573 573 var historyViewUrl = pyroutes.url(
574 574 'repo_commit_comment_history_view',
575 575 {
576 576 'repo_name': templateContext.repo_name,
577 577 'commit_id': comment_id,
578 578 'comment_history_id': comment_history_id,
579 579 }
580 580 );
581 581 successRenderCommit = function (data) {
582 582 SwalNoAnimation.fire({
583 583 html: data,
584 584 title: '',
585 585 });
586 586 };
587 587 failRenderCommit = function () {
588 588 SwalNoAnimation.fire({
589 589 html: 'Error while loading comment history',
590 590 title: '',
591 591 });
592 592 };
593 593 _submitAjaxPOST(
594 594 historyViewUrl, {'csrf_token': CSRF_TOKEN},
595 595 successRenderCommit,
596 596 failRenderCommit
597 597 );
598 598 };
599 599
600 600 this.getLineNumber = function(node) {
601 601 var $node = $(node);
602 602 var lineNo = $node.closest('td').attr('data-line-no');
603 603 if (lineNo === undefined && $node.data('commentInline')){
604 604 lineNo = $node.data('commentLineNo')
605 605 }
606 606
607 607 return lineNo
608 608 };
609 609
610 610 this.scrollToComment = function(node, offset, outdated) {
611 611 if (offset === undefined) {
612 612 offset = 0;
613 613 }
614 614 var outdated = outdated || false;
615 615 var klass = outdated ? 'div.comment-outdated' : 'div.comment-current';
616 616
617 617 if (!node) {
618 618 node = $('.comment-selected');
619 619 if (!node.length) {
620 620 node = $('comment-current')
621 621 }
622 622 }
623 623
624 624 $wrapper = $(node).closest('div.comment');
625 625
626 626 // show hidden comment when referenced.
627 627 if (!$wrapper.is(':visible')){
628 628 $wrapper.show();
629 629 }
630 630
631 631 $comment = $(node).closest(klass);
632 632 $comments = $(klass);
633 633
634 634 $('.comment-selected').removeClass('comment-selected');
635 635
636 636 var nextIdx = $(klass).index($comment) + offset;
637 637 if (nextIdx >= $comments.length) {
638 638 nextIdx = 0;
639 639 }
640 640 var $next = $(klass).eq(nextIdx);
641 641
642 642 var $cb = $next.closest('.cb');
643 643 $cb.removeClass('cb-collapsed');
644 644
645 645 var $filediffCollapseState = $cb.closest('.filediff').prev();
646 646 $filediffCollapseState.prop('checked', false);
647 647 $next.addClass('comment-selected');
648 648 scrollToElement($next);
649 649 return false;
650 650 };
651 651
652 652 this.nextComment = function(node) {
653 653 return self.scrollToComment(node, 1);
654 654 };
655 655
656 656 this.prevComment = function(node) {
657 657 return self.scrollToComment(node, -1);
658 658 };
659 659
660 660 this.nextOutdatedComment = function(node) {
661 661 return self.scrollToComment(node, 1, true);
662 662 };
663 663
664 664 this.prevOutdatedComment = function(node) {
665 665 return self.scrollToComment(node, -1, true);
666 666 };
667 667
668 668 this.cancelComment = function (node) {
669 669 var $node = $(node);
670 670 var edit = $(this).attr('edit');
671 671 var $inlineComments = $node.closest('div.inline-comments');
672 672
673 673 if (edit) {
674 674 var $general_comments = null;
675 675 if (!$inlineComments.length) {
676 676 $general_comments = $('#comments');
677 677 var $comment = $general_comments.parent().find('div.comment:hidden');
678 678 // show hidden general comment form
679 679 $('#cb-comment-general-form-placeholder').show();
680 680 } else {
681 681 var $comment = $inlineComments.find('div.comment:hidden');
682 682 }
683 683 $comment.show();
684 684 }
685 685 var $replyWrapper = $node.closest('.comment-inline-form').closest('.reply-thread-container-wrapper')
686 686 $replyWrapper.removeClass('comment-form-active');
687 687
688 688 var lastComment = $inlineComments.find('.comment-inline').last();
689 689 if ($(lastComment).hasClass('comment-outdated')) {
690 690 $replyWrapper.hide();
691 691 }
692 692
693 693 $node.closest('.comment-inline-form').remove();
694 694 return false;
695 695 };
696 696
697 697 this._deleteComment = function(node) {
698 698 var $node = $(node);
699 699 var $td = $node.closest('td');
700 700 var $comment = $node.closest('.comment');
701 701 var comment_id = $($comment).data('commentId');
702 702 var isDraft = $($comment).data('commentDraft');
703 703 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
704 704 var postData = {
705 705 'csrf_token': CSRF_TOKEN
706 706 };
707 707
708 708 $comment.addClass('comment-deleting');
709 709 $comment.hide('fast');
710 710
711 711 var success = function(response) {
712 712 $comment.remove();
713 713
714 714 if (window.updateSticky !== undefined) {
715 715 // potentially our comments change the active window size, so we
716 716 // notify sticky elements
717 717 updateSticky()
718 718 }
719 719
720 720 if (window.refreshAllComments !== undefined && !isDraft) {
721 721 // if we have this handler, run it, and refresh all comments boxes
722 722 refreshAllComments()
723 723 }
724 724 return false;
725 725 };
726 726
727 727 var failure = function(jqXHR, textStatus, errorThrown) {
728 728 var prefix = "Error while deleting this comment.\n"
729 729 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
730 730 ajaxErrorSwal(message);
731 731
732 732 $comment.show('fast');
733 733 $comment.removeClass('comment-deleting');
734 734 return false;
735 735 };
736 736 ajaxPOST(url, postData, success, failure);
737 737
738
739
740 738 }
741 739
742 740 this.deleteComment = function(node) {
743 741 var $comment = $(node).closest('.comment');
744 742 var comment_id = $comment.attr('data-comment-id');
745 743
746 744 SwalNoAnimation.fire({
747 745 title: 'Delete this comment?',
748 746 icon: 'warning',
749 747 showCancelButton: true,
750 748 confirmButtonText: _gettext('Yes, delete comment #{0}!').format(comment_id),
751 749
752 750 }).then(function(result) {
753 751 if (result.value) {
754 752 self._deleteComment(node);
755 753 }
756 754 })
757 755 };
758 756
759 757 this._finalizeDrafts = function(commentIds) {
760 window.finalizeDrafts(commentIds)
758
759 // remove the drafts so we can lock them before submit.
760 $.each(commentIds, function(idx, val){
761 $('#comment-{0}'.format(val)).remove();
762 })
763
764 var params = {
765 'pull_request_id': templateContext.pull_request_data.pull_request_id,
766 'repo_name': templateContext.repo_name,
767 };
768 var url = pyroutes.url('pullrequest_draft_comments_submit', params)
769 var postData = {'comments': commentIds, 'csrf_token': CSRF_TOKEN};
770
771 var submitSuccessCallback = function(json_data) {
772 self.attachInlineComment(json_data);
773
774 if (window.refreshDraftComments !== undefined) {
775 // if we have this handler, run it, and refresh all comments boxes
776 refreshDraftComments()
777 }
778
779 return false;
780 };
781
782 ajaxPOST(url, postData, submitSuccessCallback)
783
761 784 }
762 785
763 786 this.finalizeDrafts = function(commentIds) {
764 787
765 788 SwalNoAnimation.fire({
766 789 title: _ngettext('Submit {0} draft comment.', 'Submit {0} draft comments.', commentIds.length).format(commentIds.length),
767 790 icon: 'warning',
768 791 showCancelButton: true,
769 confirmButtonText: _gettext('Yes, finalize drafts'),
792 confirmButtonText: _gettext('Yes'),
770 793
771 794 }).then(function(result) {
772 795 if (result.value) {
773 796 self._finalizeDrafts(commentIds);
774 797 }
775 798 })
776 799 };
777 800
778 801 this.toggleWideMode = function (node) {
779 802
780 803 if ($('#content').hasClass('wrapper')) {
781 804 $('#content').removeClass("wrapper");
782 805 $('#content').addClass("wide-mode-wrapper");
783 806 $(node).addClass('btn-success');
784 807 return true
785 808 } else {
786 809 $('#content').removeClass("wide-mode-wrapper");
787 810 $('#content').addClass("wrapper");
788 811 $(node).removeClass('btn-success');
789 812 return false
790 813 }
791 814
792 815 };
793 816
794 817 /**
795 818 * Turn off/on all comments in file diff
796 819 */
797 820 this.toggleDiffComments = function(node) {
798 821 // Find closes filediff container
799 822 var $filediff = $(node).closest('.filediff');
800 823 if ($(node).hasClass('toggle-on')) {
801 824 var show = false;
802 825 } else if ($(node).hasClass('toggle-off')) {
803 826 var show = true;
804 827 }
805 828
806 829 // Toggle each individual comment block, so we can un-toggle single ones
807 830 $.each($filediff.find('.toggle-comment-action'), function(idx, val) {
808 831 self.toggleLineComments($(val), show)
809 832 })
810 833
811 834 // since we change the height of the diff container that has anchor points for upper
812 835 // sticky header, we need to tell it to re-calculate those
813 836 if (window.updateSticky !== undefined) {
814 837 // potentially our comments change the active window size, so we
815 838 // notify sticky elements
816 839 updateSticky()
817 840 }
818 841
819 842 return false;
820 843 }
821 844
822 845 this.toggleLineComments = function(node, show) {
823 846
824 847 var trElem = $(node).closest('tr')
825 848
826 849 if (show === true) {
827 850 // mark outdated comments as visible before the toggle;
828 851 $(trElem).find('.comment-outdated').show();
829 852 $(trElem).removeClass('hide-line-comments');
830 853 } else if (show === false) {
831 854 $(trElem).find('.comment-outdated').hide();
832 855 $(trElem).addClass('hide-line-comments');
833 856 } else {
834 857 // mark outdated comments as visible before the toggle;
835 858 $(trElem).find('.comment-outdated').show();
836 859 $(trElem).toggleClass('hide-line-comments');
837 860 }
838 861
839 862 // since we change the height of the diff container that has anchor points for upper
840 863 // sticky header, we need to tell it to re-calculate those
841 864 if (window.updateSticky !== undefined) {
842 865 // potentially our comments change the active window size, so we
843 866 // notify sticky elements
844 867 updateSticky()
845 868 }
846 869
847 870 };
848 871
849 872 this.createCommentForm = function(formElement, lineno, placeholderText, initAutocompleteActions, resolvesCommentId, edit, comment_id){
850 873 var pullRequestId = templateContext.pull_request_data.pull_request_id;
851 874 var commitId = templateContext.commit_data.commit_id;
852 875
853 876 var commentForm = new CommentForm(
854 877 formElement, commitId, pullRequestId, lineno, initAutocompleteActions, resolvesCommentId, edit, comment_id);
855 878 var cm = commentForm.getCmInstance();
856 879
857 880 if (resolvesCommentId){
858 881 placeholderText = _gettext('Leave a resolution comment, or click resolve button to resolve TODO comment #{0}').format(resolvesCommentId);
859 882 }
860 883
861 884 setTimeout(function() {
862 885 // callbacks
863 886 if (cm !== undefined) {
864 887 commentForm.setPlaceholder(placeholderText);
865 888 if (commentForm.isInline()) {
866 889 cm.focus();
867 890 cm.refresh();
868 891 }
869 892 }
870 893 }, 10);
871 894
872 895 // trigger scrolldown to the resolve comment, since it might be away
873 896 // from the clicked
874 897 if (resolvesCommentId){
875 898 var actionNode = $(commentForm.resolvesActionId).offset();
876 899
877 900 setTimeout(function() {
878 901 if (actionNode) {
879 902 $('body, html').animate({scrollTop: actionNode.top}, 10);
880 903 }
881 904 }, 100);
882 905 }
883 906
884 907 // add dropzone support
885 908 var insertAttachmentText = function (cm, attachmentName, attachmentStoreUrl, isRendered) {
886 909 var renderer = templateContext.visual.default_renderer;
887 910 if (renderer == 'rst') {
888 911 var attachmentUrl = '`#{0} <{1}>`_'.format(attachmentName, attachmentStoreUrl);
889 912 if (isRendered){
890 913 attachmentUrl = '\n.. image:: {0}'.format(attachmentStoreUrl);
891 914 }
892 915 } else if (renderer == 'markdown') {
893 916 var attachmentUrl = '[{0}]({1})'.format(attachmentName, attachmentStoreUrl);
894 917 if (isRendered){
895 918 attachmentUrl = '!' + attachmentUrl;
896 919 }
897 920 } else {
898 921 var attachmentUrl = '{}'.format(attachmentStoreUrl);
899 922 }
900 923 cm.replaceRange(attachmentUrl+'\n', CodeMirror.Pos(cm.lastLine()));
901 924
902 925 return false;
903 926 };
904 927
905 928 //see: https://www.dropzonejs.com/#configuration
906 929 var storeUrl = pyroutes.url('repo_commit_comment_attachment_upload',
907 930 {'repo_name': templateContext.repo_name,
908 931 'commit_id': templateContext.commit_data.commit_id})
909 932
910 933 var previewTmpl = $(formElement).find('.comment-attachment-uploader-template').get(0);
911 934 if (previewTmpl !== undefined){
912 935 var selectLink = $(formElement).find('.pick-attachment').get(0);
913 936 $(formElement).find('.comment-attachment-uploader').dropzone({
914 937 url: storeUrl,
915 938 headers: {"X-CSRF-Token": CSRF_TOKEN},
916 939 paramName: function () {
917 940 return "attachment"
918 941 }, // The name that will be used to transfer the file
919 942 clickable: selectLink,
920 943 parallelUploads: 1,
921 944 maxFiles: 10,
922 945 maxFilesize: templateContext.attachment_store.max_file_size_mb,
923 946 uploadMultiple: false,
924 947 autoProcessQueue: true, // if false queue will not be processed automatically.
925 948 createImageThumbnails: false,
926 949 previewTemplate: previewTmpl.innerHTML,
927 950
928 951 accept: function (file, done) {
929 952 done();
930 953 },
931 954 init: function () {
932 955
933 956 this.on("sending", function (file, xhr, formData) {
934 957 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').hide();
935 958 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').show();
936 959 });
937 960
938 961 this.on("success", function (file, response) {
939 962 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').show();
940 963 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
941 964
942 965 var isRendered = false;
943 966 var ext = file.name.split('.').pop();
944 967 var imageExts = templateContext.attachment_store.image_ext;
945 968 if (imageExts.indexOf(ext) !== -1){
946 969 isRendered = true;
947 970 }
948 971
949 972 insertAttachmentText(cm, file.name, response.repo_fqn_access_path, isRendered)
950 973 });
951 974
952 975 this.on("error", function (file, errorMessage, xhr) {
953 976 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
954 977
955 978 var error = null;
956 979
957 980 if (xhr !== undefined){
958 981 var httpStatus = xhr.status + " " + xhr.statusText;
959 982 if (xhr !== undefined && xhr.status >= 500) {
960 983 error = httpStatus;
961 984 }
962 985 }
963 986
964 987 if (error === null) {
965 988 error = errorMessage.error || errorMessage || httpStatus;
966 989 }
967 990 $(file.previewElement).find('.dz-error-message').html('ERROR: {0}'.format(error));
968 991
969 992 });
970 993 }
971 994 });
972 995 }
973 996 return commentForm;
974 997 };
975 998
976 999 this.createGeneralComment = function (lineNo, placeholderText, resolvesCommentId) {
977 1000
978 1001 var tmpl = $('#cb-comment-general-form-template').html();
979 1002 tmpl = tmpl.format(null, 'general');
980 1003 var $form = $(tmpl);
981 1004
982 1005 var $formPlaceholder = $('#cb-comment-general-form-placeholder');
983 1006 var curForm = $formPlaceholder.find('form');
984 1007 if (curForm){
985 1008 curForm.remove();
986 1009 }
987 1010 $formPlaceholder.append($form);
988 1011
989 1012 var _form = $($form[0]);
990 1013 var autocompleteActions = ['approve', 'reject', 'as_note', 'as_todo'];
991 1014 var edit = false;
992 1015 var comment_id = null;
993 1016 var commentForm = this.createCommentForm(
994 1017 _form, lineNo, placeholderText, autocompleteActions, resolvesCommentId, edit, comment_id);
995 1018 commentForm.initStatusChangeSelector();
996 1019
997 1020 return commentForm;
998 1021 };
999 1022
1000 1023 this.editComment = function(node, line_no, f_path) {
1001 1024 self.edit = true;
1002 1025 var $node = $(node);
1003 1026 var $td = $node.closest('td');
1004 1027
1005 1028 var $comment = $(node).closest('.comment');
1006 1029 var comment_id = $($comment).data('commentId');
1007 1030 var isDraft = $($comment).data('commentDraft');
1008 1031 var $editForm = null
1009 1032
1010 1033 var $comments = $node.closest('div.inline-comments');
1011 1034 var $general_comments = null;
1012 1035
1013 1036 if($comments.length){
1014 1037 // inline comments setup
1015 1038 $editForm = $comments.find('.comment-inline-form');
1016 1039 line_no = self.getLineNumber(node)
1017 1040 }
1018 1041 else{
1019 1042 // general comments setup
1020 1043 $comments = $('#comments');
1021 1044 $editForm = $comments.find('.comment-inline-form');
1022 1045 line_no = $comment[0].id
1023 1046 $('#cb-comment-general-form-placeholder').hide();
1024 1047 }
1025 1048
1026 1049 if ($editForm.length === 0) {
1027 1050
1028 1051 // unhide all comments if they are hidden for a proper REPLY mode
1029 1052 var $filediff = $node.closest('.filediff');
1030 1053 $filediff.removeClass('hide-comments');
1031 1054
1032 1055 $editForm = self.createNewFormWrapper(f_path, line_no);
1033 1056 if(f_path && line_no) {
1034 1057 $editForm.addClass('comment-inline-form-edit')
1035 1058 }
1036 1059
1037 1060 $comment.after($editForm)
1038 1061
1039 1062 var _form = $($editForm[0]).find('form');
1040 1063 var autocompleteActions = ['as_note',];
1041 1064 var commentForm = this.createCommentForm(
1042 1065 _form, line_no, '', autocompleteActions, resolvesCommentId,
1043 1066 this.edit, comment_id);
1044 1067 var old_comment_text_binary = $comment.attr('data-comment-text');
1045 1068 var old_comment_text = b64DecodeUnicode(old_comment_text_binary);
1046 1069 commentForm.cm.setValue(old_comment_text);
1047 1070 $comment.hide();
1048 1071 tooltipActivate();
1049 1072
1050 1073 // set a CUSTOM submit handler for inline comment edit action.
1051 1074 commentForm.setHandleFormSubmit(function(o) {
1052 1075 var text = commentForm.cm.getValue();
1053 1076 var commentType = commentForm.getCommentType();
1054 1077
1055 1078 if (text === "") {
1056 1079 return;
1057 1080 }
1058 1081
1059 1082 if (old_comment_text == text) {
1060 1083 SwalNoAnimation.fire({
1061 1084 title: 'Unable to edit comment',
1062 1085 html: _gettext('Comment body was not changed.'),
1063 1086 });
1064 1087 return;
1065 1088 }
1066 1089 var excludeCancelBtn = false;
1067 1090 var submitEvent = true;
1068 1091 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
1069 1092 commentForm.cm.setOption("readOnly", true);
1070 1093
1071 1094 // Read last version known
1072 1095 var versionSelector = $('#comment_versions_{0}'.format(comment_id));
1073 1096 var version = versionSelector.data('lastVersion');
1074 1097
1075 1098 if (!version) {
1076 1099 version = 0;
1077 1100 }
1078 1101
1079 1102 var postData = {
1080 1103 'text': text,
1081 1104 'f_path': f_path,
1082 1105 'line': line_no,
1083 1106 'comment_type': commentType,
1084 1107 'draft': isDraft,
1085 1108 'version': version,
1086 1109 'csrf_token': CSRF_TOKEN
1087 1110 };
1088 1111
1089 1112 var submitSuccessCallback = function(json_data) {
1090 1113 $editForm.remove();
1091 1114 $comment.show();
1092 1115 var postData = {
1093 1116 'text': text,
1094 1117 'renderer': $comment.attr('data-comment-renderer'),
1095 1118 'csrf_token': CSRF_TOKEN
1096 1119 };
1097 1120
1098 1121 /* Inject new edited version selector */
1099 1122 var updateCommentVersionDropDown = function () {
1100 1123 var versionSelectId = '#comment_versions_'+comment_id;
1101 1124 var preLoadVersionData = [
1102 1125 {
1103 1126 id: json_data['comment_version'],
1104 1127 text: "v{0}".format(json_data['comment_version']),
1105 1128 action: function () {
1106 1129 Rhodecode.comments.showVersion(
1107 1130 json_data['comment_id'],
1108 1131 json_data['comment_history_id']
1109 1132 )
1110 1133 },
1111 1134 comment_version: json_data['comment_version'],
1112 1135 comment_author_username: json_data['comment_author_username'],
1113 1136 comment_author_gravatar: json_data['comment_author_gravatar'],
1114 1137 comment_created_on: json_data['comment_created_on'],
1115 1138 },
1116 1139 ]
1117 1140
1118 1141
1119 1142 if ($(versionSelectId).data('select2')) {
1120 1143 var oldData = $(versionSelectId).data('select2').opts.data.results;
1121 1144 $(versionSelectId).select2("destroy");
1122 1145 preLoadVersionData = oldData.concat(preLoadVersionData)
1123 1146 }
1124 1147
1125 1148 initVersionSelector(versionSelectId, {results: preLoadVersionData});
1126 1149
1127 1150 $comment.attr('data-comment-text', utf8ToB64(text));
1128 1151
1129 1152 var versionSelector = $('#comment_versions_'+comment_id);
1130 1153
1131 1154 // set lastVersion so we know our last edit version
1132 1155 versionSelector.data('lastVersion', json_data['comment_version'])
1133 1156 versionSelector.parent().show();
1134 1157 }
1135 1158 updateCommentVersionDropDown();
1136 1159
1137 1160 // by default we reset state of comment preserving the text
1138 1161 var failRenderCommit = function(jqXHR, textStatus, errorThrown) {
1139 1162 var prefix = "Error while editing this comment.\n"
1140 1163 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1141 1164 ajaxErrorSwal(message);
1142 1165 };
1143 1166
1144 1167 var successRenderCommit = function(o){
1145 1168 $comment.show();
1146 1169 $comment[0].lastElementChild.innerHTML = o;
1147 1170 };
1148 1171
1149 1172 var previewUrl = pyroutes.url(
1150 1173 'repo_commit_comment_preview',
1151 1174 {'repo_name': templateContext.repo_name,
1152 1175 'commit_id': templateContext.commit_data.commit_id});
1153 1176
1154 1177 _submitAjaxPOST(
1155 1178 previewUrl, postData, successRenderCommit, failRenderCommit
1156 1179 );
1157 1180
1158 1181 try {
1159 1182 var html = json_data.rendered_text;
1160 1183 var lineno = json_data.line_no;
1161 1184 var target_id = json_data.target_id;
1162 1185
1163 1186 $comments.find('.cb-comment-add-button').before(html);
1164 1187
1165 1188 // run global callback on submit
1166 1189 commentForm.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
1167 1190
1168 1191 } catch (e) {
1169 1192 console.error(e);
1170 1193 }
1171 1194
1172 1195 // re trigger the linkification of next/prev navigation
1173 1196 linkifyComments($('.inline-comment-injected'));
1174 1197 timeagoActivate();
1175 1198 tooltipActivate();
1176 1199
1177 1200 if (window.updateSticky !== undefined) {
1178 1201 // potentially our comments change the active window size, so we
1179 1202 // notify sticky elements
1180 1203 updateSticky()
1181 1204 }
1182 1205
1183 1206 if (window.refreshAllComments !== undefined && !isDraft) {
1184 1207 // if we have this handler, run it, and refresh all comments boxes
1185 1208 refreshAllComments()
1186 1209 }
1187 1210
1188 1211 commentForm.setActionButtonsDisabled(false);
1189 1212
1190 1213 };
1191 1214
1192 1215 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
1193 1216 var prefix = "Error while editing comment.\n"
1194 1217 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1195 1218 if (jqXHR.status == 409){
1196 1219 message = 'This comment was probably changed somewhere else. Please reload the content of this comment.'
1197 1220 ajaxErrorSwal(message, 'Comment version mismatch.');
1198 1221 } else {
1199 1222 ajaxErrorSwal(message);
1200 1223 }
1201 1224
1202 1225 commentForm.resetCommentFormState(text)
1203 1226 };
1204 1227 commentForm.submitAjaxPOST(
1205 1228 commentForm.submitUrl, postData,
1206 1229 submitSuccessCallback,
1207 1230 submitFailCallback);
1208 1231 });
1209 1232 }
1210 1233
1211 1234 $editForm.addClass('comment-inline-form-open');
1212 1235 };
1213 1236
1214 1237 this.attachComment = function(json_data) {
1215 1238 var self = this;
1216 1239 $.each(json_data, function(idx, val) {
1217 1240 var json_data_elem = [val]
1218 1241 var isInline = val.comment_f_path && val.comment_lineno
1219 1242
1220 1243 if (isInline) {
1221 1244 self.attachInlineComment(json_data_elem)
1222 1245 } else {
1223 1246 self.attachGeneralComment(json_data_elem)
1224 1247 }
1225 1248 })
1226 1249
1227 1250 }
1228 1251
1229 1252 this.attachGeneralComment = function(json_data) {
1230 1253 $.each(json_data, function(idx, val) {
1231 1254 $('#injected_page_comments').append(val.rendered_text);
1232 1255 })
1233 1256 }
1234 1257
1235 1258 this.attachInlineComment = function(json_data) {
1236 1259
1237 1260 $.each(json_data, function (idx, val) {
1238 1261 var line_qry = '*[data-line-no="{0}"]'.format(val.line_no);
1239 1262 var html = val.rendered_text;
1240 1263 var $inlineComments = $('#' + val.target_id)
1241 1264 .find(line_qry)
1242 1265 .find('.inline-comments');
1243 1266
1244 1267 var lastComment = $inlineComments.find('.comment-inline').last();
1245 1268
1246 1269 if (lastComment.length === 0) {
1247 1270 // first comment, we append simply
1248 1271 $inlineComments.find('.reply-thread-container-wrapper').before(html);
1249 1272 } else {
1250 1273 $(lastComment).after(html)
1251 1274 }
1252 1275
1253 1276 })
1254 1277
1255 1278 };
1256 1279
1257 1280 this.createNewFormWrapper = function(f_path, line_no) {
1258 1281 // create a new reply HTML form from template
1259 1282 var tmpl = $('#cb-comment-inline-form-template').html();
1260 1283 tmpl = tmpl.format(escapeHtml(f_path), line_no);
1261 1284 return $(tmpl);
1262 1285 }
1263 1286
1264 1287 this.createComment = function(node, f_path, line_no, resolutionComment) {
1265 1288 self.edit = false;
1266 1289 var $node = $(node);
1267 1290 var $td = $node.closest('td');
1268 1291 var resolvesCommentId = resolutionComment || null;
1269 1292
1270 1293 var $replyForm = $td.find('.comment-inline-form');
1271 1294
1272 1295 // if form isn't existing, we're generating a new one and injecting it.
1273 1296 if ($replyForm.length === 0) {
1274 1297
1275 1298 // unhide/expand all comments if they are hidden for a proper REPLY mode
1276 1299 self.toggleLineComments($node, true);
1277 1300
1278 1301 $replyForm = self.createNewFormWrapper(f_path, line_no);
1279 1302
1280 1303 var $comments = $td.find('.inline-comments');
1281 1304
1282 1305 // There aren't any comments, we init the `.inline-comments` with `reply-thread-container` first
1283 1306 if ($comments.length===0) {
1284 1307 var replBtn = '<button class="cb-comment-add-button" onclick="return Rhodecode.comments.createComment(this, \'{0}\', \'{1}\', null)">Reply...</button>'.format(f_path, line_no)
1285 1308 var $reply_container = $('#cb-comments-inline-container-template')
1286 1309 $reply_container.find('button.cb-comment-add-button').replaceWith(replBtn);
1287 1310 $td.append($($reply_container).html());
1288 1311 }
1289 1312
1290 1313 // default comment button exists, so we prepend the form for leaving initial comment
1291 1314 $td.find('.cb-comment-add-button').before($replyForm);
1292 1315 // set marker, that we have a open form
1293 1316 var $replyWrapper = $td.find('.reply-thread-container-wrapper')
1294 1317 $replyWrapper.addClass('comment-form-active');
1295 1318
1296 1319 var lastComment = $comments.find('.comment-inline').last();
1297 1320 if ($(lastComment).hasClass('comment-outdated')) {
1298 1321 $replyWrapper.show();
1299 1322 }
1300 1323
1301 1324 var _form = $($replyForm[0]).find('form');
1302 1325 var autocompleteActions = ['as_note', 'as_todo'];
1303 1326 var comment_id=null;
1304 1327 var placeholderText = _gettext('Leave a comment on file {0} line {1}.').format(f_path, line_no);
1305 1328 var commentForm = self.createCommentForm(
1306 1329 _form, line_no, placeholderText, autocompleteActions, resolvesCommentId,
1307 1330 self.edit, comment_id);
1308 1331
1309 1332 // set a CUSTOM submit handler for inline comments.
1310 1333 commentForm.setHandleFormSubmit(function(o) {
1311 1334 var text = commentForm.cm.getValue();
1312 1335 var commentType = commentForm.getCommentType();
1313 1336 var resolvesCommentId = commentForm.getResolvesId();
1314 1337 var isDraft = commentForm.getDraftState();
1315 1338
1316 1339 if (text === "") {
1317 1340 return;
1318 1341 }
1319 1342
1320 1343 if (line_no === undefined) {
1321 1344 alert('Error: unable to fetch line number for this inline comment !');
1322 1345 return;
1323 1346 }
1324 1347
1325 1348 if (f_path === undefined) {
1326 1349 alert('Error: unable to fetch file path for this inline comment !');
1327 1350 return;
1328 1351 }
1329 1352
1330 1353 var excludeCancelBtn = false;
1331 1354 var submitEvent = true;
1332 1355 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
1333 1356 commentForm.cm.setOption("readOnly", true);
1334 1357 var postData = {
1335 1358 'text': text,
1336 1359 'f_path': f_path,
1337 1360 'line': line_no,
1338 1361 'comment_type': commentType,
1339 1362 'draft': isDraft,
1340 1363 'csrf_token': CSRF_TOKEN
1341 1364 };
1342 1365 if (resolvesCommentId){
1343 1366 postData['resolves_comment_id'] = resolvesCommentId;
1344 1367 }
1345 1368
1346 1369 // submitSuccess for inline commits
1347 1370 var submitSuccessCallback = function(json_data) {
1348 1371
1349 1372 $replyForm.remove();
1350 1373 $td.find('.reply-thread-container-wrapper').removeClass('comment-form-active');
1351 1374
1352 1375 try {
1353 1376
1354 1377 // inject newly created comments, json_data is {<comment_id>: {}}
1355 1378 self.attachInlineComment(json_data)
1356 1379
1357 1380 //mark visually which comment was resolved
1358 1381 if (resolvesCommentId) {
1359 1382 commentForm.markCommentResolved(resolvesCommentId);
1360 1383 }
1361 1384
1362 1385 // run global callback on submit
1363 1386 commentForm.globalSubmitSuccessCallback({
1364 1387 draft: isDraft,
1365 1388 comment_id: comment_id
1366 1389 });
1367 1390
1368 1391 } catch (e) {
1369 1392 console.error(e);
1370 1393 }
1371 1394
1372 1395 if (window.updateSticky !== undefined) {
1373 1396 // potentially our comments change the active window size, so we
1374 1397 // notify sticky elements
1375 1398 updateSticky()
1376 1399 }
1377 1400
1378 1401 if (window.refreshAllComments !== undefined && !isDraft) {
1379 1402 // if we have this handler, run it, and refresh all comments boxes
1380 1403 refreshAllComments()
1381 1404 }
1382 1405
1383 1406 commentForm.setActionButtonsDisabled(false);
1384 1407
1385 1408 // re trigger the linkification of next/prev navigation
1386 1409 linkifyComments($('.inline-comment-injected'));
1387 1410 timeagoActivate();
1388 1411 tooltipActivate();
1389 1412 };
1390 1413
1391 1414 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
1392 1415 var prefix = "Error while submitting comment.\n"
1393 1416 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1394 1417 ajaxErrorSwal(message);
1395 1418 commentForm.resetCommentFormState(text)
1396 1419 };
1397 1420
1398 1421 commentForm.submitAjaxPOST(
1399 1422 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
1400 1423 });
1401 1424 }
1402 1425
1403 1426 // Finally "open" our reply form, since we know there are comments and we have the "attached" old form
1404 1427 $replyForm.addClass('comment-inline-form-open');
1405 1428 tooltipActivate();
1406 1429 };
1407 1430
1408 1431 this.createResolutionComment = function(commentId){
1409 1432 // hide the trigger text
1410 1433 $('#resolve-comment-{0}'.format(commentId)).hide();
1411 1434
1412 1435 var comment = $('#comment-'+commentId);
1413 1436 var commentData = comment.data();
1414 1437 if (commentData.commentInline) {
1415 1438 var f_path = commentData.fPath;
1416 1439 var line_no = commentData.lineNo;
1417 1440 //TODO check this if we need to give f_path/line_no
1418 1441 this.createComment(comment, f_path, line_no, commentId)
1419 1442 } else {
1420 1443 this.createGeneralComment('general', "$placeholder", commentId)
1421 1444 }
1422 1445
1423 1446 return false;
1424 1447 };
1425 1448
1426 1449 this.submitResolution = function(commentId){
1427 1450 var form = $('#resolve_comment_{0}'.format(commentId)).closest('form');
1428 1451 var commentForm = form.get(0).CommentForm;
1429 1452
1430 1453 var cm = commentForm.getCmInstance();
1431 1454 var renderer = templateContext.visual.default_renderer;
1432 1455 if (renderer == 'rst'){
1433 1456 var commentUrl = '`#{0} <{1}#comment-{0}>`_'.format(commentId, commentForm.selfUrl);
1434 1457 } else if (renderer == 'markdown') {
1435 1458 var commentUrl = '[#{0}]({1}#comment-{0})'.format(commentId, commentForm.selfUrl);
1436 1459 } else {
1437 1460 var commentUrl = '{1}#comment-{0}'.format(commentId, commentForm.selfUrl);
1438 1461 }
1439 1462
1440 1463 cm.setValue(_gettext('TODO from comment {0} was fixed.').format(commentUrl));
1441 1464 form.submit();
1442 1465 return false;
1443 1466 };
1444 1467
1445 1468 };
1446 1469
1447 1470 window.commentHelp = function(renderer) {
1448 1471 var funcData = {'renderer': renderer}
1449 1472 return renderTemplate('commentHelpHovercard', funcData)
1450 1473 } No newline at end of file
@@ -1,1191 +1,1195 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(
186 186 _gettext('All reviewers must vote.'))
187 187 );
188 188 return self.forbidUsers
189 189 }
190 190
191 191 if (data.rules.voting !== undefined) {
192 192 if (data.rules.voting < 0) {
193 193 self.$rulesList.append(
194 194 self.addRule(
195 195 _gettext('All individual reviewers must vote.'))
196 196 )
197 197 } else if (data.rules.voting === 1) {
198 198 self.$rulesList.append(
199 199 self.addRule(
200 200 _gettext('At least {0} reviewer must vote.').format(data.rules.voting))
201 201 )
202 202
203 203 } else {
204 204 self.$rulesList.append(
205 205 self.addRule(
206 206 _gettext('At least {0} reviewers must vote.').format(data.rules.voting))
207 207 )
208 208 }
209 209 }
210 210
211 211 if (data.rules.voting_groups !== undefined) {
212 212 $.each(data.rules.voting_groups, function (index, rule_data) {
213 213 self.$rulesList.append(
214 214 self.addRule(rule_data.text)
215 215 )
216 216 });
217 217 }
218 218
219 219 if (data.rules.use_code_authors_for_review) {
220 220 self.$rulesList.append(
221 221 self.addRule(
222 222 _gettext('Reviewers picked from source code changes.'))
223 223 )
224 224 }
225 225
226 226 if (data.rules.forbid_adding_reviewers) {
227 227 $('#add_reviewer_input').remove();
228 228 self.$rulesList.append(
229 229 self.addRule(
230 230 _gettext('Adding new reviewers is forbidden.'))
231 231 )
232 232 }
233 233
234 234 if (data.rules.forbid_author_to_review) {
235 235 self.forbidUsers.push(data.rules_data.pr_author);
236 236 self.$rulesList.append(
237 237 self.addRule(
238 238 _gettext('Author is not allowed to be a reviewer.'))
239 239 )
240 240 }
241 241
242 242 if (data.rules.forbid_commit_author_to_review) {
243 243
244 244 if (data.rules_data.forbidden_users) {
245 245 $.each(data.rules_data.forbidden_users, function (index, member_data) {
246 246 self.forbidUsers.push(member_data)
247 247 });
248 248 }
249 249
250 250 self.$rulesList.append(
251 251 self.addRule(
252 252 _gettext('Commit Authors are not allowed to be a reviewer.'))
253 253 )
254 254 }
255 255
256 256 // we don't have any rules set, so we inform users about it
257 257 if (self.enabledRules.length === 0) {
258 258 self.addRule(
259 259 _gettext('No review rules set.'))
260 260 }
261 261
262 262 return self.forbidUsers
263 263 };
264 264
265 265 this.emptyTables = function () {
266 266 self.emptyReviewersTable();
267 267 self.emptyObserversTable();
268 268
269 269 // Also reset counters.
270 270 self.resetCounter();
271 271 }
272 272
273 273 this.emptyReviewersTable = function (withText) {
274 274 self.$reviewMembers.empty();
275 275 if (withText !== undefined) {
276 276 self.$reviewMembers.html(withText)
277 277 }
278 278 };
279 279
280 280 this.emptyObserversTable = function (withText) {
281 281 self.$observerMembers.empty();
282 282 if (withText !== undefined) {
283 283 self.$observerMembers.html(withText)
284 284 }
285 285 }
286 286
287 287 this.loadDefaultReviewers = function (sourceRepo, sourceRef, targetRepo, targetRef) {
288 288
289 289 if (self.currentRequest) {
290 290 // make sure we cleanup old running requests before triggering this again
291 291 self.currentRequest.abort();
292 292 }
293 293
294 294 self.$loadingIndicator.show();
295 295
296 296 // reset reviewer/observe members
297 297 self.emptyTables();
298 298
299 299 prButtonLock(true, null, 'reviewers');
300 300 $('#user').hide(); // hide user autocomplete before load
301 301 $('#observer').hide(); //hide observer autocomplete before load
302 302
303 303 // lock PR button, so we cannot send PR before it's calculated
304 304 prButtonLock(true, _gettext('Loading diff ...'), 'compare');
305 305
306 306 if (sourceRef.length !== 3 || targetRef.length !== 3) {
307 307 // don't load defaults in case we're missing some refs...
308 308 self.$loadingIndicator.hide();
309 309 return
310 310 }
311 311
312 312 var url = pyroutes.url('repo_default_reviewers_data',
313 313 {
314 314 'repo_name': templateContext.repo_name,
315 315 'source_repo': sourceRepo,
316 316 'source_ref_type': sourceRef[0],
317 317 'source_ref_name': sourceRef[1],
318 318 'source_ref': sourceRef[2],
319 319 'target_repo': targetRepo,
320 320 'target_ref': targetRef[2],
321 321 'target_ref_type': sourceRef[0],
322 322 'target_ref_name': sourceRef[1]
323 323 });
324 324
325 325 self.currentRequest = $.ajax({
326 326 url: url,
327 327 headers: {'X-PARTIAL-XHR': true},
328 328 type: 'GET',
329 329 success: function (data) {
330 330
331 331 self.currentRequest = null;
332 332
333 333 // review rules
334 334 self.loadReviewRules(data);
335 335 var diffHandled = self.handleDiffData(data["diff_info"]);
336 336 if (diffHandled === false) {
337 337 return
338 338 }
339 339
340 340 for (var i = 0; i < data.reviewers.length; i++) {
341 341 var reviewer = data.reviewers[i];
342 342 // load reviewer rules from the repo data
343 343 self.addMember(reviewer, reviewer.reasons, reviewer.mandatory, reviewer.role);
344 344 }
345 345
346 346
347 347 self.$loadingIndicator.hide();
348 348 prButtonLock(false, null, 'reviewers');
349 349
350 350 $('#user').show(); // show user autocomplete before load
351 351 $('#observer').show(); // show observer autocomplete before load
352 352
353 353 var commitElements = data["diff_info"]['commits'];
354 354
355 355 if (commitElements.length === 0) {
356 356 var noCommitsMsg = '<span class="alert-text-warning">{0}</span>'.format(
357 357 _gettext('There are no commits to merge.'));
358 358 prButtonLock(true, noCommitsMsg, 'all');
359 359
360 360 } else {
361 361 // un-lock PR button, so we cannot send PR before it's calculated
362 362 prButtonLock(false, null, 'compare');
363 363 }
364 364
365 365 },
366 366 error: function (jqXHR, textStatus, errorThrown) {
367 367 var prefix = "Loading diff and reviewers/observers failed\n"
368 368 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
369 369 ajaxErrorSwal(message);
370 370 }
371 371 });
372 372
373 373 };
374 374
375 375 // check those, refactor
376 376 this.removeMember = function (reviewer_id, mark_delete) {
377 377 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
378 378
379 379 if (typeof (mark_delete) === undefined) {
380 380 mark_delete = false;
381 381 }
382 382
383 383 if (mark_delete === true) {
384 384 if (reviewer) {
385 385 // now delete the input
386 386 $('#reviewer_{0} input'.format(reviewer_id)).remove();
387 387 $('#reviewer_{0}_rules input'.format(reviewer_id)).remove();
388 388 // mark as to-delete
389 389 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
390 390 obj.addClass('to-delete');
391 391 obj.css({"text-decoration": "line-through", "opacity": 0.5});
392 392 }
393 393 } else {
394 394 $('#reviewer_{0}'.format(reviewer_id)).remove();
395 395 }
396 396 };
397 397
398 398 this.addMember = function (reviewer_obj, reasons, mandatory, role) {
399 399
400 400 var id = reviewer_obj.user_id;
401 401 var username = reviewer_obj.username;
402 402
403 403 reasons = reasons || [];
404 404 mandatory = mandatory || false;
405 405 role = role || self.ROLE_REVIEWER
406 406
407 407 // register current set IDS to check if we don't have this ID already in
408 408 // and prevent duplicates
409 409 var currentIds = [];
410 410
411 411 $.each($('.reviewer_entry'), function (index, value) {
412 412 currentIds.push($(value).data('reviewerUserId'))
413 413 })
414 414
415 415 var userAllowedReview = function (userId) {
416 416 var allowed = true;
417 417 $.each(self.forbidUsers, function (index, member_data) {
418 418 if (parseInt(userId) === member_data['user_id']) {
419 419 allowed = false;
420 420 return false // breaks the loop
421 421 }
422 422 });
423 423 return allowed
424 424 };
425 425
426 426 var userAllowed = userAllowedReview(id);
427 427
428 428 if (!userAllowed) {
429 429 alert(_gettext('User `{0}` not allowed to be a reviewer').format(username));
430 430 } else {
431 431 // only add if it's not there
432 432 var alreadyReviewer = currentIds.indexOf(id) != -1;
433 433
434 434 if (alreadyReviewer) {
435 435 alert(_gettext('User `{0}` already in reviewers/observers').format(username));
436 436 } else {
437 437
438 438 var reviewerEntry = renderTemplate('reviewMemberEntry', {
439 439 'member': reviewer_obj,
440 440 'mandatory': mandatory,
441 441 'role': role,
442 442 'reasons': reasons,
443 443 'allowed_to_update': true,
444 444 'review_status': 'not_reviewed',
445 445 'review_status_label': _gettext('Not Reviewed'),
446 446 'user_group': reviewer_obj.user_group,
447 447 'create': true,
448 448 'rule_show': true,
449 449 })
450 450
451 451 if (role === self.ROLE_REVIEWER) {
452 452 $(self.$reviewMembers.selector).append(reviewerEntry);
453 453 self.increaseCounter(self.ROLE_REVIEWER);
454 454 $('#reviewer-empty-msg').remove()
455 455 }
456 456 else if (role === self.ROLE_OBSERVER) {
457 457 $(self.$observerMembers.selector).append(reviewerEntry);
458 458 self.increaseCounter(self.ROLE_OBSERVER);
459 459 $('#observer-empty-msg').remove();
460 460 }
461 461
462 462 tooltipActivate();
463 463 }
464 464 }
465 465
466 466 };
467 467
468 468 this.updateReviewers = function (repo_name, pull_request_id, role) {
469 469 if (role === 'reviewer') {
470 470 var postData = $('#reviewers input').serialize();
471 471 _updatePullRequest(repo_name, pull_request_id, postData);
472 472 } else if (role === 'observer') {
473 473 var postData = $('#observers input').serialize();
474 474 _updatePullRequest(repo_name, pull_request_id, postData);
475 475 }
476 476 };
477 477
478 478 this.handleDiffData = function (data) {
479 479 return self.diffDataHandler(data)
480 480 }
481 481 };
482 482
483 483
484 484 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
485 485 var url = pyroutes.url(
486 486 'pullrequest_update',
487 487 {"repo_name": repo_name, "pull_request_id": pull_request_id});
488 488 if (typeof postData === 'string' ) {
489 489 postData += '&csrf_token=' + CSRF_TOKEN;
490 490 } else {
491 491 postData.csrf_token = CSRF_TOKEN;
492 492 }
493 493
494 494 var success = function(o) {
495 495 var redirectUrl = o['redirect_url'];
496 496 if (redirectUrl !== undefined && redirectUrl !== null && redirectUrl !== '') {
497 497 window.location = redirectUrl;
498 498 } else {
499 499 window.location.reload();
500 500 }
501 501 };
502 502
503 503 ajaxPOST(url, postData, success);
504 504 };
505 505
506 506 /**
507 507 * PULL REQUEST update commits
508 508 */
509 509 var updateCommits = function(repo_name, pull_request_id, force) {
510 510 var postData = {
511 511 'update_commits': true
512 512 };
513 513 if (force !== undefined && force === true) {
514 514 postData['force_refresh'] = true
515 515 }
516 516 _updatePullRequest(repo_name, pull_request_id, postData);
517 517 };
518 518
519 519
520 520 /**
521 521 * PULL REQUEST edit info
522 522 */
523 523 var editPullRequest = function(repo_name, pull_request_id, title, description, renderer) {
524 524 var url = pyroutes.url(
525 525 'pullrequest_update',
526 526 {"repo_name": repo_name, "pull_request_id": pull_request_id});
527 527
528 528 var postData = {
529 529 'title': title,
530 530 'description': description,
531 531 'description_renderer': renderer,
532 532 'edit_pull_request': true,
533 533 'csrf_token': CSRF_TOKEN
534 534 };
535 535 var success = function(o) {
536 536 window.location.reload();
537 537 };
538 538 ajaxPOST(url, postData, success);
539 539 };
540 540
541 541
542 542 /**
543 543 * autocomplete handler for reviewers/observers
544 544 */
545 545 var autoCompleteHandler = function (inputId, controller, role) {
546 546
547 547 return function (element, data) {
548 548 var mandatory = false;
549 549 var reasons = [_gettext('added manually by "{0}"').format(
550 550 templateContext.rhodecode_user.username)];
551 551
552 552 // add whole user groups
553 553 if (data.value_type == 'user_group') {
554 554 reasons.push(_gettext('member of "{0}"').format(data.value_display));
555 555
556 556 $.each(data.members, function (index, member_data) {
557 557 var reviewer = member_data;
558 558 reviewer['user_id'] = member_data['id'];
559 559 reviewer['gravatar_link'] = member_data['icon_link'];
560 560 reviewer['user_link'] = member_data['profile_link'];
561 561 reviewer['rules'] = [];
562 562 controller.addMember(reviewer, reasons, mandatory, role);
563 563 })
564 564 }
565 565 // add single user
566 566 else {
567 567 var reviewer = data;
568 568 reviewer['user_id'] = data['id'];
569 569 reviewer['gravatar_link'] = data['icon_link'];
570 570 reviewer['user_link'] = data['profile_link'];
571 571 reviewer['rules'] = [];
572 572 controller.addMember(reviewer, reasons, mandatory, role);
573 573 }
574 574
575 575 $(inputId).val('');
576 576 }
577 577 }
578 578
579 579 /**
580 580 * Reviewer autocomplete
581 581 */
582 582 var ReviewerAutoComplete = function (inputId, controller) {
583 583 var self = this;
584 584 self.controller = controller;
585 585 self.inputId = inputId;
586 586 var handler = autoCompleteHandler(inputId, controller, controller.ROLE_REVIEWER);
587 587
588 588 $(inputId).autocomplete({
589 589 serviceUrl: pyroutes.url('user_autocomplete_data'),
590 590 minChars: 2,
591 591 maxHeight: 400,
592 592 deferRequestBy: 300, //miliseconds
593 593 showNoSuggestionNotice: true,
594 594 tabDisabled: true,
595 595 autoSelectFirst: true,
596 596 params: {
597 597 user_id: templateContext.rhodecode_user.user_id,
598 598 user_groups: true,
599 599 user_groups_expand: true,
600 600 skip_default_user: true
601 601 },
602 602 formatResult: autocompleteFormatResult,
603 603 lookupFilter: autocompleteFilterResult,
604 604 onSelect: handler
605 605 });
606 606 };
607 607
608 608 /**
609 609 * Observers autocomplete
610 610 */
611 611 var ObserverAutoComplete = function(inputId, controller) {
612 612 var self = this;
613 613 self.controller = controller;
614 614 self.inputId = inputId;
615 615 var handler = autoCompleteHandler(inputId, controller, controller.ROLE_OBSERVER);
616 616
617 617 $(inputId).autocomplete({
618 618 serviceUrl: pyroutes.url('user_autocomplete_data'),
619 619 minChars: 2,
620 620 maxHeight: 400,
621 621 deferRequestBy: 300, //miliseconds
622 622 showNoSuggestionNotice: true,
623 623 tabDisabled: true,
624 624 autoSelectFirst: true,
625 625 params: {
626 626 user_id: templateContext.rhodecode_user.user_id,
627 627 user_groups: true,
628 628 user_groups_expand: true,
629 629 skip_default_user: true
630 630 },
631 631 formatResult: autocompleteFormatResult,
632 632 lookupFilter: autocompleteFilterResult,
633 633 onSelect: handler
634 634 });
635 635 }
636 636
637 637
638 638 window.VersionController = function () {
639 639 var self = this;
640 640 this.$verSource = $('input[name=ver_source]');
641 641 this.$verTarget = $('input[name=ver_target]');
642 642 this.$showVersionDiff = $('#show-version-diff');
643 643
644 644 this.adjustRadioSelectors = function (curNode) {
645 645 var getVal = function (item) {
646 646 if (item === 'latest') {
647 647 return Number.MAX_SAFE_INTEGER
648 648 }
649 649 else {
650 650 return parseInt(item)
651 651 }
652 652 };
653 653
654 654 var curVal = getVal($(curNode).val());
655 655 var cleared = false;
656 656
657 657 $.each(self.$verSource, function (index, value) {
658 658 var elVal = getVal($(value).val());
659 659
660 660 if (elVal > curVal) {
661 661 if ($(value).is(':checked')) {
662 662 cleared = true;
663 663 }
664 664 $(value).attr('disabled', 'disabled');
665 665 $(value).removeAttr('checked');
666 666 $(value).css({'opacity': 0.1});
667 667 }
668 668 else {
669 669 $(value).css({'opacity': 1});
670 670 $(value).removeAttr('disabled');
671 671 }
672 672 });
673 673
674 674 if (cleared) {
675 675 // if we unchecked an active, set the next one to same loc.
676 676 $(this.$verSource).filter('[value={0}]'.format(
677 677 curVal)).attr('checked', 'checked');
678 678 }
679 679
680 680 self.setLockAction(false,
681 681 $(curNode).data('verPos'),
682 682 $(this.$verSource).filter(':checked').data('verPos')
683 683 );
684 684 };
685 685
686 686
687 687 this.attachVersionListener = function () {
688 688 self.$verTarget.change(function (e) {
689 689 self.adjustRadioSelectors(this)
690 690 });
691 691 self.$verSource.change(function (e) {
692 692 self.adjustRadioSelectors(self.$verTarget.filter(':checked'))
693 693 });
694 694 };
695 695
696 696 this.init = function () {
697 697
698 698 var curNode = self.$verTarget.filter(':checked');
699 699 self.adjustRadioSelectors(curNode);
700 700 self.setLockAction(true);
701 701 self.attachVersionListener();
702 702
703 703 };
704 704
705 705 this.setLockAction = function (state, selectedVersion, otherVersion) {
706 706 var $showVersionDiff = this.$showVersionDiff;
707 707
708 708 if (state) {
709 709 $showVersionDiff.attr('disabled', 'disabled');
710 710 $showVersionDiff.addClass('disabled');
711 711 $showVersionDiff.html($showVersionDiff.data('labelTextLocked'));
712 712 }
713 713 else {
714 714 $showVersionDiff.removeAttr('disabled');
715 715 $showVersionDiff.removeClass('disabled');
716 716
717 717 if (selectedVersion == otherVersion) {
718 718 $showVersionDiff.html($showVersionDiff.data('labelTextShow'));
719 719 } else {
720 720 $showVersionDiff.html($showVersionDiff.data('labelTextDiff'));
721 721 }
722 722 }
723 723
724 724 };
725 725
726 726 this.showVersionDiff = function () {
727 727 var target = self.$verTarget.filter(':checked');
728 728 var source = self.$verSource.filter(':checked');
729 729
730 730 if (target.val() && source.val()) {
731 731 var params = {
732 732 'pull_request_id': templateContext.pull_request_data.pull_request_id,
733 733 'repo_name': templateContext.repo_name,
734 734 'version': target.val(),
735 735 'from_version': source.val()
736 736 };
737 737 window.location = pyroutes.url('pullrequest_show', params)
738 738 }
739 739
740 740 return false;
741 741 };
742 742
743 743 this.toggleVersionView = function (elem) {
744 744
745 745 if (this.$showVersionDiff.is(':visible')) {
746 746 $('.version-pr').hide();
747 747 this.$showVersionDiff.hide();
748 748 $(elem).html($(elem).data('toggleOn'))
749 749 } else {
750 750 $('.version-pr').show();
751 751 this.$showVersionDiff.show();
752 752 $(elem).html($(elem).data('toggleOff'))
753 753 }
754 754
755 755 return false
756 756 };
757 757
758 758 };
759 759
760 760
761 761 window.UpdatePrController = function () {
762 762 var self = this;
763 763 this.$updateCommits = $('#update_commits');
764 764 this.$updateCommitsSwitcher = $('#update_commits_switcher');
765 765
766 766 this.lockUpdateButton = function (label) {
767 767 self.$updateCommits.attr('disabled', 'disabled');
768 768 self.$updateCommitsSwitcher.attr('disabled', 'disabled');
769 769
770 770 self.$updateCommits.addClass('disabled');
771 771 self.$updateCommitsSwitcher.addClass('disabled');
772 772
773 773 self.$updateCommits.removeClass('btn-primary');
774 774 self.$updateCommitsSwitcher.removeClass('btn-primary');
775 775
776 776 self.$updateCommits.text(_gettext(label));
777 777 };
778 778
779 779 this.isUpdateLocked = function () {
780 780 return self.$updateCommits.attr('disabled') !== undefined;
781 781 };
782 782
783 783 this.updateCommits = function (curNode) {
784 784 if (self.isUpdateLocked()) {
785 785 return
786 786 }
787 787 self.lockUpdateButton(_gettext('Updating...'));
788 788 updateCommits(
789 789 templateContext.repo_name,
790 790 templateContext.pull_request_data.pull_request_id);
791 791 };
792 792
793 793 this.forceUpdateCommits = function () {
794 794 if (self.isUpdateLocked()) {
795 795 return
796 796 }
797 797 self.lockUpdateButton(_gettext('Force updating...'));
798 798 var force = true;
799 799 updateCommits(
800 800 templateContext.repo_name,
801 801 templateContext.pull_request_data.pull_request_id, force);
802 802 };
803 803 };
804 804
805 805
806 806 /**
807 807 * Reviewer display panel
808 808 */
809 809 window.ReviewersPanel = {
810 810 editButton: null,
811 811 closeButton: null,
812 812 addButton: null,
813 813 removeButtons: null,
814 814 reviewRules: null,
815 815 setReviewers: null,
816 816 controller: null,
817 817
818 818 setSelectors: function () {
819 819 var self = this;
820 820 self.editButton = $('#open_edit_reviewers');
821 821 self.closeButton =$('#close_edit_reviewers');
822 822 self.addButton = $('#add_reviewer');
823 823 self.removeButtons = $('.reviewer_member_remove,.reviewer_member_mandatory_remove');
824 824 },
825 825
826 826 init: function (controller, reviewRules, setReviewers) {
827 827 var self = this;
828 828 self.setSelectors();
829 829
830 830 self.controller = controller;
831 831 self.reviewRules = reviewRules;
832 832 self.setReviewers = setReviewers;
833 833
834 834 self.editButton.on('click', function (e) {
835 835 self.edit();
836 836 });
837 837 self.closeButton.on('click', function (e) {
838 838 self.close();
839 839 self.renderReviewers();
840 840 });
841 841
842 842 self.renderReviewers();
843 843
844 844 },
845 845
846 846 renderReviewers: function () {
847 847 var self = this;
848 848
849 849 if (self.setReviewers.reviewers === undefined) {
850 850 return
851 851 }
852 852 if (self.setReviewers.reviewers.length === 0) {
853 853 self.controller.emptyReviewersTable('<tr id="reviewer-empty-msg"><td colspan="6">No reviewers</td></tr>');
854 854 return
855 855 }
856 856
857 857 self.controller.emptyReviewersTable();
858 858
859 859 $.each(self.setReviewers.reviewers, function (key, val) {
860 860
861 861 var member = val;
862 862 if (member.role === self.controller.ROLE_REVIEWER) {
863 863 var entry = renderTemplate('reviewMemberEntry', {
864 864 'member': member,
865 865 'mandatory': member.mandatory,
866 866 'role': member.role,
867 867 'reasons': member.reasons,
868 868 'allowed_to_update': member.allowed_to_update,
869 869 'review_status': member.review_status,
870 870 'review_status_label': member.review_status_label,
871 871 'user_group': member.user_group,
872 872 'create': false
873 873 });
874 874
875 875 $(self.controller.$reviewMembers.selector).append(entry)
876 876 }
877 877 });
878 878
879 879 tooltipActivate();
880 880 },
881 881
882 882 edit: function (event) {
883 883 var self = this;
884 884 self.editButton.hide();
885 885 self.closeButton.show();
886 886 self.addButton.show();
887 887 $(self.removeButtons.selector).css('visibility', 'visible');
888 888 // review rules
889 889 self.controller.loadReviewRules(this.reviewRules);
890 890 },
891 891
892 892 close: function (event) {
893 893 var self = this;
894 894 this.editButton.show();
895 895 this.closeButton.hide();
896 896 this.addButton.hide();
897 897 $(this.removeButtons.selector).css('visibility', 'hidden');
898 898 // hide review rules
899 899 self.controller.hideReviewRules();
900 900 }
901 901 };
902 902
903 903 /**
904 904 * Reviewer display panel
905 905 */
906 906 window.ObserversPanel = {
907 907 editButton: null,
908 908 closeButton: null,
909 909 addButton: null,
910 910 removeButtons: null,
911 911 reviewRules: null,
912 912 setReviewers: null,
913 913 controller: null,
914 914
915 915 setSelectors: function () {
916 916 var self = this;
917 917 self.editButton = $('#open_edit_observers');
918 918 self.closeButton =$('#close_edit_observers');
919 919 self.addButton = $('#add_observer');
920 920 self.removeButtons = $('.observer_member_remove,.observer_member_mandatory_remove');
921 921 },
922 922
923 923 init: function (controller, reviewRules, setReviewers) {
924 924 var self = this;
925 925 self.setSelectors();
926 926
927 927 self.controller = controller;
928 928 self.reviewRules = reviewRules;
929 929 self.setReviewers = setReviewers;
930 930
931 931 self.editButton.on('click', function (e) {
932 932 self.edit();
933 933 });
934 934 self.closeButton.on('click', function (e) {
935 935 self.close();
936 936 self.renderObservers();
937 937 });
938 938
939 939 self.renderObservers();
940 940
941 941 },
942 942
943 943 renderObservers: function () {
944 944 var self = this;
945 945 if (self.setReviewers.observers === undefined) {
946 946 return
947 947 }
948 948 if (self.setReviewers.observers.length === 0) {
949 949 self.controller.emptyObserversTable('<tr id="observer-empty-msg"><td colspan="6">No observers</td></tr>');
950 950 return
951 951 }
952 952
953 953 self.controller.emptyObserversTable();
954 954
955 955 $.each(self.setReviewers.observers, function (key, val) {
956 956 var member = val;
957 957 if (member.role === self.controller.ROLE_OBSERVER) {
958 958 var entry = renderTemplate('reviewMemberEntry', {
959 959 'member': member,
960 960 'mandatory': member.mandatory,
961 961 'role': member.role,
962 962 'reasons': member.reasons,
963 963 'allowed_to_update': member.allowed_to_update,
964 964 'review_status': member.review_status,
965 965 'review_status_label': member.review_status_label,
966 966 'user_group': member.user_group,
967 967 'create': false
968 968 });
969 969
970 970 $(self.controller.$observerMembers.selector).append(entry)
971 971 }
972 972 });
973 973
974 974 tooltipActivate();
975 975 },
976 976
977 977 edit: function (event) {
978 978 this.editButton.hide();
979 979 this.closeButton.show();
980 980 this.addButton.show();
981 981 $(this.removeButtons.selector).css('visibility', 'visible');
982 982 },
983 983
984 984 close: function (event) {
985 985 this.editButton.show();
986 986 this.closeButton.hide();
987 987 this.addButton.hide();
988 988 $(this.removeButtons.selector).css('visibility', 'hidden');
989 989 }
990 990
991 991 };
992 992
993 993 window.PRDetails = {
994 994 editButton: null,
995 995 closeButton: null,
996 996 deleteButton: null,
997 997 viewFields: null,
998 998 editFields: null,
999 999
1000 1000 setSelectors: function () {
1001 1001 var self = this;
1002 1002 self.editButton = $('#open_edit_pullrequest')
1003 1003 self.closeButton = $('#close_edit_pullrequest')
1004 1004 self.deleteButton = $('#delete_pullrequest')
1005 1005 self.viewFields = $('#pr-desc, #pr-title')
1006 1006 self.editFields = $('#pr-desc-edit, #pr-title-edit, .pr-save')
1007 1007 },
1008 1008
1009 1009 init: function () {
1010 1010 var self = this;
1011 1011 self.setSelectors();
1012 1012 self.editButton.on('click', function (e) {
1013 1013 self.edit();
1014 1014 });
1015 1015 self.closeButton.on('click', function (e) {
1016 1016 self.view();
1017 1017 });
1018 1018 },
1019 1019
1020 1020 edit: function (event) {
1021 1021 var cmInstance = $('#pr-description-input').get(0).MarkupForm.cm;
1022 1022 this.viewFields.hide();
1023 1023 this.editButton.hide();
1024 1024 this.deleteButton.hide();
1025 1025 this.closeButton.show();
1026 1026 this.editFields.show();
1027 1027 cmInstance.refresh();
1028 1028 },
1029 1029
1030 1030 view: function (event) {
1031 1031 this.editButton.show();
1032 1032 this.deleteButton.show();
1033 1033 this.editFields.hide();
1034 1034 this.closeButton.hide();
1035 1035 this.viewFields.show();
1036 1036 }
1037 1037 };
1038 1038
1039 1039 /**
1040 1040 * OnLine presence using channelstream
1041 1041 */
1042 1042 window.ReviewerPresenceController = function (channel) {
1043 1043 var self = this;
1044 1044 this.channel = channel;
1045 1045 this.users = {};
1046 1046
1047 1047 this.storeUsers = function (users) {
1048 1048 self.users = {}
1049 1049 $.each(users, function (index, value) {
1050 1050 var userId = value.state.id;
1051 1051 self.users[userId] = value.state;
1052 1052 })
1053 1053 }
1054 1054
1055 1055 this.render = function () {
1056 1056 $.each($('.reviewer_entry'), function (index, value) {
1057 1057 var userData = $(value).data();
1058 1058 if (self.users[userData.reviewerUserId] !== undefined) {
1059 1059 $(value).find('.presence-state').show();
1060 1060 } else {
1061 1061 $(value).find('.presence-state').hide();
1062 1062 }
1063 1063 })
1064 1064 };
1065 1065
1066 1066 this.handlePresence = function (data) {
1067 1067 if (data.type == 'presence' && data.channel === self.channel) {
1068 1068 this.storeUsers(data.users);
1069 this.render()
1069 this.render();
1070 1070 }
1071 1071 };
1072 1072
1073 1073 this.handleChannelUpdate = function (data) {
1074 1074 if (data.channel === this.channel) {
1075 1075 this.storeUsers(data.state.users);
1076 this.render()
1076 this.render();
1077 1077 }
1078 1078
1079 1079 };
1080 1080
1081 1081 /* subscribe to the current presence */
1082 1082 $.Topic('/connection_controller/presence').subscribe(this.handlePresence.bind(this));
1083 1083 /* subscribe to updates e.g connect/disconnect */
1084 1084 $.Topic('/connection_controller/channel_update').subscribe(this.handleChannelUpdate.bind(this));
1085 1085
1086 1086 };
1087 1087
1088 1088 window.refreshComments = function (version) {
1089 1089 version = version || templateContext.pull_request_data.pull_request_version || '';
1090 1090
1091 1091 // Pull request case
1092 1092 if (templateContext.pull_request_data.pull_request_id !== null) {
1093 1093 var params = {
1094 1094 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1095 1095 'repo_name': templateContext.repo_name,
1096 1096 'version': version,
1097 1097 };
1098 1098 var loadUrl = pyroutes.url('pullrequest_comments', params);
1099 1099 } // commit case
1100 1100 else {
1101 1101 return
1102 1102 }
1103 1103
1104 1104 var currentIDs = []
1105 1105 $.each($('.comment'), function (idx, element) {
1106 1106 currentIDs.push($(element).data('commentId'));
1107 1107 });
1108 1108 var data = {"comments": currentIDs};
1109 1109
1110 1110 var $targetElem = $('.comments-content-table');
1111 1111 $targetElem.css('opacity', 0.3);
1112 1112
1113 1113 var success = function (data) {
1114 1114 var $counterElem = $('#comments-count');
1115 1115 var newCount = $(data).data('counter');
1116 1116 if (newCount !== undefined) {
1117 1117 var callback = function () {
1118 1118 $counterElem.animate({'opacity': 1.00}, 200)
1119 1119 $counterElem.html(newCount);
1120 1120 };
1121 1121 $counterElem.animate({'opacity': 0.15}, 200, callback);
1122 1122 }
1123 1123
1124 1124 $targetElem.css('opacity', 1);
1125 1125 $targetElem.html(data);
1126 1126 tooltipActivate();
1127 1127 }
1128 1128
1129 1129 ajaxPOST(loadUrl, data, success, null, {})
1130 1130
1131 1131 }
1132 1132
1133 1133 window.refreshTODOs = function (version) {
1134 1134 version = version || templateContext.pull_request_data.pull_request_version || '';
1135 1135 // Pull request case
1136 1136 if (templateContext.pull_request_data.pull_request_id !== null) {
1137 1137 var params = {
1138 1138 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1139 1139 'repo_name': templateContext.repo_name,
1140 1140 'version': version,
1141 1141 };
1142 1142 var loadUrl = pyroutes.url('pullrequest_todos', params);
1143 1143 } // commit case
1144 1144 else {
1145 1145 return
1146 1146 }
1147 1147
1148 1148 var currentIDs = []
1149 1149 $.each($('.comment'), function (idx, element) {
1150 1150 currentIDs.push($(element).data('commentId'));
1151 1151 });
1152 1152
1153 1153 var data = {"comments": currentIDs};
1154 1154 var $targetElem = $('.todos-content-table');
1155 1155 $targetElem.css('opacity', 0.3);
1156 1156
1157 1157 var success = function (data) {
1158 1158 var $counterElem = $('#todos-count')
1159 1159 var newCount = $(data).data('counter');
1160 1160 if (newCount !== undefined) {
1161 1161 var callback = function () {
1162 1162 $counterElem.animate({'opacity': 1.00}, 200)
1163 1163 $counterElem.html(newCount);
1164 1164 };
1165 1165 $counterElem.animate({'opacity': 0.15}, 200, callback);
1166 1166 }
1167 1167
1168 1168 $targetElem.css('opacity', 1);
1169 1169 $targetElem.html(data);
1170 1170 tooltipActivate();
1171 1171 }
1172 1172
1173 1173 ajaxPOST(loadUrl, data, success, null, {})
1174 1174
1175 1175 }
1176 1176
1177 1177 window.refreshAllComments = function (version) {
1178 1178 version = version || templateContext.pull_request_data.pull_request_version || '';
1179 1179
1180 1180 refreshComments(version);
1181 1181 refreshTODOs(version);
1182 1182 };
1183 1183
1184 window.refreshDraftComments = function () {
1185 alert('TODO: refresh Draft Comments needs implementation')
1186 };
1187
1184 1188 window.sidebarComment = function (commentId) {
1185 1189 var jsonData = $('#commentHovercard{0}'.format(commentId)).data('commentJsonB64');
1186 1190 if (!jsonData) {
1187 1191 return 'Failed to load comment {0}'.format(commentId)
1188 1192 }
1189 1193 var funcData = JSON.parse(atob(jsonData));
1190 1194 return renderTemplate('sideBarCommentHovercard', funcData)
1191 1195 };
@@ -1,560 +1,558 b''
1 1 ## -*- coding: utf-8 -*-
2 2 ## usage:
3 3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
4 4 ## ${comment.comment_block(comment)}
5 5 ##
6 6 <%namespace name="base" file="/base/base.mako"/>
7 7
8 8 <%!
9 9 from rhodecode.lib import html_filters
10 10 %>
11 11
12 12
13 13 <%def name="comment_block(comment, inline=False, active_pattern_entries=None, is_new=False)">
14 14
15 15 <%
16 16 from rhodecode.model.comment import CommentsModel
17 17 comment_model = CommentsModel()
18 18
19 19 comment_ver = comment.get_index_version(getattr(c, 'versions', []))
20 20 latest_ver = len(getattr(c, 'versions', []))
21 21 visible_for_user = True
22 22 if comment.draft:
23 23 visible_for_user = comment.user_id == c.rhodecode_user.user_id
24 24 %>
25 25
26 26 % if inline:
27 27 <% outdated_at_ver = comment.outdated_at_version(c.at_version_num) %>
28 28 % else:
29 29 <% outdated_at_ver = comment.older_than_version(c.at_version_num) %>
30 30 % endif
31 31
32 32 % if visible_for_user:
33 33 <div class="comment
34 34 ${'comment-inline' if inline else 'comment-general'}
35 35 ${'comment-outdated' if outdated_at_ver else 'comment-current'}"
36 36 id="comment-${comment.comment_id}"
37 37 line="${comment.line_no}"
38 38 data-comment-id="${comment.comment_id}"
39 39 data-comment-type="${comment.comment_type}"
40 40 data-comment-draft=${h.json.dumps(comment.draft)}
41 41 data-comment-renderer="${comment.renderer}"
42 42 data-comment-text="${comment.text | html_filters.base64,n}"
43 43 data-comment-f-path="${comment.f_path}"
44 44 data-comment-line-no="${comment.line_no}"
45 45 data-comment-inline=${h.json.dumps(inline)}
46 46 style="${'display: none;' if outdated_at_ver else ''}">
47 47
48 48 <div class="meta">
49 49 <div class="comment-type-label">
50 50 % if comment.draft:
51 51 <div class="tooltip comment-draft" title="${_('Draft comments are only visible to the author until submitted')}.">
52 52 DRAFT
53 53 </div>
54 % elif is_new:
55 <div class="tooltip comment-new" title="${_('This comment was added while you browsed this page')}.">
56 NEW
57 </div>
54 58 % endif
55 59
56 % if is_new:
57 <div class="tooltip comment-new" title="${_('This comment was added while you browsed this page')}.">
58 NEW
59 </div>
60 % endif
61
62 60 <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}">
63 61
64 62 ## TODO COMMENT
65 63 % if comment.comment_type == 'todo':
66 64 % if comment.resolved:
67 65 <div class="resolved tooltip" title="${_('Resolved by comment #{}').format(comment.resolved.comment_id)}">
68 66 <i class="icon-flag-filled"></i>
69 67 <a href="#comment-${comment.resolved.comment_id}">${comment.comment_type}</a>
70 68 </div>
71 69 % else:
72 70 <div class="resolved tooltip" style="display: none">
73 71 <span>${comment.comment_type}</span>
74 72 </div>
75 73 <div class="resolve tooltip" onclick="return Rhodecode.comments.createResolutionComment(${comment.comment_id});" title="${_('Click to create resolution comment.')}">
76 74 <i class="icon-flag-filled"></i>
77 75 ${comment.comment_type}
78 76 </div>
79 77 % endif
80 78 ## NOTE COMMENT
81 79 % else:
82 80 ## RESOLVED NOTE
83 81 % if comment.resolved_comment:
84 82 <div class="tooltip" title="${_('This comment resolves TODO #{}').format(comment.resolved_comment.comment_id)}">
85 83 fix
86 84 <a href="#comment-${comment.resolved_comment.comment_id}" onclick="Rhodecode.comments.scrollToComment($('#comment-${comment.resolved_comment.comment_id}'), 0, ${h.json.dumps(comment.resolved_comment.outdated)})">
87 85 <span style="text-decoration: line-through">#${comment.resolved_comment.comment_id}</span>
88 86 </a>
89 87 </div>
90 88 ## STATUS CHANGE NOTE
91 89 % elif not comment.is_inline and comment.status_change:
92 90 <%
93 91 if comment.pull_request:
94 92 status_change_title = 'Status of review for pull request !{}'.format(comment.pull_request.pull_request_id)
95 93 else:
96 94 status_change_title = 'Status of review for commit {}'.format(h.short_id(comment.commit_id))
97 95 %>
98 96
99 97 <i class="icon-circle review-status-${comment.review_status}"></i>
100 98 <div class="changeset-status-lbl tooltip" title="${status_change_title}">
101 99 ${comment.review_status_lbl}
102 100 </div>
103 101 % else:
104 102 <div>
105 103 <i class="icon-comment"></i>
106 104 ${(comment.comment_type or 'note')}
107 105 </div>
108 106 % endif
109 107 % endif
110 108
111 109 </div>
112 110 </div>
113 111 ## NOTE 0 and .. => because we disable it for now until UI ready
114 112 % if 0 and comment.status_change:
115 113 <div class="pull-left">
116 114 <span class="tag authortag tooltip" title="${_('Status from pull request.')}">
117 115 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}">
118 116 ${'!{}'.format(comment.pull_request.pull_request_id)}
119 117 </a>
120 118 </span>
121 119 </div>
122 120 % endif
123 121 ## Since only author can see drafts, we don't show it
124 122 % if not comment.draft:
125 123 <div class="author ${'author-inline' if inline else 'author-general'}">
126 124 ${base.gravatar_with_user(comment.author.email, 16, tooltip=True)}
127 125 </div>
128 126 % endif
129 127
130 128 <div class="date">
131 129 ${h.age_component(comment.modified_at, time_is_local=True)}
132 130 </div>
133 131
134 132 % if comment.pull_request and comment.pull_request.author.user_id == comment.author.user_id:
135 133 <span class="tag authortag tooltip" title="${_('Pull request author')}">
136 134 ${_('author')}
137 135 </span>
138 136 % endif
139 137
140 138 <%
141 139 comment_version_selector = 'comment_versions_{}'.format(comment.comment_id)
142 140 %>
143 141
144 142 % if comment.history:
145 143 <div class="date">
146 144
147 145 <input id="${comment_version_selector}" name="${comment_version_selector}"
148 146 type="hidden"
149 147 data-last-version="${comment.history[-1].version}">
150 148
151 149 <script type="text/javascript">
152 150
153 151 var preLoadVersionData = [
154 152 % for comment_history in comment.history:
155 153 {
156 154 id: ${comment_history.comment_history_id},
157 155 text: 'v${comment_history.version}',
158 156 action: function () {
159 157 Rhodecode.comments.showVersion(
160 158 "${comment.comment_id}",
161 159 "${comment_history.comment_history_id}"
162 160 )
163 161 },
164 162 comment_version: "${comment_history.version}",
165 163 comment_author_username: "${comment_history.author.username}",
166 164 comment_author_gravatar: "${h.gravatar_url(comment_history.author.email, 16)}",
167 165 comment_created_on: '${h.age_component(comment_history.created_on, time_is_local=True)}',
168 166 },
169 167 % endfor
170 168 ]
171 169 initVersionSelector("#${comment_version_selector}", {results: preLoadVersionData});
172 170
173 171 </script>
174 172
175 173 </div>
176 174 % else:
177 175 <div class="date" style="display: none">
178 176 <input id="${comment_version_selector}" name="${comment_version_selector}"
179 177 type="hidden"
180 178 data-last-version="0">
181 179 </div>
182 180 %endif
183 181
184 182 <div class="comment-links-block">
185 183
186 184 % if inline:
187 185 <a class="pr-version-inline" href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
188 186 % if outdated_at_ver:
189 187 <strong class="comment-outdated-label">outdated</strong> <code class="tooltip pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">${'v{}'.format(comment_ver)}</code>
190 188 <code class="action-divider">|</code>
191 189 % elif comment_ver:
192 190 <code class="tooltip pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">${'v{}'.format(comment_ver)}</code>
193 191 <code class="action-divider">|</code>
194 192 % endif
195 193 </a>
196 194 % else:
197 195 % if comment_ver:
198 196
199 197 % if comment.outdated:
200 198 <a class="pr-version"
201 199 href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}"
202 200 >
203 201 ${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}
204 202 </a>
205 203 <code class="action-divider">|</code>
206 204 % else:
207 205 <a class="tooltip pr-version"
208 206 title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}"
209 207 href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id, version=comment.pull_request_version_id)}"
210 208 >
211 209 <code class="pr-version-num">${'v{}'.format(comment_ver)}</code>
212 210 </a>
213 211 <code class="action-divider">|</code>
214 212 % endif
215 213
216 214 % endif
217 215 % endif
218 216
219 217 <details class="details-reset details-inline-block">
220 218 <summary class="noselect"><i class="icon-options cursor-pointer"></i></summary>
221 219 <details-menu class="details-dropdown">
222 220
223 221 <div class="dropdown-item">
224 222 ${_('Comment')} #${comment.comment_id}
225 223 <span class="pull-right icon-clipboard clipboard-action" data-clipboard-text="${comment_model.get_url(comment,request, permalink=True, anchor='comment-{}'.format(comment.comment_id))}" title="${_('Copy permalink')}"></span>
226 224 </div>
227 225
228 226 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
229 227 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
230 228 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
231 229 ## permissions to delete
232 230 %if comment.immutable is False and (c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id):
233 231 <div class="dropdown-divider"></div>
234 232 <div class="dropdown-item">
235 233 <a onclick="return Rhodecode.comments.editComment(this, '${comment.line_no}', '${comment.f_path}');" class="btn btn-link btn-sm edit-comment">${_('Edit')}</a>
236 234 </div>
237 235 <div class="dropdown-item">
238 236 <a onclick="return Rhodecode.comments.deleteComment(this);" class="btn btn-link btn-sm btn-danger delete-comment">${_('Delete')}</a>
239 237 </div>
240 238 ## Only available in EE edition
241 239 % if comment.draft and c.rhodecode_edition_id == 'EE':
242 240 <div class="dropdown-item">
243 241 <a onclick="return Rhodecode.comments.finalizeDrafts([${comment.comment_id}]);" class="btn btn-link btn-sm finalize-draft-comment">${_('Submit draft')}</a>
244 242 </div>
245 243 % endif
246 244 %else:
247 245 <div class="dropdown-divider"></div>
248 246 <div class="dropdown-item">
249 247 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Edit')}</a>
250 248 </div>
251 249 <div class="dropdown-item">
252 250 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a>
253 251 </div>
254 252 %endif
255 253 %else:
256 254 <div class="dropdown-divider"></div>
257 255 <div class="dropdown-item">
258 256 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Edit')}</a>
259 257 </div>
260 258 <div class="dropdown-item">
261 259 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a>
262 260 </div>
263 261 %endif
264 262 </details-menu>
265 263 </details>
266 264
267 265 <code class="action-divider">|</code>
268 266 % if outdated_at_ver:
269 267 <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous outdated comment')}"> <i class="icon-angle-left"></i> </a>
270 268 <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="tooltip next-comment" title="${_('Jump to the next outdated comment')}"> <i class="icon-angle-right"></i></a>
271 269 % else:
272 270 <a onclick="return Rhodecode.comments.prevComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous comment')}"> <i class="icon-angle-left"></i></a>
273 271 <a onclick="return Rhodecode.comments.nextComment(this);" class="tooltip next-comment" title="${_('Jump to the next comment')}"> <i class="icon-angle-right"></i></a>
274 272 % endif
275 273
276 274 </div>
277 275 </div>
278 276 <div class="text">
279 277 ${h.render(comment.text, renderer=comment.renderer, mentions=True, repo_name=getattr(c, 'repo_name', None), active_pattern_entries=active_pattern_entries)}
280 278 </div>
281 279
282 280 </div>
283 281 % endif
284 282 </%def>
285 283
286 284 ## generate main comments
287 285 <%def name="generate_comments(comments, include_pull_request=False, is_pull_request=False)">
288 286 <%
289 287 active_pattern_entries = h.get_active_pattern_entries(getattr(c, 'repo_name', None))
290 288 %>
291 289
292 290 <div class="general-comments" id="comments">
293 291 %for comment in comments:
294 292 <div id="comment-tr-${comment.comment_id}">
295 293 ## only render comments that are not from pull request, or from
296 294 ## pull request and a status change
297 295 %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request:
298 296 ${comment_block(comment, active_pattern_entries=active_pattern_entries)}
299 297 %endif
300 298 </div>
301 299 %endfor
302 300 ## to anchor ajax comments
303 301 <div id="injected_page_comments"></div>
304 302 </div>
305 303 </%def>
306 304
307 305
308 306 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
309 307
310 308 <div class="comments">
311 309 <%
312 310 if is_pull_request:
313 311 placeholder = _('Leave a comment on this Pull Request.')
314 312 elif is_compare:
315 313 placeholder = _('Leave a comment on {} commits in this range.').format(len(form_extras))
316 314 else:
317 315 placeholder = _('Leave a comment on this Commit.')
318 316 %>
319 317
320 318 % if c.rhodecode_user.username != h.DEFAULT_USER:
321 319 <div class="js-template" id="cb-comment-general-form-template">
322 320 ## template generated for injection
323 321 ${comment_form(form_type='general', review_statuses=c.commit_statuses, form_extras=form_extras)}
324 322 </div>
325 323
326 324 <div id="cb-comment-general-form-placeholder" class="comment-form ac">
327 325 ## inject form here
328 326 </div>
329 327 <script type="text/javascript">
330 328 var lineNo = 'general';
331 329 var resolvesCommentId = null;
332 330 var generalCommentForm = Rhodecode.comments.createGeneralComment(
333 331 lineNo, "${placeholder}", resolvesCommentId);
334 332
335 333 // set custom success callback on rangeCommit
336 334 % if is_compare:
337 335 generalCommentForm.setHandleFormSubmit(function(o) {
338 336 var self = generalCommentForm;
339 337
340 338 var text = self.cm.getValue();
341 339 var status = self.getCommentStatus();
342 340 var commentType = self.getCommentType();
343 341 var isDraft = self.getDraftState();
344 342
345 343 if (text === "" && !status) {
346 344 return;
347 345 }
348 346
349 347 // we can pick which commits we want to make the comment by
350 348 // selecting them via click on preview pane, this will alter the hidden inputs
351 349 var cherryPicked = $('#changeset_compare_view_content .compare_select.hl').length > 0;
352 350
353 351 var commitIds = [];
354 352 $('#changeset_compare_view_content .compare_select').each(function(el) {
355 353 var commitId = this.id.replace('row-', '');
356 354 if ($(this).hasClass('hl') || !cherryPicked) {
357 355 $("input[data-commit-id='{0}']".format(commitId)).val(commitId);
358 356 commitIds.push(commitId);
359 357 } else {
360 358 $("input[data-commit-id='{0}']".format(commitId)).val('')
361 359 }
362 360 });
363 361
364 362 self.setActionButtonsDisabled(true);
365 363 self.cm.setOption("readOnly", true);
366 364 var postData = {
367 365 'text': text,
368 366 'changeset_status': status,
369 367 'comment_type': commentType,
370 368 'draft': isDraft,
371 369 'commit_ids': commitIds,
372 370 'csrf_token': CSRF_TOKEN
373 371 };
374 372
375 373 var submitSuccessCallback = function(o) {
376 374 location.reload(true);
377 375 };
378 376 var submitFailCallback = function(){
379 377 self.resetCommentFormState(text)
380 378 };
381 379 self.submitAjaxPOST(
382 380 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
383 381 });
384 382 % endif
385 383
386 384 </script>
387 385 % else:
388 386 ## form state when not logged in
389 387 <div class="comment-form ac">
390 388
391 389 <div class="comment-area">
392 390 <div class="comment-area-header">
393 391 <ul class="nav-links clearfix">
394 392 <li class="active">
395 393 <a class="disabled" href="#edit-btn" disabled="disabled" onclick="return false">${_('Write')}</a>
396 394 </li>
397 395 <li class="">
398 396 <a class="disabled" href="#preview-btn" disabled="disabled" onclick="return false">${_('Preview')}</a>
399 397 </li>
400 398 </ul>
401 399 </div>
402 400
403 401 <div class="comment-area-write" style="display: block;">
404 402 <div id="edit-container">
405 403 <div style="padding: 20px 0px 0px 0;">
406 404 ${_('You need to be logged in to leave comments.')}
407 405 <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
408 406 </div>
409 407 </div>
410 408 <div id="preview-container" class="clearfix" style="display: none;">
411 409 <div id="preview-box" class="preview-box"></div>
412 410 </div>
413 411 </div>
414 412
415 413 <div class="comment-area-footer">
416 414 <div class="toolbar">
417 415 <div class="toolbar-text">
418 416 </div>
419 417 </div>
420 418 </div>
421 419 </div>
422 420
423 421 <div class="comment-footer">
424 422 </div>
425 423
426 424 </div>
427 425 % endif
428 426
429 427 <script type="text/javascript">
430 428 bindToggleButtons();
431 429 </script>
432 430 </div>
433 431 </%def>
434 432
435 433
436 434 <%def name="comment_form(form_type, form_id='', lineno_id='{1}', review_statuses=None, form_extras=None)">
437 435
438 436 ## comment injected based on assumption that user is logged in
439 437 <form ${('id="{}"'.format(form_id) if form_id else '') |n} action="#" method="GET">
440 438
441 439 <div class="comment-area">
442 440 <div class="comment-area-header">
443 441 <div class="pull-left">
444 442 <ul class="nav-links clearfix">
445 443 <li class="active">
446 444 <a href="#edit-btn" tabindex="-1" id="edit-btn_${lineno_id}">${_('Write')}</a>
447 445 </li>
448 446 <li class="">
449 447 <a href="#preview-btn" tabindex="-1" id="preview-btn_${lineno_id}">${_('Preview')}</a>
450 448 </li>
451 449 </ul>
452 450 </div>
453 451 <div class="pull-right">
454 452 <span class="comment-area-text">${_('Mark as')}:</span>
455 453 <select class="comment-type" id="comment_type_${lineno_id}" name="comment_type">
456 454 % for val in c.visual.comment_types:
457 455 <option value="${val}">${val.upper()}</option>
458 456 % endfor
459 457 </select>
460 458 </div>
461 459 </div>
462 460
463 461 <div class="comment-area-write" style="display: block;">
464 462 <div id="edit-container_${lineno_id}" style="margin-top: -1px">
465 463 <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea>
466 464 </div>
467 465 <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;">
468 466 <div id="preview-box_${lineno_id}" class="preview-box"></div>
469 467 </div>
470 468 </div>
471 469
472 470 <div class="comment-area-footer comment-attachment-uploader">
473 471 <div class="toolbar">
474 472
475 473 <div class="comment-attachment-text">
476 474 <div class="dropzone-text">
477 475 ${_("Drag'n Drop files here or")} <span class="link pick-attachment">${_('Choose your files')}</span>.<br>
478 476 </div>
479 477 <div class="dropzone-upload" style="display:none">
480 478 <i class="icon-spin animate-spin"></i> ${_('uploading...')}
481 479 </div>
482 480 </div>
483 481
484 482 ## comments dropzone template, empty on purpose
485 483 <div style="display: none" class="comment-attachment-uploader-template">
486 484 <div class="dz-file-preview" style="margin: 0">
487 485 <div class="dz-error-message"></div>
488 486 </div>
489 487 </div>
490 488
491 489 </div>
492 490 </div>
493 491 </div>
494 492
495 493 <div class="comment-footer">
496 494
497 495 ## inject extra inputs into the form
498 496 % if form_extras and isinstance(form_extras, (list, tuple)):
499 497 <div id="comment_form_extras">
500 498 % for form_ex_el in form_extras:
501 499 ${form_ex_el|n}
502 500 % endfor
503 501 </div>
504 502 % endif
505 503
506 504 <div class="action-buttons">
507 505 % if form_type != 'inline':
508 506 <div class="action-buttons-extra"></div>
509 507 % endif
510 508
511 509 <input class="btn btn-success comment-button-input submit-comment-action" id="save_${lineno_id}" name="save" type="submit" value="${_('Add comment')}" data-is-draft=false onclick="$(this).addClass('submitter')">
512 510
513 511 % if form_type == 'inline':
514 512 % if c.rhodecode_edition_id == 'EE':
515 513 ## Disable the button for CE, the "real" validation is in the backend code anyway
516 514 <input class="btn btn-warning comment-button-input submit-draft-action" id="save_draft_${lineno_id}" name="save_draft" type="submit" value="${_('Add draft')}" data-is-draft=true onclick="$(this).addClass('submitter')">
517 515 % else:
518 <input class="tooltip btn btn-warning comment-button-input submit-draft-action disabled" type="submit" disabled="disabled" value="${_('Add draft')}" onclick="return false;" title="Draft comments only available in EE edition of RhodeCode">
516 <input class="btn btn-warning comment-button-input submit-draft-action disabled" disabled="disabled" type="submit" value="${_('Add draft')}" onclick="return false;" title="Draft comments only available in EE edition of RhodeCode">
519 517 % endif
520 518 % endif
521 519
522 520 % if review_statuses:
523 521 <div class="comment-status-box">
524 522 <select id="change_status_${lineno_id}" name="changeset_status">
525 523 <option></option> ## Placeholder
526 524 % for status, lbl in review_statuses:
527 525 <option value="${status}" data-status="${status}">${lbl}</option>
528 526 %if is_pull_request and change_status and status in ('approved', 'rejected'):
529 527 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
530 528 %endif
531 529 % endfor
532 530 </select>
533 531 </div>
534 532 % endif
535 533
536 534 ## inline for has a file, and line-number together with cancel hide button.
537 535 % if form_type == 'inline':
538 536 <input type="hidden" name="f_path" value="{0}">
539 537 <input type="hidden" name="line" value="${lineno_id}">
540 538 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
541 539 <i class="icon-cancel-circled2"></i>
542 540 </button>
543 541 % endif
544 542 </div>
545 543
546 544 <div class="toolbar-text">
547 545 <% renderer_url = '<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper()) %>
548 546 <p>${_('Styling with {} is supported.').format(renderer_url)|n}
549 547
550 548 <i class="icon-info-circled tooltip-hovercard"
551 549 data-hovercard-alt="ALT"
552 550 data-hovercard-url="javascript:commentHelp('${c.visual.default_renderer.upper()}')"
553 551 data-comment-json-b64='${h.b64(h.json.dumps({}))}'></i>
554 552 </p>
555 553 </div>
556 554 </div>
557 555
558 556 </form>
559 557
560 558 </%def> No newline at end of file
@@ -1,1012 +1,1035 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 // TODO: marcink switch this to pyroutes
36 36 AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
37 37 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
38 38 templateContext.pull_request_data.pull_request_version = '${request.GET.get('version', '')}';
39 39 </script>
40 40
41 41 <div class="box">
42 42
43 43 <div class="box pr-summary">
44 44
45 45 <div class="summary-details block-left">
46 46 <div id="pr-title">
47 47 % if c.pull_request.is_closed():
48 48 <span class="pr-title-closed-tag tag">${_('Closed')}</span>
49 49 % endif
50 50 <input class="pr-title-input large disabled" disabled="disabled" name="pullrequest_title" type="text" value="${c.pull_request.title}">
51 51 </div>
52 52 <div id="pr-title-edit" class="input" style="display: none;">
53 53 <input class="pr-title-input large" id="pr-title-input" name="pullrequest_title" type="text" value="${c.pull_request.title}">
54 54 </div>
55 55
56 56 <% summary = lambda n:{False:'summary-short'}.get(n) %>
57 57 <div class="pr-details-title">
58 58 <div class="pull-left">
59 59 <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>
60 60 ${_('Created on')}
61 61 <span class="tooltip" title="${_('Last updated on')} ${h.format_date(c.pull_request.updated_on)}">${h.format_date(c.pull_request.created_on)},</span>
62 62 <span class="pr-details-title-author-pref">${_('by')}</span>
63 63 </div>
64 64
65 65 <div class="pull-left">
66 66 ${self.gravatar_with_user(c.pull_request.author.email, 16, tooltip=True)}
67 67 </div>
68 68
69 69 %if c.allowed_to_update:
70 70 <div class="pull-right">
71 71 <div id="edit_pull_request" class="action_button pr-save" style="display: none;">${_('Update title & description')}</div>
72 72 <div id="delete_pullrequest" class="action_button pr-save ${('' if c.allowed_to_delete else 'disabled' )}" style="display: none;">
73 73 % if c.allowed_to_delete:
74 74 ${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)}
75 75 <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}"
76 76 onclick="submitConfirm(event, this, _gettext('Confirm to delete this pull request'), _gettext('Delete'), '${'!{}'.format(c.pull_request.pull_request_id)}')"
77 77 type="submit" value="${_('Delete pull request')}">
78 78 ${h.end_form()}
79 79 % else:
80 80 <span class="tooltip" title="${_('Not allowed to delete this pull request')}">${_('Delete pull request')}</span>
81 81 % endif
82 82 </div>
83 83 <div id="open_edit_pullrequest" class="action_button">${_('Edit')}</div>
84 84 <div id="close_edit_pullrequest" class="action_button" style="display: none;">${_('Cancel')}</div>
85 85 </div>
86 86
87 87 %endif
88 88 </div>
89 89
90 90 <div id="pr-desc" class="input" title="${_('Rendered using {} renderer').format(c.renderer)}">
91 91 ${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name, issues_container=c.referenced_desc_issues)}
92 92 </div>
93 93
94 94 <div id="pr-desc-edit" class="input textarea" style="display: none;">
95 95 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
96 96 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
97 97 </div>
98 98
99 99 <div id="summary" class="fields pr-details-content">
100 100
101 101 ## source
102 102 <div class="field">
103 103 <div class="label-pr-detail">
104 104 <label>${_('Commit flow')}:</label>
105 105 </div>
106 106 <div class="input">
107 107 <div class="pr-commit-flow">
108 108 ## Source
109 109 %if c.pull_request.source_ref_parts.type == 'branch':
110 110 <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>
111 111 %else:
112 112 <code class="pr-source-info">${'{}:{}'.format(c.pull_request.source_ref_parts.type, c.pull_request.source_ref_parts.name)}</code>
113 113 %endif
114 114 ${_('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>
115 115 &rarr;
116 116 ## Target
117 117 %if c.pull_request.target_ref_parts.type == 'branch':
118 118 <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>
119 119 %else:
120 120 <code class="pr-target-info">${'{}:{}'.format(c.pull_request.target_ref_parts.type, c.pull_request.target_ref_parts.name)}</code>
121 121 %endif
122 122
123 123 ${_('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>
124 124
125 125 <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>'>
126 126 <i class="icon-angle-down">more details</i>
127 127 </a>
128 128
129 129 </div>
130 130
131 131 <div class="source-details" style="display: none">
132 132
133 133 <ul>
134 134
135 135 ## common ancestor
136 136 <li>
137 137 ${_('Common ancestor')}:
138 138 % if c.ancestor_commit:
139 139 <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>
140 140 % else:
141 141 ${_('not available')}
142 142 % endif
143 143 </li>
144 144
145 145 ## pull url
146 146 <li>
147 147 %if h.is_hg(c.pull_request.source_repo):
148 148 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
149 149 %elif h.is_git(c.pull_request.source_repo):
150 150 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
151 151 %endif
152 152
153 153 <span>${_('Pull changes from source')}</span>: <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
154 154 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
155 155 </li>
156 156
157 157 ## Shadow repo
158 158 <li>
159 159 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
160 160 %if h.is_hg(c.pull_request.target_repo):
161 161 <% 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) %>
162 162 %elif h.is_git(c.pull_request.target_repo):
163 163 <% 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) %>
164 164 %endif
165 165
166 166 <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">
167 167 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
168 168
169 169 % else:
170 170 <div class="">
171 171 ${_('Shadow repository data not available')}.
172 172 </div>
173 173 % endif
174 174 </li>
175 175
176 176 </ul>
177 177
178 178 </div>
179 179
180 180 </div>
181 181
182 182 </div>
183 183
184 184 ## versions
185 185 <div class="field">
186 186 <div class="label-pr-detail">
187 187 <label>${_('Versions')}:</label>
188 188 </div>
189 189
190 190 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
191 191 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
192 192
193 193 <div class="pr-versions">
194 194 % if c.show_version_changes:
195 195 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
196 196 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
197 197 ${_ungettext('{} version available for this pull request, ', '{} versions available for this pull request, ', len(c.versions)).format(len(c.versions))}
198 198 <a id="show-pr-versions" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
199 199 data-toggle-on="${_('show versions')}."
200 200 data-toggle-off="${_('hide versions')}.">
201 201 ${_('show versions')}.
202 202 </a>
203 203 <table>
204 204 ## SHOW ALL VERSIONS OF PR
205 205 <% ver_pr = None %>
206 206
207 207 % for data in reversed(list(enumerate(c.versions, 1))):
208 208 <% ver_pos = data[0] %>
209 209 <% ver = data[1] %>
210 210 <% ver_pr = ver.pull_request_version_id %>
211 211 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
212 212
213 213 <tr class="version-pr" style="display: ${display_row}">
214 214 <td>
215 215 <code>
216 216 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
217 217 </code>
218 218 </td>
219 219 <td>
220 220 <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}"/>
221 221 <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}"/>
222 222 </td>
223 223 <td>
224 224 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
225 225 <i class="tooltip icon-circle review-status-${review_status}" title="${_('Your review status at this version')}"></i>
226 226
227 227 </td>
228 228 <td>
229 229 % if c.at_version_num != ver_pr:
230 230 <i class="tooltip icon-comment" title="${_('Comments from pull request version v{0}').format(ver_pos)}"></i>
231 231 <code>
232 232 General:${len(c.comment_versions[ver_pr]['at'])} / Inline:${len(c.inline_versions[ver_pr]['at'])}
233 233 </code>
234 234 % endif
235 235 </td>
236 236 <td>
237 237 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
238 238 </td>
239 239 <td>
240 240 <code>${h.age_component(ver.updated_on, time_is_local=True, tooltip=False)}</code>
241 241 </td>
242 242 </tr>
243 243 % endfor
244 244
245 245 <tr>
246 246 <td colspan="6">
247 247 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
248 248 data-label-text-locked="${_('select versions to show changes')}"
249 249 data-label-text-diff="${_('show changes between versions')}"
250 250 data-label-text-show="${_('show pull request for this version')}"
251 251 >
252 252 ${_('select versions to show changes')}
253 253 </button>
254 254 </td>
255 255 </tr>
256 256 </table>
257 257 % else:
258 258 <div>
259 259 ${_('Pull request versions not available')}.
260 260 </div>
261 261 % endif
262 262 </div>
263 263 </div>
264 264
265 265 </div>
266 266
267 267 </div>
268 268
269 269
270 270 </div>
271 271
272 272 </div>
273 273
274 274 <div class="box">
275 275
276 276 % if c.state_progressing:
277 277
278 278 <h2 style="text-align: center">
279 279 ${_('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>
280 280
281 281 % if c.is_super_admin:
282 282 <br/>
283 283 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.
284 284 % endif
285 285 </h2>
286 286
287 287 % else:
288 288
289 289 ## Diffs rendered here
290 290 <div class="table" >
291 291 <div id="changeset_compare_view_content">
292 292 ##CS
293 293 % if c.missing_requirements:
294 294 <div class="box">
295 295 <div class="alert alert-warning">
296 296 <div>
297 297 <strong>${_('Missing requirements:')}</strong>
298 298 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
299 299 </div>
300 300 </div>
301 301 </div>
302 302 % elif c.missing_commits:
303 303 <div class="box">
304 304 <div class="alert alert-warning">
305 305 <div>
306 306 <strong>${_('Missing commits')}:</strong>
307 307 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}<br/>
308 308 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}<br/>
309 309 ${_('Consider doing a `force update commits` in case you think this is an error.')}
310 310 </div>
311 311 </div>
312 312 </div>
313 313 % elif c.pr_merge_source_commit.changed and not c.pull_request.is_closed():
314 314 <div class="box">
315 315 <div class="alert alert-info">
316 316 <div>
317 317 <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>
318 318 </div>
319 319 </div>
320 320 </div>
321 321 % endif
322 322
323 323 <div class="compare_view_commits_title">
324 324 % if not c.compare_mode:
325 325
326 326 % if c.at_version_index:
327 327 <h4>
328 328 ${_('Showing changes at v{}, commenting is disabled.').format(c.at_version_index)}
329 329 </h4>
330 330 % endif
331 331
332 332 <div class="pull-left">
333 333 <div class="btn-group">
334 334 <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)} >
335 335 % if c.collapse_all_commits:
336 336 <i class="icon-plus-squared-alt icon-no-margin"></i>
337 337 ${_ungettext('Expand {} commit', 'Expand {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
338 338 % else:
339 339 <i class="icon-minus-squared-alt icon-no-margin"></i>
340 340 ${_ungettext('Collapse {} commit', 'Collapse {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
341 341 % endif
342 342 </a>
343 343 </div>
344 344 </div>
345 345
346 346 <div class="pull-right">
347 347 % if c.allowed_to_update and not c.pull_request.is_closed():
348 348
349 349 <div class="btn-group btn-group-actions">
350 350 <a id="update_commits" class="btn btn-primary no-margin" onclick="updateController.updateCommits(this); return false">
351 351 ${_('Update commits')}
352 352 </a>
353 353
354 354 <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')}">
355 355 <i class="icon-down"></i>
356 356 </a>
357 357
358 358 <div class="btn-action-switcher-container right-align" id="update-commits-switcher">
359 359 <ul class="btn-action-switcher" role="menu" style="min-width: 300px;">
360 360 <li>
361 361 <a href="#forceUpdate" onclick="updateController.forceUpdateCommits(this); return false">
362 362 ${_('Force update commits')}
363 363 </a>
364 364 <div class="action-help-block">
365 365 ${_('Update commits and force refresh this pull request.')}
366 366 </div>
367 367 </li>
368 368 </ul>
369 369 </div>
370 370 </div>
371 371
372 372 % else:
373 373 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
374 374 % endif
375 375
376 376 </div>
377 377 % endif
378 378 </div>
379 379
380 380 % if not c.missing_commits:
381 381 ## COMPARE RANGE DIFF MODE
382 382 % if c.compare_mode:
383 383 % if c.at_version:
384 384 <h4>
385 385 ${_('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')}:
386 386 </h4>
387 387
388 388 <div class="subtitle-compare">
389 389 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
390 390 </div>
391 391
392 392 <div class="container">
393 393 <table class="rctable compare_view_commits">
394 394 <tr>
395 395 <th></th>
396 396 <th>${_('Time')}</th>
397 397 <th>${_('Author')}</th>
398 398 <th>${_('Commit')}</th>
399 399 <th></th>
400 400 <th>${_('Description')}</th>
401 401 </tr>
402 402
403 403 % for c_type, commit in c.commit_changes:
404 404 % if c_type in ['a', 'r']:
405 405 <%
406 406 if c_type == 'a':
407 407 cc_title = _('Commit added in displayed changes')
408 408 elif c_type == 'r':
409 409 cc_title = _('Commit removed in displayed changes')
410 410 else:
411 411 cc_title = ''
412 412 %>
413 413 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
414 414 <td>
415 415 <div class="commit-change-indicator color-${c_type}-border">
416 416 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
417 417 ${c_type.upper()}
418 418 </div>
419 419 </div>
420 420 </td>
421 421 <td class="td-time">
422 422 ${h.age_component(commit.date)}
423 423 </td>
424 424 <td class="td-user">
425 425 ${base.gravatar_with_user(commit.author, 16, tooltip=True)}
426 426 </td>
427 427 <td class="td-hash">
428 428 <code>
429 429 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
430 430 r${commit.idx}:${h.short_id(commit.raw_id)}
431 431 </a>
432 432 ${h.hidden('revisions', commit.raw_id)}
433 433 </code>
434 434 </td>
435 435 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
436 436 <i class="icon-expand-linked"></i>
437 437 </td>
438 438 <td class="mid td-description">
439 439 <div class="log-container truncate-wrap">
440 440 <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>
441 441 </div>
442 442 </td>
443 443 </tr>
444 444 % endif
445 445 % endfor
446 446 </table>
447 447 </div>
448 448
449 449 % endif
450 450
451 451 ## Regular DIFF
452 452 % else:
453 453 <%include file="/compare/compare_commits.mako" />
454 454 % endif
455 455
456 456 <div class="cs_files">
457 457 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
458 458
459 459 <%
460 460 pr_menu_data = {
461 461 'outdated_comm_count_ver': outdated_comm_count_ver,
462 462 'pull_request': c.pull_request
463 463 }
464 464 %>
465 465
466 466 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on, pull_request_menu=pr_menu_data)}
467 467
468 468 % if c.range_diff_on:
469 469 % for commit in c.commit_ranges:
470 470 ${cbdiffs.render_diffset(
471 471 c.changes[commit.raw_id],
472 472 commit=commit, use_comments=True,
473 473 collapse_when_files_over=5,
474 474 disable_new_comments=True,
475 475 deleted_files_comments=c.deleted_files_comments,
476 476 inline_comments=c.inline_comments,
477 477 pull_request_menu=pr_menu_data, show_todos=False)}
478 478 % endfor
479 479 % else:
480 480 ${cbdiffs.render_diffset(
481 481 c.diffset, use_comments=True,
482 482 collapse_when_files_over=30,
483 483 disable_new_comments=not c.allowed_to_comment,
484 484 deleted_files_comments=c.deleted_files_comments,
485 485 inline_comments=c.inline_comments,
486 486 pull_request_menu=pr_menu_data, show_todos=False)}
487 487 % endif
488 488
489 489 </div>
490 490 % else:
491 491 ## skipping commits we need to clear the view for missing commits
492 492 <div style="clear:both;"></div>
493 493 % endif
494 494
495 495 </div>
496 496 </div>
497 497
498 498 ## template for inline comment form
499 499 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
500 500
501 501 ## comments heading with count
502 502 <div class="comments-heading">
503 503 <i class="icon-comment"></i>
504 504 ${_('General Comments')} ${len(c.comments)}
505 505 </div>
506 506
507 507 ## render general comments
508 508 <div id="comment-tr-show">
509 509 % if general_outdated_comm_count_ver:
510 510 <div class="info-box">
511 511 % if general_outdated_comm_count_ver == 1:
512 512 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
513 513 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
514 514 % else:
515 515 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
516 516 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
517 517 % endif
518 518 </div>
519 519 % endif
520 520 </div>
521 521
522 522 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
523 523
524 524 % if not c.pull_request.is_closed():
525 525 ## main comment form and it status
526 526 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
527 527 pull_request_id=c.pull_request.pull_request_id),
528 528 c.pull_request_review_status,
529 529 is_pull_request=True, change_status=c.allowed_to_change_status)}
530 530
531 531 ## merge status, and merge action
532 532 <div class="pull-request-merge">
533 533 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
534 534 </div>
535 535
536 536 %endif
537 537
538 538 % endif
539 539 </div>
540 540
541 541
542 542 ### NAV SIDEBAR
543 543 <aside class="right-sidebar right-sidebar-expanded" id="pr-nav-sticky" style="display: none">
544 544 <div class="sidenav navbar__inner" >
545 545 ## TOGGLE
546 546 <div class="sidebar-toggle" onclick="toggleSidebar(); return false">
547 547 <a href="#toggleSidebar" class="grey-link-action">
548 548
549 549 </a>
550 550 </div>
551 551
552 552 ## CONTENT
553 553 <div class="sidebar-content">
554 554
555 ## Drafts
556 % if c.rhodecode_edition_id == 'EE':
557 <div class="sidebar-element clear-both">
558 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Drafts')}">
559 <i class="icon-comment icon-draft"></i>
560 <span id="comments-count">${0}</span>
561 </div>
562
563 <div class="right-sidebar-expanded-state pr-details-title">
564 <span class="sidebar-heading noselect">
565 <i class="icon-comment icon-draft"></i>
566 ${_('Drafts')}
567 </span>
568 </div>
569
570 <div id="drafts" class="right-sidebar-expanded-state pr-details-content reviewers">
571 ## members redering block
572
573
574 ???
575
576
577 ## end members redering block
578
579 </div>
580
581 </div>
582 % endif
583
555 584 ## RULES SUMMARY/RULES
556 585 <div class="sidebar-element clear-both">
557 586 <% vote_title = _ungettext(
558 587 'Status calculated based on votes from {} reviewer',
559 588 'Status calculated based on votes from {} reviewers', c.reviewers_count).format(c.reviewers_count)
560 589 %>
561 590
562 591 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
563 592 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
564 593 ${c.reviewers_count}
565 594 </div>
566 595
567 596 ## REVIEW RULES
568 597 <div id="review_rules" style="display: none" class="">
569 598 <div class="right-sidebar-expanded-state pr-details-title">
570 599 <span class="sidebar-heading">
571 600 ${_('Reviewer rules')}
572 601 </span>
573 602
574 603 </div>
575 604 <div class="pr-reviewer-rules">
576 605 ## review rules will be appended here, by default reviewers logic
577 606 </div>
578 607 <input id="review_data" type="hidden" name="review_data" value="">
579 608 </div>
580 609
581 610 ## REVIEWERS
582 611 <div class="right-sidebar-expanded-state pr-details-title">
583 612 <span class="tooltip sidebar-heading" title="${vote_title}">
584 613 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
585 614 ${_('Reviewers')}
586 615 </span>
587 616 %if c.allowed_to_update:
588 617 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
589 618 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
590 619 %else:
591 620 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Show rules')}</span>
592 621 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
593 622 %endif
594 623 </div>
595 624
596 625 <div id="reviewers" class="right-sidebar-expanded-state pr-details-content reviewers">
597 626
598 627 ## members redering block
599 628 <input type="hidden" name="__start__" value="review_members:sequence">
600 629
601 630 <table id="review_members" class="group_members">
602 631 ## This content is loaded via JS and ReviewersPanel
603 632 </table>
604 633
605 634 <input type="hidden" name="__end__" value="review_members:sequence">
606 635 ## end members redering block
607 636
608 637 %if not c.pull_request.is_closed():
609 638 <div id="add_reviewer" class="ac" style="display: none;">
610 639 %if c.allowed_to_update:
611 640 % if not c.forbid_adding_reviewers:
612 641 <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px">
613 642 <input class="ac-input" id="user" name="user" placeholder="${_('Add reviewer or reviewer group')}" type="text" autocomplete="off">
614 643 <div id="reviewers_container"></div>
615 644 </div>
616 645 % endif
617 646 <div class="pull-right" style="margin-bottom: 15px">
618 647 <button data-role="reviewer" id="update_reviewers" class="btn btn-small no-margin">${_('Save Changes')}</button>
619 648 </div>
620 649 %endif
621 650 </div>
622 651 %endif
623 652 </div>
624 653 </div>
625 654
626 655 ## OBSERVERS
627 656 % if c.rhodecode_edition_id == 'EE':
628 657 <div class="sidebar-element clear-both">
629 658 <% vote_title = _ungettext(
630 659 '{} observer without voting right.',
631 660 '{} observers without voting right.', c.observers_count).format(c.observers_count)
632 661 %>
633 662
634 663 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
635 664 <i class="icon-circle-thin"></i>
636 665 ${c.observers_count}
637 666 </div>
638 667
639 668 <div class="right-sidebar-expanded-state pr-details-title">
640 669 <span class="tooltip sidebar-heading" title="${vote_title}">
641 670 <i class="icon-circle-thin"></i>
642 671 ${_('Observers')}
643 672 </span>
644 673 %if c.allowed_to_update:
645 674 <span id="open_edit_observers" class="block-right action_button last-item">${_('Edit')}</span>
646 675 <span id="close_edit_observers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
647 676 %endif
648 677 </div>
649 678
650 679 <div id="observers" class="right-sidebar-expanded-state pr-details-content reviewers">
651 680 ## members redering block
652 681 <input type="hidden" name="__start__" value="observer_members:sequence">
653 682
654 683 <table id="observer_members" class="group_members">
655 684 ## This content is loaded via JS and ReviewersPanel
656 685 </table>
657 686
658 687 <input type="hidden" name="__end__" value="observer_members:sequence">
659 688 ## end members redering block
660 689
661 690 %if not c.pull_request.is_closed():
662 691 <div id="add_observer" class="ac" style="display: none;">
663 692 %if c.allowed_to_update:
664 693 % if not c.forbid_adding_reviewers or 1:
665 694 <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px" >
666 695 <input class="ac-input" id="observer" name="observer" placeholder="${_('Add observer or observer group')}" type="text" autocomplete="off">
667 696 <div id="observers_container"></div>
668 697 </div>
669 698 % endif
670 699 <div class="pull-right" style="margin-bottom: 15px">
671 700 <button data-role="observer" id="update_observers" class="btn btn-small no-margin">${_('Save Changes')}</button>
672 701 </div>
673 702 %endif
674 703 </div>
675 704 %endif
676 705 </div>
677 706 </div>
678 707 % endif
679 708
680 709 ## TODOs
681 710 <div class="sidebar-element clear-both">
682 711 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs">
683 712 <i class="icon-flag-filled"></i>
684 713 <span id="todos-count">${len(c.unresolved_comments)}</span>
685 714 </div>
686 715
687 716 <div class="right-sidebar-expanded-state pr-details-title">
688 717 ## Only show unresolved, that is only what matters
689 718 <span class="sidebar-heading noselect" onclick="refreshTODOs(); return false">
690 719 <i class="icon-flag-filled"></i>
691 720 TODOs
692 721 </span>
693 722
694 723 % if not c.at_version:
695 724 % if c.resolved_comments:
696 725 <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>
697 726 % else:
698 727 <span class="block-right last-item noselect">Show resolved</span>
699 728 % endif
700 729 % endif
701 730 </div>
702 731
703 732 <div class="right-sidebar-expanded-state pr-details-content">
704 733
705 734 % if c.at_version:
706 735 <table>
707 736 <tr>
708 737 <td class="unresolved-todo-text">${_('TODOs unavailable when browsing versions')}.</td>
709 738 </tr>
710 739 </table>
711 740 % else:
712 741 % if c.unresolved_comments + c.resolved_comments:
713 742 ${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True)}
714 743 % else:
715 744 <table>
716 745 <tr>
717 746 <td>
718 747 ${_('No TODOs yet')}
719 748 </td>
720 749 </tr>
721 750 </table>
722 751 % endif
723 752 % endif
724 753 </div>
725 754 </div>
726 755
727 756 ## COMMENTS
728 757 <div class="sidebar-element clear-both">
729 758 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}">
730 759 <i class="icon-comment" style="color: #949494"></i>
731 760 <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span>
732 761 <span class="display-none" id="general-comments-count">${len(c.comments)}</span>
733 762 <span class="display-none" id="inline-comments-count">${len(c.inline_comments_flat)}</span>
734 763 </div>
735 764
736 765 <div class="right-sidebar-expanded-state pr-details-title">
737 766 <span class="sidebar-heading noselect" onclick="refreshComments(); return false">
738 767 <i class="icon-comment" style="color: #949494"></i>
739 768 ${_('Comments')}
740 769
741 770 ## % if outdated_comm_count_ver:
742 771 ## <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">
743 772 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
744 773 ## </a>
745 774 ## <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated')}</a>
746 775 ## <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated')}</a>
747 776
748 777 ## % else:
749 778 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
750 779 ## % endif
751 780
752 781 </span>
753 782
754 783 % if outdated_comm_count_ver:
755 784 <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>
756 785 % else:
757 786 <span class="block-right last-item noselect">Show hidden</span>
758 787 % endif
759 788
760 789 </div>
761 790
762 791 <div class="right-sidebar-expanded-state pr-details-content">
763 792 % if c.inline_comments_flat + c.comments:
764 793 ${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments))}
765 794 % else:
766 795 <table>
767 796 <tr>
768 797 <td>
769 798 ${_('No Comments yet')}
770 799 </td>
771 800 </tr>
772 801 </table>
773 802 % endif
774 803 </div>
775 804
776 805 </div>
777 806
778 807 ## Referenced Tickets
779 808 <div class="sidebar-element clear-both">
780 809 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Referenced Tickets')}">
781 810 <i class="icon-info-circled"></i>
782 811 ${(len(c.referenced_desc_issues) + len(c.referenced_commit_issues))}
783 812 </div>
784 813
785 814 <div class="right-sidebar-expanded-state pr-details-title">
786 815 <span class="sidebar-heading">
787 816 <i class="icon-info-circled"></i>
788 817 ${_('Referenced Tickets')}
789 818 </span>
790 819 </div>
791 820 <div class="right-sidebar-expanded-state pr-details-content">
792 821 <table>
793 822
794 823 <tr><td><code>${_('In pull request description')}:</code></td></tr>
795 824 % if c.referenced_desc_issues:
796 825 % for ticket_dict in sorted(c.referenced_desc_issues):
797 826 <tr>
798 827 <td>
799 828 <a href="${ticket_dict.get('url')}">
800 829 ${ticket_dict.get('id')}
801 830 </a>
802 831 </td>
803 832 </tr>
804 833 % endfor
805 834 % else:
806 835 <tr>
807 836 <td>
808 837 ${_('No Ticket data found.')}
809 838 </td>
810 839 </tr>
811 840 % endif
812 841
813 842 <tr><td style="padding-top: 10px"><code>${_('In commit messages')}:</code></td></tr>
814 843 % if c.referenced_commit_issues:
815 844 % for ticket_dict in sorted(c.referenced_commit_issues):
816 845 <tr>
817 846 <td>
818 847 <a href="${ticket_dict.get('url')}">
819 848 ${ticket_dict.get('id')}
820 849 </a>
821 850 </td>
822 851 </tr>
823 852 % endfor
824 853 % else:
825 854 <tr>
826 855 <td>
827 856 ${_('No Ticket data found.')}
828 857 </td>
829 858 </tr>
830 859 % endif
831 860 </table>
832 861
833 862 </div>
834 863 </div>
835 864
836 865 </div>
837 866
838 867 </div>
839 868 </aside>
840 869
841 870 ## This JS needs to be at the end
842 871 <script type="text/javascript">
843 872
844 873 versionController = new VersionController();
845 874 versionController.init();
846 875
847 876 reviewersController = new ReviewersController();
848 877 commitsController = new CommitsController();
849 878 commentsController = new CommentsController();
850 879
851 880 updateController = new UpdatePrController();
852 881
853 882 window.reviewerRulesData = ${c.pull_request_default_reviewers_data_json | n};
854 883 window.setReviewersData = ${c.pull_request_set_reviewers_data_json | n};
855 884 window.setObserversData = ${c.pull_request_set_observers_data_json | n};
856 885
857 886 (function () {
858 887 "use strict";
859 888
860 889 // custom code mirror
861 890 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
862 891
863 892 PRDetails.init();
864 893 ReviewersPanel.init(reviewersController, reviewerRulesData, setReviewersData);
865 894 ObserversPanel.init(reviewersController, reviewerRulesData, setObserversData);
866 895
867 896 window.showOutdated = function (self) {
868 897 $('.comment-inline.comment-outdated').show();
869 898 $('.filediff-outdated').show();
870 899 $('.showOutdatedComments').hide();
871 900 $('.hideOutdatedComments').show();
872 901 };
873 902
874 903 window.hideOutdated = function (self) {
875 904 $('.comment-inline.comment-outdated').hide();
876 905 $('.filediff-outdated').hide();
877 906 $('.hideOutdatedComments').hide();
878 907 $('.showOutdatedComments').show();
879 908 };
880 909
881 910 window.refreshMergeChecks = function () {
882 911 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
883 912 $('.pull-request-merge').css('opacity', 0.3);
884 913 $('.action-buttons-extra').css('opacity', 0.3);
885 914
886 915 $('.pull-request-merge').load(
887 916 loadUrl, function () {
888 917 $('.pull-request-merge').css('opacity', 1);
889 918
890 919 $('.action-buttons-extra').css('opacity', 1);
891 920 }
892 921 );
893 922 };
894 923
895 924 window.closePullRequest = function (status) {
896 925 if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) {
897 926 return false;
898 927 }
899 928 // inject closing flag
900 929 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
901 930 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
902 931 $(generalCommentForm.submitForm).submit();
903 932 };
904 933
905 934 //TODO this functionality is now missing
906 935 $('#show-outdated-comments').on('click', function (e) {
907 936 var button = $(this);
908 937 var outdated = $('.comment-outdated');
909 938
910 939 if (button.html() === "(Show)") {
911 940 button.html("(Hide)");
912 941 outdated.show();
913 942 } else {
914 943 button.html("(Show)");
915 944 outdated.hide();
916 945 }
917 946 });
918 947
919 948 $('#merge_pull_request_form').submit(function () {
920 949 if (!$('#merge_pull_request').attr('disabled')) {
921 950 $('#merge_pull_request').attr('disabled', 'disabled');
922 951 }
923 952 return true;
924 953 });
925 954
926 955 $('#edit_pull_request').on('click', function (e) {
927 956 var title = $('#pr-title-input').val();
928 957 var description = codeMirrorInstance.getValue();
929 958 var renderer = $('#pr-renderer-input').val();
930 959 editPullRequest(
931 960 "${c.repo_name}", "${c.pull_request.pull_request_id}",
932 961 title, description, renderer);
933 962 });
934 963
935 964 var $updateButtons = $('#update_reviewers,#update_observers');
936 965 $updateButtons.on('click', function (e) {
937 966 var role = $(this).data('role');
938 967 $updateButtons.attr('disabled', 'disabled');
939 968 $updateButtons.addClass('disabled');
940 969 $updateButtons.html(_gettext('Saving...'));
941 970 reviewersController.updateReviewers(
942 971 templateContext.repo_name,
943 972 templateContext.pull_request_data.pull_request_id,
944 973 role
945 974 );
946 975 });
947 976
948 977 // fixing issue with caches on firefox
949 978 $('#update_commits').removeAttr("disabled");
950 979
951 980 $('.show-inline-comments').on('click', function (e) {
952 981 var boxid = $(this).attr('data-comment-id');
953 982 var button = $(this);
954 983
955 984 if (button.hasClass("comments-visible")) {
956 985 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
957 986 $(this).hide();
958 987 });
959 988 button.removeClass("comments-visible");
960 989 } else {
961 990 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
962 991 $(this).show();
963 992 });
964 993 button.addClass("comments-visible");
965 994 }
966 995 });
967 996
968 997 $('.show-inline-comments').on('change', function (e) {
969 998 var show = 'none';
970 999 var target = e.currentTarget;
971 1000 if (target.checked) {
972 1001 show = ''
973 1002 }
974 1003 var boxid = $(target).attr('id_for');
975 1004 var comments = $('#{0} .inline-comments'.format(boxid));
976 1005 var fn_display = function (idx) {
977 1006 $(this).css('display', show);
978 1007 };
979 1008 $(comments).each(fn_display);
980 1009 var btns = $('#{0} .inline-comments-button'.format(boxid));
981 1010 $(btns).each(fn_display);
982 1011 });
983 1012
984 1013 // register submit callback on commentForm form to track TODOs, and refresh mergeChecks conditions
985 1014 window.commentFormGlobalSubmitSuccessCallback = function (comment) {
986 1015 if (!comment.draft) {
987 1016 refreshMergeChecks();
988 1017 }
989 1018 };
990 1019
991 1020 ReviewerAutoComplete('#user', reviewersController);
992 1021 ObserverAutoComplete('#observer', reviewersController);
993 1022
994 1023 })();
995 1024
996 1025 $(document).ready(function () {
997 1026
998 1027 var channel = '${c.pr_broadcast_channel}';
999 1028 new ReviewerPresenceController(channel)
1000
1001
1002 window.finalizeDrafts = function(commentIds) {
1003 alert('okok !' + commentIds)
1004
1005 }
1006 1029 // register globally so inject comment logic can re-use it.
1007 1030 window.commentsController = commentsController;
1008 1031
1009 1032 })
1010 1033 </script>
1011 1034
1012 1035 </%def>
General Comments 0
You need to be logged in to leave comments. Login now