##// END OF EJS Templates
comments: escape file-paths on commenting to prevent html breakage
ergo -
r2182:2a2643df default
parent child Browse files
Show More
@@ -1,830 +1,830 b''
1 1 // # Copyright (C) 2010-2017 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 /* Comment form for main and inline comments */
46 46 (function(mod) {
47 47
48 48 if (typeof exports == "object" && typeof module == "object") {
49 49 // CommonJS
50 50 module.exports = mod();
51 51 }
52 52 else {
53 53 // Plain browser env
54 54 (this || window).CommentForm = mod();
55 55 }
56 56
57 57 })(function() {
58 58 "use strict";
59 59
60 60 function CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId) {
61 61 if (!(this instanceof CommentForm)) {
62 62 return new CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId);
63 63 }
64 64
65 65 // bind the element instance to our Form
66 66 $(formElement).get(0).CommentForm = this;
67 67
68 68 this.withLineNo = function(selector) {
69 69 var lineNo = this.lineNo;
70 70 if (lineNo === undefined) {
71 71 return selector
72 72 } else {
73 73 return selector + '_' + lineNo;
74 74 }
75 75 };
76 76
77 77 this.commitId = commitId;
78 78 this.pullRequestId = pullRequestId;
79 79 this.lineNo = lineNo;
80 80 this.initAutocompleteActions = initAutocompleteActions;
81 81
82 82 this.previewButton = this.withLineNo('#preview-btn');
83 83 this.previewContainer = this.withLineNo('#preview-container');
84 84
85 85 this.previewBoxSelector = this.withLineNo('#preview-box');
86 86
87 87 this.editButton = this.withLineNo('#edit-btn');
88 88 this.editContainer = this.withLineNo('#edit-container');
89 89 this.cancelButton = this.withLineNo('#cancel-btn');
90 90 this.commentType = this.withLineNo('#comment_type');
91 91
92 92 this.resolvesId = null;
93 93 this.resolvesActionId = null;
94 94
95 95 this.closesPr = '#close_pull_request';
96 96
97 97 this.cmBox = this.withLineNo('#text');
98 98 this.cm = initCommentBoxCodeMirror(this, this.cmBox, this.initAutocompleteActions);
99 99
100 100 this.statusChange = this.withLineNo('#change_status');
101 101
102 102 this.submitForm = formElement;
103 103 this.submitButton = $(this.submitForm).find('input[type="submit"]');
104 104 this.submitButtonText = this.submitButton.val();
105 105
106 106 this.previewUrl = pyroutes.url('repo_commit_comment_preview',
107 107 {'repo_name': templateContext.repo_name,
108 108 'commit_id': templateContext.commit_data.commit_id});
109 109
110 110 if (resolvesCommentId){
111 111 this.resolvesId = '#resolve_comment_{0}'.format(resolvesCommentId);
112 112 this.resolvesActionId = '#resolve_comment_action_{0}'.format(resolvesCommentId);
113 113 $(this.commentType).prop('disabled', true);
114 114 $(this.commentType).addClass('disabled');
115 115
116 116 // disable select
117 117 setTimeout(function() {
118 118 $(self.statusChange).select2('readonly', true);
119 119 }, 10);
120 120
121 121 var resolvedInfo = (
122 122 '<li class="resolve-action">' +
123 123 '<input type="hidden" id="resolve_comment_{0}" name="resolve_comment_{0}" value="{0}">' +
124 124 '<button id="resolve_comment_action_{0}" class="resolve-text btn btn-sm" onclick="return Rhodecode.comments.submitResolution({0})">{1} #{0}</button>' +
125 125 '</li>'
126 126 ).format(resolvesCommentId, _gettext('resolve comment'));
127 127 $(resolvedInfo).insertAfter($(this.commentType).parent());
128 128 }
129 129
130 130 // based on commitId, or pullRequestId decide where do we submit
131 131 // out data
132 132 if (this.commitId){
133 133 this.submitUrl = pyroutes.url('repo_commit_comment_create',
134 134 {'repo_name': templateContext.repo_name,
135 135 'commit_id': this.commitId});
136 136 this.selfUrl = pyroutes.url('repo_commit',
137 137 {'repo_name': templateContext.repo_name,
138 138 'commit_id': this.commitId});
139 139
140 140 } else if (this.pullRequestId) {
141 141 this.submitUrl = pyroutes.url('pullrequest_comment_create',
142 142 {'repo_name': templateContext.repo_name,
143 143 'pull_request_id': this.pullRequestId});
144 144 this.selfUrl = pyroutes.url('pullrequest_show',
145 145 {'repo_name': templateContext.repo_name,
146 146 'pull_request_id': this.pullRequestId});
147 147
148 148 } else {
149 149 throw new Error(
150 150 'CommentForm requires pullRequestId, or commitId to be specified.')
151 151 }
152 152
153 153 // FUNCTIONS and helpers
154 154 var self = this;
155 155
156 156 this.isInline = function(){
157 157 return this.lineNo && this.lineNo != 'general';
158 158 };
159 159
160 160 this.getCmInstance = function(){
161 161 return this.cm
162 162 };
163 163
164 164 this.setPlaceholder = function(placeholder) {
165 165 var cm = this.getCmInstance();
166 166 if (cm){
167 167 cm.setOption('placeholder', placeholder);
168 168 }
169 169 };
170 170
171 171 this.getCommentStatus = function() {
172 172 return $(this.submitForm).find(this.statusChange).val();
173 173 };
174 174 this.getCommentType = function() {
175 175 return $(this.submitForm).find(this.commentType).val();
176 176 };
177 177
178 178 this.getResolvesId = function() {
179 179 return $(this.submitForm).find(this.resolvesId).val() || null;
180 180 };
181 181
182 182 this.getClosePr = function() {
183 183 return $(this.submitForm).find(this.closesPr).val() || null;
184 184 };
185 185
186 186 this.markCommentResolved = function(resolvedCommentId){
187 187 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolved').show();
188 188 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolve').hide();
189 189 };
190 190
191 191 this.isAllowedToSubmit = function() {
192 192 return !$(this.submitButton).prop('disabled');
193 193 };
194 194
195 195 this.initStatusChangeSelector = function(){
196 196 var formatChangeStatus = function(state, escapeMarkup) {
197 197 var originalOption = state.element;
198 198 return '<div class="flag_status ' + $(originalOption).data('status') + ' pull-left"></div>' +
199 199 '<span>' + escapeMarkup(state.text) + '</span>';
200 200 };
201 201 var formatResult = function(result, container, query, escapeMarkup) {
202 202 return formatChangeStatus(result, escapeMarkup);
203 203 };
204 204
205 205 var formatSelection = function(data, container, escapeMarkup) {
206 206 return formatChangeStatus(data, escapeMarkup);
207 207 };
208 208
209 209 $(this.submitForm).find(this.statusChange).select2({
210 210 placeholder: _gettext('Status Review'),
211 211 formatResult: formatResult,
212 212 formatSelection: formatSelection,
213 213 containerCssClass: "drop-menu status_box_menu",
214 214 dropdownCssClass: "drop-menu-dropdown",
215 215 dropdownAutoWidth: true,
216 216 minimumResultsForSearch: -1
217 217 });
218 218 $(this.submitForm).find(this.statusChange).on('change', function() {
219 219 var status = self.getCommentStatus();
220 220
221 221 if (status && !self.isInline()) {
222 222 $(self.submitButton).prop('disabled', false);
223 223 }
224 224
225 225 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
226 226 self.setPlaceholder(placeholderText)
227 227 })
228 228 };
229 229
230 230 // reset the comment form into it's original state
231 231 this.resetCommentFormState = function(content) {
232 232 content = content || '';
233 233
234 234 $(this.editContainer).show();
235 235 $(this.editButton).parent().addClass('active');
236 236
237 237 $(this.previewContainer).hide();
238 238 $(this.previewButton).parent().removeClass('active');
239 239
240 240 this.setActionButtonsDisabled(true);
241 241 self.cm.setValue(content);
242 242 self.cm.setOption("readOnly", false);
243 243
244 244 if (this.resolvesId) {
245 245 // destroy the resolve action
246 246 $(this.resolvesId).parent().remove();
247 247 }
248 248 // reset closingPR flag
249 249 $('.close-pr-input').remove();
250 250
251 251 $(this.statusChange).select2('readonly', false);
252 252 };
253 253
254 254 this.globalSubmitSuccessCallback = function(){
255 255 // default behaviour is to call GLOBAL hook, if it's registered.
256 256 if (window.commentFormGlobalSubmitSuccessCallback !== undefined){
257 257 commentFormGlobalSubmitSuccessCallback()
258 258 }
259 259 };
260 260
261 261 this.submitAjaxPOST = function(url, postData, successHandler, failHandler) {
262 262 failHandler = failHandler || function() {};
263 263 var postData = toQueryString(postData);
264 264 var request = $.ajax({
265 265 url: url,
266 266 type: 'POST',
267 267 data: postData,
268 268 headers: {'X-PARTIAL-XHR': true}
269 269 })
270 270 .done(function(data) {
271 271 successHandler(data);
272 272 })
273 273 .fail(function(data, textStatus, errorThrown){
274 274 alert(
275 275 "Error while submitting comment.\n" +
276 276 "Error code {0} ({1}).".format(data.status, data.statusText));
277 277 failHandler()
278 278 });
279 279 return request;
280 280 };
281 281
282 282 // overwrite a submitHandler, we need to do it for inline comments
283 283 this.setHandleFormSubmit = function(callback) {
284 284 this.handleFormSubmit = callback;
285 285 };
286 286
287 287 // overwrite a submitSuccessHandler
288 288 this.setGlobalSubmitSuccessCallback = function(callback) {
289 289 this.globalSubmitSuccessCallback = callback;
290 290 };
291 291
292 292 // default handler for for submit for main comments
293 293 this.handleFormSubmit = function() {
294 294 var text = self.cm.getValue();
295 295 var status = self.getCommentStatus();
296 296 var commentType = self.getCommentType();
297 297 var resolvesCommentId = self.getResolvesId();
298 298 var closePullRequest = self.getClosePr();
299 299
300 300 if (text === "" && !status) {
301 301 return;
302 302 }
303 303
304 304 var excludeCancelBtn = false;
305 305 var submitEvent = true;
306 306 self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
307 307 self.cm.setOption("readOnly", true);
308 308
309 309 var postData = {
310 310 'text': text,
311 311 'changeset_status': status,
312 312 'comment_type': commentType,
313 313 'csrf_token': CSRF_TOKEN
314 314 };
315 315
316 316 if (resolvesCommentId) {
317 317 postData['resolves_comment_id'] = resolvesCommentId;
318 318 }
319 319
320 320 if (closePullRequest) {
321 321 postData['close_pull_request'] = true;
322 322 }
323 323
324 324 var submitSuccessCallback = function(o) {
325 325 // reload page if we change status for single commit.
326 326 if (status && self.commitId) {
327 327 location.reload(true);
328 328 } else {
329 329 $('#injected_page_comments').append(o.rendered_text);
330 330 self.resetCommentFormState();
331 331 timeagoActivate();
332 332
333 333 // mark visually which comment was resolved
334 334 if (resolvesCommentId) {
335 335 self.markCommentResolved(resolvesCommentId);
336 336 }
337 337 }
338 338
339 339 // run global callback on submit
340 340 self.globalSubmitSuccessCallback();
341 341
342 342 };
343 343 var submitFailCallback = function(){
344 344 self.resetCommentFormState(text);
345 345 };
346 346 self.submitAjaxPOST(
347 347 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
348 348 };
349 349
350 350 this.previewSuccessCallback = function(o) {
351 351 $(self.previewBoxSelector).html(o);
352 352 $(self.previewBoxSelector).removeClass('unloaded');
353 353
354 354 // swap buttons, making preview active
355 355 $(self.previewButton).parent().addClass('active');
356 356 $(self.editButton).parent().removeClass('active');
357 357
358 358 // unlock buttons
359 359 self.setActionButtonsDisabled(false);
360 360 };
361 361
362 362 this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) {
363 363 excludeCancelBtn = excludeCancelBtn || false;
364 364 submitEvent = submitEvent || false;
365 365
366 366 $(this.editButton).prop('disabled', state);
367 367 $(this.previewButton).prop('disabled', state);
368 368
369 369 if (!excludeCancelBtn) {
370 370 $(this.cancelButton).prop('disabled', state);
371 371 }
372 372
373 373 var submitState = state;
374 374 if (!submitEvent && this.getCommentStatus() && !self.isInline()) {
375 375 // if the value of commit review status is set, we allow
376 376 // submit button, but only on Main form, isInline means inline
377 377 submitState = false
378 378 }
379 379
380 380 $(this.submitButton).prop('disabled', submitState);
381 381 if (submitEvent) {
382 382 $(this.submitButton).val(_gettext('Submitting...'));
383 383 } else {
384 384 $(this.submitButton).val(this.submitButtonText);
385 385 }
386 386
387 387 };
388 388
389 389 // lock preview/edit/submit buttons on load, but exclude cancel button
390 390 var excludeCancelBtn = true;
391 391 this.setActionButtonsDisabled(true, excludeCancelBtn);
392 392
393 393 // anonymous users don't have access to initialized CM instance
394 394 if (this.cm !== undefined){
395 395 this.cm.on('change', function(cMirror) {
396 396 if (cMirror.getValue() === "") {
397 397 self.setActionButtonsDisabled(true, excludeCancelBtn)
398 398 } else {
399 399 self.setActionButtonsDisabled(false, excludeCancelBtn)
400 400 }
401 401 });
402 402 }
403 403
404 404 $(this.editButton).on('click', function(e) {
405 405 e.preventDefault();
406 406
407 407 $(self.previewButton).parent().removeClass('active');
408 408 $(self.previewContainer).hide();
409 409
410 410 $(self.editButton).parent().addClass('active');
411 411 $(self.editContainer).show();
412 412
413 413 });
414 414
415 415 $(this.previewButton).on('click', function(e) {
416 416 e.preventDefault();
417 417 var text = self.cm.getValue();
418 418
419 419 if (text === "") {
420 420 return;
421 421 }
422 422
423 423 var postData = {
424 424 'text': text,
425 425 'renderer': templateContext.visual.default_renderer,
426 426 'csrf_token': CSRF_TOKEN
427 427 };
428 428
429 429 // lock ALL buttons on preview
430 430 self.setActionButtonsDisabled(true);
431 431
432 432 $(self.previewBoxSelector).addClass('unloaded');
433 433 $(self.previewBoxSelector).html(_gettext('Loading ...'));
434 434
435 435 $(self.editContainer).hide();
436 436 $(self.previewContainer).show();
437 437
438 438 // by default we reset state of comment preserving the text
439 439 var previewFailCallback = function(){
440 440 self.resetCommentFormState(text)
441 441 };
442 442 self.submitAjaxPOST(
443 443 self.previewUrl, postData, self.previewSuccessCallback,
444 444 previewFailCallback);
445 445
446 446 $(self.previewButton).parent().addClass('active');
447 447 $(self.editButton).parent().removeClass('active');
448 448 });
449 449
450 450 $(this.submitForm).submit(function(e) {
451 451 e.preventDefault();
452 452 var allowedToSubmit = self.isAllowedToSubmit();
453 453 if (!allowedToSubmit){
454 454 return false;
455 455 }
456 456 self.handleFormSubmit();
457 457 });
458 458
459 459 }
460 460
461 461 return CommentForm;
462 462 });
463 463
464 464 /* comments controller */
465 465 var CommentsController = function() {
466 466 var mainComment = '#text';
467 467 var self = this;
468 468
469 469 this.cancelComment = function(node) {
470 470 var $node = $(node);
471 471 var $td = $node.closest('td');
472 472 $node.closest('.comment-inline-form').remove();
473 473 return false;
474 474 };
475 475
476 476 this.getLineNumber = function(node) {
477 477 var $node = $(node);
478 478 return $node.closest('td').attr('data-line-number');
479 479 };
480 480
481 481 this.scrollToComment = function(node, offset, outdated) {
482 482 if (offset === undefined) {
483 483 offset = 0;
484 484 }
485 485 var outdated = outdated || false;
486 486 var klass = outdated ? 'div.comment-outdated' : 'div.comment-current';
487 487
488 488 if (!node) {
489 489 node = $('.comment-selected');
490 490 if (!node.length) {
491 491 node = $('comment-current')
492 492 }
493 493 }
494 494 $wrapper = $(node).closest('div.comment');
495 495 $comment = $(node).closest(klass);
496 496 $comments = $(klass);
497 497
498 498 // show hidden comment when referenced.
499 499 if (!$wrapper.is(':visible')){
500 500 $wrapper.show();
501 501 }
502 502
503 503 $('.comment-selected').removeClass('comment-selected');
504 504
505 505 var nextIdx = $(klass).index($comment) + offset;
506 506 if (nextIdx >= $comments.length) {
507 507 nextIdx = 0;
508 508 }
509 509 var $next = $(klass).eq(nextIdx);
510 510
511 511 var $cb = $next.closest('.cb');
512 512 $cb.removeClass('cb-collapsed');
513 513
514 514 var $filediffCollapseState = $cb.closest('.filediff').prev();
515 515 $filediffCollapseState.prop('checked', false);
516 516 $next.addClass('comment-selected');
517 517 scrollToElement($next);
518 518 return false;
519 519 };
520 520
521 521 this.nextComment = function(node) {
522 522 return self.scrollToComment(node, 1);
523 523 };
524 524
525 525 this.prevComment = function(node) {
526 526 return self.scrollToComment(node, -1);
527 527 };
528 528
529 529 this.nextOutdatedComment = function(node) {
530 530 return self.scrollToComment(node, 1, true);
531 531 };
532 532
533 533 this.prevOutdatedComment = function(node) {
534 534 return self.scrollToComment(node, -1, true);
535 535 };
536 536
537 537 this.deleteComment = function(node) {
538 538 if (!confirm(_gettext('Delete this comment?'))) {
539 539 return false;
540 540 }
541 541 var $node = $(node);
542 542 var $td = $node.closest('td');
543 543 var $comment = $node.closest('.comment');
544 544 var comment_id = $comment.attr('data-comment-id');
545 545 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
546 546 var postData = {
547 547 'csrf_token': CSRF_TOKEN
548 548 };
549 549
550 550 $comment.addClass('comment-deleting');
551 551 $comment.hide('fast');
552 552
553 553 var success = function(response) {
554 554 $comment.remove();
555 555 return false;
556 556 };
557 557 var failure = function(data, textStatus, xhr) {
558 558 alert("error processing request: " + textStatus);
559 559 $comment.show('fast');
560 560 $comment.removeClass('comment-deleting');
561 561 return false;
562 562 };
563 563 ajaxPOST(url, postData, success, failure);
564 564 };
565 565
566 566 this.toggleWideMode = function (node) {
567 567 if ($('#content').hasClass('wrapper')) {
568 568 $('#content').removeClass("wrapper");
569 569 $('#content').addClass("wide-mode-wrapper");
570 570 $(node).addClass('btn-success');
571 571 } else {
572 572 $('#content').removeClass("wide-mode-wrapper");
573 573 $('#content').addClass("wrapper");
574 574 $(node).removeClass('btn-success');
575 575 }
576 576 return false;
577 577 };
578 578
579 579 this.toggleComments = function(node, show) {
580 580 var $filediff = $(node).closest('.filediff');
581 581 if (show === true) {
582 582 $filediff.removeClass('hide-comments');
583 583 } else if (show === false) {
584 584 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
585 585 $filediff.addClass('hide-comments');
586 586 } else {
587 587 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
588 588 $filediff.toggleClass('hide-comments');
589 589 }
590 590 return false;
591 591 };
592 592
593 593 this.toggleLineComments = function(node) {
594 594 self.toggleComments(node, true);
595 595 var $node = $(node);
596 596 $node.closest('tr').toggleClass('hide-line-comments');
597 597 };
598 598
599 599 this.createCommentForm = function(formElement, lineno, placeholderText, initAutocompleteActions, resolvesCommentId){
600 600 var pullRequestId = templateContext.pull_request_data.pull_request_id;
601 601 var commitId = templateContext.commit_data.commit_id;
602 602
603 603 var commentForm = new CommentForm(
604 604 formElement, commitId, pullRequestId, lineno, initAutocompleteActions, resolvesCommentId);
605 605 var cm = commentForm.getCmInstance();
606 606
607 607 if (resolvesCommentId){
608 608 var placeholderText = _gettext('Leave a comment, or click resolve button to resolve TODO comment #{0}').format(resolvesCommentId);
609 609 }
610 610
611 611 setTimeout(function() {
612 612 // callbacks
613 613 if (cm !== undefined) {
614 614 commentForm.setPlaceholder(placeholderText);
615 615 if (commentForm.isInline()) {
616 616 cm.focus();
617 617 cm.refresh();
618 618 }
619 619 }
620 620 }, 10);
621 621
622 622 // trigger scrolldown to the resolve comment, since it might be away
623 623 // from the clicked
624 624 if (resolvesCommentId){
625 625 var actionNode = $(commentForm.resolvesActionId).offset();
626 626
627 627 setTimeout(function() {
628 628 if (actionNode) {
629 629 $('body, html').animate({scrollTop: actionNode.top}, 10);
630 630 }
631 631 }, 100);
632 632 }
633 633
634 634 return commentForm;
635 635 };
636 636
637 637 this.createGeneralComment = function (lineNo, placeholderText, resolvesCommentId) {
638 638
639 639 var tmpl = $('#cb-comment-general-form-template').html();
640 640 tmpl = tmpl.format(null, 'general');
641 641 var $form = $(tmpl);
642 642
643 643 var $formPlaceholder = $('#cb-comment-general-form-placeholder');
644 644 var curForm = $formPlaceholder.find('form');
645 645 if (curForm){
646 646 curForm.remove();
647 647 }
648 648 $formPlaceholder.append($form);
649 649
650 650 var _form = $($form[0]);
651 651 var autocompleteActions = ['approve', 'reject', 'as_note', 'as_todo'];
652 652 var commentForm = this.createCommentForm(
653 653 _form, lineNo, placeholderText, autocompleteActions, resolvesCommentId);
654 654 commentForm.initStatusChangeSelector();
655 655
656 656 return commentForm;
657 657 };
658 658
659 659 this.createComment = function(node, resolutionComment) {
660 660 var resolvesCommentId = resolutionComment || null;
661 661 var $node = $(node);
662 662 var $td = $node.closest('td');
663 663 var $form = $td.find('.comment-inline-form');
664 664
665 665 if (!$form.length) {
666 666
667 667 var $filediff = $node.closest('.filediff');
668 668 $filediff.removeClass('hide-comments');
669 669 var f_path = $filediff.attr('data-f-path');
670 670 var lineno = self.getLineNumber(node);
671 671 // create a new HTML from template
672 672 var tmpl = $('#cb-comment-inline-form-template').html();
673 tmpl = tmpl.format(f_path, lineno);
673 tmpl = tmpl.format(escapeHtml(f_path), lineno);
674 674 $form = $(tmpl);
675 675
676 676 var $comments = $td.find('.inline-comments');
677 677 if (!$comments.length) {
678 678 $comments = $(
679 679 $('#cb-comments-inline-container-template').html());
680 680 $td.append($comments);
681 681 }
682 682
683 683 $td.find('.cb-comment-add-button').before($form);
684 684
685 685 var placeholderText = _gettext('Leave a comment on line {0}.').format(lineno);
686 686 var _form = $($form[0]).find('form');
687 687 var autocompleteActions = ['as_note', 'as_todo'];
688 688 var commentForm = this.createCommentForm(
689 689 _form, lineno, placeholderText, autocompleteActions, resolvesCommentId);
690 690
691 691 $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({
692 692 form: _form,
693 693 parent: $td[0],
694 694 lineno: lineno,
695 695 f_path: f_path}
696 696 );
697 697
698 698 // set a CUSTOM submit handler for inline comments.
699 699 commentForm.setHandleFormSubmit(function(o) {
700 700 var text = commentForm.cm.getValue();
701 701 var commentType = commentForm.getCommentType();
702 702 var resolvesCommentId = commentForm.getResolvesId();
703 703
704 704 if (text === "") {
705 705 return;
706 706 }
707 707
708 708 if (lineno === undefined) {
709 709 alert('missing line !');
710 710 return;
711 711 }
712 712 if (f_path === undefined) {
713 713 alert('missing file path !');
714 714 return;
715 715 }
716 716
717 717 var excludeCancelBtn = false;
718 718 var submitEvent = true;
719 719 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
720 720 commentForm.cm.setOption("readOnly", true);
721 721 var postData = {
722 722 'text': text,
723 723 'f_path': f_path,
724 724 'line': lineno,
725 725 'comment_type': commentType,
726 726 'csrf_token': CSRF_TOKEN
727 727 };
728 728 if (resolvesCommentId){
729 729 postData['resolves_comment_id'] = resolvesCommentId;
730 730 }
731 731
732 732 var submitSuccessCallback = function(json_data) {
733 733 $form.remove();
734 734 try {
735 735 var html = json_data.rendered_text;
736 736 var lineno = json_data.line_no;
737 737 var target_id = json_data.target_id;
738 738
739 739 $comments.find('.cb-comment-add-button').before(html);
740 740
741 741 //mark visually which comment was resolved
742 742 if (resolvesCommentId) {
743 743 commentForm.markCommentResolved(resolvesCommentId);
744 744 }
745 745
746 746 // run global callback on submit
747 747 commentForm.globalSubmitSuccessCallback();
748 748
749 749 } catch (e) {
750 750 console.error(e);
751 751 }
752 752
753 753 // re trigger the linkification of next/prev navigation
754 754 linkifyComments($('.inline-comment-injected'));
755 755 timeagoActivate();
756 756 commentForm.setActionButtonsDisabled(false);
757 757
758 758 };
759 759 var submitFailCallback = function(){
760 760 commentForm.resetCommentFormState(text)
761 761 };
762 762 commentForm.submitAjaxPOST(
763 763 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
764 764 });
765 765 }
766 766
767 767 $form.addClass('comment-inline-form-open');
768 768 };
769 769
770 770 this.createResolutionComment = function(commentId){
771 771 // hide the trigger text
772 772 $('#resolve-comment-{0}'.format(commentId)).hide();
773 773
774 774 var comment = $('#comment-'+commentId);
775 775 var commentData = comment.data();
776 776 if (commentData.commentInline) {
777 777 this.createComment(comment, commentId)
778 778 } else {
779 779 Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId)
780 780 }
781 781
782 782 return false;
783 783 };
784 784
785 785 this.submitResolution = function(commentId){
786 786 var form = $('#resolve_comment_{0}'.format(commentId)).closest('form');
787 787 var commentForm = form.get(0).CommentForm;
788 788
789 789 var cm = commentForm.getCmInstance();
790 790 var renderer = templateContext.visual.default_renderer;
791 791 if (renderer == 'rst'){
792 792 var commentUrl = '`#{0} <{1}#comment-{0}>`_'.format(commentId, commentForm.selfUrl);
793 793 } else if (renderer == 'markdown') {
794 794 var commentUrl = '[#{0}]({1}#comment-{0})'.format(commentId, commentForm.selfUrl);
795 795 } else {
796 796 var commentUrl = '{1}#comment-{0}'.format(commentId, commentForm.selfUrl);
797 797 }
798 798
799 799 cm.setValue(_gettext('TODO from comment {0} was fixed.').format(commentUrl));
800 800 form.submit();
801 801 return false;
802 802 };
803 803
804 804 this.renderInlineComments = function(file_comments) {
805 805 show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true;
806 806
807 807 for (var i = 0; i < file_comments.length; i++) {
808 808 var box = file_comments[i];
809 809
810 810 var target_id = $(box).attr('target_id');
811 811
812 812 // actually comments with line numbers
813 813 var comments = box.children;
814 814
815 815 for (var j = 0; j < comments.length; j++) {
816 816 var data = {
817 817 'rendered_text': comments[j].outerHTML,
818 818 'line_no': $(comments[j]).attr('line'),
819 819 'target_id': target_id
820 820 };
821 821 }
822 822 }
823 823
824 824 // since order of injection is random, we're now re-iterating
825 825 // from correct order and filling in links
826 826 linkifyComments($('.inline-comment-injected'));
827 827 firefoxAnchorFix();
828 828 };
829 829
830 830 };
General Comments 0
You need to be logged in to leave comments. Login now