notes.js 36.8 KB
Newer Older
1
/* eslint-disable no-restricted-properties, func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, camelcase, no-unused-expressions, quotes, max-len, one-var, one-var-declaration-per-line, default-case, prefer-template, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, brace-style, no-lonely-if, vars-on-top, no-unused-vars, no-sequences, no-shadow, newline-per-chained-call, no-useless-escape */
2 3 4
/* global Flash */
/* global Autosave */
/* global ResolveService */
5
/* global mrRefreshWidgetUrl */
F
Fatih Acet 已提交
6

7 8
import Cookies from 'js-cookie';

9 10 11 12 13 14 15
require('./autosave');
window.autosize = require('vendor/autosize');
window.Dropzone = require('dropzone');
require('./dropzone_input');
require('./gfm_auto_complete');
require('vendor/jquery.caret'); // required by jquery.atwho
require('vendor/jquery.atwho');
16
require('./task_list');
F
Fatih Acet 已提交
17 18

(function() {
19
  var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
F
Fatih Acet 已提交
20 21

  this.Notes = (function() {
22
    const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
F
Fatih Acet 已提交
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

    Notes.interval = null;

    function Notes(notes_url, note_ids, last_fetched_at, view) {
      this.updateTargetButtons = bind(this.updateTargetButtons, this);
      this.updateCloseButton = bind(this.updateCloseButton, this);
      this.visibilityChange = bind(this.visibilityChange, this);
      this.cancelDiscussionForm = bind(this.cancelDiscussionForm, this);
      this.addDiffNote = bind(this.addDiffNote, this);
      this.setupDiscussionNoteForm = bind(this.setupDiscussionNoteForm, this);
      this.replyToDiscussionNote = bind(this.replyToDiscussionNote, this);
      this.removeNote = bind(this.removeNote, this);
      this.cancelEdit = bind(this.cancelEdit, this);
      this.updateNote = bind(this.updateNote, this);
      this.addDiscussionNote = bind(this.addDiscussionNote, this);
      this.addNoteError = bind(this.addNoteError, this);
      this.addNote = bind(this.addNote, this);
      this.resetMainTargetForm = bind(this.resetMainTargetForm, this);
      this.refresh = bind(this.refresh, this);
      this.keydownNoteText = bind(this.keydownNoteText, this);
43
      this.toggleCommitList = bind(this.toggleCommitList, this);
F
Fatih Acet 已提交
44 45 46 47 48 49 50 51 52 53 54
      this.notes_url = notes_url;
      this.note_ids = note_ids;
      this.last_fetched_at = last_fetched_at;
      this.noteable_url = document.URL;
      this.notesCountBadge || (this.notesCountBadge = $(".issuable-details").find(".notes-tab .badge"));
      this.basePollingInterval = 15000;
      this.maxPollingSteps = 4;
      this.cleanBinding();
      this.addBinding();
      this.setPollingInterval();
      this.setupMainTargetNoteForm();
55 56
      this.taskList = new gl.TaskList({
        dataType: 'note',
57
        fieldName: 'note',
58
        selector: '.notes'
59
      });
60
      this.collapseLongCommitList();
61
      this.setViewType(view);
62 63 64 65

      // We are in the Merge Requests page so we need another edit form for Changes tab
      if (gl.utils.getPagePath(1) === 'merge_requests') {
        $('.note-edit-form').clone()
F
Fatih Acet 已提交
66
          .addClass('mr-note-edit-form').insertAfter('.note-edit-form');
67
      }
F
Fatih Acet 已提交
68 69
    }

70 71 72 73
    Notes.prototype.setViewType = function(view) {
      this.view = Cookies.get('diff_view') || view;
    };

F
Fatih Acet 已提交
74
    Notes.prototype.addBinding = function() {
75
      // add note to UI after creation
F
Fatih Acet 已提交
76 77
      $(document).on("ajax:success", ".js-main-target-form", this.addNote);
      $(document).on("ajax:success", ".js-discussion-note-form", this.addDiscussionNote);
78
      // catch note ajax errors
F
Fatih Acet 已提交
79
      $(document).on("ajax:error", ".js-main-target-form", this.addNoteError);
80
      // change note in UI after update
F
Fatih Acet 已提交
81
      $(document).on("ajax:success", "form.edit-note", this.updateNote);
82
      // Edit note link
83
      $(document).on("click", ".js-note-edit", this.showEditForm.bind(this));
F
Fatih Acet 已提交
84
      $(document).on("click", ".note-edit-cancel", this.cancelEdit);
85
      // Reopen and close actions for Issue/MR combined with note form submit
F
Fatih Acet 已提交
86 87
      $(document).on("click", ".js-comment-button", this.updateCloseButton);
      $(document).on("keyup input", ".js-note-text", this.updateTargetButtons);
88
      // resolve a discussion
89
      $(document).on('click', '.js-comment-resolve-button', this.resolveDiscussion);
90
      // remove a note (in general)
F
Fatih Acet 已提交
91
      $(document).on("click", ".js-note-delete", this.removeNote);
92
      // delete note attachment
F
Fatih Acet 已提交
93
      $(document).on("click", ".js-note-attachment-delete", this.removeAttachment);
94
      // reset main target form after submit
F
Fatih Acet 已提交
95 96
      $(document).on("ajax:complete", ".js-main-target-form", this.reenableTargetFormSubmitButton);
      $(document).on("ajax:success", ".js-main-target-form", this.resetMainTargetForm);
97
      // reset main target form when clicking discard
F
Fatih Acet 已提交
98
      $(document).on("click", ".js-note-discard", this.resetMainTargetForm);
99
      // update the file name when an attachment is selected
F
Fatih Acet 已提交
100
      $(document).on("change", ".js-note-attachment-input", this.updateFormAttachment);
101
      // reply to diff/discussion notes
F
Fatih Acet 已提交
102
      $(document).on("click", ".js-discussion-reply-button", this.replyToDiscussionNote);
103
      // add diff note
F
Fatih Acet 已提交
104
      $(document).on("click", ".js-add-diff-note-button", this.addDiffNote);
105
      // hide diff note form
F
Fatih Acet 已提交
106
      $(document).on("click", ".js-close-discussion-note-form", this.cancelDiscussionForm);
107 108
      // toggle commit list
      $(document).on("click", '.system-note-commit-list-toggler', this.toggleCommitList);
109
      // fetch notes when tab becomes visible
F
Fatih Acet 已提交
110
      $(document).on("visibilitychange", this.visibilityChange);
111
      // when issue status changes, we need to refresh data
F
Fatih Acet 已提交
112
      $(document).on("issuable:change", this.refresh);
113

114
      // when a key is clicked on the notes
F
Fatih Acet 已提交
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
      return $(document).on("keydown", ".js-note-text", this.keydownNoteText);
    };

    Notes.prototype.cleanBinding = function() {
      $(document).off("ajax:success", ".js-main-target-form");
      $(document).off("ajax:success", ".js-discussion-note-form");
      $(document).off("ajax:success", "form.edit-note");
      $(document).off("click", ".js-note-edit");
      $(document).off("click", ".note-edit-cancel");
      $(document).off("click", ".js-note-delete");
      $(document).off("click", ".js-note-attachment-delete");
      $(document).off("ajax:complete", ".js-main-target-form");
      $(document).off("ajax:success", ".js-main-target-form");
      $(document).off("click", ".js-discussion-reply-button");
      $(document).off("click", ".js-add-diff-note-button");
      $(document).off("visibilitychange");
      $(document).off("keyup", ".js-note-text");
      $(document).off("click", ".js-note-target-reopen");
      $(document).off("click", ".js-note-target-close");
      $(document).off("click", ".js-note-discard");
      $(document).off("keydown", ".js-note-text");
136
      $(document).off('click', '.js-comment-resolve-button');
137
      $(document).off("click", '.system-note-commit-list-toggler');
F
Fatih Acet 已提交
138 139 140 141
    };

    Notes.prototype.keydownNoteText = function(e) {
      var $textarea, discussionNoteForm, editNote, myLastNote, myLastNoteEditBtn, newText, originalText;
142
      if (gl.utils.isMetaKey(e)) {
F
Fatih Acet 已提交
143 144
        return;
      }
145

F
Fatih Acet 已提交
146
      $textarea = $(e.target);
147
      // Edit previous note when UP arrow is hit
F
Fatih Acet 已提交
148 149 150 151 152 153 154 155 156 157 158
      switch (e.which) {
        case 38:
          if ($textarea.val() !== '') {
            return;
          }
          myLastNote = $("li.note[data-author-id='" + gon.current_user_id + "'][data-editable]:last");
          if (myLastNote.length) {
            myLastNoteEditBtn = myLastNote.find('.js-note-edit');
            return myLastNoteEditBtn.trigger('click', [true, myLastNote]);
          }
          break;
159
        // Cancel creating diff note or editing any note when ESCAPE is hit
F
Fatih Acet 已提交
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
        case 27:
          discussionNoteForm = $textarea.closest('.js-discussion-note-form');
          if (discussionNoteForm.length) {
            if ($textarea.val() !== '') {
              if (!confirm('Are you sure you want to cancel creating this comment?')) {
                return;
              }
            }
            this.removeDiscussionNoteForm(discussionNoteForm);
            return;
          }
          editNote = $textarea.closest('.note');
          if (editNote.length) {
            originalText = $textarea.closest('form').data('original-note');
            newText = $textarea.val();
            if (originalText !== newText) {
              if (!confirm('Are you sure you want to cancel editing this comment?')) {
                return;
              }
            }
            return this.removeNoteEditForm(editNote);
          }
      }
    };

    Notes.prototype.initRefresh = function() {
      clearInterval(Notes.interval);
      return Notes.interval = setInterval((function(_this) {
        return function() {
          return _this.refresh();
        };
      })(this), this.pollingInterval);
    };

    Notes.prototype.refresh = function() {
      if (!document.hidden && document.URL.indexOf(this.noteable_url) === 0) {
        return this.getContent();
      }
    };

    Notes.prototype.getContent = function() {
      if (this.refreshing) {
        return;
      }
      this.refreshing = true;
      return $.ajax({
        url: this.notes_url,
207
        headers: { "X-Last-Fetched-At": this.last_fetched_at },
F
Fatih Acet 已提交
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
        dataType: "json",
        success: (function(_this) {
          return function(data) {
            var notes;
            notes = data.notes;
            _this.last_fetched_at = data.last_fetched_at;
            _this.setPollingInterval(data.notes.length);
            return $.each(notes, function(i, note) {
              if (note.discussion_html != null) {
                return _this.renderDiscussionNote(note);
              } else {
                return _this.renderNote(note);
              }
            });
          };
        })(this)
      }).always((function(_this) {
        return function() {
          return _this.refreshing = false;
        };
      })(this));
    };

    /*
    Increase @pollingInterval up to 120 seconds on every function call,
    if `shouldReset` has a truthy value, 'null' or 'undefined' the variable
    will reset to @basePollingInterval.
235

F
Fatih Acet 已提交
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
    Note: this function is used to gradually increase the polling interval
    if there aren't new notes coming from the server
     */

    Notes.prototype.setPollingInterval = function(shouldReset) {
      var nthInterval;
      if (shouldReset == null) {
        shouldReset = true;
      }
      nthInterval = this.basePollingInterval * Math.pow(2, this.maxPollingSteps - 1);
      if (shouldReset) {
        this.pollingInterval = this.basePollingInterval;
      } else if (this.pollingInterval < nthInterval) {
        this.pollingInterval *= 2;
      }
      return this.initRefresh();
    };

254
    Notes.prototype.handleCreateChanges = function(note) {
M
mhasbini 已提交
255
      var votesBlock;
256 257 258 259
      if (typeof note === 'undefined') {
        return;
      }

M
mhasbini 已提交
260 261 262 263 264 265 266 267 268 269
      if (note.commands_changes) {
        if ('merge' in note.commands_changes) {
          $.get(mrRefreshWidgetUrl);
        }

        if ('emoji_award' in note.commands_changes) {
          votesBlock = $('.js-awards-block').eq(0);
          gl.awardsHandler.addAwardToEmojiBar(votesBlock, note.commands_changes.emoji_award);
          return gl.awardsHandler.scrollToAwards();
        }
270 271 272
      }
    };

F
Fatih Acet 已提交
273 274
    /*
    Render note in main comments area.
275

F
Fatih Acet 已提交
276 277 278 279
    Note: for rendering inline notes use renderDiscussionNote
     */

    Notes.prototype.renderNote = function(note) {
M
mhasbini 已提交
280
      var $notesList;
F
Fatih Acet 已提交
281
      if (!note.valid) {
M
mhasbini 已提交
282 283 284
        if (note.errors.commands_only) {
          new Flash(note.errors.commands_only, 'notice', this.parentTimeline);
          this.refresh();
F
Fatih Acet 已提交
285 286 287
        }
        return;
      }
M
mhasbini 已提交
288 289

      if (this.isNewNote(note)) {
F
Fatih Acet 已提交
290 291 292
        this.note_ids.push(note.id);
        $notesList = $('ul.main-notes-list');
        $notesList.append(note.html).syntaxHighlight();
293
        // Update datetime format on the recent note
F
Fatih Acet 已提交
294
        gl.utils.localTimeAgo($notesList.find("#note_" + note.id + " .js-timeago"), false);
295
        this.collapseLongCommitList();
296
        this.taskList.init();
297
        this.refresh();
F
Fatih Acet 已提交
298 299 300 301 302 303 304 305 306 307 308 309 310
        return this.updateNotesCount(1);
      }
    };

    /*
    Check if note does not exists on page
     */

    Notes.prototype.isNewNote = function(note) {
      return $.inArray(note.id, this.note_ids) === -1;
    };

    Notes.prototype.isParallelView = function() {
311
      return Cookies.get('diff_view') === 'parallel';
F
Fatih Acet 已提交
312 313 314 315
    };

    /*
    Render note in discussion area.
316

F
Fatih Acet 已提交
317 318 319 320
    Note: for rendering inline notes use renderDiscussionNote
     */

    Notes.prototype.renderDiscussionNote = function(note) {
321
      var discussionContainer, form, note_html, row, lineType, diffAvatarContainer;
F
Fatih Acet 已提交
322 323 324 325 326 327 328 329 330
      if (!this.isNewNote(note)) {
        return;
      }
      this.note_ids.push(note.id);
      form = $("#new-discussion-note-form-" + note.discussion_id);
      if ((note.original_discussion_id != null) && form.length === 0) {
        form = $("#new-discussion-note-form-" + note.original_discussion_id);
      }
      row = form.closest("tr");
331 332
      lineType = this.isParallelView() ? form.find('#line_type').val() : 'old';
      diffAvatarContainer = row.prevAll('.line_holder').first().find('.js-avatar-container.' + lineType + '_line');
F
Fatih Acet 已提交
333
      note_html = $(note.html);
334
      note_html.renderGFM();
335
      // is this the first note of discussion?
F
Fatih Acet 已提交
336 337 338 339 340
      discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']");
      if ((note.original_discussion_id != null) && discussionContainer.length === 0) {
        discussionContainer = $(".notes[data-discussion-id='" + note.original_discussion_id + "']");
      }
      if (discussionContainer.length === 0) {
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
        if (!this.isParallelView() || row.hasClass('js-temp-notes-holder')) {
          // insert the note and the reply button after the temp row
          row.after(note.diff_discussion_html);

          // remove the note (will be added again below)
          row.next().find(".note").remove();
        } else {
          // Merge new discussion HTML in
          var $discussion = $(note.diff_discussion_html);
          var $notes = $discussion.find('.notes[data-discussion-id="' + note.discussion_id + '"]');
          var contentContainerClass = '.' + $notes.closest('.notes_content')
            .attr('class')
            .split(' ')
            .join('.');

          // remove the note (will be added again below)
          $notes.find('.note').remove();

          row.find(contentContainerClass + ' .content').append($notes.closest('.content').children());
        }
361
        // Before that, the container didn't exist
F
Fatih Acet 已提交
362
        discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']");
363
        // Add note to 'Changes' page discussions
F
Fatih Acet 已提交
364
        discussionContainer.append(note_html);
365
        // Init discussion on 'Discussion' page if it is merge request page
F
Fatih Acet 已提交
366
        if ($('body').attr('data-page').indexOf('projects:merge_request') === 0) {
367
          $('ul.main-notes-list').append(note.discussion_html).renderGFM();
F
Fatih Acet 已提交
368 369
        }
      } else {
370
        // append new note to all matching discussions
F
Fatih Acet 已提交
371 372
        discussionContainer.append(note_html);
      }
373

374
      if (typeof gl.diffNotesCompileComponents !== 'undefined' && note.discussion_id) {
375
        gl.diffNotesCompileComponents();
376
        this.renderDiscussionAvatar(diffAvatarContainer, note);
377 378
      }

379
      gl.utils.localTimeAgo($('.js-timeago'), false);
F
Fatih Acet 已提交
380 381 382
      return this.updateNotesCount(1);
    };

383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407
    Notes.prototype.getLineHolder = function(changesDiscussionContainer) {
      return $(changesDiscussionContainer).closest('.notes_holder')
        .prevAll('.line_holder')
        .first()
        .get(0);
    };

    Notes.prototype.renderDiscussionAvatar = function(diffAvatarContainer, note) {
      var commentButton = diffAvatarContainer.find('.js-add-diff-note-button');
      var avatarHolder = diffAvatarContainer.find('.diff-comment-avatar-holders');

      if (!avatarHolder.length) {
        avatarHolder = document.createElement('diff-note-avatars');
        avatarHolder.setAttribute('discussion-id', note.discussion_id);

        diffAvatarContainer.append(avatarHolder);

        gl.diffNotesCompileComponents();
      }

      if (commentButton.length) {
        commentButton.remove();
      }
    };

F
Fatih Acet 已提交
408 409
    /*
    Called in response the main target form has been successfully submitted.
410

F
Fatih Acet 已提交
411 412 413 414 415 416 417 418
    Removes any errors.
    Resets text and preview.
    Resets buttons.
     */

    Notes.prototype.resetMainTargetForm = function(e) {
      var form;
      form = $(".js-main-target-form");
419
      // remove validation errors
F
Fatih Acet 已提交
420
      form.find(".js-errors").remove();
421
      // reset text and preview
F
Fatih Acet 已提交
422 423 424
      form.find(".js-md-write-button").click();
      form.find(".js-note-text").val("").trigger("input");
      form.find(".js-note-text").data("autosave").reset();
425 426 427 428 429 430

      var event = document.createEvent('Event');
      event.initEvent('autosize:update', true, false);
      form.find('.js-autosize')[0].dispatchEvent(event);

      this.updateTargetButtons(e);
F
Fatih Acet 已提交
431 432 433 434 435 436 437 438 439 440
    };

    Notes.prototype.reenableTargetFormSubmitButton = function() {
      var form;
      form = $(".js-main-target-form");
      return form.find(".js-note-text").trigger("input");
    };

    /*
    Shows the main form and does some setup on it.
441

F
Fatih Acet 已提交
442 443 444 445 446
    Sets some hidden fields in the form.
     */

    Notes.prototype.setupMainTargetNoteForm = function() {
      var form;
447
      // find the form
F
Fatih Acet 已提交
448
      form = $(".js-new-note-form");
449
      // Set a global clone of the form for later cloning
F
Fatih Acet 已提交
450
      this.formClone = form.clone();
451
      // show the form
F
Fatih Acet 已提交
452
      this.setupNoteForm(form);
453
      // fix classes
F
Fatih Acet 已提交
454 455 456 457 458
      form.removeClass("js-new-note-form");
      form.addClass("js-main-target-form");
      form.find("#note_line_code").remove();
      form.find("#note_position").remove();
      form.find("#note_type").remove();
459
      form.find('.js-comment-resolve-button').closest('comment-and-resolve-btn').remove();
F
Fatih Acet 已提交
460 461 462 463 464
      return this.parentTimeline = form.parents('.timeline');
    };

    /*
    General note form setup.
465

F
Fatih Acet 已提交
466 467 468 469 470 471 472 473
    deactivates the submit button when text is empty
    hides the preview button when text is empty
    setup GFM auto complete
    show the form
     */

    Notes.prototype.setupNoteForm = function(form) {
      var textarea;
L
Luke "Jared" Bennett 已提交
474
      new gl.GLForm(form);
F
Fatih Acet 已提交
475 476 477 478 479 480
      textarea = form.find(".js-note-text");
      return new Autosave(textarea, ["Note", form.find("#note_noteable_type").val(), form.find("#note_noteable_id").val(), form.find("#note_commit_id").val(), form.find("#note_type").val(), form.find("#note_line_code").val(), form.find("#note_position").val()]);
    };

    /*
    Called in response to the new note form being submitted
481

F
Fatih Acet 已提交
482 483 484 485
    Adds new note to list.
     */

    Notes.prototype.addNote = function(xhr, note, status) {
486
      this.handleCreateChanges(note);
F
Fatih Acet 已提交
487 488 489 490 491 492 493 494 495
      return this.renderNote(note);
    };

    Notes.prototype.addNoteError = function(xhr, note, status) {
      return new Flash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', this.parentTimeline);
    };

    /*
    Called in response to the new note form being submitted
496

F
Fatih Acet 已提交
497 498 499 500
    Adds new note to list.
     */

    Notes.prototype.addDiscussionNote = function(xhr, note, status) {
501 502 503
      var $form = $(xhr.target);

      if ($form.attr('data-resolve-all') != null) {
504 505 506
        var projectPath = $form.data('project-path');
        var discussionId = $form.data('discussion-id');
        var mergeRequestId = $form.data('noteable-iid');
507 508

        if (ResolveService != null) {
P
Phil Hughes 已提交
509
          ResolveService.toggleResolveForDiscussion(mergeRequestId, discussionId);
510 511
        }
      }
512

F
Fatih Acet 已提交
513
      this.renderDiscussionNote(note);
514
      // cleanup after successfully creating a diff/discussion note
515
      this.removeDiscussionNoteForm($form);
F
Fatih Acet 已提交
516 517 518 519
    };

    /*
    Called in response to the edit note form being submitted
520

F
Fatih Acet 已提交
521 522 523 524 525
    Updates the current note field.
     */

    Notes.prototype.updateNote = function(_xhr, note, _status) {
      var $html, $note_li;
526
      // Convert returned HTML to a jQuery object so we can modify it further
F
Fatih Acet 已提交
527
      $html = $(note.html);
528
      this.revertNoteEditForm();
F
Fatih Acet 已提交
529
      gl.utils.localTimeAgo($('.js-timeago', $html));
530
      $html.renderGFM();
F
Fatih Acet 已提交
531
      $html.find('.js-task-list-container').taskList('enable');
532
      // Find the note's `li` element by ID and replace it with the updated HTML
F
Fatih Acet 已提交
533
      $note_li = $('.note-row-' + note.id);
534 535 536

      $note_li.replaceWith($html);

537 538
      if (typeof gl.diffNotesCompileComponents !== 'undefined') {
        gl.diffNotesCompileComponents();
539
      }
F
Fatih Acet 已提交
540 541
    };

542 543 544 545 546 547 548 549 550 551
    Notes.prototype.checkContentToAllowEditing = function($el) {
      var initialContent = $el.find('.original-note-content').text().trim();
      var currentContent = $el.find('.note-textarea').val();
      var isAllowed = true;

      if (currentContent === initialContent) {
        this.removeNoteEditForm($el);
      }
      else {
        var $buttons = $el.find('.note-form-actions');
F
Fatih Acet 已提交
552
        var isWidgetVisible = gl.utils.isInViewport($el.get(0));
553

554
        if (!isWidgetVisible) {
F
Fatih Acet 已提交
555
          gl.utils.scrollToElement($el);
556 557 558 559 560 561 562
        }

        $el.find('.js-edit-warning').show();
        isAllowed = false;
      }

      return isAllowed;
563
    };
564

F
Fatih Acet 已提交
565 566
    /*
    Called in response to clicking the edit note link
567

F
Fatih Acet 已提交
568 569
    Replaces the note text with the note edit form
    Adds a data attribute to the form with the original content of the note for cancellations
570
    */
F
Fatih Acet 已提交
571 572
    Notes.prototype.showEditForm = function(e, scrollTo, myLastNote) {
      e.preventDefault();
573

574
      var $target = $(e.target);
575
      var $editForm = $(this.getEditFormSelector($target));
576
      var $note = $target.closest('.note');
577
      var $currentlyEditing = $('.note.is-editting:visible');
578

579 580 581 582 583 584 585 586
      if ($currentlyEditing.length) {
        var isEditAllowed = this.checkContentToAllowEditing($currentlyEditing);

        if (!isEditAllowed) {
          return;
        }
      }

587
      $note.find('.js-note-attachment-delete').show();
588 589 590
      $editForm.addClass('current-note-edit-form');
      $note.addClass('is-editting');
      this.putEditFormInPlace($target);
F
Fatih Acet 已提交
591 592 593 594
    };

    /*
    Called in response to clicking the edit note link
595

F
Fatih Acet 已提交
596 597 598 599 600
    Hides edit form and restores the original note text to the editor textarea.
     */

    Notes.prototype.cancelEdit = function(e) {
      e.preventDefault();
601 602
      var $target = $(e.target);
      var note = $target.closest('.note');
603
      note.find('.js-edit-warning').hide();
604
      this.revertNoteEditForm($target);
F
Fatih Acet 已提交
605 606 607
      return this.removeNoteEditForm(note);
    };

608 609 610 611
    Notes.prototype.revertNoteEditForm = function($target) {
      $target = $target || $('.note.is-editting:visible');
      var selector = this.getEditFormSelector($target);
      var $editForm = $(selector);
612 613 614

      $editForm.insertBefore('.notes-form');
      $editForm.find('.js-comment-button').enable();
615
      $editForm.find('.js-edit-warning').hide();
616
    };
617

618
    Notes.prototype.getEditFormSelector = function($el) {
F
Fatih Acet 已提交
619
      var selector = '.note-edit-form:not(.mr-note-edit-form)';
620 621

      if ($el.parents('#diffs').length) {
F
Fatih Acet 已提交
622
        selector = '.note-edit-form.mr-note-edit-form';
623 624 625 626
      }

      return selector;
    };
627

F
Fatih Acet 已提交
628
    Notes.prototype.removeNoteEditForm = function(note) {
629 630 631
      var form = note.find('.current-note-edit-form');
      note.removeClass('is-editting');
      form.removeClass('current-note-edit-form');
F
Fatih Acet 已提交
632
      form.find('.js-edit-warning').hide();
633
      // Replace markdown textarea text with original note text.
634
      return form.find('.js-note-text').val(form.find('form.edit-note').data('original-note'));
F
Fatih Acet 已提交
635 636 637 638
    };

    /*
    Called in response to deleting a note of any kind.
639

F
Fatih Acet 已提交
640 641 642 643 644
    Removes the actual note from view.
    Removes the whole discussion if the last note is being removed.
     */

    Notes.prototype.removeNote = function(e) {
645 646 647 648 649 650 651 652
      var noteElId, noteId, dataNoteId, $note, lineHolder;
      $note = $(e.currentTarget).closest('.note');
      noteElId = $note.attr('id');
      noteId = $note.attr('data-note-id');
      lineHolder = $(e.currentTarget).closest('.notes[data-discussion-id]')
        .closest('.notes_holder')
        .prev('.line_holder');
      $(".note[id='" + noteElId + "']").each((function(_this) {
653 654 655
        // A same note appears in the "Discussion" and in the "Changes" tab, we have
        // to remove all. Using $(".note[id='noteId']") ensure we get all the notes,
        // where $("#noteId") would return only one.
F
Fatih Acet 已提交
656 657 658 659
        return function(i, el) {
          var note, notes;
          note = $(el);
          notes = note.closest(".notes");
660

661
          if (typeof gl.diffNotesCompileComponents !== 'undefined') {
662 663
            if (gl.diffNoteApps[noteElId]) {
              gl.diffNoteApps[noteElId].$destroy();
664 665 666
            }
          }

667 668
          note.remove();

669
          // check if this is the last note for this line
670 671 672
          if (notes.find(".note").length === 0) {
            var notesTr = notes.closest("tr");

673
            // "Discussions" tab
F
Fatih Acet 已提交
674
            notes.closest(".timeline-entry").remove();
675 676 677 678 679 680 681

            if (!_this.isParallelView() || notesTr.find('.note').length === 0) {
              // "Changes" tab / commit view
              notesTr.remove();
            } else {
              notes.closest('.content').empty();
            }
F
Fatih Acet 已提交
682 683 684 685
          }
          return note.remove();
        };
      })(this));
686
      // Decrement the "Discussions" counter only once
F
Fatih Acet 已提交
687 688 689 690 691
      return this.updateNotesCount(-1);
    };

    /*
    Called in response to clicking the delete attachment link
692

F
Fatih Acet 已提交
693 694 695 696 697 698 699 700 701 702 703 704 705 706 707
    Removes the attachment wrapper view, including image tag if it exists
    Resets the note editing form
     */

    Notes.prototype.removeAttachment = function() {
      var note;
      note = $(this).closest(".note");
      note.find(".note-attachment").remove();
      note.find(".note-body > .note-text").show();
      note.find(".note-header").show();
      return note.find(".current-note-edit-form").remove();
    };

    /*
    Called when clicking on the "reply" button for a diff line.
708

F
Fatih Acet 已提交
709 710 711 712 713 714 715
    Shows the note form below the notes.
     */

    Notes.prototype.replyToDiscussionNote = function(e) {
      var form, replyLink;
      form = this.formClone.clone();
      replyLink = $(e.target).closest(".js-discussion-reply-button");
716
      // insert the form after the button
717 718 719 720
      replyLink
        .closest('.discussion-reply-holder')
        .hide()
        .after(form);
721
      // show the form
F
Fatih Acet 已提交
722 723 724 725 726
      return this.setupDiscussionNoteForm(replyLink, form);
    };

    /*
    Shows the diff or discussion form and does some setup on it.
727

F
Fatih Acet 已提交
728
    Sets some hidden fields in the form.
729

F
Fatih Acet 已提交
730 731 732 733 734
    Note: dataHolder must have the "discussionId", "lineCode", "noteableType"
    and "noteableId" data attributes set.
     */

    Notes.prototype.setupDiscussionNoteForm = function(dataHolder, form) {
735
      // setup note target
F
Fatih Acet 已提交
736 737 738 739 740 741 742 743 744 745
      form.attr('id', "new-discussion-note-form-" + (dataHolder.data("discussionId")));
      form.attr("data-line-code", dataHolder.data("lineCode"));
      form.find("#note_type").val(dataHolder.data("noteType"));
      form.find("#line_type").val(dataHolder.data("lineType"));
      form.find("#note_commit_id").val(dataHolder.data("commitId"));
      form.find("#note_line_code").val(dataHolder.data("lineCode"));
      form.find("#note_position").val(dataHolder.attr("data-position"));
      form.find("#note_noteable_type").val(dataHolder.data("noteableType"));
      form.find("#note_noteable_id").val(dataHolder.data("noteableId"));
      form.find('.js-note-discard').show().removeClass('js-note-discard').addClass('js-close-discussion-note-form').text(form.find('.js-close-discussion-note-form').data('cancel-text'));
746
      form.find('.js-note-target-close').remove();
F
Fatih Acet 已提交
747
      this.setupNoteForm(form);
748

749
      if (typeof gl.diffNotesCompileComponents !== 'undefined') {
750
        var $commentBtn = form.find('comment-and-resolve-btn');
751
        $commentBtn
752
          .attr(':discussion-id', "'" + dataHolder.data('discussionId') + "'");
P
Phil Hughes 已提交
753

754
        gl.diffNotesCompileComponents();
755 756
      }

F
Fatih Acet 已提交
757
      form.find(".js-note-text").focus();
758 759 760 761 762 763
      form
        .find('.js-comment-resolve-button')
        .attr('data-discussion-id', dataHolder.data('discussionId'));
      form
        .removeClass('js-main-target-form')
        .addClass("discussion-form js-discussion-note-form");
F
Fatih Acet 已提交
764 765 766 767
    };

    /*
    Called when clicking on the "add a comment" button on the side of a diff line.
768

F
Fatih Acet 已提交
769 770 771 772 773
    Inserts a temporary row for the form below the line.
    Sets up the form and shows it.
     */

    Notes.prototype.addDiffNote = function(e) {
774
      var $link, addForm, hasNotes, lineType, newForm, nextRow, noteForm, notesContent, notesContentSelector, replyButton, row, rowCssToAdd, targetContent, isDiffCommentAvatar;
F
Fatih Acet 已提交
775
      e.preventDefault();
776
      $link = $(e.currentTarget || e.target);
F
Fatih Acet 已提交
777 778 779 780
      row = $link.closest("tr");
      nextRow = row.next();
      hasNotes = nextRow.is(".notes_holder");
      addForm = false;
781 782
      notesContentSelector = ".notes_content";
      rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\" colspan=\"2\"></td><td class=\"notes_content\"><div class=\"content\"></div></td></tr>";
783
      isDiffCommentAvatar = $link.hasClass('js-diff-comment-avatar');
784
      // In parallel view, look inside the correct left/right pane
F
Fatih Acet 已提交
785 786
      if (this.isParallelView()) {
        lineType = $link.data("lineType");
787 788
        notesContentSelector += "." + lineType;
        rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line old\"></td><td class=\"notes_content parallel old\"><div class=\"content\"></div></td><td class=\"notes_line new\"></td><td class=\"notes_content parallel new\"><div class=\"content\"></div></td></tr>";
F
Fatih Acet 已提交
789
      }
790
      notesContentSelector += " .content";
791 792 793
      notesContent = nextRow.find(notesContentSelector);

      if (hasNotes && !isDiffCommentAvatar) {
794 795
        nextRow.show();
        notesContent = nextRow.find(notesContentSelector);
F
Fatih Acet 已提交
796
        if (notesContent.length) {
797
          notesContent.show();
F
Fatih Acet 已提交
798 799 800 801 802
          replyButton = notesContent.find(".js-discussion-reply-button:visible");
          if (replyButton.length) {
            e.target = replyButton[0];
            $.proxy(this.replyToDiscussionNote, replyButton[0], e).call();
          } else {
803
            // In parallel view, the form may not be present in one of the panes
F
Fatih Acet 已提交
804 805 806 807 808 809
            noteForm = notesContent.find(".js-discussion-note-form");
            if (noteForm.length === 0) {
              addForm = true;
            }
          }
        }
810
      } else if (!isDiffCommentAvatar) {
811
        // add a notes row and insert the form
F
Fatih Acet 已提交
812
        row.after(rowCssToAdd);
813 814
        nextRow = row.next();
        notesContent = nextRow.find(notesContentSelector);
F
Fatih Acet 已提交
815
        addForm = true;
816 817 818 819 820 821 822
      } else {
        nextRow.show();
        notesContent.toggle(!notesContent.is(':visible'));

        if (!nextRow.find('.content:not(:empty)').is(':visible')) {
          nextRow.hide();
        }
F
Fatih Acet 已提交
823
      }
824

F
Fatih Acet 已提交
825 826
      if (addForm) {
        newForm = this.formClone.clone();
827
        newForm.appendTo(notesContent);
828
        // show the form
F
Fatih Acet 已提交
829 830 831 832 833 834
        return this.setupDiscussionNoteForm($link, newForm);
      }
    };

    /*
    Called in response to "cancel" on a diff note form.
835

F
Fatih Acet 已提交
836 837 838 839 840 841 842 843 844 845
    Shows the reply button again.
    Removes the form and if necessary it's temporary row.
     */

    Notes.prototype.removeDiscussionNoteForm = function(form) {
      var glForm, row;
      row = form.closest("tr");
      glForm = form.data('gl-form');
      glForm.destroy();
      form.find(".js-note-text").data("autosave").reset();
846
      // show the reply button (will only work for replies)
847 848 849
      form
        .prev('.discussion-reply-holder')
        .show();
F
Fatih Acet 已提交
850
      if (row.is(".js-temp-notes-holder")) {
851
        // remove temporary row for diff lines
F
Fatih Acet 已提交
852 853
        return row.remove();
      } else {
854
        // only remove the form
F
Fatih Acet 已提交
855 856 857 858 859 860 861 862 863 864 865 866 867
        return form.remove();
      }
    };

    Notes.prototype.cancelDiscussionForm = function(e) {
      var form;
      e.preventDefault();
      form = $(e.target).closest(".js-discussion-note-form");
      return this.removeDiscussionNoteForm(form);
    };

    /*
    Called after an attachment file has been selected.
868

F
Fatih Acet 已提交
869 870 871 872 873 874
    Updates the file name for the selected attachment.
     */

    Notes.prototype.updateFormAttachment = function() {
      var filename, form;
      form = $(this).closest("form");
875
      // get only the basename
F
Fatih Acet 已提交
876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941
      filename = $(this).val().replace(/^.*[\\\/]/, "");
      return form.find(".js-attachment-filename").text(filename);
    };

    /*
    Called when the tab visibility changes
     */

    Notes.prototype.visibilityChange = function() {
      return this.refresh();
    };

    Notes.prototype.updateCloseButton = function(e) {
      var closebtn, form, textarea;
      textarea = $(e.target);
      form = textarea.parents('form');
      closebtn = form.find('.js-note-target-close');
      return closebtn.text(closebtn.data('original-text'));
    };

    Notes.prototype.updateTargetButtons = function(e) {
      var closebtn, closetext, discardbtn, form, reopenbtn, reopentext, textarea;
      textarea = $(e.target);
      form = textarea.parents('form');
      reopenbtn = form.find('.js-note-target-reopen');
      closebtn = form.find('.js-note-target-close');
      discardbtn = form.find('.js-note-discard');
      if (textarea.val().trim().length > 0) {
        reopentext = reopenbtn.data('alternative-text');
        closetext = closebtn.data('alternative-text');
        if (reopenbtn.text() !== reopentext) {
          reopenbtn.text(reopentext);
        }
        if (closebtn.text() !== closetext) {
          closebtn.text(closetext);
        }
        if (reopenbtn.is(':not(.btn-comment-and-reopen)')) {
          reopenbtn.addClass('btn-comment-and-reopen');
        }
        if (closebtn.is(':not(.btn-comment-and-close)')) {
          closebtn.addClass('btn-comment-and-close');
        }
        if (discardbtn.is(':hidden')) {
          return discardbtn.show();
        }
      } else {
        reopentext = reopenbtn.data('original-text');
        closetext = closebtn.data('original-text');
        if (reopenbtn.text() !== reopentext) {
          reopenbtn.text(reopentext);
        }
        if (closebtn.text() !== closetext) {
          closebtn.text(closetext);
        }
        if (reopenbtn.is('.btn-comment-and-reopen')) {
          reopenbtn.removeClass('btn-comment-and-reopen');
        }
        if (closebtn.is('.btn-comment-and-close')) {
          closebtn.removeClass('btn-comment-and-close');
        }
        if (discardbtn.is(':visible')) {
          return discardbtn.hide();
        }
      }
    };

942
    Notes.prototype.putEditFormInPlace = function($el) {
943
      var $editForm = $(this.getEditFormSelector($el));
944 945 946 947 948 949 950 951 952 953
      var $note = $el.closest('.note');

      $editForm.insertAfter($note.find('.note-text'));

      var $originalContentEl = $note.find('.original-note-content');
      var originalContent = $originalContentEl.text().trim();
      var postUrl = $originalContentEl.data('post-url');
      var targetId = $originalContentEl.data('target-id');
      var targetType = $originalContentEl.data('target-type');

L
Luke "Jared" Bennett 已提交
954
      new gl.GLForm($editForm.find('form'));
955

956 957 958
      $editForm.find('form')
        .attr('action', postUrl)
        .attr('data-remote', 'true');
F
Fatih Acet 已提交
959 960
      $editForm.find('.js-form-target-id').val(targetId);
      $editForm.find('.js-form-target-type').val(targetType);
961
      $editForm.find('.js-note-text').focus().val(originalContent);
F
Fatih Acet 已提交
962 963
      $editForm.find('.js-md-write-button').trigger('click');
      $editForm.find('.referenced-users').hide();
964
    };
965

F
Fatih Acet 已提交
966
    Notes.prototype.updateNotesCount = function(updateCount) {
967
      return this.notesCountBadge.text(parseInt(this.notesCountBadge.text(), 10) + updateCount);
F
Fatih Acet 已提交
968 969
    };

970 971 972
    Notes.prototype.resolveDiscussion = function() {
      var $this = $(this);
      var discussionId = $this.attr('data-discussion-id');
973 974 975 976 977

      $this
        .closest('form')
        .attr('data-discussion-id', discussionId)
        .attr('data-resolve-all', 'true')
978
        .attr('data-project-path', $this.attr('data-project-path'));
979 980
    };

981
    Notes.prototype.toggleCommitList = function(e) {
982
      const $element = $(e.currentTarget);
983 984
      const $closestSystemCommitList = $element.siblings('.system-note-commit-list');

985
      $element.find('.fa').toggleClass('fa-angle-down').toggleClass('fa-angle-up');
986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011
      $closestSystemCommitList.toggleClass('hide-shade');
    };

    /**
    Scans system notes with `ul` elements in system note body
    then collapse long commit list pushed by user to make it less
    intrusive.
     */
    Notes.prototype.collapseLongCommitList = function() {
      const systemNotes = $('#notes-list').find('li.system-note').has('ul');

      $.each(systemNotes, function(index, systemNote) {
        const $systemNote = $(systemNote);
        const headerMessage = $systemNote.find('.note-text').find('p:first').text().replace(':', '');

        $systemNote.find('.note-header .system-note-message').html(headerMessage);

        if ($systemNote.find('li').length > MAX_VISIBLE_COMMIT_LIST_COUNT) {
          $systemNote.find('.note-text').addClass('system-note-commit-list');
          $systemNote.find('.system-note-commit-list-toggler').show();
        } else {
          $systemNote.find('.note-text').addClass('system-note-commit-list hide-shade');
        }
      });
    };

F
Fatih Acet 已提交
1012 1013
    return Notes;
  })();
1014
}).call(window);