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