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