##// END OF EJS Templates
comment-history: UI fixes
marcink -
r4404:56b5a39d default
parent child Browse files
Show More
@@ -1,629 +1,636 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: 0.6;
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-label {
65 65 float: left;
66 66
67 67 padding: 0.4em 0.4em;
68 68 margin: 3px 5px 0px -10px;
69 69 display: inline-block;
70 70 min-height: 0;
71 71
72 72 text-align: center;
73 73 font-size: 10px;
74 74 line-height: .8em;
75 75
76 76 font-family: @text-italic;
77 77 font-style: italic;
78 78 background: #fff none;
79 79 color: @grey4;
80 80 border: 1px solid @grey4;
81 81 white-space: nowrap;
82 82
83 83 text-transform: uppercase;
84 84 min-width: 40px;
85 85
86 86 &.todo {
87 87 color: @color5;
88 88 font-style: italic;
89 89 font-weight: @text-bold-italic-weight;
90 90 font-family: @text-bold-italic;
91 91 }
92 92
93 93 .resolve {
94 94 cursor: pointer;
95 95 text-decoration: underline;
96 96 }
97 97
98 98 .resolved {
99 99 text-decoration: line-through;
100 100 color: @color1;
101 101 }
102 102 .resolved a {
103 103 text-decoration: line-through;
104 104 color: @color1;
105 105 }
106 106 .resolve-text {
107 107 color: @color1;
108 108 margin: 2px 8px;
109 109 font-family: @text-italic;
110 110 font-style: italic;
111 111 }
112 112 }
113 113
114 114 .has-spacer-after {
115 115 &:after {
116 116 content: ' | ';
117 117 color: @grey5;
118 118 }
119 119 }
120 120
121 121 .has-spacer-before {
122 122 &:before {
123 123 content: ' | ';
124 124 color: @grey5;
125 125 }
126 126 }
127 127
128 128 .comment {
129 129
130 130 &.comment-general {
131 131 border: 1px solid @grey5;
132 132 padding: 5px 5px 5px 5px;
133 133 }
134 134
135 135 margin: @padding 0;
136 136 padding: 4px 0 0 0;
137 137 line-height: 1em;
138 138
139 139 .rc-user {
140 140 min-width: 0;
141 141 margin: 0px .5em 0 0;
142 142
143 143 .user {
144 144 display: inline;
145 145 }
146 146 }
147 147
148 148 .meta {
149 149 position: relative;
150 150 width: 100%;
151 151 border-bottom: 1px solid @grey5;
152 152 margin: -5px 0px;
153 153 line-height: 24px;
154 154
155 155 &:hover .permalink {
156 156 visibility: visible;
157 157 color: @rcblue;
158 158 }
159 159 }
160 160
161 161 .author,
162 162 .date {
163 163 display: inline;
164 164
165 165 &:after {
166 166 content: ' | ';
167 167 color: @grey5;
168 168 }
169 169 }
170 170
171 171 .author-general img {
172 172 top: 3px;
173 173 }
174 174 .author-inline img {
175 175 top: 3px;
176 176 }
177 177
178 178 .status-change,
179 179 .permalink,
180 180 .changeset-status-lbl {
181 181 display: inline;
182 182 }
183 183
184 184 .permalink {
185 185 visibility: hidden;
186 186 }
187 187
188 188 .comment-links-divider {
189 189 display: inline;
190 190 }
191 191
192 192 .comment-links-block {
193 193 float:right;
194 194 text-align: right;
195 195 min-width: 85px;
196 196
197 197 [class^="icon-"]:before,
198 198 [class*=" icon-"]:before {
199 199 margin-left: 0;
200 200 margin-right: 0;
201 201 }
202 202 }
203 203
204 204 .comment-previous-link {
205 205 display: inline-block;
206 206
207 207 .arrow_comment_link{
208 208 cursor: pointer;
209 209 i {
210 210 font-size:10px;
211 211 }
212 212 }
213 213 .arrow_comment_link.disabled {
214 214 cursor: default;
215 215 color: @grey5;
216 216 }
217 217 }
218 218
219 219 .comment-next-link {
220 220 display: inline-block;
221 221
222 222 .arrow_comment_link{
223 223 cursor: pointer;
224 224 i {
225 225 font-size:10px;
226 226 }
227 227 }
228 228 .arrow_comment_link.disabled {
229 229 cursor: default;
230 230 color: @grey5;
231 231 }
232 232 }
233 233
234 234 .delete-comment {
235 235 display: inline-block;
236 236 color: @rcblue;
237 237
238 238 &:hover {
239 239 cursor: pointer;
240 240 }
241 241 }
242 242
243 243 .text {
244 244 clear: both;
245 245 .border-radius(@border-radius);
246 246 .box-sizing(border-box);
247 247
248 248 .markdown-block p,
249 249 .rst-block p {
250 250 margin: .5em 0 !important;
251 251 // TODO: lisa: This is needed because of other rst !important rules :[
252 252 }
253 253 }
254 254
255 255 .pr-version {
256 256 float: left;
257 257 margin: 0px 4px;
258 258 }
259 259 .pr-version-inline {
260 260 float: left;
261 261 margin: 0px 4px;
262 262 }
263 263 .pr-version-num {
264 264 font-size: 10px;
265 265 }
266 266 }
267 267
268 268 @comment-padding: 5px;
269 269
270 270 .general-comments {
271 271 .comment-outdated {
272 272 opacity: @comment-outdated-opacity;
273 273 }
274 274 }
275 275
276 276 .inline-comments {
277 277 border-radius: @border-radius;
278 278 .comment {
279 279 margin: 0;
280 280 border-radius: @border-radius;
281 281 }
282 282 .comment-outdated {
283 283 opacity: @comment-outdated-opacity;
284 284 }
285 285
286 286 .comment-inline {
287 287 background: white;
288 288 padding: @comment-padding @comment-padding;
289 289 border: @comment-padding solid @grey6;
290 290
291 291 .text {
292 292 border: none;
293 293 }
294 294 .meta {
295 295 border-bottom: 1px solid @grey6;
296 296 margin: -5px 0px;
297 297 line-height: 24px;
298 298 }
299 299 }
300 300 .comment-selected {
301 301 border-left: 6px solid @comment-highlight-color;
302 302 }
303 303 .comment-inline-form {
304 304 padding: @comment-padding;
305 305 display: none;
306 306 }
307 307 .cb-comment-add-button {
308 308 margin: @comment-padding;
309 309 }
310 310 /* hide add comment button when form is open */
311 311 .comment-inline-form-open ~ .cb-comment-add-button {
312 312 display: none;
313 313 }
314 314 .comment-inline-form-open {
315 315 display: block;
316 316 }
317 317 /* hide add comment button when form but no comments */
318 318 .comment-inline-form:first-child + .cb-comment-add-button {
319 319 display: none;
320 320 }
321 321 /* hide add comment button when no comments or form */
322 322 .cb-comment-add-button:first-child {
323 323 display: none;
324 324 }
325 325 /* hide add comment button when only comment is being deleted */
326 326 .comment-deleting:first-child + .cb-comment-add-button {
327 327 display: none;
328 328 }
329 329 }
330 330
331 331
332 332 .show-outdated-comments {
333 333 display: inline;
334 334 color: @rcblue;
335 335 }
336 336
337 337 // Comment Form
338 338 div.comment-form {
339 339 margin-top: 20px;
340 340 }
341 341
342 342 .comment-form strong {
343 343 display: block;
344 344 margin-bottom: 15px;
345 345 }
346 346
347 347 .comment-form textarea {
348 348 width: 100%;
349 349 height: 100px;
350 350 font-family: @text-monospace;
351 351 }
352 352
353 353 form.comment-form {
354 354 margin-top: 10px;
355 355 margin-left: 10px;
356 356 }
357 357
358 358 .comment-inline-form .comment-block-ta,
359 359 .comment-form .comment-block-ta,
360 360 .comment-form .preview-box {
361 361 .border-radius(@border-radius);
362 362 .box-sizing(border-box);
363 363 background-color: white;
364 364 }
365 365
366 366 .comment-form-submit {
367 367 margin-top: 5px;
368 368 margin-left: 525px;
369 369 }
370 370
371 371 .file-comments {
372 372 display: none;
373 373 }
374 374
375 375 .comment-form .preview-box.unloaded,
376 376 .comment-inline-form .preview-box.unloaded {
377 377 height: 50px;
378 378 text-align: center;
379 379 padding: 20px;
380 380 background-color: white;
381 381 }
382 382
383 383 .comment-footer {
384 384 position: relative;
385 385 width: 100%;
386 386 min-height: 42px;
387 387
388 388 .status_box,
389 389 .cancel-button {
390 390 float: left;
391 391 display: inline-block;
392 392 }
393 393
394 394 .status_box {
395 395 margin-left: 10px;
396 396 }
397 397
398 398 .action-buttons {
399 399 float: left;
400 400 display: inline-block;
401 401 }
402 402
403 403 .action-buttons-extra {
404 404 display: inline-block;
405 405 }
406 406 }
407 407
408 408 .comment-form {
409 409
410 410 .comment {
411 411 margin-left: 10px;
412 412 }
413 413
414 414 .comment-help {
415 415 color: @grey4;
416 416 padding: 5px 0 5px 0;
417 417 }
418 418
419 419 .comment-title {
420 420 padding: 5px 0 5px 0;
421 421 }
422 422
423 423 .comment-button {
424 424 display: inline-block;
425 425 }
426 426
427 427 .comment-button-input {
428 428 margin-right: 0;
429 429 }
430 430
431 431 .comment-footer {
432 432 margin-bottom: 50px;
433 433 margin-top: 10px;
434 434 }
435 435 }
436 436
437 437
438 438 .comment-form-login {
439 439 .comment-help {
440 440 padding: 0.7em; //same as the button
441 441 }
442 442
443 443 div.clearfix {
444 444 clear: both;
445 445 width: 100%;
446 446 display: block;
447 447 }
448 448 }
449 449
450 .comment-version-select {
451 margin: 0px;
452 border-radius: inherit;
453 border-color: @grey6;
454 height: 20px;
455 }
456
450 457 .comment-type {
451 458 margin: 0px;
452 459 border-radius: inherit;
453 460 border-color: @grey6;
454 461 }
455 462
456 463 .preview-box {
457 464 min-height: 105px;
458 465 margin-bottom: 15px;
459 466 background-color: white;
460 467 .border-radius(@border-radius);
461 468 .box-sizing(border-box);
462 469 }
463 470
464 471 .add-another-button {
465 472 margin-left: 10px;
466 473 margin-top: 10px;
467 474 margin-bottom: 10px;
468 475 }
469 476
470 477 .comment .buttons {
471 478 float: right;
472 479 margin: -1px 0px 0px 0px;
473 480 }
474 481
475 482 // Inline Comment Form
476 483 .injected_diff .comment-inline-form,
477 484 .comment-inline-form {
478 485 background-color: white;
479 486 margin-top: 10px;
480 487 margin-bottom: 20px;
481 488 }
482 489
483 490 .inline-form {
484 491 padding: 10px 7px;
485 492 }
486 493
487 494 .inline-form div {
488 495 max-width: 100%;
489 496 }
490 497
491 498 .overlay {
492 499 display: none;
493 500 position: absolute;
494 501 width: 100%;
495 502 text-align: center;
496 503 vertical-align: middle;
497 504 font-size: 16px;
498 505 background: none repeat scroll 0 0 white;
499 506
500 507 &.submitting {
501 508 display: block;
502 509 opacity: 0.5;
503 510 z-index: 100;
504 511 }
505 512 }
506 513 .comment-inline-form .overlay.submitting .overlay-text {
507 514 margin-top: 5%;
508 515 }
509 516
510 517 .comment-inline-form .clearfix,
511 518 .comment-form .clearfix {
512 519 .border-radius(@border-radius);
513 520 margin: 0px;
514 521 }
515 522
516 523 .comment-inline-form .comment-footer {
517 524 margin: 10px 0px 0px 0px;
518 525 }
519 526
520 527 .hide-inline-form-button {
521 528 margin-left: 5px;
522 529 }
523 530 .comment-button .hide-inline-form {
524 531 background: white;
525 532 }
526 533
527 534 .comment-area {
528 535 padding: 6px 8px;
529 536 border: 1px solid @grey5;
530 537 .border-radius(@border-radius);
531 538
532 539 .resolve-action {
533 540 padding: 1px 0px 0px 6px;
534 541 }
535 542
536 543 }
537 544
538 545 comment-area-text {
539 546 color: @grey3;
540 547 }
541 548
542 549 .comment-area-header {
543 550 height: 35px;
544 551 }
545 552
546 553 .comment-area-header .nav-links {
547 554 display: flex;
548 555 flex-flow: row wrap;
549 556 -webkit-flex-flow: row wrap;
550 557 width: 100%;
551 558 }
552 559
553 560 .comment-area-footer {
554 561 min-height: 30px;
555 562 }
556 563
557 564 .comment-footer .toolbar {
558 565
559 566 }
560 567
561 568 .comment-attachment-uploader {
562 569 border: 1px dashed white;
563 570 border-radius: @border-radius;
564 571 margin-top: -10px;
565 572 line-height: 30px;
566 573 &.dz-drag-hover {
567 574 border-color: @grey3;
568 575 }
569 576
570 577 .dz-error-message {
571 578 padding-top: 0;
572 579 }
573 580 }
574 581
575 582 .comment-attachment-text {
576 583 clear: both;
577 584 font-size: 11px;
578 585 color: #8F8F8F;
579 586 width: 100%;
580 587 .pick-attachment {
581 588 color: #8F8F8F;
582 589 }
583 590 .pick-attachment:hover {
584 591 color: @rcblue;
585 592 }
586 593 }
587 594
588 595 .nav-links {
589 596 padding: 0;
590 597 margin: 0;
591 598 list-style: none;
592 599 height: auto;
593 600 border-bottom: 1px solid @grey5;
594 601 }
595 602 .nav-links li {
596 603 display: inline-block;
597 604 list-style-type: none;
598 605 }
599 606
600 607 .nav-links li a.disabled {
601 608 cursor: not-allowed;
602 609 }
603 610
604 611 .nav-links li.active a {
605 612 border-bottom: 2px solid @rcblue;
606 613 color: #000;
607 614 font-weight: 600;
608 615 }
609 616 .nav-links li a {
610 617 display: inline-block;
611 618 padding: 0px 10px 5px 10px;
612 619 margin-bottom: -1px;
613 620 font-size: 14px;
614 621 line-height: 28px;
615 622 color: #8f8f8f;
616 623 border-bottom: 2px solid transparent;
617 624 }
618 625
619 626 .toolbar-text {
620 627 float: right;
621 628 font-size: 11px;
622 629 color: @grey4;
623 630 text-align: right;
624 631
625 632 a {
626 633 color: @grey4;
627 634 }
628 635 }
629 636
@@ -1,1202 +1,1200 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 this.submitButton = $(this.submitForm).find('input[type="submit"]');
128 128 this.submitButtonText = this.submitButton.val();
129 129
130 130
131 131 this.previewUrl = pyroutes.url('repo_commit_comment_preview',
132 132 {'repo_name': templateContext.repo_name,
133 133 'commit_id': templateContext.commit_data.commit_id});
134 134
135 135 if (edit){
136 136 this.submitButtonText = _gettext('Updated Comment');
137 137 $(this.commentType).prop('disabled', true);
138 138 $(this.commentType).addClass('disabled');
139 139 var editInfo =
140 '<div class="comment-label note" id="comment-label-6" title="line: ">' +
141 'editing' +
142 '</div>';
140 '';
143 141 $(editInfo).insertBefore($(this.editButton).parent());
144 142 }
145 143
146 144 if (resolvesCommentId){
147 145 this.resolvesId = '#resolve_comment_{0}'.format(resolvesCommentId);
148 146 this.resolvesActionId = '#resolve_comment_action_{0}'.format(resolvesCommentId);
149 147 $(this.commentType).prop('disabled', true);
150 148 $(this.commentType).addClass('disabled');
151 149
152 150 // disable select
153 151 setTimeout(function() {
154 152 $(self.statusChange).select2('readonly', true);
155 153 }, 10);
156 154
157 155 var resolvedInfo = (
158 156 '<li class="resolve-action">' +
159 157 '<input type="hidden" id="resolve_comment_{0}" name="resolve_comment_{0}" value="{0}">' +
160 158 '<button id="resolve_comment_action_{0}" class="resolve-text btn btn-sm" onclick="return Rhodecode.comments.submitResolution({0})">{1} #{0}</button>' +
161 159 '</li>'
162 160 ).format(resolvesCommentId, _gettext('resolve comment'));
163 161 $(resolvedInfo).insertAfter($(this.commentType).parent());
164 162 }
165 163
166 164 // based on commitId, or pullRequestId decide where do we submit
167 165 // out data
168 166 if (this.commitId){
169 167 var pyurl = 'repo_commit_comment_create';
170 168 if(edit){
171 169 pyurl = 'repo_commit_comment_edit';
172 170 }
173 171 this.submitUrl = pyroutes.url(pyurl,
174 172 {'repo_name': templateContext.repo_name,
175 173 'commit_id': this.commitId,
176 174 'comment_id': comment_id});
177 175 this.selfUrl = pyroutes.url('repo_commit',
178 176 {'repo_name': templateContext.repo_name,
179 177 'commit_id': this.commitId});
180 178
181 179 } else if (this.pullRequestId) {
182 180 var pyurl = 'pullrequest_comment_create';
183 181 if(edit){
184 182 pyurl = 'pullrequest_comment_edit';
185 183 }
186 184 this.submitUrl = pyroutes.url(pyurl,
187 185 {'repo_name': templateContext.repo_name,
188 186 'pull_request_id': this.pullRequestId,
189 187 'comment_id': comment_id});
190 188 this.selfUrl = pyroutes.url('pullrequest_show',
191 189 {'repo_name': templateContext.repo_name,
192 190 'pull_request_id': this.pullRequestId});
193 191
194 192 } else {
195 193 throw new Error(
196 194 'CommentForm requires pullRequestId, or commitId to be specified.')
197 195 }
198 196
199 197 // FUNCTIONS and helpers
200 198 var self = this;
201 199
202 200 this.isInline = function(){
203 201 return this.lineNo && this.lineNo != 'general';
204 202 };
205 203
206 204 this.getCmInstance = function(){
207 205 return this.cm
208 206 };
209 207
210 208 this.setPlaceholder = function(placeholder) {
211 209 var cm = this.getCmInstance();
212 210 if (cm){
213 211 cm.setOption('placeholder', placeholder);
214 212 }
215 213 };
216 214
217 215 this.getCommentStatus = function() {
218 216 return $(this.submitForm).find(this.statusChange).val();
219 217 };
220 218 this.getCommentType = function() {
221 219 return $(this.submitForm).find(this.commentType).val();
222 220 };
223 221
224 222 this.getResolvesId = function() {
225 223 return $(this.submitForm).find(this.resolvesId).val() || null;
226 224 };
227 225
228 226 this.getClosePr = function() {
229 227 return $(this.submitForm).find(this.closesPr).val() || null;
230 228 };
231 229
232 230 this.markCommentResolved = function(resolvedCommentId){
233 231 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolved').show();
234 232 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolve').hide();
235 233 };
236 234
237 235 this.isAllowedToSubmit = function() {
238 236 return !$(this.submitButton).prop('disabled');
239 237 };
240 238
241 239 this.initStatusChangeSelector = function(){
242 240 var formatChangeStatus = function(state, escapeMarkup) {
243 241 var originalOption = state.element;
244 242 var tmpl = '<i class="icon-circle review-status-{0}"></i><span>{1}</span>'.format($(originalOption).data('status'), escapeMarkup(state.text));
245 243 return tmpl
246 244 };
247 245 var formatResult = function(result, container, query, escapeMarkup) {
248 246 return formatChangeStatus(result, escapeMarkup);
249 247 };
250 248
251 249 var formatSelection = function(data, container, escapeMarkup) {
252 250 return formatChangeStatus(data, escapeMarkup);
253 251 };
254 252
255 253 $(this.submitForm).find(this.statusChange).select2({
256 254 placeholder: _gettext('Status Review'),
257 255 formatResult: formatResult,
258 256 formatSelection: formatSelection,
259 257 containerCssClass: "drop-menu status_box_menu",
260 258 dropdownCssClass: "drop-menu-dropdown",
261 259 dropdownAutoWidth: true,
262 260 minimumResultsForSearch: -1
263 261 });
264 262 $(this.submitForm).find(this.statusChange).on('change', function() {
265 263 var status = self.getCommentStatus();
266 264
267 265 if (status && !self.isInline()) {
268 266 $(self.submitButton).prop('disabled', false);
269 267 }
270 268
271 269 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
272 270 self.setPlaceholder(placeholderText)
273 271 })
274 272 };
275 273
276 274 // reset the comment form into it's original state
277 275 this.resetCommentFormState = function(content) {
278 276 content = content || '';
279 277
280 278 $(this.editContainer).show();
281 279 $(this.editButton).parent().addClass('active');
282 280
283 281 $(this.previewContainer).hide();
284 282 $(this.previewButton).parent().removeClass('active');
285 283
286 284 this.setActionButtonsDisabled(true);
287 285 self.cm.setValue(content);
288 286 self.cm.setOption("readOnly", false);
289 287
290 288 if (this.resolvesId) {
291 289 // destroy the resolve action
292 290 $(this.resolvesId).parent().remove();
293 291 }
294 292 // reset closingPR flag
295 293 $('.close-pr-input').remove();
296 294
297 295 $(this.statusChange).select2('readonly', false);
298 296 };
299 297
300 298 this.globalSubmitSuccessCallback = function(){
301 299 // default behaviour is to call GLOBAL hook, if it's registered.
302 300 if (window.commentFormGlobalSubmitSuccessCallback !== undefined){
303 301 commentFormGlobalSubmitSuccessCallback();
304 302 }
305 303 };
306 304
307 305 this.submitAjaxPOST = function(url, postData, successHandler, failHandler) {
308 306 return _submitAjaxPOST(url, postData, successHandler, failHandler);
309 307 };
310 308
311 309 // overwrite a submitHandler, we need to do it for inline comments
312 310 this.setHandleFormSubmit = function(callback) {
313 311 this.handleFormSubmit = callback;
314 312 };
315 313
316 314 // overwrite a submitSuccessHandler
317 315 this.setGlobalSubmitSuccessCallback = function(callback) {
318 316 this.globalSubmitSuccessCallback = callback;
319 317 };
320 318
321 319 // default handler for for submit for main comments
322 320 this.handleFormSubmit = function() {
323 321 var text = self.cm.getValue();
324 322 var status = self.getCommentStatus();
325 323 var commentType = self.getCommentType();
326 324 var resolvesCommentId = self.getResolvesId();
327 325 var closePullRequest = self.getClosePr();
328 326
329 327 if (text === "" && !status) {
330 328 return;
331 329 }
332 330
333 331 var excludeCancelBtn = false;
334 332 var submitEvent = true;
335 333 self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
336 334 self.cm.setOption("readOnly", true);
337 335
338 336 var postData = {
339 337 'text': text,
340 338 'changeset_status': status,
341 339 'comment_type': commentType,
342 340 'csrf_token': CSRF_TOKEN
343 341 };
344 342
345 343 if (resolvesCommentId) {
346 344 postData['resolves_comment_id'] = resolvesCommentId;
347 345 }
348 346
349 347 if (closePullRequest) {
350 348 postData['close_pull_request'] = true;
351 349 }
352 350
353 351 var submitSuccessCallback = function(o) {
354 352 // reload page if we change status for single commit.
355 353 if (status && self.commitId) {
356 354 location.reload(true);
357 355 } else {
358 356 $('#injected_page_comments').append(o.rendered_text);
359 357 self.resetCommentFormState();
360 358 timeagoActivate();
361 359 tooltipActivate();
362 360
363 361 // mark visually which comment was resolved
364 362 if (resolvesCommentId) {
365 363 self.markCommentResolved(resolvesCommentId);
366 364 }
367 365 }
368 366
369 367 // run global callback on submit
370 368 self.globalSubmitSuccessCallback();
371 369
372 370 };
373 371 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
374 372 var prefix = "Error while submitting comment.\n"
375 373 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
376 374 ajaxErrorSwal(message);
377 375 self.resetCommentFormState(text);
378 376 };
379 377 self.submitAjaxPOST(
380 378 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
381 379 };
382 380
383 381 this.previewSuccessCallback = function(o) {
384 382 $(self.previewBoxSelector).html(o);
385 383 $(self.previewBoxSelector).removeClass('unloaded');
386 384
387 385 // swap buttons, making preview active
388 386 $(self.previewButton).parent().addClass('active');
389 387 $(self.editButton).parent().removeClass('active');
390 388
391 389 // unlock buttons
392 390 self.setActionButtonsDisabled(false);
393 391 };
394 392
395 393 this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) {
396 394 excludeCancelBtn = excludeCancelBtn || false;
397 395 submitEvent = submitEvent || false;
398 396
399 397 $(this.editButton).prop('disabled', state);
400 398 $(this.previewButton).prop('disabled', state);
401 399
402 400 if (!excludeCancelBtn) {
403 401 $(this.cancelButton).prop('disabled', state);
404 402 }
405 403
406 404 var submitState = state;
407 405 if (!submitEvent && this.getCommentStatus() && !self.isInline()) {
408 406 // if the value of commit review status is set, we allow
409 407 // submit button, but only on Main form, isInline means inline
410 408 submitState = false
411 409 }
412 410
413 411 $(this.submitButton).prop('disabled', submitState);
414 412 if (submitEvent) {
415 413 $(this.submitButton).val(_gettext('Submitting...'));
416 414 } else {
417 415 $(this.submitButton).val(this.submitButtonText);
418 416 }
419 417
420 418 };
421 419
422 420 // lock preview/edit/submit buttons on load, but exclude cancel button
423 421 var excludeCancelBtn = true;
424 422 this.setActionButtonsDisabled(true, excludeCancelBtn);
425 423
426 424 // anonymous users don't have access to initialized CM instance
427 425 if (this.cm !== undefined){
428 426 this.cm.on('change', function(cMirror) {
429 427 if (cMirror.getValue() === "") {
430 428 self.setActionButtonsDisabled(true, excludeCancelBtn)
431 429 } else {
432 430 self.setActionButtonsDisabled(false, excludeCancelBtn)
433 431 }
434 432 });
435 433 }
436 434
437 435 $(this.editButton).on('click', function(e) {
438 436 e.preventDefault();
439 437
440 438 $(self.previewButton).parent().removeClass('active');
441 439 $(self.previewContainer).hide();
442 440
443 441 $(self.editButton).parent().addClass('active');
444 442 $(self.editContainer).show();
445 443
446 444 });
447 445
448 446 $(this.previewButton).on('click', function(e) {
449 447 e.preventDefault();
450 448 var text = self.cm.getValue();
451 449
452 450 if (text === "") {
453 451 return;
454 452 }
455 453
456 454 var postData = {
457 455 'text': text,
458 456 'renderer': templateContext.visual.default_renderer,
459 457 'csrf_token': CSRF_TOKEN
460 458 };
461 459
462 460 // lock ALL buttons on preview
463 461 self.setActionButtonsDisabled(true);
464 462
465 463 $(self.previewBoxSelector).addClass('unloaded');
466 464 $(self.previewBoxSelector).html(_gettext('Loading ...'));
467 465
468 466 $(self.editContainer).hide();
469 467 $(self.previewContainer).show();
470 468
471 469 // by default we reset state of comment preserving the text
472 470 var previewFailCallback = function(jqXHR, textStatus, errorThrown) {
473 471 var prefix = "Error while preview of comment.\n"
474 472 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
475 473 ajaxErrorSwal(message);
476 474
477 475 self.resetCommentFormState(text)
478 476 };
479 477 self.submitAjaxPOST(
480 478 self.previewUrl, postData, self.previewSuccessCallback,
481 479 previewFailCallback);
482 480
483 481 $(self.previewButton).parent().addClass('active');
484 482 $(self.editButton).parent().removeClass('active');
485 483 });
486 484
487 485 $(this.submitForm).submit(function(e) {
488 486 e.preventDefault();
489 487 var allowedToSubmit = self.isAllowedToSubmit();
490 488 if (!allowedToSubmit){
491 489 return false;
492 490 }
493 491 self.handleFormSubmit();
494 492 });
495 493
496 494 }
497 495
498 496 return CommentForm;
499 497 });
500 498
501 499 /* comments controller */
502 500 var CommentsController = function() {
503 501 var mainComment = '#text';
504 502 var self = this;
505 503
506 504 this.cancelComment = function (node) {
507 505 var $node = $(node);
508 506 var edit = $(this).attr('edit');
509 507 if (edit) {
510 508 var $general_comments = null;
511 509 var $inline_comments = $node.closest('div.inline-comments');
512 510 if (!$inline_comments.length) {
513 511 $general_comments = $('#comments');
514 512 var $comment = $general_comments.parent().find('div.comment:hidden');
515 513 // show hidden general comment form
516 514 $('#cb-comment-general-form-placeholder').show();
517 515 } else {
518 516 var $comment = $inline_comments.find('div.comment:hidden');
519 517 }
520 518 $comment.show();
521 519 }
522 520 $node.closest('.comment-inline-form').remove();
523 521 return false;
524 522 };
525 523
526 524 this.showVersion = function (node) {
527 525 var $node = $(node);
528 526 var selectedIndex = $node.context.selectedIndex;
529 527 var option = $node.find('option[value="'+ selectedIndex +'"]');
530 528 var zero_option = $node.find('option[value="0"]');
531 529 if (!option){
532 530 return;
533 531 }
534 532
535 533 // little trick to cheat onchange and allow to display the same version again
536 534 $node.context.selectedIndex = 0;
537 535 zero_option.text(selectedIndex);
538 536
539 537 var comment_history_id = option.attr('data-comment-history-id');
540 538 var comment_id = option.attr('data-comment-id');
541 539 var historyViewUrl = pyroutes.url(
542 540 'repo_commit_comment_history_view',
543 541 {
544 542 'repo_name': templateContext.repo_name,
545 543 'commit_id': comment_id,
546 544 'comment_history_id': comment_history_id,
547 545 }
548 546 );
549 547 successRenderCommit = function (data) {
550 548 SwalNoAnimation.fire({
551 549 html: data,
552 550 title: '',
553 551 });
554 552 };
555 553 failRenderCommit = function () {
556 554 SwalNoAnimation.fire({
557 html: 'Error while loading comment',
555 html: 'Error while loading comment history',
558 556 title: '',
559 557 });
560 558 };
561 559 _submitAjaxPOST(
562 560 historyViewUrl, {'csrf_token': CSRF_TOKEN}, successRenderCommit,
563 561 failRenderCommit
564 562 );
565 563 };
566 564
567 565 this.getLineNumber = function(node) {
568 566 var $node = $(node);
569 567 var lineNo = $node.closest('td').attr('data-line-no');
570 568 if (lineNo === undefined && $node.data('commentInline')){
571 569 lineNo = $node.data('commentLineNo')
572 570 }
573 571
574 572 return lineNo
575 573 };
576 574
577 575 this.scrollToComment = function(node, offset, outdated) {
578 576 if (offset === undefined) {
579 577 offset = 0;
580 578 }
581 579 var outdated = outdated || false;
582 580 var klass = outdated ? 'div.comment-outdated' : 'div.comment-current';
583 581
584 582 if (!node) {
585 583 node = $('.comment-selected');
586 584 if (!node.length) {
587 585 node = $('comment-current')
588 586 }
589 587 }
590 588
591 589 $wrapper = $(node).closest('div.comment');
592 590
593 591 // show hidden comment when referenced.
594 592 if (!$wrapper.is(':visible')){
595 593 $wrapper.show();
596 594 }
597 595
598 596 $comment = $(node).closest(klass);
599 597 $comments = $(klass);
600 598
601 599 $('.comment-selected').removeClass('comment-selected');
602 600
603 601 var nextIdx = $(klass).index($comment) + offset;
604 602 if (nextIdx >= $comments.length) {
605 603 nextIdx = 0;
606 604 }
607 605 var $next = $(klass).eq(nextIdx);
608 606
609 607 var $cb = $next.closest('.cb');
610 608 $cb.removeClass('cb-collapsed');
611 609
612 610 var $filediffCollapseState = $cb.closest('.filediff').prev();
613 611 $filediffCollapseState.prop('checked', false);
614 612 $next.addClass('comment-selected');
615 613 scrollToElement($next);
616 614 return false;
617 615 };
618 616
619 617 this.nextComment = function(node) {
620 618 return self.scrollToComment(node, 1);
621 619 };
622 620
623 621 this.prevComment = function(node) {
624 622 return self.scrollToComment(node, -1);
625 623 };
626 624
627 625 this.nextOutdatedComment = function(node) {
628 626 return self.scrollToComment(node, 1, true);
629 627 };
630 628
631 629 this.prevOutdatedComment = function(node) {
632 630 return self.scrollToComment(node, -1, true);
633 631 };
634 632
635 633 this._deleteComment = function(node) {
636 634 var $node = $(node);
637 635 var $td = $node.closest('td');
638 636 var $comment = $node.closest('.comment');
639 637 var comment_id = $comment.attr('data-comment-id');
640 638 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
641 639 var postData = {
642 640 'csrf_token': CSRF_TOKEN
643 641 };
644 642
645 643 $comment.addClass('comment-deleting');
646 644 $comment.hide('fast');
647 645
648 646 var success = function(response) {
649 647 $comment.remove();
650 648 return false;
651 649 };
652 650 var failure = function(jqXHR, textStatus, errorThrown) {
653 651 var prefix = "Error while deleting this comment.\n"
654 652 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
655 653 ajaxErrorSwal(message);
656 654
657 655 $comment.show('fast');
658 656 $comment.removeClass('comment-deleting');
659 657 return false;
660 658 };
661 659 ajaxPOST(url, postData, success, failure);
662 660 }
663 661
664 662 this.deleteComment = function(node) {
665 663 var $comment = $(node).closest('.comment');
666 664 var comment_id = $comment.attr('data-comment-id');
667 665
668 666 SwalNoAnimation.fire({
669 667 title: 'Delete this comment?',
670 668 icon: 'warning',
671 669 showCancelButton: true,
672 670 confirmButtonText: _gettext('Yes, delete comment #{0}!').format(comment_id),
673 671
674 672 }).then(function(result) {
675 673 if (result.value) {
676 674 self._deleteComment(node);
677 675 }
678 676 })
679 677 };
680 678
681 679 this.toggleWideMode = function (node) {
682 680 if ($('#content').hasClass('wrapper')) {
683 681 $('#content').removeClass("wrapper");
684 682 $('#content').addClass("wide-mode-wrapper");
685 683 $(node).addClass('btn-success');
686 684 return true
687 685 } else {
688 686 $('#content').removeClass("wide-mode-wrapper");
689 687 $('#content').addClass("wrapper");
690 688 $(node).removeClass('btn-success');
691 689 return false
692 690 }
693 691
694 692 };
695 693
696 694 this.toggleComments = function(node, show) {
697 695 var $filediff = $(node).closest('.filediff');
698 696 if (show === true) {
699 697 $filediff.removeClass('hide-comments');
700 698 } else if (show === false) {
701 699 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
702 700 $filediff.addClass('hide-comments');
703 701 } else {
704 702 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
705 703 $filediff.toggleClass('hide-comments');
706 704 }
707 705 return false;
708 706 };
709 707
710 708 this.toggleLineComments = function(node) {
711 709 self.toggleComments(node, true);
712 710 var $node = $(node);
713 711 // mark outdated comments as visible before the toggle;
714 712 $(node.closest('tr')).find('.comment-outdated').show();
715 713 $node.closest('tr').toggleClass('hide-line-comments');
716 714 };
717 715
718 716 this.createCommentForm = function(formElement, lineno, placeholderText, initAutocompleteActions, resolvesCommentId, edit, comment_id){
719 717 var pullRequestId = templateContext.pull_request_data.pull_request_id;
720 718 var commitId = templateContext.commit_data.commit_id;
721 719
722 720 var commentForm = new CommentForm(
723 721 formElement, commitId, pullRequestId, lineno, initAutocompleteActions, resolvesCommentId, edit, comment_id);
724 722 var cm = commentForm.getCmInstance();
725 723
726 724 if (resolvesCommentId){
727 725 var placeholderText = _gettext('Leave a resolution comment, or click resolve button to resolve TODO comment #{0}').format(resolvesCommentId);
728 726 }
729 727
730 728 setTimeout(function() {
731 729 // callbacks
732 730 if (cm !== undefined) {
733 731 commentForm.setPlaceholder(placeholderText);
734 732 if (commentForm.isInline()) {
735 733 cm.focus();
736 734 cm.refresh();
737 735 }
738 736 }
739 737 }, 10);
740 738
741 739 // trigger scrolldown to the resolve comment, since it might be away
742 740 // from the clicked
743 741 if (resolvesCommentId){
744 742 var actionNode = $(commentForm.resolvesActionId).offset();
745 743
746 744 setTimeout(function() {
747 745 if (actionNode) {
748 746 $('body, html').animate({scrollTop: actionNode.top}, 10);
749 747 }
750 748 }, 100);
751 749 }
752 750
753 751 // add dropzone support
754 752 var insertAttachmentText = function (cm, attachmentName, attachmentStoreUrl, isRendered) {
755 753 var renderer = templateContext.visual.default_renderer;
756 754 if (renderer == 'rst') {
757 755 var attachmentUrl = '`#{0} <{1}>`_'.format(attachmentName, attachmentStoreUrl);
758 756 if (isRendered){
759 757 attachmentUrl = '\n.. image:: {0}'.format(attachmentStoreUrl);
760 758 }
761 759 } else if (renderer == 'markdown') {
762 760 var attachmentUrl = '[{0}]({1})'.format(attachmentName, attachmentStoreUrl);
763 761 if (isRendered){
764 762 attachmentUrl = '!' + attachmentUrl;
765 763 }
766 764 } else {
767 765 var attachmentUrl = '{}'.format(attachmentStoreUrl);
768 766 }
769 767 cm.replaceRange(attachmentUrl+'\n', CodeMirror.Pos(cm.lastLine()));
770 768
771 769 return false;
772 770 };
773 771
774 772 //see: https://www.dropzonejs.com/#configuration
775 773 var storeUrl = pyroutes.url('repo_commit_comment_attachment_upload',
776 774 {'repo_name': templateContext.repo_name,
777 775 'commit_id': templateContext.commit_data.commit_id})
778 776
779 777 var previewTmpl = $(formElement).find('.comment-attachment-uploader-template').get(0);
780 778 if (previewTmpl !== undefined){
781 779 var selectLink = $(formElement).find('.pick-attachment').get(0);
782 780 $(formElement).find('.comment-attachment-uploader').dropzone({
783 781 url: storeUrl,
784 782 headers: {"X-CSRF-Token": CSRF_TOKEN},
785 783 paramName: function () {
786 784 return "attachment"
787 785 }, // The name that will be used to transfer the file
788 786 clickable: selectLink,
789 787 parallelUploads: 1,
790 788 maxFiles: 10,
791 789 maxFilesize: templateContext.attachment_store.max_file_size_mb,
792 790 uploadMultiple: false,
793 791 autoProcessQueue: true, // if false queue will not be processed automatically.
794 792 createImageThumbnails: false,
795 793 previewTemplate: previewTmpl.innerHTML,
796 794
797 795 accept: function (file, done) {
798 796 done();
799 797 },
800 798 init: function () {
801 799
802 800 this.on("sending", function (file, xhr, formData) {
803 801 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').hide();
804 802 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').show();
805 803 });
806 804
807 805 this.on("success", function (file, response) {
808 806 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').show();
809 807 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
810 808
811 809 var isRendered = false;
812 810 var ext = file.name.split('.').pop();
813 811 var imageExts = templateContext.attachment_store.image_ext;
814 812 if (imageExts.indexOf(ext) !== -1){
815 813 isRendered = true;
816 814 }
817 815
818 816 insertAttachmentText(cm, file.name, response.repo_fqn_access_path, isRendered)
819 817 });
820 818
821 819 this.on("error", function (file, errorMessage, xhr) {
822 820 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
823 821
824 822 var error = null;
825 823
826 824 if (xhr !== undefined){
827 825 var httpStatus = xhr.status + " " + xhr.statusText;
828 826 if (xhr !== undefined && xhr.status >= 500) {
829 827 error = httpStatus;
830 828 }
831 829 }
832 830
833 831 if (error === null) {
834 832 error = errorMessage.error || errorMessage || httpStatus;
835 833 }
836 834 $(file.previewElement).find('.dz-error-message').html('ERROR: {0}'.format(error));
837 835
838 836 });
839 837 }
840 838 });
841 839 }
842 840 return commentForm;
843 841 };
844 842
845 843 this.createGeneralComment = function (lineNo, placeholderText, resolvesCommentId) {
846 844
847 845 var tmpl = $('#cb-comment-general-form-template').html();
848 846 tmpl = tmpl.format(null, 'general');
849 847 var $form = $(tmpl);
850 848
851 849 var $formPlaceholder = $('#cb-comment-general-form-placeholder');
852 850 var curForm = $formPlaceholder.find('form');
853 851 if (curForm){
854 852 curForm.remove();
855 853 }
856 854 $formPlaceholder.append($form);
857 855
858 856 var _form = $($form[0]);
859 857 var autocompleteActions = ['approve', 'reject', 'as_note', 'as_todo'];
860 858 var edit = false;
861 859 var comment_id = null;
862 860 var commentForm = this.createCommentForm(
863 861 _form, lineNo, placeholderText, autocompleteActions, resolvesCommentId, edit, comment_id);
864 862 commentForm.initStatusChangeSelector();
865 863
866 864 return commentForm;
867 865 };
868 866 this.editComment = function(node) {
869 867 var $node = $(node);
870 868 var $comment = $(node).closest('.comment');
871 869 var comment_id = $comment.attr('data-comment-id');
872 870 var $form = null
873 871
874 872 var $comments = $node.closest('div.inline-comments');
875 873 var $general_comments = null;
876 874 var lineno = null;
877 875
878 876 if($comments.length){
879 877 // inline comments setup
880 878 $form = $comments.find('.comment-inline-form');
881 879 lineno = self.getLineNumber(node)
882 880 }
883 881 else{
884 882 // general comments setup
885 883 $comments = $('#comments');
886 884 $form = $comments.find('.comment-inline-form');
887 885 lineno = $comment[0].id
888 886 $('#cb-comment-general-form-placeholder').hide();
889 887 }
890 888
891 889 this.edit = true;
892 890
893 891 if (!$form.length) {
894 892
895 893 var $filediff = $node.closest('.filediff');
896 894 $filediff.removeClass('hide-comments');
897 895 var f_path = $filediff.attr('data-f-path');
898 896
899 897 // create a new HTML from template
900 898
901 899 var tmpl = $('#cb-comment-inline-form-template').html();
902 900 tmpl = tmpl.format(escapeHtml(f_path), lineno);
903 901 $form = $(tmpl);
904 902 $comment.after($form)
905 903
906 904 var _form = $($form[0]).find('form');
907 905 var autocompleteActions = ['as_note',];
908 906 var commentForm = this.createCommentForm(
909 907 _form, lineno, '', autocompleteActions, resolvesCommentId,
910 908 this.edit, comment_id);
911 909 var old_comment_text_binary = $comment.attr('data-comment-text');
912 910 var old_comment_text = b64DecodeUnicode(old_comment_text_binary);
913 911 commentForm.cm.setValue(old_comment_text);
914 912 $comment.hide();
915 913
916 914 $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({
917 915 form: _form,
918 916 parent: $comments,
919 917 lineno: lineno,
920 918 f_path: f_path}
921 919 );
922 920 // set a CUSTOM submit handler for inline comments.
923 921 commentForm.setHandleFormSubmit(function(o) {
924 922 var text = commentForm.cm.getValue();
925 923 var commentType = commentForm.getCommentType();
926 924 var resolvesCommentId = commentForm.getResolvesId();
927 925
928 926 if (text === "") {
929 927 return;
930 928 }
931 929 if (old_comment_text == text) {
932 930 SwalNoAnimation.fire({
933 931 title: 'Unable to edit comment',
934 932 html: _gettext('Comment body was not changed.'),
935 933 });
936 934 return;
937 935 }
938 936 var excludeCancelBtn = false;
939 937 var submitEvent = true;
940 938 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
941 939 commentForm.cm.setOption("readOnly", true);
942 940 var dropDown = $('#comment_history_for_comment_'+comment_id);
943 941
944 942 var version = dropDown.children().last().val()
945 943 if(!version){
946 944 version = 0;
947 945 }
948 946 var postData = {
949 947 'text': text,
950 948 'f_path': f_path,
951 949 'line': lineno,
952 950 'comment_type': commentType,
953 951 'csrf_token': CSRF_TOKEN,
954 952 'version': version,
955 953 };
956 954
957 955 var submitSuccessCallback = function(json_data) {
958 956 $form.remove();
959 957 $comment.show();
960 958 var postData = {
961 959 'text': text,
962 960 'renderer': $comment.attr('data-comment-renderer'),
963 961 'csrf_token': CSRF_TOKEN
964 962 };
965 963
966 964 var updateCommentVersionDropDown = function () {
967 965 var dropDown = $('#comment_history_for_comment_'+comment_id);
968 966 $comment.attr('data-comment-text', btoa(text));
969 967 var version = json_data['comment_version']
970 968 var option = new Option(version, version);
971 969 var $option = $(option);
972 970 $option.attr('data-comment-history-id', json_data['comment_history_id']);
973 971 $option.attr('data-comment-id', json_data['comment_id']);
974 972 dropDown.append(option);
975 973 dropDown.parent().show();
976 974 }
977 975 updateCommentVersionDropDown();
978 976 // by default we reset state of comment preserving the text
979 977 var failRenderCommit = function(jqXHR, textStatus, errorThrown) {
980 978 var prefix = "Error while editing of comment.\n"
981 979 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
982 980 ajaxErrorSwal(message);
983 981
984 982 };
985 983 var successRenderCommit = function(o){
986 984 $comment.show();
987 985 $comment[0].lastElementChild.innerHTML = o;
988 986 }
989 987 var previewUrl = pyroutes.url('repo_commit_comment_preview',
990 988 {'repo_name': templateContext.repo_name,
991 989 'commit_id': templateContext.commit_data.commit_id});
992 990
993 991 _submitAjaxPOST(
994 992 previewUrl, postData, successRenderCommit,
995 993 failRenderCommit
996 994 );
997 995
998 996 try {
999 997 var html = json_data.rendered_text;
1000 998 var lineno = json_data.line_no;
1001 999 var target_id = json_data.target_id;
1002 1000
1003 1001 $comments.find('.cb-comment-add-button').before(html);
1004 1002
1005 1003 //mark visually which comment was resolved
1006 1004 if (resolvesCommentId) {
1007 1005 commentForm.markCommentResolved(resolvesCommentId);
1008 1006 }
1009 1007
1010 1008 // run global callback on submit
1011 1009 commentForm.globalSubmitSuccessCallback();
1012 1010
1013 1011 } catch (e) {
1014 1012 console.error(e);
1015 1013 }
1016 1014
1017 1015 // re trigger the linkification of next/prev navigation
1018 1016 linkifyComments($('.inline-comment-injected'));
1019 1017 timeagoActivate();
1020 1018 tooltipActivate();
1021 1019
1022 1020 if (window.updateSticky !== undefined) {
1023 1021 // potentially our comments change the active window size, so we
1024 1022 // notify sticky elements
1025 1023 updateSticky()
1026 1024 }
1027 1025
1028 1026 commentForm.setActionButtonsDisabled(false);
1029 1027
1030 1028 };
1031 1029 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
1032 1030 var prefix = "Error while editing comment.\n"
1033 1031 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1034 1032 ajaxErrorSwal(message);
1035 1033 commentForm.resetCommentFormState(text)
1036 1034 };
1037 1035 commentForm.submitAjaxPOST(
1038 1036 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
1039 1037 });
1040 1038 }
1041 1039
1042 1040 $form.addClass('comment-inline-form-open');
1043 1041 };
1044 1042 this.createComment = function(node, resolutionComment) {
1045 1043 var resolvesCommentId = resolutionComment || null;
1046 1044 var $node = $(node);
1047 1045 var $td = $node.closest('td');
1048 1046 var $form = $td.find('.comment-inline-form');
1049 1047 this.edit = false;
1050 1048
1051 1049 if (!$form.length) {
1052 1050
1053 1051 var $filediff = $node.closest('.filediff');
1054 1052 $filediff.removeClass('hide-comments');
1055 1053 var f_path = $filediff.attr('data-f-path');
1056 1054 var lineno = self.getLineNumber(node);
1057 1055 // create a new HTML from template
1058 1056 var tmpl = $('#cb-comment-inline-form-template').html();
1059 1057 tmpl = tmpl.format(escapeHtml(f_path), lineno);
1060 1058 $form = $(tmpl);
1061 1059
1062 1060 var $comments = $td.find('.inline-comments');
1063 1061 if (!$comments.length) {
1064 1062 $comments = $(
1065 1063 $('#cb-comments-inline-container-template').html());
1066 1064 $td.append($comments);
1067 1065 }
1068 1066
1069 1067 $td.find('.cb-comment-add-button').before($form);
1070 1068
1071 1069 var placeholderText = _gettext('Leave a comment on line {0}.').format(lineno);
1072 1070 var _form = $($form[0]).find('form');
1073 1071 var autocompleteActions = ['as_note', 'as_todo'];
1074 1072 var comment_id=null;
1075 1073 var commentForm = this.createCommentForm(
1076 1074 _form, lineno, placeholderText, autocompleteActions, resolvesCommentId, this.edit, comment_id);
1077 1075
1078 1076 $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({
1079 1077 form: _form,
1080 1078 parent: $td[0],
1081 1079 lineno: lineno,
1082 1080 f_path: f_path}
1083 1081 );
1084 1082
1085 1083 // set a CUSTOM submit handler for inline comments.
1086 1084 commentForm.setHandleFormSubmit(function(o) {
1087 1085 var text = commentForm.cm.getValue();
1088 1086 var commentType = commentForm.getCommentType();
1089 1087 var resolvesCommentId = commentForm.getResolvesId();
1090 1088
1091 1089 if (text === "") {
1092 1090 return;
1093 1091 }
1094 1092
1095 1093 if (lineno === undefined) {
1096 1094 alert('missing line !');
1097 1095 return;
1098 1096 }
1099 1097 if (f_path === undefined) {
1100 1098 alert('missing file path !');
1101 1099 return;
1102 1100 }
1103 1101
1104 1102 var excludeCancelBtn = false;
1105 1103 var submitEvent = true;
1106 1104 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
1107 1105 commentForm.cm.setOption("readOnly", true);
1108 1106 var postData = {
1109 1107 'text': text,
1110 1108 'f_path': f_path,
1111 1109 'line': lineno,
1112 1110 'comment_type': commentType,
1113 1111 'csrf_token': CSRF_TOKEN
1114 1112 };
1115 1113 if (resolvesCommentId){
1116 1114 postData['resolves_comment_id'] = resolvesCommentId;
1117 1115 }
1118 1116
1119 1117 var submitSuccessCallback = function(json_data) {
1120 1118 $form.remove();
1121 1119 try {
1122 1120 var html = json_data.rendered_text;
1123 1121 var lineno = json_data.line_no;
1124 1122 var target_id = json_data.target_id;
1125 1123
1126 1124 $comments.find('.cb-comment-add-button').before(html);
1127 1125
1128 1126 //mark visually which comment was resolved
1129 1127 if (resolvesCommentId) {
1130 1128 commentForm.markCommentResolved(resolvesCommentId);
1131 1129 }
1132 1130
1133 1131 // run global callback on submit
1134 1132 commentForm.globalSubmitSuccessCallback();
1135 1133
1136 1134 } catch (e) {
1137 1135 console.error(e);
1138 1136 }
1139 1137
1140 1138 // re trigger the linkification of next/prev navigation
1141 1139 linkifyComments($('.inline-comment-injected'));
1142 1140 timeagoActivate();
1143 1141 tooltipActivate();
1144 1142
1145 1143 if (window.updateSticky !== undefined) {
1146 1144 // potentially our comments change the active window size, so we
1147 1145 // notify sticky elements
1148 1146 updateSticky()
1149 1147 }
1150 1148
1151 1149 commentForm.setActionButtonsDisabled(false);
1152 1150
1153 1151 };
1154 1152 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
1155 1153 var prefix = "Error while submitting comment.\n"
1156 1154 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1157 1155 ajaxErrorSwal(message);
1158 1156 commentForm.resetCommentFormState(text)
1159 1157 };
1160 1158 commentForm.submitAjaxPOST(
1161 1159 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
1162 1160 });
1163 1161 }
1164 1162
1165 1163 $form.addClass('comment-inline-form-open');
1166 1164 };
1167 1165
1168 1166 this.createResolutionComment = function(commentId){
1169 1167 // hide the trigger text
1170 1168 $('#resolve-comment-{0}'.format(commentId)).hide();
1171 1169
1172 1170 var comment = $('#comment-'+commentId);
1173 1171 var commentData = comment.data();
1174 1172 if (commentData.commentInline) {
1175 1173 this.createComment(comment, commentId)
1176 1174 } else {
1177 1175 Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId)
1178 1176 }
1179 1177
1180 1178 return false;
1181 1179 };
1182 1180
1183 1181 this.submitResolution = function(commentId){
1184 1182 var form = $('#resolve_comment_{0}'.format(commentId)).closest('form');
1185 1183 var commentForm = form.get(0).CommentForm;
1186 1184
1187 1185 var cm = commentForm.getCmInstance();
1188 1186 var renderer = templateContext.visual.default_renderer;
1189 1187 if (renderer == 'rst'){
1190 1188 var commentUrl = '`#{0} <{1}#comment-{0}>`_'.format(commentId, commentForm.selfUrl);
1191 1189 } else if (renderer == 'markdown') {
1192 1190 var commentUrl = '[#{0}]({1}#comment-{0})'.format(commentId, commentForm.selfUrl);
1193 1191 } else {
1194 1192 var commentUrl = '{1}#comment-{0}'.format(commentId, commentForm.selfUrl);
1195 1193 }
1196 1194
1197 1195 cm.setValue(_gettext('TODO from comment {0} was fixed.').format(commentUrl));
1198 1196 form.submit();
1199 1197 return false;
1200 1198 };
1201 1199
1202 1200 };
@@ -1,465 +1,467 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
7 7 <%!
8 8 from rhodecode.lib import html_filters
9 9 %>
10 10
11 11 <%namespace name="base" file="/base/base.mako"/>
12 12 <%def name="comment_block(comment, inline=False, active_pattern_entries=None)">
13 13 <% pr_index_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
14 14 <% latest_ver = len(getattr(c, 'versions', [])) %>
15 15 % if inline:
16 16 <% outdated_at_ver = comment.outdated_at_version(getattr(c, 'at_version_num', None)) %>
17 17 % else:
18 18 <% outdated_at_ver = comment.older_than_version(getattr(c, 'at_version_num', None)) %>
19 19 % endif
20 20
21 21 <div class="comment
22 22 ${'comment-inline' if inline else 'comment-general'}
23 23 ${'comment-outdated' if outdated_at_ver else 'comment-current'}"
24 24 id="comment-${comment.comment_id}"
25 25 line="${comment.line_no}"
26 26 data-comment-id="${comment.comment_id}"
27 27 data-comment-type="${comment.comment_type}"
28 28 data-comment-renderer="${comment.renderer}"
29 29 data-comment-text="${comment.text | html_filters.base64,n}"
30 30 data-comment-line-no="${comment.line_no}"
31 31 data-comment-inline=${h.json.dumps(inline)}
32 32 style="${'display: none;' if outdated_at_ver else ''}">
33 33
34 34 <div class="meta">
35 35 <div class="comment-type-label">
36 36 <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}" title="line: ${comment.line_no}">
37 37 % if comment.comment_type == 'todo':
38 38 % if comment.resolved:
39 39 <div class="resolved tooltip" title="${_('Resolved by comment #{}').format(comment.resolved.comment_id)}">
40 40 <a href="#comment-${comment.resolved.comment_id}">${comment.comment_type}</a>
41 41 </div>
42 42 % else:
43 43 <div class="resolved tooltip" style="display: none">
44 44 <span>${comment.comment_type}</span>
45 45 </div>
46 46 <div class="resolve tooltip" onclick="return Rhodecode.comments.createResolutionComment(${comment.comment_id});" title="${_('Click to resolve this comment')}">
47 47 ${comment.comment_type}
48 48 </div>
49 49 % endif
50 50 % else:
51 51 % if comment.resolved_comment:
52 52 fix
53 53 <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)})">
54 54 <span style="text-decoration: line-through">#${comment.resolved_comment.comment_id}</span>
55 55 </a>
56 56 % else:
57 57 ${comment.comment_type or 'note'}
58 58 % endif
59 59 % endif
60 60 </div>
61 61 </div>
62 62
63 63 <div class="author ${'author-inline' if inline else 'author-general'}">
64 64 ${base.gravatar_with_user(comment.author.email, 16, tooltip=True)}
65 65 </div>
66 66 <div class="date">
67 67 ${h.age_component(comment.modified_at, time_is_local=True)}
68 68 </div>
69 69 % if comment.history:
70 70 <div class="date">
71 <span class="comment-area-text">${_('Comment version')}:</span>
72 <select class="comment-type" id="comment_history_for_comment_${comment.comment_id}"
71 <span class="comment-area-text">${_('Edited')}:</span>
72 <select class="comment-version-select" id="comment_history_for_comment_${comment.comment_id}"
73 73 onchange="return Rhodecode.comments.showVersion(this)"
74 74 name="comment_type">
75
75 76 <option style="display: none" value="0">---</option>
76 77 %for comment_history in comment.history:
77 <option
78 data-comment-history-id="${comment_history.comment_history_id}",
79 data-comment-id="${comment.comment_id}",
80 value="${comment_history.version}">${comment_history.version}</option>
78 <option data-comment-history-id="${comment_history.comment_history_id}"
79 data-comment-id="${comment.comment_id}"
80 value="${comment_history.version}">
81 ${comment_history.version}
82 </option>
81 83 %endfor
82 84 </select>
83 85 </div>
84 86 % else:
85 87 <div class="date" style="display: none">
86 <span class="comment-area-text">${_('Comment version')}</span>
87 <select class="comment-type" id="comment_history_for_comment_${comment.comment_id}"
88 <span class="comment-area-text">${_('Edited')}</span>
89 <select class="comment-version-select" id="comment_history_for_comment_${comment.comment_id}"
88 90 onchange="return Rhodecode.comments.showVersion(this)"
89 91 name="comment_type">
90 92 <option style="display: none" value="0">---</option>
91 93 </select>
92 94 </div>
93 95 %endif
94 96 % if inline:
95 97 <span></span>
96 98 % else:
97 99 <div class="status-change">
98 100 % if comment.pull_request:
99 101 <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)}">
100 102 % if comment.status_change:
101 103 ${_('pull request !{}').format(comment.pull_request.pull_request_id)}:
102 104 % else:
103 105 ${_('pull request !{}').format(comment.pull_request.pull_request_id)}
104 106 % endif
105 107 </a>
106 108 % else:
107 109 % if comment.status_change:
108 110 ${_('Status change on commit')}:
109 111 % endif
110 112 % endif
111 113 </div>
112 114 % endif
113 115
114 116 % if comment.status_change:
115 117 <i class="icon-circle review-status-${comment.status_change[0].status}"></i>
116 118 <div title="${_('Commit status')}" class="changeset-status-lbl">
117 119 ${comment.status_change[0].status_lbl}
118 120 </div>
119 121 % endif
120 122
121 123 <a class="permalink" href="#comment-${comment.comment_id}"> &para;</a>
122 124
123 125 <div class="comment-links-block">
124 126 % if comment.pull_request and comment.pull_request.author.user_id == comment.author.user_id:
125 127 <span class="tag authortag tooltip" title="${_('Pull request author')}">
126 128 ${_('author')}
127 129 </span>
128 130 |
129 131 % endif
130 132 % if inline:
131 133 <div class="pr-version-inline">
132 134 <a href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
133 135 % if outdated_at_ver:
134 136 <code class="pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
135 137 outdated ${'v{}'.format(pr_index_ver)} |
136 138 </code>
137 139 % elif pr_index_ver:
138 140 <code class="pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
139 141 ${'v{}'.format(pr_index_ver)} |
140 142 </code>
141 143 % endif
142 144 </a>
143 145 </div>
144 146 % else:
145 147 % if comment.pull_request_version_id and pr_index_ver:
146 148 |
147 149 <div class="pr-version">
148 150 % if comment.outdated:
149 151 <a href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}">
150 152 ${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}
151 153 </a>
152 154 % else:
153 155 <div title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
154 156 <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, version=comment.pull_request_version_id)}">
155 157 <code class="pr-version-num">
156 158 ${'v{}'.format(pr_index_ver)}
157 159 </code>
158 160 </a>
159 161 </div>
160 162 % endif
161 163 </div>
162 164 % endif
163 165 % endif
164 166
165 167 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
166 168 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
167 169 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
168 170 ## permissions to delete
169 171 %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):
170 172 %if comment.comment_type == 'note':
171 173 <a onclick="return Rhodecode.comments.editComment(this);"
172 174 class="edit-comment"> ${_('Edit')}</a>
173 175 %else:
174 176 <button class="btn-link" disabled="disabled"> ${_('Edit')}</button>
175 177 %endif
176 178 | <a onclick="return Rhodecode.comments.deleteComment(this);"
177 179 class="delete-comment"> ${_('Delete')}</a>
178 180 %else:
179 181 <button class="btn-link" disabled="disabled"> ${_('Edit')}</button>
180 182 | <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
181 183 %endif
182 184 %else:
183 185 <button class="btn-link" disabled="disabled"> ${_('Edit')}</button>
184 186 | <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
185 187 %endif
186 188
187 189 % if outdated_at_ver:
188 190 | <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>
189 191 | <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>
190 192 % else:
191 193 | <a onclick="return Rhodecode.comments.prevComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous comment')}"> <i class="icon-angle-left"></i></a>
192 194 | <a onclick="return Rhodecode.comments.nextComment(this);" class="tooltip next-comment" title="${_('Jump to the next comment')}"> <i class="icon-angle-right"></i></a>
193 195 % endif
194 196
195 197 </div>
196 198 </div>
197 199 <div class="text">
198 200 ${h.render(comment.text, renderer=comment.renderer, mentions=True, repo_name=getattr(c, 'repo_name', None), active_pattern_entries=active_pattern_entries)}
199 201 </div>
200 202
201 203 </div>
202 204 </%def>
203 205
204 206 ## generate main comments
205 207 <%def name="generate_comments(comments, include_pull_request=False, is_pull_request=False)">
206 208 <%
207 209 active_pattern_entries = h.get_active_pattern_entries(getattr(c, 'repo_name', None))
208 210 %>
209 211
210 212 <div class="general-comments" id="comments">
211 213 %for comment in comments:
212 214 <div id="comment-tr-${comment.comment_id}">
213 215 ## only render comments that are not from pull request, or from
214 216 ## pull request and a status change
215 217 %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request:
216 218 ${comment_block(comment, active_pattern_entries=active_pattern_entries)}
217 219 %endif
218 220 </div>
219 221 %endfor
220 222 ## to anchor ajax comments
221 223 <div id="injected_page_comments"></div>
222 224 </div>
223 225 </%def>
224 226
225 227
226 228 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
227 229
228 230 <div class="comments">
229 231 <%
230 232 if is_pull_request:
231 233 placeholder = _('Leave a comment on this Pull Request.')
232 234 elif is_compare:
233 235 placeholder = _('Leave a comment on {} commits in this range.').format(len(form_extras))
234 236 else:
235 237 placeholder = _('Leave a comment on this Commit.')
236 238 %>
237 239
238 240 % if c.rhodecode_user.username != h.DEFAULT_USER:
239 241 <div class="js-template" id="cb-comment-general-form-template">
240 242 ## template generated for injection
241 243 ${comment_form(form_type='general', review_statuses=c.commit_statuses, form_extras=form_extras)}
242 244 </div>
243 245
244 246 <div id="cb-comment-general-form-placeholder" class="comment-form ac">
245 247 ## inject form here
246 248 </div>
247 249 <script type="text/javascript">
248 250 var lineNo = 'general';
249 251 var resolvesCommentId = null;
250 252 var generalCommentForm = Rhodecode.comments.createGeneralComment(
251 253 lineNo, "${placeholder}", resolvesCommentId);
252 254
253 255 // set custom success callback on rangeCommit
254 256 % if is_compare:
255 257 generalCommentForm.setHandleFormSubmit(function(o) {
256 258 var self = generalCommentForm;
257 259
258 260 var text = self.cm.getValue();
259 261 var status = self.getCommentStatus();
260 262 var commentType = self.getCommentType();
261 263
262 264 if (text === "" && !status) {
263 265 return;
264 266 }
265 267
266 268 // we can pick which commits we want to make the comment by
267 269 // selecting them via click on preview pane, this will alter the hidden inputs
268 270 var cherryPicked = $('#changeset_compare_view_content .compare_select.hl').length > 0;
269 271
270 272 var commitIds = [];
271 273 $('#changeset_compare_view_content .compare_select').each(function(el) {
272 274 var commitId = this.id.replace('row-', '');
273 275 if ($(this).hasClass('hl') || !cherryPicked) {
274 276 $("input[data-commit-id='{0}']".format(commitId)).val(commitId);
275 277 commitIds.push(commitId);
276 278 } else {
277 279 $("input[data-commit-id='{0}']".format(commitId)).val('')
278 280 }
279 281 });
280 282
281 283 self.setActionButtonsDisabled(true);
282 284 self.cm.setOption("readOnly", true);
283 285 var postData = {
284 286 'text': text,
285 287 'changeset_status': status,
286 288 'comment_type': commentType,
287 289 'commit_ids': commitIds,
288 290 'csrf_token': CSRF_TOKEN
289 291 };
290 292
291 293 var submitSuccessCallback = function(o) {
292 294 location.reload(true);
293 295 };
294 296 var submitFailCallback = function(){
295 297 self.resetCommentFormState(text)
296 298 };
297 299 self.submitAjaxPOST(
298 300 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
299 301 });
300 302 % endif
301 303
302 304 </script>
303 305 % else:
304 306 ## form state when not logged in
305 307 <div class="comment-form ac">
306 308
307 309 <div class="comment-area">
308 310 <div class="comment-area-header">
309 311 <ul class="nav-links clearfix">
310 312 <li class="active">
311 313 <a class="disabled" href="#edit-btn" disabled="disabled" onclick="return false">${_('Write')}</a>
312 314 </li>
313 315 <li class="">
314 316 <a class="disabled" href="#preview-btn" disabled="disabled" onclick="return false">${_('Preview')}</a>
315 317 </li>
316 318 </ul>
317 319 </div>
318 320
319 321 <div class="comment-area-write" style="display: block;">
320 322 <div id="edit-container">
321 323 <div style="padding: 40px 0">
322 324 ${_('You need to be logged in to leave comments.')}
323 325 <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
324 326 </div>
325 327 </div>
326 328 <div id="preview-container" class="clearfix" style="display: none;">
327 329 <div id="preview-box" class="preview-box"></div>
328 330 </div>
329 331 </div>
330 332
331 333 <div class="comment-area-footer">
332 334 <div class="toolbar">
333 335 <div class="toolbar-text">
334 336 </div>
335 337 </div>
336 338 </div>
337 339 </div>
338 340
339 341 <div class="comment-footer">
340 342 </div>
341 343
342 344 </div>
343 345 % endif
344 346
345 347 <script type="text/javascript">
346 348 bindToggleButtons();
347 349 </script>
348 350 </div>
349 351 </%def>
350 352
351 353
352 354 <%def name="comment_form(form_type, form_id='', lineno_id='{1}', review_statuses=None, form_extras=None)">
353 355
354 356 ## comment injected based on assumption that user is logged in
355 357 <form ${('id="{}"'.format(form_id) if form_id else '') |n} action="#" method="GET">
356 358
357 359 <div class="comment-area">
358 360 <div class="comment-area-header">
359 361 <div class="pull-left">
360 362 <ul class="nav-links clearfix">
361 363 <li class="active">
362 364 <a href="#edit-btn" tabindex="-1" id="edit-btn_${lineno_id}">${_('Write')}</a>
363 365 </li>
364 366 <li class="">
365 367 <a href="#preview-btn" tabindex="-1" id="preview-btn_${lineno_id}">${_('Preview')}</a>
366 368 </li>
367 369 </ul>
368 370 </div>
369 371 <div class="pull-right">
370 372 <span class="comment-area-text">${_('Mark as')}:</span>
371 373 <select class="comment-type" id="comment_type_${lineno_id}" name="comment_type">
372 374 % for val in c.visual.comment_types:
373 375 <option value="${val}">${val.upper()}</option>
374 376 % endfor
375 377 </select>
376 378 </div>
377 379 </div>
378 380
379 381 <div class="comment-area-write" style="display: block;">
380 382 <div id="edit-container_${lineno_id}">
381 383 <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea>
382 384 </div>
383 385 <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;">
384 386 <div id="preview-box_${lineno_id}" class="preview-box"></div>
385 387 </div>
386 388 </div>
387 389
388 390 <div class="comment-area-footer comment-attachment-uploader">
389 391 <div class="toolbar">
390 392
391 393 <div class="comment-attachment-text">
392 394 <div class="dropzone-text">
393 395 ${_("Drag'n Drop files here or")} <span class="link pick-attachment">${_('Choose your files')}</span>.<br>
394 396 </div>
395 397 <div class="dropzone-upload" style="display:none">
396 398 <i class="icon-spin animate-spin"></i> ${_('uploading...')}
397 399 </div>
398 400 </div>
399 401
400 402 ## comments dropzone template, empty on purpose
401 403 <div style="display: none" class="comment-attachment-uploader-template">
402 404 <div class="dz-file-preview" style="margin: 0">
403 405 <div class="dz-error-message"></div>
404 406 </div>
405 407 </div>
406 408
407 409 </div>
408 410 </div>
409 411 </div>
410 412
411 413 <div class="comment-footer">
412 414
413 415 ## inject extra inputs into the form
414 416 % if form_extras and isinstance(form_extras, (list, tuple)):
415 417 <div id="comment_form_extras">
416 418 % for form_ex_el in form_extras:
417 419 ${form_ex_el|n}
418 420 % endfor
419 421 </div>
420 422 % endif
421 423
422 424 <div class="action-buttons">
423 425 % if form_type != 'inline':
424 426 <div class="action-buttons-extra"></div>
425 427 % endif
426 428
427 429 <input class="btn btn-success comment-button-input" id="save_${lineno_id}" name="save" type="submit" value="${_('Comment')}">
428 430
429 431 ## inline for has a file, and line-number together with cancel hide button.
430 432 % if form_type == 'inline':
431 433 <input type="hidden" name="f_path" value="{0}">
432 434 <input type="hidden" name="line" value="${lineno_id}">
433 435 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
434 436 ${_('Cancel')}
435 437 </button>
436 438 % endif
437 439 </div>
438 440
439 441 % if review_statuses:
440 442 <div class="status_box">
441 443 <select id="change_status_${lineno_id}" name="changeset_status">
442 444 <option></option> ## Placeholder
443 445 % for status, lbl in review_statuses:
444 446 <option value="${status}" data-status="${status}">${lbl}</option>
445 447 %if is_pull_request and change_status and status in ('approved', 'rejected'):
446 448 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
447 449 %endif
448 450 % endfor
449 451 </select>
450 452 </div>
451 453 % endif
452 454
453 455 <div class="toolbar-text">
454 456 <% renderer_url = '<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper()) %>
455 457 ${_('Comments parsed using {} syntax.').format(renderer_url)|n} <br/>
456 458 <span class="tooltip" title="${_('Use @username inside this text to send notification to this RhodeCode user')}">@mention</span>
457 459 ${_('and')}
458 460 <span class="tooltip" title="${_('Start typing with / for certain actions to be triggered via text box.')}">`/` autocomplete</span>
459 461 ${_('actions supported.')}
460 462 </div>
461 463 </div>
462 464
463 465 </form>
464 466
465 467 </%def> No newline at end of file
@@ -1,7 +1,31 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 ${c.comment_history.author.email}
3 <%
4 active_pattern_entries = h.get_active_pattern_entries(getattr(c, 'repo_name', None))
5 %>
6
7 ## NOTE, inline styles are here to override the default rendering of
8 ## the swal JS dialog which this template is displayed
9
10 <div style="text-align: left;">
11
12 <div style="border-bottom: 1px solid #dbd9da; padding-bottom: 5px; height: 20px">
13
14 <div class="pull-left">
4 15 ${base.gravatar_with_user(c.comment_history.author.email, 16, tooltip=True)}
5 ${h.age_component(c.comment_history.created_on)}
6 ${c.comment_history.text}
7 ${c.comment_history.version} No newline at end of file
16 </div>
17
18 <div class="pull-right">
19 <code>edited: ${h.age_component(c.comment_history.created_on)}</code>
20 </div>
21
22 </div>
23
24 <div style="margin: 5px 0px">
25 <code>comment body at v${c.comment_history.version}:</code>
26 </div>
27 <div class="text" style="padding-top: 20px; border: 1px solid #dbd9da">
28 ${h.render(c.comment_history.text, renderer=c.comment_history.comment.renderer, mentions=True, repo_name=getattr(c, 'repo_name', None), active_pattern_entries=active_pattern_entries)}
29 </div>
30
31 </div> No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now