##// END OF EJS Templates
uploads: handle server errors nicer
marcink -
r3994:0ef8f167 default
parent child Browse files
Show More
@@ -1,927 +1,927 b''
1 1 // # Copyright (C) 2010-2019 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) {
84 84 if (!(this instanceof CommentForm)) {
85 85 return new CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId);
86 86 }
87 87
88 88 // bind the element instance to our Form
89 89 $(formElement).get(0).CommentForm = this;
90 90
91 91 this.withLineNo = function(selector) {
92 92 var lineNo = this.lineNo;
93 93 if (lineNo === undefined) {
94 94 return selector
95 95 } else {
96 96 return selector + '_' + lineNo;
97 97 }
98 98 };
99 99
100 100 this.commitId = commitId;
101 101 this.pullRequestId = pullRequestId;
102 102 this.lineNo = lineNo;
103 103 this.initAutocompleteActions = initAutocompleteActions;
104 104
105 105 this.previewButton = this.withLineNo('#preview-btn');
106 106 this.previewContainer = this.withLineNo('#preview-container');
107 107
108 108 this.previewBoxSelector = this.withLineNo('#preview-box');
109 109
110 110 this.editButton = this.withLineNo('#edit-btn');
111 111 this.editContainer = this.withLineNo('#edit-container');
112 112 this.cancelButton = this.withLineNo('#cancel-btn');
113 113 this.commentType = this.withLineNo('#comment_type');
114 114
115 115 this.resolvesId = null;
116 116 this.resolvesActionId = null;
117 117
118 118 this.closesPr = '#close_pull_request';
119 119
120 120 this.cmBox = this.withLineNo('#text');
121 121 this.cm = initCommentBoxCodeMirror(this, this.cmBox, this.initAutocompleteActions);
122 122
123 123 this.statusChange = this.withLineNo('#change_status');
124 124
125 125 this.submitForm = formElement;
126 126 this.submitButton = $(this.submitForm).find('input[type="submit"]');
127 127 this.submitButtonText = this.submitButton.val();
128 128
129 129 this.previewUrl = pyroutes.url('repo_commit_comment_preview',
130 130 {'repo_name': templateContext.repo_name,
131 131 'commit_id': templateContext.commit_data.commit_id});
132 132
133 133 if (resolvesCommentId){
134 134 this.resolvesId = '#resolve_comment_{0}'.format(resolvesCommentId);
135 135 this.resolvesActionId = '#resolve_comment_action_{0}'.format(resolvesCommentId);
136 136 $(this.commentType).prop('disabled', true);
137 137 $(this.commentType).addClass('disabled');
138 138
139 139 // disable select
140 140 setTimeout(function() {
141 141 $(self.statusChange).select2('readonly', true);
142 142 }, 10);
143 143
144 144 var resolvedInfo = (
145 145 '<li class="resolve-action">' +
146 146 '<input type="hidden" id="resolve_comment_{0}" name="resolve_comment_{0}" value="{0}">' +
147 147 '<button id="resolve_comment_action_{0}" class="resolve-text btn btn-sm" onclick="return Rhodecode.comments.submitResolution({0})">{1} #{0}</button>' +
148 148 '</li>'
149 149 ).format(resolvesCommentId, _gettext('resolve comment'));
150 150 $(resolvedInfo).insertAfter($(this.commentType).parent());
151 151 }
152 152
153 153 // based on commitId, or pullRequestId decide where do we submit
154 154 // out data
155 155 if (this.commitId){
156 156 this.submitUrl = pyroutes.url('repo_commit_comment_create',
157 157 {'repo_name': templateContext.repo_name,
158 158 'commit_id': this.commitId});
159 159 this.selfUrl = pyroutes.url('repo_commit',
160 160 {'repo_name': templateContext.repo_name,
161 161 'commit_id': this.commitId});
162 162
163 163 } else if (this.pullRequestId) {
164 164 this.submitUrl = pyroutes.url('pullrequest_comment_create',
165 165 {'repo_name': templateContext.repo_name,
166 166 'pull_request_id': this.pullRequestId});
167 167 this.selfUrl = pyroutes.url('pullrequest_show',
168 168 {'repo_name': templateContext.repo_name,
169 169 'pull_request_id': this.pullRequestId});
170 170
171 171 } else {
172 172 throw new Error(
173 173 'CommentForm requires pullRequestId, or commitId to be specified.')
174 174 }
175 175
176 176 // FUNCTIONS and helpers
177 177 var self = this;
178 178
179 179 this.isInline = function(){
180 180 return this.lineNo && this.lineNo != 'general';
181 181 };
182 182
183 183 this.getCmInstance = function(){
184 184 return this.cm
185 185 };
186 186
187 187 this.setPlaceholder = function(placeholder) {
188 188 var cm = this.getCmInstance();
189 189 if (cm){
190 190 cm.setOption('placeholder', placeholder);
191 191 }
192 192 };
193 193
194 194 this.getCommentStatus = function() {
195 195 return $(this.submitForm).find(this.statusChange).val();
196 196 };
197 197 this.getCommentType = function() {
198 198 return $(this.submitForm).find(this.commentType).val();
199 199 };
200 200
201 201 this.getResolvesId = function() {
202 202 return $(this.submitForm).find(this.resolvesId).val() || null;
203 203 };
204 204
205 205 this.getClosePr = function() {
206 206 return $(this.submitForm).find(this.closesPr).val() || null;
207 207 };
208 208
209 209 this.markCommentResolved = function(resolvedCommentId){
210 210 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolved').show();
211 211 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolve').hide();
212 212 };
213 213
214 214 this.isAllowedToSubmit = function() {
215 215 return !$(this.submitButton).prop('disabled');
216 216 };
217 217
218 218 this.initStatusChangeSelector = function(){
219 219 var formatChangeStatus = function(state, escapeMarkup) {
220 220 var originalOption = state.element;
221 221 var tmpl = '<i class="icon-circle review-status-{0}"></i><span>{1}</span>'.format($(originalOption).data('status'), escapeMarkup(state.text));
222 222 return tmpl
223 223 };
224 224 var formatResult = function(result, container, query, escapeMarkup) {
225 225 return formatChangeStatus(result, escapeMarkup);
226 226 };
227 227
228 228 var formatSelection = function(data, container, escapeMarkup) {
229 229 return formatChangeStatus(data, escapeMarkup);
230 230 };
231 231
232 232 $(this.submitForm).find(this.statusChange).select2({
233 233 placeholder: _gettext('Status Review'),
234 234 formatResult: formatResult,
235 235 formatSelection: formatSelection,
236 236 containerCssClass: "drop-menu status_box_menu",
237 237 dropdownCssClass: "drop-menu-dropdown",
238 238 dropdownAutoWidth: true,
239 239 minimumResultsForSearch: -1
240 240 });
241 241 $(this.submitForm).find(this.statusChange).on('change', function() {
242 242 var status = self.getCommentStatus();
243 243
244 244 if (status && !self.isInline()) {
245 245 $(self.submitButton).prop('disabled', false);
246 246 }
247 247
248 248 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
249 249 self.setPlaceholder(placeholderText)
250 250 })
251 251 };
252 252
253 253 // reset the comment form into it's original state
254 254 this.resetCommentFormState = function(content) {
255 255 content = content || '';
256 256
257 257 $(this.editContainer).show();
258 258 $(this.editButton).parent().addClass('active');
259 259
260 260 $(this.previewContainer).hide();
261 261 $(this.previewButton).parent().removeClass('active');
262 262
263 263 this.setActionButtonsDisabled(true);
264 264 self.cm.setValue(content);
265 265 self.cm.setOption("readOnly", false);
266 266
267 267 if (this.resolvesId) {
268 268 // destroy the resolve action
269 269 $(this.resolvesId).parent().remove();
270 270 }
271 271 // reset closingPR flag
272 272 $('.close-pr-input').remove();
273 273
274 274 $(this.statusChange).select2('readonly', false);
275 275 };
276 276
277 277 this.globalSubmitSuccessCallback = function(){
278 278 // default behaviour is to call GLOBAL hook, if it's registered.
279 279 if (window.commentFormGlobalSubmitSuccessCallback !== undefined){
280 280 commentFormGlobalSubmitSuccessCallback()
281 281 }
282 282 };
283 283
284 284 this.submitAjaxPOST = function(url, postData, successHandler, failHandler) {
285 285 return _submitAjaxPOST(url, postData, successHandler, failHandler);
286 286 };
287 287
288 288 // overwrite a submitHandler, we need to do it for inline comments
289 289 this.setHandleFormSubmit = function(callback) {
290 290 this.handleFormSubmit = callback;
291 291 };
292 292
293 293 // overwrite a submitSuccessHandler
294 294 this.setGlobalSubmitSuccessCallback = function(callback) {
295 295 this.globalSubmitSuccessCallback = callback;
296 296 };
297 297
298 298 // default handler for for submit for main comments
299 299 this.handleFormSubmit = function() {
300 300 var text = self.cm.getValue();
301 301 var status = self.getCommentStatus();
302 302 var commentType = self.getCommentType();
303 303 var resolvesCommentId = self.getResolvesId();
304 304 var closePullRequest = self.getClosePr();
305 305
306 306 if (text === "" && !status) {
307 307 return;
308 308 }
309 309
310 310 var excludeCancelBtn = false;
311 311 var submitEvent = true;
312 312 self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
313 313 self.cm.setOption("readOnly", true);
314 314
315 315 var postData = {
316 316 'text': text,
317 317 'changeset_status': status,
318 318 'comment_type': commentType,
319 319 'csrf_token': CSRF_TOKEN
320 320 };
321 321
322 322 if (resolvesCommentId) {
323 323 postData['resolves_comment_id'] = resolvesCommentId;
324 324 }
325 325
326 326 if (closePullRequest) {
327 327 postData['close_pull_request'] = true;
328 328 }
329 329
330 330 var submitSuccessCallback = function(o) {
331 331 // reload page if we change status for single commit.
332 332 if (status && self.commitId) {
333 333 location.reload(true);
334 334 } else {
335 335 $('#injected_page_comments').append(o.rendered_text);
336 336 self.resetCommentFormState();
337 337 timeagoActivate();
338 338
339 339 // mark visually which comment was resolved
340 340 if (resolvesCommentId) {
341 341 self.markCommentResolved(resolvesCommentId);
342 342 }
343 343 }
344 344
345 345 // run global callback on submit
346 346 self.globalSubmitSuccessCallback();
347 347
348 348 };
349 349 var submitFailCallback = function(data) {
350 350 alert(
351 351 "Error while submitting comment.\n" +
352 352 "Error code {0} ({1}).".format(data.status, data.statusText)
353 353 );
354 354 self.resetCommentFormState(text);
355 355 };
356 356 self.submitAjaxPOST(
357 357 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
358 358 };
359 359
360 360 this.previewSuccessCallback = function(o) {
361 361 $(self.previewBoxSelector).html(o);
362 362 $(self.previewBoxSelector).removeClass('unloaded');
363 363
364 364 // swap buttons, making preview active
365 365 $(self.previewButton).parent().addClass('active');
366 366 $(self.editButton).parent().removeClass('active');
367 367
368 368 // unlock buttons
369 369 self.setActionButtonsDisabled(false);
370 370 };
371 371
372 372 this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) {
373 373 excludeCancelBtn = excludeCancelBtn || false;
374 374 submitEvent = submitEvent || false;
375 375
376 376 $(this.editButton).prop('disabled', state);
377 377 $(this.previewButton).prop('disabled', state);
378 378
379 379 if (!excludeCancelBtn) {
380 380 $(this.cancelButton).prop('disabled', state);
381 381 }
382 382
383 383 var submitState = state;
384 384 if (!submitEvent && this.getCommentStatus() && !self.isInline()) {
385 385 // if the value of commit review status is set, we allow
386 386 // submit button, but only on Main form, isInline means inline
387 387 submitState = false
388 388 }
389 389
390 390 $(this.submitButton).prop('disabled', submitState);
391 391 if (submitEvent) {
392 392 $(this.submitButton).val(_gettext('Submitting...'));
393 393 } else {
394 394 $(this.submitButton).val(this.submitButtonText);
395 395 }
396 396
397 397 };
398 398
399 399 // lock preview/edit/submit buttons on load, but exclude cancel button
400 400 var excludeCancelBtn = true;
401 401 this.setActionButtonsDisabled(true, excludeCancelBtn);
402 402
403 403 // anonymous users don't have access to initialized CM instance
404 404 if (this.cm !== undefined){
405 405 this.cm.on('change', function(cMirror) {
406 406 if (cMirror.getValue() === "") {
407 407 self.setActionButtonsDisabled(true, excludeCancelBtn)
408 408 } else {
409 409 self.setActionButtonsDisabled(false, excludeCancelBtn)
410 410 }
411 411 });
412 412 }
413 413
414 414 $(this.editButton).on('click', function(e) {
415 415 e.preventDefault();
416 416
417 417 $(self.previewButton).parent().removeClass('active');
418 418 $(self.previewContainer).hide();
419 419
420 420 $(self.editButton).parent().addClass('active');
421 421 $(self.editContainer).show();
422 422
423 423 });
424 424
425 425 $(this.previewButton).on('click', function(e) {
426 426 e.preventDefault();
427 427 var text = self.cm.getValue();
428 428
429 429 if (text === "") {
430 430 return;
431 431 }
432 432
433 433 var postData = {
434 434 'text': text,
435 435 'renderer': templateContext.visual.default_renderer,
436 436 'csrf_token': CSRF_TOKEN
437 437 };
438 438
439 439 // lock ALL buttons on preview
440 440 self.setActionButtonsDisabled(true);
441 441
442 442 $(self.previewBoxSelector).addClass('unloaded');
443 443 $(self.previewBoxSelector).html(_gettext('Loading ...'));
444 444
445 445 $(self.editContainer).hide();
446 446 $(self.previewContainer).show();
447 447
448 448 // by default we reset state of comment preserving the text
449 449 var previewFailCallback = function(data){
450 450 alert(
451 451 "Error while preview of comment.\n" +
452 452 "Error code {0} ({1}).".format(data.status, data.statusText)
453 453 );
454 454 self.resetCommentFormState(text)
455 455 };
456 456 self.submitAjaxPOST(
457 457 self.previewUrl, postData, self.previewSuccessCallback,
458 458 previewFailCallback);
459 459
460 460 $(self.previewButton).parent().addClass('active');
461 461 $(self.editButton).parent().removeClass('active');
462 462 });
463 463
464 464 $(this.submitForm).submit(function(e) {
465 465 e.preventDefault();
466 466 var allowedToSubmit = self.isAllowedToSubmit();
467 467 if (!allowedToSubmit){
468 468 return false;
469 469 }
470 470 self.handleFormSubmit();
471 471 });
472 472
473 473 }
474 474
475 475 return CommentForm;
476 476 });
477 477
478 478 /* comments controller */
479 479 var CommentsController = function() {
480 480 var mainComment = '#text';
481 481 var self = this;
482 482
483 483 this.cancelComment = function(node) {
484 484 var $node = $(node);
485 485 var $td = $node.closest('td');
486 486 $node.closest('.comment-inline-form').remove();
487 487 return false;
488 488 };
489 489
490 490 this.getLineNumber = function(node) {
491 491 var $node = $(node);
492 492 var lineNo = $node.closest('td').attr('data-line-no');
493 493 if (lineNo === undefined && $node.data('commentInline')){
494 494 lineNo = $node.data('commentLineNo')
495 495 }
496 496
497 497 return lineNo
498 498 };
499 499
500 500 this.scrollToComment = function(node, offset, outdated) {
501 501 if (offset === undefined) {
502 502 offset = 0;
503 503 }
504 504 var outdated = outdated || false;
505 505 var klass = outdated ? 'div.comment-outdated' : 'div.comment-current';
506 506
507 507 if (!node) {
508 508 node = $('.comment-selected');
509 509 if (!node.length) {
510 510 node = $('comment-current')
511 511 }
512 512 }
513 513 $wrapper = $(node).closest('div.comment');
514 514 $comment = $(node).closest(klass);
515 515 $comments = $(klass);
516 516
517 517 // show hidden comment when referenced.
518 518 if (!$wrapper.is(':visible')){
519 519 $wrapper.show();
520 520 }
521 521
522 522 $('.comment-selected').removeClass('comment-selected');
523 523
524 524 var nextIdx = $(klass).index($comment) + offset;
525 525 if (nextIdx >= $comments.length) {
526 526 nextIdx = 0;
527 527 }
528 528 var $next = $(klass).eq(nextIdx);
529 529
530 530 var $cb = $next.closest('.cb');
531 531 $cb.removeClass('cb-collapsed');
532 532
533 533 var $filediffCollapseState = $cb.closest('.filediff').prev();
534 534 $filediffCollapseState.prop('checked', false);
535 535 $next.addClass('comment-selected');
536 536 scrollToElement($next);
537 537 return false;
538 538 };
539 539
540 540 this.nextComment = function(node) {
541 541 return self.scrollToComment(node, 1);
542 542 };
543 543
544 544 this.prevComment = function(node) {
545 545 return self.scrollToComment(node, -1);
546 546 };
547 547
548 548 this.nextOutdatedComment = function(node) {
549 549 return self.scrollToComment(node, 1, true);
550 550 };
551 551
552 552 this.prevOutdatedComment = function(node) {
553 553 return self.scrollToComment(node, -1, true);
554 554 };
555 555
556 556 this.deleteComment = function(node) {
557 557 if (!confirm(_gettext('Delete this comment?'))) {
558 558 return false;
559 559 }
560 560 var $node = $(node);
561 561 var $td = $node.closest('td');
562 562 var $comment = $node.closest('.comment');
563 563 var comment_id = $comment.attr('data-comment-id');
564 564 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
565 565 var postData = {
566 566 'csrf_token': CSRF_TOKEN
567 567 };
568 568
569 569 $comment.addClass('comment-deleting');
570 570 $comment.hide('fast');
571 571
572 572 var success = function(response) {
573 573 $comment.remove();
574 574 return false;
575 575 };
576 576 var failure = function(data, textStatus, xhr) {
577 577 alert("error processing request: " + textStatus);
578 578 $comment.show('fast');
579 579 $comment.removeClass('comment-deleting');
580 580 return false;
581 581 };
582 582 ajaxPOST(url, postData, success, failure);
583 583 };
584 584
585 585 this.toggleWideMode = function (node) {
586 586 if ($('#content').hasClass('wrapper')) {
587 587 $('#content').removeClass("wrapper");
588 588 $('#content').addClass("wide-mode-wrapper");
589 589 $(node).addClass('btn-success');
590 590 return true
591 591 } else {
592 592 $('#content').removeClass("wide-mode-wrapper");
593 593 $('#content').addClass("wrapper");
594 594 $(node).removeClass('btn-success');
595 595 return false
596 596 }
597 597
598 598 };
599 599
600 600 this.toggleComments = function(node, show) {
601 601 var $filediff = $(node).closest('.filediff');
602 602 if (show === true) {
603 603 $filediff.removeClass('hide-comments');
604 604 } else if (show === false) {
605 605 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
606 606 $filediff.addClass('hide-comments');
607 607 } else {
608 608 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
609 609 $filediff.toggleClass('hide-comments');
610 610 }
611 611 return false;
612 612 };
613 613
614 614 this.toggleLineComments = function(node) {
615 615 self.toggleComments(node, true);
616 616 var $node = $(node);
617 617 // mark outdated comments as visible before the toggle;
618 618 $(node.closest('tr')).find('.comment-outdated').show();
619 619 $node.closest('tr').toggleClass('hide-line-comments');
620 620 };
621 621
622 622 this.createCommentForm = function(formElement, lineno, placeholderText, initAutocompleteActions, resolvesCommentId){
623 623 var pullRequestId = templateContext.pull_request_data.pull_request_id;
624 624 var commitId = templateContext.commit_data.commit_id;
625 625
626 626 var commentForm = new CommentForm(
627 627 formElement, commitId, pullRequestId, lineno, initAutocompleteActions, resolvesCommentId);
628 628 var cm = commentForm.getCmInstance();
629 629
630 630 if (resolvesCommentId){
631 631 var placeholderText = _gettext('Leave a comment, or click resolve button to resolve TODO comment #{0}').format(resolvesCommentId);
632 632 }
633 633
634 634 setTimeout(function() {
635 635 // callbacks
636 636 if (cm !== undefined) {
637 637 commentForm.setPlaceholder(placeholderText);
638 638 if (commentForm.isInline()) {
639 639 cm.focus();
640 640 cm.refresh();
641 641 }
642 642 }
643 643 }, 10);
644 644
645 645 // trigger scrolldown to the resolve comment, since it might be away
646 646 // from the clicked
647 647 if (resolvesCommentId){
648 648 var actionNode = $(commentForm.resolvesActionId).offset();
649 649
650 650 setTimeout(function() {
651 651 if (actionNode) {
652 652 $('body, html').animate({scrollTop: actionNode.top}, 10);
653 653 }
654 654 }, 100);
655 655 }
656 656
657 657 // add dropzone support
658 658 var insertAttachmentText = function (cm, attachmentName, attachmentStoreUrl, isRendered) {
659 659 var renderer = templateContext.visual.default_renderer;
660 660 if (renderer == 'rst') {
661 661 var attachmentUrl = '`#{0} <{1}>`_'.format(attachmentName, attachmentStoreUrl);
662 662 if (isRendered){
663 663 attachmentUrl = '\n.. image:: {0}'.format(attachmentStoreUrl);
664 664 }
665 665 } else if (renderer == 'markdown') {
666 666 var attachmentUrl = '[{0}]({1})'.format(attachmentName, attachmentStoreUrl);
667 667 if (isRendered){
668 668 attachmentUrl = '!' + attachmentUrl;
669 669 }
670 670 } else {
671 671 var attachmentUrl = '{}'.format(attachmentStoreUrl);
672 672 }
673 673 cm.replaceRange(attachmentUrl+'\n', CodeMirror.Pos(cm.lastLine()));
674 674
675 675 return false;
676 676 };
677 677
678 678 //see: https://www.dropzonejs.com/#configuration
679 679 var storeUrl = pyroutes.url('repo_commit_comment_attachment_upload',
680 680 {'repo_name': templateContext.repo_name,
681 681 'commit_id': templateContext.commit_data.commit_id})
682 682
683 683 var previewTmpl = $(formElement).find('.comment-attachment-uploader-template').get(0);
684 684 if (previewTmpl !== undefined){
685 685 var selectLink = $(formElement).find('.pick-attachment').get(0);
686 686 $(formElement).find('.comment-attachment-uploader').dropzone({
687 687 url: storeUrl,
688 688 headers: {"X-CSRF-Token": CSRF_TOKEN},
689 689 paramName: function () {
690 690 return "attachment"
691 691 }, // The name that will be used to transfer the file
692 692 clickable: selectLink,
693 693 parallelUploads: 1,
694 694 maxFiles: 10,
695 695 maxFilesize: templateContext.attachment_store.max_file_size_mb,
696 696 uploadMultiple: false,
697 697 autoProcessQueue: true, // if false queue will not be processed automatically.
698 698 createImageThumbnails: false,
699 699 previewTemplate: previewTmpl.innerHTML,
700 700
701 701 accept: function (file, done) {
702 702 done();
703 703 },
704 704 init: function () {
705 705
706 706 this.on("sending", function (file, xhr, formData) {
707 707 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').hide();
708 708 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').show();
709 709 });
710 710
711 711 this.on("success", function (file, response) {
712 712 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').show();
713 713 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
714 714
715 715 var isRendered = false;
716 716 var ext = file.name.split('.').pop();
717 717 var imageExts = templateContext.attachment_store.image_ext;
718 718 if (imageExts.indexOf(ext) !== -1){
719 719 isRendered = true;
720 720 }
721 721
722 722 insertAttachmentText(cm, file.name, response.repo_fqn_access_path, isRendered)
723 723 });
724 724
725 725 this.on("error", function (file, errorMessage, xhr) {
726 726 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
727 727
728 728 var error = null;
729 729
730 730 if (xhr !== undefined){
731 731 var httpStatus = xhr.status + " " + xhr.statusText;
732 if (xhr.status >= 500) {
732 if (xhr !== undefined && xhr.status >= 500) {
733 733 error = httpStatus;
734 734 }
735 735 }
736 736
737 737 if (error === null) {
738 738 error = errorMessage.error || errorMessage || httpStatus;
739 739 }
740 740 $(file.previewElement).find('.dz-error-message').html('ERROR: {0}'.format(error));
741 741
742 742 });
743 743 }
744 744 });
745 745 }
746 746 return commentForm;
747 747 };
748 748
749 749 this.createGeneralComment = function (lineNo, placeholderText, resolvesCommentId) {
750 750
751 751 var tmpl = $('#cb-comment-general-form-template').html();
752 752 tmpl = tmpl.format(null, 'general');
753 753 var $form = $(tmpl);
754 754
755 755 var $formPlaceholder = $('#cb-comment-general-form-placeholder');
756 756 var curForm = $formPlaceholder.find('form');
757 757 if (curForm){
758 758 curForm.remove();
759 759 }
760 760 $formPlaceholder.append($form);
761 761
762 762 var _form = $($form[0]);
763 763 var autocompleteActions = ['approve', 'reject', 'as_note', 'as_todo'];
764 764 var commentForm = this.createCommentForm(
765 765 _form, lineNo, placeholderText, autocompleteActions, resolvesCommentId);
766 766 commentForm.initStatusChangeSelector();
767 767
768 768 return commentForm;
769 769 };
770 770
771 771 this.createComment = function(node, resolutionComment) {
772 772 var resolvesCommentId = resolutionComment || null;
773 773 var $node = $(node);
774 774 var $td = $node.closest('td');
775 775 var $form = $td.find('.comment-inline-form');
776 776
777 777 if (!$form.length) {
778 778
779 779 var $filediff = $node.closest('.filediff');
780 780 $filediff.removeClass('hide-comments');
781 781 var f_path = $filediff.attr('data-f-path');
782 782 var lineno = self.getLineNumber(node);
783 783 // create a new HTML from template
784 784 var tmpl = $('#cb-comment-inline-form-template').html();
785 785 tmpl = tmpl.format(escapeHtml(f_path), lineno);
786 786 $form = $(tmpl);
787 787
788 788 var $comments = $td.find('.inline-comments');
789 789 if (!$comments.length) {
790 790 $comments = $(
791 791 $('#cb-comments-inline-container-template').html());
792 792 $td.append($comments);
793 793 }
794 794
795 795 $td.find('.cb-comment-add-button').before($form);
796 796
797 797 var placeholderText = _gettext('Leave a comment on line {0}.').format(lineno);
798 798 var _form = $($form[0]).find('form');
799 799 var autocompleteActions = ['as_note', 'as_todo'];
800 800 var commentForm = this.createCommentForm(
801 801 _form, lineno, placeholderText, autocompleteActions, resolvesCommentId);
802 802
803 803 $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({
804 804 form: _form,
805 805 parent: $td[0],
806 806 lineno: lineno,
807 807 f_path: f_path}
808 808 );
809 809
810 810 // set a CUSTOM submit handler for inline comments.
811 811 commentForm.setHandleFormSubmit(function(o) {
812 812 var text = commentForm.cm.getValue();
813 813 var commentType = commentForm.getCommentType();
814 814 var resolvesCommentId = commentForm.getResolvesId();
815 815
816 816 if (text === "") {
817 817 return;
818 818 }
819 819
820 820 if (lineno === undefined) {
821 821 alert('missing line !');
822 822 return;
823 823 }
824 824 if (f_path === undefined) {
825 825 alert('missing file path !');
826 826 return;
827 827 }
828 828
829 829 var excludeCancelBtn = false;
830 830 var submitEvent = true;
831 831 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
832 832 commentForm.cm.setOption("readOnly", true);
833 833 var postData = {
834 834 'text': text,
835 835 'f_path': f_path,
836 836 'line': lineno,
837 837 'comment_type': commentType,
838 838 'csrf_token': CSRF_TOKEN
839 839 };
840 840 if (resolvesCommentId){
841 841 postData['resolves_comment_id'] = resolvesCommentId;
842 842 }
843 843
844 844 var submitSuccessCallback = function(json_data) {
845 845 $form.remove();
846 846 try {
847 847 var html = json_data.rendered_text;
848 848 var lineno = json_data.line_no;
849 849 var target_id = json_data.target_id;
850 850
851 851 $comments.find('.cb-comment-add-button').before(html);
852 852
853 853 //mark visually which comment was resolved
854 854 if (resolvesCommentId) {
855 855 commentForm.markCommentResolved(resolvesCommentId);
856 856 }
857 857
858 858 // run global callback on submit
859 859 commentForm.globalSubmitSuccessCallback();
860 860
861 861 } catch (e) {
862 862 console.error(e);
863 863 }
864 864
865 865 // re trigger the linkification of next/prev navigation
866 866 linkifyComments($('.inline-comment-injected'));
867 867 timeagoActivate();
868 868
869 869 if (window.updateSticky !== undefined) {
870 870 // potentially our comments change the active window size, so we
871 871 // notify sticky elements
872 872 updateSticky()
873 873 }
874 874
875 875 commentForm.setActionButtonsDisabled(false);
876 876
877 877 };
878 878 var submitFailCallback = function(data){
879 879 alert(
880 880 "Error while submitting comment.\n" +
881 881 "Error code {0} ({1}).".format(data.status, data.statusText)
882 882 );
883 883 commentForm.resetCommentFormState(text)
884 884 };
885 885 commentForm.submitAjaxPOST(
886 886 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
887 887 });
888 888 }
889 889
890 890 $form.addClass('comment-inline-form-open');
891 891 };
892 892
893 893 this.createResolutionComment = function(commentId){
894 894 // hide the trigger text
895 895 $('#resolve-comment-{0}'.format(commentId)).hide();
896 896
897 897 var comment = $('#comment-'+commentId);
898 898 var commentData = comment.data();
899 899 if (commentData.commentInline) {
900 900 this.createComment(comment, commentId)
901 901 } else {
902 902 Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId)
903 903 }
904 904
905 905 return false;
906 906 };
907 907
908 908 this.submitResolution = function(commentId){
909 909 var form = $('#resolve_comment_{0}'.format(commentId)).closest('form');
910 910 var commentForm = form.get(0).CommentForm;
911 911
912 912 var cm = commentForm.getCmInstance();
913 913 var renderer = templateContext.visual.default_renderer;
914 914 if (renderer == 'rst'){
915 915 var commentUrl = '`#{0} <{1}#comment-{0}>`_'.format(commentId, commentForm.selfUrl);
916 916 } else if (renderer == 'markdown') {
917 917 var commentUrl = '[#{0}]({1}#comment-{0})'.format(commentId, commentForm.selfUrl);
918 918 } else {
919 919 var commentUrl = '{1}#comment-{0}'.format(commentId, commentForm.selfUrl);
920 920 }
921 921
922 922 cm.setValue(_gettext('TODO from comment {0} was fixed.').format(commentUrl));
923 923 form.submit();
924 924 return false;
925 925 };
926 926
927 927 };
@@ -1,211 +1,211 b''
1 1 <%inherit file="/base/base.mako"/>
2 2
3 3 <%def name="title()">
4 4 ${_('{} Files Upload').format(c.repo_name)}
5 5 %if c.rhodecode_name:
6 6 &middot; ${h.branding(c.rhodecode_name)}
7 7 %endif
8 8 </%def>
9 9
10 10 <%def name="menu_bar_nav()">
11 11 ${self.menu_items(active='repositories')}
12 12 </%def>
13 13
14 14 <%def name="breadcrumbs_links()"></%def>
15 15
16 16 <%def name="menu_bar_subnav()">
17 17 ${self.repo_menu(active='files')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21
22 22 <div class="box">
23 23 ## Template for uploads
24 24 <div style="display: none" id="tpl-dropzone">
25 25 <div class="dz-preview dz-file-preview">
26 26 <div class="dz-details">
27 27
28 28 <div class="dz-filename">
29 29 <span data-dz-name></span>
30 30 </div>
31 31 <div class="dz-filename-size">
32 32 <span class="dz-size" data-dz-size></span>
33 33
34 34 </div>
35 35
36 36 <div class="dz-sending" style="display: none">${_('Uploading...')}</div>
37 37 <div class="dz-response" style="display: none">
38 38 ${_('Uploaded')} 100%
39 39 </div>
40 40
41 41 </div>
42 42 <div class="dz-progress">
43 43 <span class="dz-upload" data-dz-uploadprogress></span>
44 44 </div>
45 45
46 46 <div class="dz-error-message">
47 47 </div>
48 48 </div>
49 49 </div>
50 50
51 51 <div class="edit-file-title">
52 52 <span class="title-heading">${_('Upload new file')} @ <code>${h.show_id(c.commit)}</code></span>
53 53 % if c.commit.branch:
54 54 <span class="tag branchtag">
55 55 <i class="icon-branch"></i> ${c.commit.branch}
56 56 </span>
57 57 % endif
58 58 </div>
59 59
60 60 <% form_url = h.route_path('repo_files_upload_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path) %>
61 61 ##${h.secure_form(form_url, id='eform', enctype="multipart/form-data", request=request)}
62 62 <div class="edit-file-fieldset">
63 63 <div class="path-items">
64 64 <ul>
65 65 <li class="breadcrumb-path">
66 66 <div>
67 67 <a href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path='')}"><i class="icon-home"></i></a> /
68 68 <a href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path)}">${c.f_path}</a> ${('/' if c.f_path else '')}
69 69 </div>
70 70 </li>
71 71 <li class="location-path">
72 72
73 73 </li>
74 74 </ul>
75 75 </div>
76 76
77 77 </div>
78 78
79 79 <div class="upload-form table">
80 80 <div>
81 81
82 82 <div class="dropzone-wrapper" id="file-uploader">
83 83 <div class="dropzone-pure">
84 84 <div class="dz-message">
85 85 <i class="icon-upload" style="font-size:36px"></i></br>
86 86 ${_("Drag'n Drop files here or")} <span class="link">${_('Choose your files')}</span>.<br>
87 87 </div>
88 88 </div>
89 89
90 90 </div>
91 91 </div>
92 92
93 93 </div>
94 94
95 95 <div class="upload-form edit-file-fieldset">
96 96 <div class="fieldset">
97 97 <div class="message">
98 98 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
99 99 </div>
100 100 </div>
101 101 <div class="pull-left">
102 102 ${h.submit('commit_btn',_('Commit changes'), class_="btn btn-small btn-success")}
103 103 </div>
104 104 </div>
105 105 ##${h.end_form()}
106 106
107 107 <div class="file-upload-transaction-wrapper" style="display: none">
108 108 <div class="file-upload-transaction">
109 109 <h3>${_('Commiting...')}</h3>
110 110 <p>${_('Please wait while the files are being uploaded')}</p>
111 111 <p class="error" style="display: none">
112 112
113 113 </p>
114 114 <i class="icon-spin animate-spin"></i>
115 115 <p></p>
116 116 </div>
117 117 </div>
118 118
119 119 </div>
120 120
121 121 <script type="text/javascript">
122 122
123 123 $(document).ready(function () {
124 124
125 125 //see: https://www.dropzonejs.com/#configuration
126 126 myDropzone = new Dropzone("div#file-uploader", {
127 127 url: "${form_url}",
128 128 headers: {"X-CSRF-Token": CSRF_TOKEN},
129 129 paramName: function () {
130 130 return "files_upload"
131 131 }, // The name that will be used to transfer the file
132 132 parallelUploads: 20,
133 133 maxFiles: 20,
134 134 uploadMultiple: true,
135 135 //chunking: true, // use chunking transfer, not supported at the moment
136 136 //maxFilesize: 2, // in MBs
137 137 autoProcessQueue: false, // if false queue will not be processed automatically.
138 138 createImageThumbnails: false,
139 139 previewTemplate: document.querySelector('#tpl-dropzone').innerHTML,
140 140 accept: function (file, done) {
141 141 done();
142 142 },
143 143 init: function () {
144 144 this.on("addedfile", function (file) {
145 145
146 146 });
147 147
148 148 this.on("sending", function (file, xhr, formData) {
149 149 formData.append("message", $('#commit').val());
150 150 $(file.previewElement).find('.dz-sending').show();
151 151 });
152 152
153 153 this.on("success", function (file, response) {
154 154 $(file.previewElement).find('.dz-sending').hide();
155 155 $(file.previewElement).find('.dz-response').show();
156 156
157 157 if (response.error !== null) {
158 158 $('.file-upload-transaction-wrapper .error').html('ERROR: {0}'.format(response.error));
159 159 $('.file-upload-transaction-wrapper .error').show();
160 160 $('.file-upload-transaction-wrapper i').hide()
161 161 }
162 162
163 163 var redirect_url = response.redirect_url || '/';
164 164 window.location = redirect_url
165 165
166 166 });
167 167
168 168 this.on("error", function (file, errorMessage, xhr) {
169 169 var error = null;
170 170
171 171 if (xhr !== undefined){
172 172 var httpStatus = xhr.status + " " + xhr.statusText;
173 if (xhr.status >= 500) {
173 if (xhr !== undefined && xhr.status >= 500) {
174 174 error = httpStatus;
175 175 }
176 176 }
177 177
178 178 if (error === null) {
179 179 error = errorMessage.error || errorMessage || httpStatus;
180 180 }
181 181
182 182 $(file.previewElement).find('.dz-error-message').html('ERROR: {0}'.format(error));
183 183 });
184 184 }
185 185 });
186 186
187 187 $('#commit_btn').on('click', function(e) {
188 188 e.preventDefault();
189 189 var button = $(this);
190 190 if (button.hasClass('clicked')) {
191 191 button.attr('disabled', true);
192 192 } else {
193 193 button.addClass('clicked');
194 194 }
195 195
196 196 var files = myDropzone.getQueuedFiles();
197 197 if (files.length === 0) {
198 198 alert("Missing files");
199 199 e.preventDefault();
200 200 }
201 201
202 202 $('.upload-form').hide();
203 203 $('.file-upload-transaction-wrapper').show();
204 204 myDropzone.processQueue();
205 205
206 206 });
207 207
208 208 });
209 209
210 210 </script>
211 211 </%def>
General Comments 0
You need to be logged in to leave comments. Login now