notes.js 59.2 KB
Newer Older
1 2
/* eslint-disable no-restricted-properties, camelcase,
no-unused-expressions, default-case,
3
consistent-return, no-alert, no-param-reassign, no-else-return,
4
no-shadow, no-useless-escape,
5
class-methods-use-this */
6

7
/* global ResolveService */
F
Fatih Acet 已提交
8

9 10 11 12
/*
old_notes_spec.js is the spec for the legacy, jQuery notes application. It has nothing to do with the new, fancy Vue notes app.
 */

13
import $ from 'jquery';
14
import _ from 'underscore';
15
import Cookies from 'js-cookie';
16
import Autosize from 'autosize';
17 18
import 'jquery.caret'; // required by at.js
import 'at.js';
19
import Vue from 'vue';
C
Clement Ho 已提交
20
import { GlSkeletonLoading } from '@gitlab/ui';
21 22
import AjaxCache from '~/lib/utils/ajax_cache';
import syntaxHighlight from '~/syntax_highlight';
P
Phil Hughes 已提交
23
import axios from './lib/utils/axios_utils';
P
Phil Hughes 已提交
24
import { getLocationHash } from './lib/utils/url_utility';
P
Phil Hughes 已提交
25
import Flash from './flash';
K
Kushal Pandya 已提交
26
import { defaultAutocompleteConfig } from './gfm_auto_complete';
27
import CommentTypeToggle from './comment_type_toggle';
28
import GLForm from './gl_form';
29
import loadAwardsHandler from './awards_handler';
30
import Autosave from './autosave';
31
import TaskList from './task_list';
F
Fatih Acet 已提交
32 33 34 35 36
import {
  isInViewport,
  getPagePath,
  scrollToElement,
  isMetaKey,
F
Felipe Artur 已提交
37
  isInMRPage,
F
Fatih Acet 已提交
38
} from './lib/utils/common_utils';
39
import { localTimeAgo } from './lib/utils/datetime_utility';
40
import { sprintf, s__, __ } from './locale';
41

42
window.autosize = Autosize;
F
Fatih Acet 已提交
43

44
function normalizeNewlines(str) {
45
  return str.replace(/\r\n/g, '\n');
46 47 48 49 50 51
}

const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm;

export default class Notes {
K
Kushal Pandya 已提交
52
  static initialize(notes_url, note_ids, last_fetched_at, view, enableGFM) {
P
Phil Hughes 已提交
53
    if (!this.instance) {
F
Felipe Artur 已提交
54
      this.instance = new Notes(notes_url, note_ids, last_fetched_at, view, enableGFM);
P
Phil Hughes 已提交
55 56 57
    }
  }

58 59 60 61
  static getInstance() {
    return this.instance;
  }

K
Kushal Pandya 已提交
62
  constructor(notes_url, note_ids, last_fetched_at, view, enableGFM = defaultAutocompleteConfig) {
63 64 65 66 67
    this.updateTargetButtons = this.updateTargetButtons.bind(this);
    this.updateComment = this.updateComment.bind(this);
    this.visibilityChange = this.visibilityChange.bind(this);
    this.cancelDiscussionForm = this.cancelDiscussionForm.bind(this);
    this.onAddDiffNote = this.onAddDiffNote.bind(this);
F
Felipe Artur 已提交
68
    this.onAddImageDiffNote = this.onAddImageDiffNote.bind(this);
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
    this.setupDiscussionNoteForm = this.setupDiscussionNoteForm.bind(this);
    this.onReplyToDiscussionNote = this.onReplyToDiscussionNote.bind(this);
    this.removeNote = this.removeNote.bind(this);
    this.cancelEdit = this.cancelEdit.bind(this);
    this.updateNote = this.updateNote.bind(this);
    this.addDiscussionNote = this.addDiscussionNote.bind(this);
    this.addNoteError = this.addNoteError.bind(this);
    this.addNote = this.addNote.bind(this);
    this.resetMainTargetForm = this.resetMainTargetForm.bind(this);
    this.refresh = this.refresh.bind(this);
    this.keydownNoteText = this.keydownNoteText.bind(this);
    this.toggleCommitList = this.toggleCommitList.bind(this);
    this.postComment = this.postComment.bind(this);
    this.clearFlashWrapper = this.clearFlash.bind(this);
    this.onHashChange = this.onHashChange.bind(this);

    this.notes_url = notes_url;
    this.note_ids = note_ids;
    this.enableGFM = enableGFM;
    // Used to keep track of updated notes while people are editing things
    this.updatedNotesTrackingMap = {};
    this.last_fetched_at = last_fetched_at;
    this.noteable_url = document.URL;
F
Fatih Acet 已提交
92 93
    this.notesCountBadge ||
      (this.notesCountBadge = $('.issuable-details').find('.notes-tab .badge'));
94 95 96
    this.basePollingInterval = 15000;
    this.maxPollingSteps = 4;

F
Felipe Artur 已提交
97
    this.$wrapperEl = isInMRPage() ? $(document).find('.diffs') : $(document);
98 99 100
    this.cleanBinding();
    this.addBinding();
    this.setPollingInterval();
K
Kushal Pandya 已提交
101
    this.setupMainTargetNoteForm(enableGFM);
102
    this.taskList = new TaskList({
103 104
      dataType: 'note',
      fieldName: 'note',
F
Fatih Acet 已提交
105
      selector: '.notes',
106 107 108 109 110
    });
    this.collapseLongCommitList();
    this.setViewType(view);

    // We are in the Merge Requests page so we need another edit form for Changes tab
F
Filipa Lacerda 已提交
111
    if (getPagePath(1) === 'merge_requests') {
F
Fatih Acet 已提交
112 113 114 115
      $('.note-edit-form')
        .clone()
        .addClass('mr-note-edit-form')
        .insertAfter('.note-edit-form');
116
    }
117 118 119 120 121 122 123

    const hash = getLocationHash();
    const $anchor = hash && document.getElementById(hash);

    if ($anchor) {
      this.loadLazyDiff({ currentTarget: $anchor });
    }
124 125 126 127 128 129 130 131
  }

  setViewType(view) {
    this.view = Cookies.get('diff_view') || view;
  }

  addBinding() {
    // Edit note link
132 133
    this.$wrapperEl.on('click', '.js-note-edit', this.showEditForm.bind(this));
    this.$wrapperEl.on('click', '.note-edit-cancel', this.cancelEdit);
134
    // Reopen and close actions for Issue/MR combined with note form submit
135 136
    this.$wrapperEl.on('click', '.js-comment-submit-button', this.postComment);
    this.$wrapperEl.on('click', '.js-comment-save-button', this.updateComment);
F
Felipe Artur 已提交
137
    this.$wrapperEl.on('keyup input', '.js-note-text', this.updateTargetButtons);
138
    // resolve a discussion
139
    this.$wrapperEl.on('click', '.js-comment-resolve-button', this.postComment);
140
    // remove a note (in general)
141
    this.$wrapperEl.on('click', '.js-note-delete', this.removeNote);
142
    // delete note attachment
F
Felipe Artur 已提交
143
    this.$wrapperEl.on('click', '.js-note-attachment-delete', this.removeAttachment);
144
    // update the file name when an attachment is selected
F
Felipe Artur 已提交
145
    this.$wrapperEl.on('change', '.js-note-attachment-input', this.updateFormAttachment);
146
    // reply to diff/discussion notes
F
Felipe Artur 已提交
147
    this.$wrapperEl.on('click', '.js-discussion-reply-button', this.onReplyToDiscussionNote);
148
    // add diff note
149
    this.$wrapperEl.on('click', '.js-add-diff-note-button', this.onAddDiffNote);
F
Felipe Artur 已提交
150
    // add diff note for images
F
Felipe Artur 已提交
151
    this.$wrapperEl.on('click', '.js-add-image-diff-note-button', this.onAddImageDiffNote);
152
    // hide diff note form
F
Felipe Artur 已提交
153
    this.$wrapperEl.on('click', '.js-close-discussion-note-form', this.cancelDiscussionForm);
154
    // toggle commit list
F
Felipe Artur 已提交
155
    this.$wrapperEl.on('click', '.system-note-commit-list-toggler', this.toggleCommitList);
156 157

    this.$wrapperEl.on('click', '.js-toggle-lazy-diff', this.loadLazyDiff);
158 159 160 161 162
    this.$wrapperEl.on(
      'click',
      '.js-toggle-lazy-diff-retry-button',
      this.onClickRetryLazyLoad.bind(this),
    );
163

164
    // fetch notes when tab becomes visible
165
    this.$wrapperEl.on('visibilitychange', this.visibilityChange);
166
    // when issue status changes, we need to refresh data
167
    this.$wrapperEl.on('issuable:change', this.refresh);
168
    // ajax:events that happen on Form when actions like Reopen, Close are performed on Issues and MRs.
169
    this.$wrapperEl.on('ajax:success', '.js-main-target-form', this.addNote);
F
Felipe Artur 已提交
170 171
    this.$wrapperEl.on('ajax:success', '.js-discussion-note-form', this.addDiscussionNote);
    this.$wrapperEl.on('ajax:success', '.js-main-target-form', this.resetMainTargetForm);
F
Fatih Acet 已提交
172 173 174 175 176
    this.$wrapperEl.on(
      'ajax:complete',
      '.js-main-target-form',
      this.reenableTargetFormSubmitButton,
    );
177
    // when a key is clicked on the notes
178
    this.$wrapperEl.on('keydown', '.js-note-text', this.keydownNoteText);
179
    // When the URL fragment/hash has changed, `#note_xxx`
180
    $(window).on('hashchange', this.onHashChange);
181 182 183
  }

  cleanBinding() {
184 185 186 187 188 189 190 191 192 193 194 195 196 197
    this.$wrapperEl.off('click', '.js-note-edit');
    this.$wrapperEl.off('click', '.note-edit-cancel');
    this.$wrapperEl.off('click', '.js-note-delete');
    this.$wrapperEl.off('click', '.js-note-attachment-delete');
    this.$wrapperEl.off('click', '.js-discussion-reply-button');
    this.$wrapperEl.off('click', '.js-add-diff-note-button');
    this.$wrapperEl.off('click', '.js-add-image-diff-note-button');
    this.$wrapperEl.off('visibilitychange');
    this.$wrapperEl.off('keyup input', '.js-note-text');
    this.$wrapperEl.off('click', '.js-note-target-reopen');
    this.$wrapperEl.off('click', '.js-note-target-close');
    this.$wrapperEl.off('keydown', '.js-note-text');
    this.$wrapperEl.off('click', '.js-comment-resolve-button');
    this.$wrapperEl.off('click', '.system-note-commit-list-toggler');
198
    this.$wrapperEl.off('click', '.js-toggle-lazy-diff');
199
    this.$wrapperEl.off('click', '.js-toggle-lazy-diff-retry-button');
200 201 202
    this.$wrapperEl.off('ajax:success', '.js-main-target-form');
    this.$wrapperEl.off('ajax:success', '.js-discussion-note-form');
    this.$wrapperEl.off('ajax:complete', '.js-main-target-form');
203 204 205 206
    $(window).off('hashchange', this.onHashChange);
  }

  static initCommentTypeToggle(form) {
F
Felipe Artur 已提交
207 208
    const dropdownTrigger = form.querySelector('.js-comment-type-dropdown .dropdown-toggle');
    const dropdownList = form.querySelector('.js-comment-type-dropdown .dropdown-menu');
209
    const noteTypeInput = form.querySelector('#note_type');
F
Felipe Artur 已提交
210
    const submitButton = form.querySelector('.js-comment-type-dropdown .js-comment-submit-button');
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
    const closeButton = form.querySelector('.js-note-target-close');
    const reopenButton = form.querySelector('.js-note-target-reopen');

    const commentTypeToggle = new CommentTypeToggle({
      dropdownTrigger,
      dropdownList,
      noteTypeInput,
      submitButton,
      closeButton,
      reopenButton,
    });

    commentTypeToggle.initDroplab();
  }

  keydownNoteText(e) {
227 228 229 230 231 232 233
    let discussionNoteForm;
    let editNote;
    let myLastNote;
    let myLastNoteEditBtn;
    let newText;
    let originalText;

234
    if (isMetaKey(e)) {
235
      return;
F
Fatih Acet 已提交
236 237
    }

238
    const $textarea = $(e.target);
239 240 241 242 243 244
    // Edit previous note when UP arrow is hit
    switch (e.which) {
      case 38:
        if ($textarea.val() !== '') {
          return;
        }
F
Fatih Acet 已提交
245
        myLastNote = $(
F
Felipe Artur 已提交
246
          `li.note[data-author-id='${gon.current_user_id}'][data-editable]:last`,
F
Fatih Acet 已提交
247 248
          $textarea.closest('.note, .notes_holder, #notes'),
        );
249 250 251 252 253 254 255 256 257
        if (myLastNote.length) {
          myLastNoteEditBtn = myLastNote.find('.js-note-edit');
          return myLastNoteEditBtn.trigger('click', [true, myLastNote]);
        }
        break;
      // Cancel creating diff note or editing any note when ESCAPE is hit
      case 27:
        discussionNoteForm = $textarea.closest('.js-discussion-note-form');
        if (discussionNoteForm.length) {
F
Fatih Acet 已提交
258
          if ($textarea.val() !== '') {
259
            if (!window.confirm(__('Are you sure you want to cancel creating this comment?'))) {
260
              return;
F
Fatih Acet 已提交
261 262
            }
          }
263 264 265 266 267
          this.removeDiscussionNoteForm(discussionNoteForm);
          return;
        }
        editNote = $textarea.closest('.note');
        if (editNote.length) {
J
Jacob Schatz 已提交
268
          originalText = $textarea.closest('form').data('originalNote');
269 270
          newText = $textarea.val();
          if (originalText !== newText) {
271
            if (!window.confirm(__('Are you sure you want to cancel editing this comment?'))) {
272
              return;
F
Fatih Acet 已提交
273 274
            }
          }
275 276 277 278
          return this.removeNoteEditForm(editNote);
        }
    }
  }
F
Fatih Acet 已提交
279

280 281
  initRefresh() {
    if (Notes.interval) {
F
Fatih Acet 已提交
282
      clearInterval(Notes.interval);
283
    }
284
    Notes.interval = setInterval(() => this.refresh(), this.pollingInterval);
285
  }
F
Fatih Acet 已提交
286

287 288 289 290 291
  refresh() {
    if (!document.hidden) {
      return this.getContent();
    }
  }
F
Fatih Acet 已提交
292

293 294 295 296
  getContent() {
    if (this.refreshing) {
      return;
    }
297

298
    this.refreshing = true;
299

F
Fatih Acet 已提交
300 301 302 303 304 305 306
    axios
      .get(`${this.notes_url}?html=true`, {
        headers: {
          'X-Last-Fetched-At': this.last_fetched_at,
        },
      })
      .then(({ data }) => {
307
        const { notes } = data;
F
Fatih Acet 已提交
308 309 310 311 312 313 314 315 316
        this.last_fetched_at = data.last_fetched_at;
        this.setPollingInterval(data.notes.length);
        $.each(notes, (i, note) => this.renderNote(note));

        this.refreshing = false;
      })
      .catch(() => {
        this.refreshing = false;
      });
317 318 319 320 321 322 323 324 325 326 327 328 329 330
  }

  /**
   * 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.
   *
   * Note: this function is used to gradually increase the polling interval
   * if there aren't new notes coming from the server
   */
  setPollingInterval(shouldReset) {
    if (shouldReset == null) {
      shouldReset = true;
    }
331
    const nthInterval = this.basePollingInterval * Math.pow(2, this.maxPollingSteps - 1);
332 333 334 335 336 337 338 339 340
    if (shouldReset) {
      this.pollingInterval = this.basePollingInterval;
    } else if (this.pollingInterval < nthInterval) {
      this.pollingInterval *= 2;
    }
    return this.initRefresh();
  }

  handleQuickActions(noteEntity) {
341
    let votesBlock;
342 343 344
    if (noteEntity.commands_changes) {
      if ('merge' in noteEntity.commands_changes) {
        Notes.checkMergeRequestStatus();
F
Fatih Acet 已提交
345
      }
M
mhasbini 已提交
346

347 348
      if ('emoji_award' in noteEntity.commands_changes) {
        votesBlock = $('.js-awards-block').eq(0);
349

F
Fatih Acet 已提交
350 351
        loadAwardsHandler()
          .then(awardsHandler => {
F
Felipe Artur 已提交
352
            awardsHandler.addAwardToEmojiBar(votesBlock, noteEntity.commands_changes.emoji_award);
F
Fatih Acet 已提交
353 354 355 356 357
            awardsHandler.scrollToAwards();
          })
          .catch(() => {
            // ignore
          });
358
      }
359 360
    }
  }
361

362 363
  setupNewNote($note) {
    // Update datetime format on the recent note
364
    localTimeAgo($note.find('.js-timeago'), false);
F
Fatih Acet 已提交
365

366 367
    this.collapseLongCommitList();
    this.taskList.init();
368

369 370 371 372 373
    // This stops the note highlight, #note_xxx`, from being removed after real time update
    // The `:target` selector does not re-evaluate after we replace element in the DOM
    Notes.updateNoteTargetSelector($note);
    this.$noteToCleanHighlight = $note;
  }
M
mhasbini 已提交
374

375 376 377 378
  onHashChange() {
    if (this.$noteToCleanHighlight) {
      Notes.updateNoteTargetSelector(this.$noteToCleanHighlight);
    }
379

380 381 382 383
    this.$noteToCleanHighlight = null;
  }

  static updateNoteTargetSelector($note) {
P
Phil Hughes 已提交
384
    const hash = getLocationHash();
385 386 387 388 389 390 391 392 393 394 395 396 397 398
    // Needs to be an explicit true/false for the jQuery `toggleClass(force)`
    const addTargetClass = Boolean(hash && $note.filter(`#${hash}`).length > 0);
    $note.toggleClass('target', addTargetClass);
  }

  /**
   * Render note in main comments area.
   *
   * Note: for rendering inline notes use renderDiscussionNote
   */
  renderNote(noteEntity, $form, $notesList = $('.main-notes-list')) {
    if (noteEntity.discussion_html) {
      return this.renderDiscussionNote(noteEntity, $form);
    }
399

400
    if (!noteEntity.valid) {
401
      if (noteEntity.errors && noteEntity.errors.commands_only) {
F
Felipe Artur 已提交
402
        if (noteEntity.commands_changes && Object.keys(noteEntity.commands_changes).length > 0) {
403 404
          $notesList.find('.system-note.being-posted').remove();
        }
F
Felipe Artur 已提交
405
        this.addFlash(noteEntity.errors.commands_only, 'notice', this.parentTimeline.get(0));
406
        this.refresh();
F
Fatih Acet 已提交
407
      }
408 409
      return;
    }
F
Fatih Acet 已提交
410

411 412
    const $note = $notesList.find(`#note_${noteEntity.id}`);
    if (Notes.isNewNote(noteEntity, this.note_ids)) {
F
Felipe Artur 已提交
413
      if (isInMRPage()) {
414 415 416
        return;
      }

417
      this.note_ids.push(noteEntity.id);
418

419 420
      if ($notesList.length) {
        $notesList.find('.system-note.being-posted').remove();
421
      }
422
      const $newNote = Notes.animateAppendNote(noteEntity.html, $notesList);
423

424 425
      this.setupNewNote($newNote);
      this.refresh();
F
Fatih Acet 已提交
426
      return this.updateNotesCount(1);
F
Fatih Acet 已提交
427 428
    } else if (Notes.isUpdatedNote(noteEntity, $note)) {
      // The server can send the same update multiple times so we need to make sure to only update once per actual update.
429 430
      const isEditing = $note.hasClass('is-editing');
      const initialContent = normalizeNewlines(
F
Fatih Acet 已提交
431 432 433 434
        $note
          .find('.original-note-content')
          .text()
          .trim(),
435 436 437 438 439
      );
      const $textarea = $note.find('.js-note-text');
      const currentContent = $textarea.val();
      // There can be CRLF vs LF mismatches if we don't sanitize and compare the same way
      const sanitizedNoteNote = normalizeNewlines(noteEntity.note);
F
Fatih Acet 已提交
440
      const isTextareaUntouched =
F
Felipe Artur 已提交
441
        currentContent === initialContent || currentContent === sanitizedNoteNote;
442

443 444 445
      if (isEditing && isTextareaUntouched) {
        $textarea.val(noteEntity.note);
        this.updatedNotesTrackingMap[noteEntity.id] = noteEntity;
F
Fatih Acet 已提交
446
      } else if (isEditing && !isTextareaUntouched) {
447 448
        this.putConflictEditWarningInPlace(noteEntity, $note);
        this.updatedNotesTrackingMap[noteEntity.id] = noteEntity;
F
Fatih Acet 已提交
449
      } else {
450 451
        const $updatedNote = Notes.animateUpdateNote(noteEntity.html, $note);
        this.setupNewNote($updatedNote);
K
Kushal Pandya 已提交
452
      }
453 454 455 456 457 458 459 460
    }
  }

  isParallelView() {
    return Cookies.get('diff_view') === 'parallel';
  }

  /**
461
   * Render note in discussion area. To render inline notes use renderDiscussionNote.
462 463
   */
  renderDiscussionNote(noteEntity, $form) {
464 465
    let discussionContainer;
    let row;
466

467 468 469 470
    if (!Notes.isNewNote(noteEntity, this.note_ids)) {
      return;
    }
    this.note_ids.push(noteEntity.id);
471

472 473
    const form =
      $form || $(`.js-discussion-note-form[data-discussion-id="${noteEntity.discussion_id}"]`);
F
Fatih Acet 已提交
474 475 476 477
    row =
      form.length || !noteEntity.discussion_line_code
        ? form.closest('tr')
        : $(`#${noteEntity.discussion_line_code}`);
F
Felipe Artur 已提交
478 479 480 481 482

    if (noteEntity.on_image) {
      row = form;
    }

483 484
    const lineType = this.isParallelView() ? form.find('#line_type').val() : 'old';
    const diffAvatarContainer = row
F
Fatih Acet 已提交
485 486
      .prevAll('.line_holder')
      .first()
487
      .find(`.js-avatar-container.${lineType}_line`);
488
    // is this the first note of discussion?
F
Felipe Artur 已提交
489
    discussionContainer = $(`.notes[data-discussion-id="${noteEntity.discussion_id}"]`);
490 491 492 493 494
    if (!discussionContainer.length) {
      discussionContainer = form.closest('.discussion').find('.notes');
    }
    if (discussionContainer.length === 0) {
      if (noteEntity.diff_discussion_html) {
495
        const $discussion = $(noteEntity.diff_discussion_html).renderGFM();
496

F
Felipe Artur 已提交
497
        if (!this.isParallelView() || row.hasClass('js-temp-notes-holder') || noteEntity.on_image) {
498 499 500 501
          // insert the note and the reply button after the temp row
          row.after($discussion);
        } else {
          // Merge new discussion HTML in
502 503 504 505
          const $notes = $discussion.find(
            `.notes[data-discussion-id="${noteEntity.discussion_id}"]`,
          );
          const contentContainerClass = $notes
506 507 508 509
            .closest('.notes-content')
            .attr('class')
            .split(' ')
            .join('.');
F
Fatih Acet 已提交
510 511

          row
512
            .find(`.${contentContainerClass} .content`)
F
Fatih Acet 已提交
513
            .append($notes.closest('.content').children());
514
        }
F
Felipe Artur 已提交
515 516
      } else {
        Notes.animateAppendNote(noteEntity.discussion_html, $('.main-notes-list'));
K
Kushal Pandya 已提交
517
      }
518 519 520 521
    } else {
      // append new note to all matching discussions
      Notes.animateAppendNote(noteEntity.html, discussionContainer);
    }
F
Fatih Acet 已提交
522

F
Felipe Artur 已提交
523
    if (typeof gl.diffNotesCompileComponents !== 'undefined' && noteEntity.discussion_resolvable) {
524
      gl.diffNotesCompileComponents();
F
Felipe Artur 已提交
525

526 527
      this.renderDiscussionAvatar(diffAvatarContainer, noteEntity);
    }
528

529
    localTimeAgo($('.js-timeago'), false);
530 531 532
    Notes.checkMergeRequestStatus();
    return this.updateNotesCount(1);
  }
F
Fatih Acet 已提交
533

534
  getLineHolder(changesDiscussionContainer) {
F
Fatih Acet 已提交
535 536
    return $(changesDiscussionContainer)
      .closest('.notes_holder')
537 538 539 540
      .prevAll('.line_holder')
      .first()
      .get(0);
  }
541

542
  renderDiscussionAvatar(diffAvatarContainer, noteEntity) {
543
    let avatarHolder = diffAvatarContainer.find('.diff-comment-avatar-holders');
544

545 546 547
    if (!avatarHolder.length) {
      avatarHolder = document.createElement('diff-note-avatars');
      avatarHolder.setAttribute('discussion-id', noteEntity.discussion_id);
F
Fatih Acet 已提交
548

549
      diffAvatarContainer.append(avatarHolder);
550

551 552 553 554 555 556 557 558 559 560 561 562
      gl.diffNotesCompileComponents();
    }
  }

  /**
   * Called in response the main target form has been successfully submitted.
   *
   * Removes any errors.
   * Resets text and preview.
   * Resets buttons.
   */
  resetMainTargetForm(e) {
563
    const form = $('.js-main-target-form');
564 565 566 567
    // remove validation errors
    form.find('.js-errors').remove();
    // reset text and preview
    form.find('.js-md-write-button').click();
F
Fatih Acet 已提交
568 569 570 571 572 573 574 575
    form
      .find('.js-note-text')
      .val('')
      .trigger('input');
    form
      .find('.js-note-text')
      .data('autosave')
      .reset();
576

577
    const event = document.createEvent('Event');
578 579 580 581 582 583 584
    event.initEvent('autosize:update', true, false);
    form.find('.js-autosize')[0].dispatchEvent(event);

    this.updateTargetButtons(e);
  }

  reenableTargetFormSubmitButton() {
585
    const form = $('.js-main-target-form');
586 587 588 589 590 591 592 593
    return form.find('.js-note-text').trigger('input');
  }

  /**
   * Shows the main form and does some setup on it.
   *
   * Sets some hidden fields in the form.
   */
K
Kushal Pandya 已提交
594
  setupMainTargetNoteForm(enableGFM) {
595
    // find the form
596
    const form = $('.js-new-note-form');
597 598 599
    // Set a global clone of the form for later cloning
    this.formClone = form.clone();
    // show the form
K
Kushal Pandya 已提交
600
    this.setupNoteForm(form, enableGFM);
601 602 603 604 605 606
    // fix classes
    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').val('');
607
    form.find('#note_project_id').remove();
608
    form.find('#in_reply_to_discussion_id').remove();
F
Fatih Acet 已提交
609 610 611 612
    form
      .find('.js-comment-resolve-button')
      .closest('comment-and-resolve-btn')
      .remove();
613 614 615 616 617 618 619 620 621 622 623 624
    this.parentTimeline = form.parents('.timeline');

    if (form.length) {
      Notes.initCommentTypeToggle(form.get(0));
    }
  }

  /**
   * General note form setup.
   *
   * deactivates the submit button when text is empty
   * hides the preview button when text is empty
625
   * set up GFM auto complete
626 627
   * show the form
   */
K
Kushal Pandya 已提交
628 629
  setupNoteForm(form, enableGFM = defaultAutocompleteConfig) {
    this.glForm = new GLForm(form, enableGFM);
630 631
    const textarea = form.find('.js-note-text');
    const key = [
632
      s__('NoteForm|Note'),
633 634 635 636
      form.find('#note_noteable_type').val(),
      form.find('#note_noteable_id').val(),
      form.find('#note_commit_id').val(),
      form.find('#note_type').val(),
637
      form.find('#note_project_id').val(),
638
      form.find('#in_reply_to_discussion_id').val(),
639

640 641
      // LegacyDiffNote
      form.find('#note_line_code').val(),
642

643
      // DiffNote
F
Felipe Artur 已提交
644
      form.find('#note_position').val(),
645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664
    ];
    return new Autosave(textarea, key);
  }

  /**
   * Called in response to the new note form being submitted
   *
   * Adds new note to list.
   */
  addNote($form, note) {
    return this.renderNote(note);
  }

  addNoteError($form) {
    let formParentTimeline;
    if ($form.hasClass('js-main-target-form')) {
      formParentTimeline = $form.parents('.timeline');
    } else if ($form.hasClass('js-discussion-note-form')) {
      formParentTimeline = $form.closest('.discussion-notes').find('.notes');
    }
F
Fatih Acet 已提交
665
    return this.addFlash(
666 667 668
      __(
        'Your comment could not be submitted! Please check your network connection and try again.',
      ),
F
Fatih Acet 已提交
669 670 671
      'alert',
      formParentTimeline.get(0),
    );
672 673
  }

674
  updateNoteError() {
675
    // eslint-disable-next-line no-new
F
Fatih Acet 已提交
676
    new Flash(
677
      __('Your comment could not be updated! Please check your network connection and try again.'),
F
Fatih Acet 已提交
678
    );
679 680 681 682 683 684 685 686 687
  }

  /**
   * Called in response to the new note form being submitted
   *
   * Adds new note to list.
   */
  addDiscussionNote($form, note, isNewDiffComment) {
    if ($form.attr('data-resolve-all') != null) {
688 689
      const discussionId = $form.data('discussionId');
      const mergeRequestId = $form.data('noteableIid');
690 691 692 693 694

      if (ResolveService != null) {
        ResolveService.toggleResolveForDiscussion(mergeRequestId, discussionId);
      }
    }
695

696 697 698 699 700 701 702 703 704 705 706 707 708 709
    this.renderNote(note, $form);
    // cleanup after successfully creating a diff/discussion note
    if (isNewDiffComment) {
      this.removeDiscussionNoteForm($form);
    }
  }

  /**
   * Called in response to the edit note form being submitted
   *
   * Updates the current note field.
   */
  updateNote(noteEntity, $targetNote) {
    // Convert returned HTML to a jQuery object so we can modify it further
710
    const $noteEntityEl = $(noteEntity.html);
711 712 713
    this.revertNoteEditForm($targetNote);
    $noteEntityEl.renderGFM();
    // Find the note's `li` element by ID and replace it with the updated HTML
714
    const $note_li = $(`.note-row-${noteEntity.id}`);
715 716 717 718 719 720 721 722

    $note_li.replaceWith($noteEntityEl);
    this.setupNewNote($noteEntityEl);

    if (typeof gl.diffNotesCompileComponents !== 'undefined') {
      gl.diffNotesCompileComponents();
    }
  }
723

724
  checkContentToAllowEditing($el) {
725
    const initialContent = $el
F
Fatih Acet 已提交
726 727 728
      .find('.original-note-content')
      .text()
      .trim();
729 730
    const currentContent = $el.find('.js-note-text').val();
    let isAllowed = true;
731

732 733
    if (currentContent === initialContent) {
      this.removeNoteEditForm($el);
F
Fatih Acet 已提交
734
    } else {
735
      const isWidgetVisible = isInViewport($el.get(0));
736

737
      if (!isWidgetVisible) {
F
Filipa Lacerda 已提交
738
        scrollToElement($el);
739 740
      }

741 742 743
      $el.find('.js-finish-edit-warning').show();
      isAllowed = false;
    }
F
Fatih Acet 已提交
744

745 746
    return isAllowed;
  }
747

748 749 750 751 752 753
  /**
   * Called in response to clicking the edit note link
   *
   * 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
   */
754
  showEditForm(e) {
755
    e.preventDefault();
F
Fatih Acet 已提交
756

757 758 759 760
    const $target = $(e.target);
    const $editForm = $(this.getEditFormSelector($target));
    const $note = $target.closest('.note');
    const $currentlyEditing = $('.note.is-editing:visible');
761

762
    if ($currentlyEditing.length) {
763
      const isEditAllowed = this.checkContentToAllowEditing($currentlyEditing);
764

765 766
      if (!isEditAllowed) {
        return;
767
      }
768
    }
769

770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794
    $note.find('.js-note-attachment-delete').show();
    $editForm.addClass('current-note-edit-form');
    $note.addClass('is-editing');
    this.putEditFormInPlace($target);
  }

  /**
   * Called in response to clicking the edit note link
   *
   * Hides edit form and restores the original note text to the editor textarea.
   */
  cancelEdit(e) {
    e.preventDefault();
    const $target = $(e.target);
    const $note = $target.closest('.note');
    const noteId = $note.attr('data-note-id');

    this.revertNoteEditForm($target);

    if (this.updatedNotesTrackingMap[noteId]) {
      const $newNote = $(this.updatedNotesTrackingMap[noteId].html);
      $note.replaceWith($newNote);
      this.setupNewNote($newNote);
      // Now that we have taken care of the update, clear it out
      delete this.updatedNotesTrackingMap[noteId];
F
Fatih Acet 已提交
795
    } else {
796 797 798 799
      $note.find('.js-finish-edit-warning').hide();
      this.removeNoteEditForm($note);
    }
  }
800

801 802
  revertNoteEditForm($target) {
    $target = $target || $('.note.is-editing:visible');
803 804
    const selector = this.getEditFormSelector($target);
    const $editForm = $(selector);
805

806
    $editForm.insertBefore('.diffs');
807 808 809
    $editForm.find('.js-comment-save-button').enable();
    $editForm.find('.js-finish-edit-warning').hide();
  }
810

811
  getEditFormSelector($el) {
812
    let selector = '.note-edit-form:not(.mr-note-edit-form)';
813

814 815 816
    if ($el.parents('#diffs').length) {
      selector = '.note-edit-form.mr-note-edit-form';
    }
F
Fatih Acet 已提交
817

818 819 820 821
    return selector;
  }

  removeNoteEditForm($note) {
822
    const form = $note.find('.diffs .current-note-edit-form');
823

824 825 826 827
    $note.removeClass('is-editing');
    form.removeClass('current-note-edit-form');
    form.find('.js-finish-edit-warning').hide();
    // Replace markdown textarea text with original note text.
F
Felipe Artur 已提交
828
    return form.find('.js-note-text').val(form.find('form.edit-note').data('originalNote'));
829 830 831 832 833 834 835 836 837
  }

  /**
   * Called in response to deleting a note of any kind.
   *
   * Removes the actual note from view.
   * Removes the whole discussion if the last note is being removed.
   */
  removeNote(e) {
838 839
    const $note = $(e.currentTarget).closest('.note');
    const noteElId = $note.attr('id');
840 841 842 843 844 845 846 847 848 849 850 851 852
    $(`.note[id="${noteElId}"]`).each((i, el) => {
      // 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.
      const $note = $(el);
      const $notes = $note.closest('.discussion-notes');
      const discussionId = $('.notes', $notes).data('discussionId');

      if (typeof gl.diffNotesCompileComponents !== 'undefined') {
        if (gl.diffNoteApps[noteElId]) {
          gl.diffNoteApps[noteElId].$destroy();
        }
      }
F
Fatih Acet 已提交
853

854
      $note.remove();
F
Fatih Acet 已提交
855

856 857 858
      // check if this is the last note for this line
      if ($notes.find('.note').length === 0) {
        const notesTr = $notes.closest('tr');
F
Fatih Acet 已提交
859

860 861
        // "Discussions" tab
        $notes.closest('.timeline-entry').remove();
F
Fatih Acet 已提交
862

863
        $(`.js-diff-avatars-${discussionId}`).trigger('remove.vue');
F
Fatih Acet 已提交
864

865 866 867 868 869 870 871 872 873 874 875
        // The notes tr can contain multiple lists of notes, like on the parallel diff
        // notesTr does not exist for image diffs
        if (notesTr.find('.discussion-notes').length > 1 || notesTr.length === 0) {
          const $diffFile = $notes.closest('.diff-file');
          if ($diffFile.length > 0) {
            const removeBadgeEvent = new CustomEvent('removeBadge.imageDiff', {
              detail: {
                // badgeNumber's start with 1 and index starts with 0
                badgeNumber: $notes.index() + 1,
              },
            });
F
Fatih Acet 已提交
876

877
            $diffFile[0].dispatchEvent(removeBadgeEvent);
F
Fatih Acet 已提交
878
          }
879 880 881 882 883 884 885

          $notes.remove();
        } else if (notesTr.length > 0) {
          notesTr.remove();
        }
      }
    });
886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901

    Notes.checkMergeRequestStatus();
    return this.updateNotesCount(-1);
  }

  /**
   * Called in response to clicking the delete attachment link
   *
   * Removes the attachment wrapper view, including image tag if it exists
   * Resets the note editing form
   */
  removeAttachment() {
    const $note = $(this).closest('.note');
    $note.find('.note-attachment').remove();
    $note.find('.note-body > .note-text').show();
    $note.find('.note-header').show();
F
Felipe Artur 已提交
902
    return $note.find('.diffs .current-note-edit-form').remove();
903 904 905 906 907 908 909 910 911 912 913 914
  }

  /**
   * Called when clicking on the "reply" button for a diff line.
   *
   * Shows the note form below the notes.
   */
  onReplyToDiscussionNote(e) {
    this.replyToDiscussionNote(e.target);
  }

  replyToDiscussionNote(target) {
915 916
    const form = this.cleanForm(this.formClone.clone());
    const replyLink = $(target).closest('.js-discussion-reply-button');
917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933
    // insert the form after the button
    replyLink
      .closest('.discussion-reply-holder')
      .hide()
      .after(form);
    // show the form
    return this.setupDiscussionNoteForm(replyLink, form);
  }

  /**
   * Shows the diff or discussion form and does some setup on it.
   *
   * Sets some hidden fields in the form.
   *
   * Note: dataHolder must have the "discussionId" and "lineCode" data attributes set.
   */
  setupDiscussionNoteForm(dataHolder, form) {
934
    // set up note target
F
Felipe Artur 已提交
935 936 937 938 939
    let diffFileData = dataHolder.closest('.text-file');

    if (diffFileData.length === 0) {
      diffFileData = dataHolder.closest('.image');
    }
940

941
    const discussionID = dataHolder.data('discussionId');
942 943 944 945 946

    if (discussionID) {
      form.attr('data-discussion-id', discussionID);
      form.find('#in_reply_to_discussion_id').val(discussionID);
    }
F
Fatih Acet 已提交
947

948 949
    form.find('#note_project_id').val(dataHolder.data('discussionProjectId'));

950 951
    form.attr('data-line-code', dataHolder.data('lineCode'));
    form.find('#line_type').val(dataHolder.data('lineType'));
F
Fatih Acet 已提交
952

953 954 955 956
    form.find('#note_noteable_type').val(diffFileData.data('noteableType'));
    form.find('#note_noteable_id').val(diffFileData.data('noteableId'));
    form.find('#note_commit_id').val(diffFileData.data('commitId'));

957
    form.find('#note_type').val(dataHolder.data('noteType'));
958

959 960
    // LegacyDiffNote
    form.find('#note_line_code').val(dataHolder.data('lineCode'));
F
Fatih Acet 已提交
961

962 963
    // DiffNote
    form.find('#note_position').val(dataHolder.attr('data-position'));
964

965
    form
966 967 968 969 970 971 972 973
      .prepend(
        `<div class="avatar-note-form-holder"><div class="content"><a href="${escape(
          gon.current_username,
        )}" class="user-avatar-link d-none d-sm-block"><img class="avatar s40" src="${encodeURI(
          gon.current_user_avatar_url,
        )}" alt="${escape(gon.current_user_fullname)}" /></a></div></div>`,
      )
      .append('</div>')
974 975 976
      .find('.js-close-discussion-note-form')
      .show()
      .removeClass('hide');
977 978 979
    form.find('.js-note-target-close').remove();
    form.find('.js-note-new-discussion').remove();
    this.setupNoteForm(form);
F
Fatih Acet 已提交
980

F
Felipe Artur 已提交
981
    form.removeClass('js-main-target-form').addClass('discussion-form js-discussion-note-form');
982

983
    if (typeof gl.diffNotesCompileComponents !== 'undefined') {
984
      const $commentBtn = form.find('comment-and-resolve-btn');
985
      $commentBtn.attr(':discussion-id', `'${discussionID}'`);
986

987 988
      gl.diffNotesCompileComponents();
    }
F
Fatih Acet 已提交
989

990
    form.find('.js-note-text').focus();
F
Felipe Artur 已提交
991
    form.find('.js-comment-resolve-button').attr('data-discussion-id', discussionID);
992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007
  }

  /**
   * Called when clicking on the "add a comment" button on the side of a diff line.
   *
   * Inserts a temporary row for the form below the line.
   * Sets up the form and shows it.
   */
  onAddDiffNote(e) {
    e.preventDefault();
    const link = e.currentTarget || e.target;
    const $link = $(link);
    const showReplyInput = !$link.hasClass('js-diff-comment-avatar');
    this.toggleDiffNote({
      target: $link,
      lineType: link.dataset.lineType,
F
Fatih Acet 已提交
1008
      showReplyInput,
1009 1010 1011
      currentUsername: gon.current_username,
      currentUserAvatar: gon.current_user_avatar_url,
      currentUserFullname: gon.current_user_fullname,
1012 1013 1014
    });
  }

F
Felipe Artur 已提交
1015 1016 1017 1018 1019 1020 1021 1022 1023 1024
  onAddImageDiffNote(e) {
    const $link = $(e.currentTarget || e.target);
    const $diffFile = $link.closest('.diff-file');

    const clickEvent = new CustomEvent('click.imageDiff', {
      detail: e,
    });

    $diffFile[0].dispatchEvent(clickEvent);

1025
    // Set up comment form
F
Felipe Artur 已提交
1026
    let newForm;
F
Felipe Artur 已提交
1027
    const $noteContainer = $link.closest('.diff-viewer').find('.note-container');
F
Felipe Artur 已提交
1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039
    const $form = $noteContainer.find('> .discussion-form');

    if ($form.length === 0) {
      newForm = this.cleanForm(this.formClone.clone());
      newForm.appendTo($noteContainer);
    } else {
      newForm = $form;
    }

    this.setupDiscussionNoteForm($link, newForm);
  }

1040
  toggleDiffNote({ target, lineType, forceShow, showReplyInput = false }) {
1041 1042 1043 1044 1045 1046 1047
    let addForm;
    let newForm;
    let noteForm;
    let replyButton;
    let rowCssToAdd;
    const $link = $(target);
    const row = $link.closest('tr');
1048 1049 1050 1051 1052
    const nextRow = row.next();
    let targetRow = row;
    if (nextRow.is('.notes_holder')) {
      targetRow = nextRow;
    }
1053

1054
    const hasNotes = nextRow.is('.notes_holder');
1055 1056
    addForm = false;
    let lineTypeSelector = '';
F
Fatih Acet 已提交
1057
    rowCssToAdd =
1058
      '<tr class="notes_holder js-temp-notes-holder"><td class="notes-content" colspan="3"><div class="content"></div></td></tr>';
1059 1060 1061
    // In parallel view, look inside the correct left/right pane
    if (this.isParallelView()) {
      lineTypeSelector = `.${lineType}`;
F
Fatih Acet 已提交
1062
      rowCssToAdd =
1063
        '<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>';
1064
    }
1065
    const notesContentSelector = `.notes-content${lineTypeSelector} .content`;
1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082
    let notesContent = targetRow.find(notesContentSelector);

    if (hasNotes && showReplyInput) {
      targetRow.show();
      notesContent = targetRow.find(notesContentSelector);
      if (notesContent.length) {
        notesContent.show();
        replyButton = notesContent.find('.js-discussion-reply-button:visible');
        if (replyButton.length) {
          this.replyToDiscussionNote(replyButton[0]);
        } else {
          // In parallel view, the form may not be present in one of the panes
          noteForm = notesContent.find('.js-discussion-note-form');
          if (noteForm.length === 0) {
            addForm = true;
          }
        }
D
Douwe Maan 已提交
1083
      }
1084 1085 1086 1087 1088 1089 1090
    } else if (showReplyInput) {
      // add a notes row and insert the form
      row.after(rowCssToAdd);
      targetRow = row.next();
      notesContent = targetRow.find(notesContentSelector);
      addForm = true;
    } else {
F
Felipe Artur 已提交
1091
      const isCurrentlyShown = targetRow.find('.content:not(:empty)').is(':visible');
1092 1093 1094
      const isForced = forceShow === true || forceShow === false;
      const showNow = forceShow === true || (!isCurrentlyShown && !isForced);

1095 1096
      targetRow.toggleClass('hide', !showNow);
      notesContent.toggleClass('hide', !showNow);
1097
    }
1098

1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113
    if (addForm) {
      newForm = this.cleanForm(this.formClone.clone());
      newForm.appendTo(notesContent);
      // show the form
      return this.setupDiscussionNoteForm($link, newForm);
    }
  }

  /**
   * Called in response to "cancel" on a diff note form.
   *
   * Shows the reply button again.
   * Removes the form and if necessary it's temporary row.
   */
  removeDiscussionNoteForm(form) {
1114 1115
    const row = form.closest('tr');
    const glForm = form.data('glForm');
1116 1117
    glForm.destroy();
    form
F
Fatih Acet 已提交
1118 1119 1120 1121 1122
      .find('.js-note-text')
      .data('autosave')
      .reset();
    // show the reply button (will only work for replies)
    form.prev('.discussion-reply-holder').show();
1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133
    if (row.is('.js-temp-notes-holder')) {
      // remove temporary row for diff lines
      return row.remove();
    } else {
      // only remove the form
      return form.remove();
    }
  }

  cancelDiscussionForm(e) {
    e.preventDefault();
F
Felipe Artur 已提交
1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151
    const $form = $(e.target).closest('.js-discussion-note-form');
    const $discussionNote = $(e.target).closest('.discussion-notes');

    if ($discussionNote.length === 0) {
      // Only send blur event when the discussion form
      // is not part of a discussion note
      const $diffFile = $form.closest('.diff-file');

      if ($diffFile.length > 0) {
        const blurEvent = new CustomEvent('blur.imageDiff', {
          detail: e,
        });

        $diffFile[0].dispatchEvent(blurEvent);
      }
    }

    return this.removeDiscussionNoteForm($form);
1152 1153 1154 1155 1156 1157 1158 1159
  }

  /**
   * Called after an attachment file has been selected.
   *
   * Updates the file name for the selected attachment.
   */
  updateFormAttachment() {
1160
    const form = $(this).closest('form');
1161
    // get only the basename
1162
    const filename = $(this)
F
Fatih Acet 已提交
1163 1164
      .val()
      .replace(/^.*[\\\/]/, '');
1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175
    return form.find('.js-attachment-filename').text(filename);
  }

  /**
   * Called when the tab visibility changes
   */
  visibilityChange() {
    return this.refresh();
  }

  updateTargetButtons(e) {
1176 1177 1178 1179 1180 1181
    let closetext;
    let reopentext;
    const textarea = $(e.target);
    const form = textarea.parents('form');
    const reopenbtn = form.find('.js-note-target-reopen');
    const closebtn = form.find('.js-note-target-close');
1182 1183 1184 1185 1186 1187

    if (textarea.val().trim().length > 0) {
      reopentext = reopenbtn.attr('data-alternative-text');
      closetext = closebtn.attr('data-alternative-text');
      if (reopenbtn.text() !== reopentext) {
        reopenbtn.text(reopentext);
1188
      }
1189 1190
      if (closebtn.text() !== closetext) {
        closebtn.text(closetext);
1191
      }
1192 1193
      if (reopenbtn.is(':not(.btn-comment-and-reopen)')) {
        reopenbtn.addClass('btn-comment-and-reopen');
F
Fatih Acet 已提交
1194
      }
1195 1196
      if (closebtn.is(':not(.btn-comment-and-close)')) {
        closebtn.addClass('btn-comment-and-close');
F
Fatih Acet 已提交
1197
      }
1198
    } else {
J
Jacob Schatz 已提交
1199 1200
      reopentext = reopenbtn.data('originalText');
      closetext = closebtn.data('originalText');
1201 1202
      if (reopenbtn.text() !== reopentext) {
        reopenbtn.text(reopentext);
F
Fatih Acet 已提交
1203
      }
1204 1205
      if (closebtn.text() !== closetext) {
        closebtn.text(closetext);
F
Fatih Acet 已提交
1206
      }
1207 1208
      if (reopenbtn.is('.btn-comment-and-reopen')) {
        reopenbtn.removeClass('btn-comment-and-reopen');
1209
      }
1210 1211
      if (closebtn.is('.btn-comment-and-close')) {
        closebtn.removeClass('btn-comment-and-close');
1212
      }
1213 1214 1215 1216
    }
  }

  putEditFormInPlace($el) {
1217 1218
    const $editForm = $(this.getEditFormSelector($el));
    const $note = $el.closest('.note');
1219 1220 1221

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

1222 1223 1224 1225 1226
    const $originalContentEl = $note.find('.original-note-content');
    const originalContent = $originalContentEl.text().trim();
    const postUrl = $originalContentEl.data('postUrl');
    const targetId = $originalContentEl.data('targetId');
    const targetType = $originalContentEl.data('targetType');
1227

P
Phil Hughes 已提交
1228
    this.glForm = new GLForm($editForm.find('form'), this.enableGFM);
1229

F
Fatih Acet 已提交
1230 1231
    $editForm
      .find('form')
1232
      .attr('action', `${postUrl}?html=true`)
J
Jan Provaznik 已提交
1233
      .attr('data-remote', 'true');
1234 1235
    $editForm.find('.js-form-target-id').val(targetId);
    $editForm.find('.js-form-target-type').val(targetType);
F
Fatih Acet 已提交
1236 1237 1238 1239
    $editForm
      .find('.js-note-text')
      .focus()
      .val(originalContent);
1240 1241 1242 1243 1244 1245
    $editForm.find('.js-md-write-button').trigger('click');
    $editForm.find('.referenced-users').hide();
  }

  putConflictEditWarningInPlace(noteEntity, $note) {
    if ($note.find('.js-conflict-edit-warning').length === 0) {
1246
      const open_link = `<a href="#note_${noteEntity.id}" target="_blank" rel="noopener noreferrer">`;
1247
      const $alert = $(`<div class="js-conflict-edit-warning alert alert-danger">
1248 1249 1250 1251 1252 1253 1254 1255 1256
        ${sprintf(
          s__(
            'Notes|This comment has changed since you started editing, please review the %{open_link}updated comment%{close_link} to ensure information is not lost',
          ),
          {
            open_link,
            close_link: '</a>',
          },
        )}
1257 1258 1259 1260
      </div>`);
      $alert.insertAfter($note.find('.note-text'));
    }
  }
F
Fatih Acet 已提交
1261

1262
  updateNotesCount(updateCount) {
F
Felipe Artur 已提交
1263
    return this.notesCountBadge.text(parseInt(this.notesCountBadge.text(), 10) + updateCount);
1264
  }
1265

1266 1267
  static renderPlaceholderComponent($container) {
    const el = $container.find('.js-code-placeholder').get(0);
1268
    // eslint-disable-next-line no-new
F
Fatih Acet 已提交
1269
    new Vue({
1270 1271
      el,
      components: {
C
Clement Ho 已提交
1272
        GlSkeletonLoading,
1273 1274
      },
      render(createElement) {
C
Clement Ho 已提交
1275
        return createElement('gl-skeleton-loading');
1276 1277 1278 1279 1280 1281 1282 1283
      },
    });
  }

  static renderDiffContent($container, data) {
    const { discussion_html } = data;
    const lines = $(discussion_html).find('.line_holder');
    lines.addClass('fade-in');
1284
    $container.find('.diff-content > table > tbody').prepend(lines);
1285 1286 1287 1288 1289
    const fileHolder = $container.find('.file-holder');
    $container.find('.line-holder-placeholder').remove();
    syntaxHighlight(fileHolder);
  }

1290 1291 1292 1293 1294
  onClickRetryLazyLoad(e) {
    const $retryButton = $(e.currentTarget);

    $retryButton.prop('disabled', true);

1295
    return this.loadLazyDiff(e).then(() => {
1296 1297
      $retryButton.prop('disabled', false);
    });
1298 1299 1300 1301 1302 1303 1304 1305
  }

  loadLazyDiff(e) {
    const $container = $(e.currentTarget).closest('.js-toggle-container');
    Notes.renderPlaceholderComponent($container);

    $container.find('.js-toggle-lazy-diff').removeClass('js-toggle-lazy-diff');

1306 1307
    const $tableEl = $container.find('tbody');
    if ($tableEl.length === 0) return;
1308 1309 1310 1311

    const fileHolder = $container.find('.file-holder');
    const url = fileHolder.data('linesPath');

1312 1313 1314
    const $errorContainer = $container.find('.js-error-lazy-load-diff');
    const $successContainer = $container.find('.js-success-lazy-load');

1315 1316 1317 1318 1319
    /**
     * We only fetch resolved discussions.
     * Unresolved discussions don't have an endpoint being provided.
     */
    if (url) {
1320
      return axios
1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332
        .get(url)
        .then(({ data }) => {
          // Reset state in case last request returned error
          $successContainer.removeClass('hidden');
          $errorContainer.addClass('hidden');

          Notes.renderDiffContent($container, data);
        })
        .catch(() => {
          $successContainer.addClass('hidden');
          $errorContainer.removeClass('hidden');
        });
1333
    }
1334
    return Promise.resolve();
1335 1336
  }

1337 1338
  toggleCommitList(e) {
    const $element = $(e.currentTarget);
F
Felipe Artur 已提交
1339
    const $closestSystemCommitList = $element.siblings('.system-note-commit-list');
1340

F
Fatih Acet 已提交
1341 1342 1343 1344
    $element
      .find('.fa')
      .toggleClass('fa-angle-down')
      .toggleClass('fa-angle-up');
1345 1346
    $closestSystemCommitList.toggleClass('hide-shade');
  }
1347

1348 1349 1350 1351 1352 1353
  /**
   * Scans system notes with `ul` elements in system note body
   * then collapse long commit list pushed by user to make it less
   * intrusive.
   */
  collapseLongCommitList() {
F
Fatih Acet 已提交
1354 1355 1356
    const systemNotes = $('#notes-list')
      .find('li.system-note')
      .has('ul');
1357

1358
    $.each(systemNotes, (index, systemNote) => {
1359
      const $systemNote = $(systemNote);
F
Fatih Acet 已提交
1360 1361
      const headerMessage = $systemNote
        .find('.note-text')
1362 1363
        .find('p')
        .first()
F
Fatih Acet 已提交
1364 1365
        .text()
        .replace(':', '');
K
Kushal Pandya 已提交
1366

1367
      $systemNote.find('.note-header .system-note-message').html(headerMessage);
K
Kushal Pandya 已提交
1368

1369 1370 1371 1372
      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 {
F
Felipe Artur 已提交
1373
        $systemNote.find('.note-text').addClass('system-note-commit-list hide-shade');
1374 1375 1376
      }
    });
  }
1377

1378
  addFlash(...flashParams) {
P
Phil Hughes 已提交
1379
    this.flashContainer = new Flash(...flashParams);
1380
  }
1381

1382
  clearFlash() {
P
Phil Hughes 已提交
1383 1384 1385
    if (this.flashContainer) {
      this.flashContainer.style.display = 'none';
      this.flashContainer = null;
1386 1387 1388 1389 1390
    }
  }

  cleanForm($form) {
    // Remove JS classes that are not needed here
F
Fatih Acet 已提交
1391
    $form.find('.js-comment-type-dropdown').removeClass('btn-group');
1392 1393

    // Remove dropdown
F
Fatih Acet 已提交
1394
    $form.find('.dropdown-menu').remove();
1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412

    return $form;
  }

  /**
   * Check if note does not exists on page
   */
  static isNewNote(noteEntity, noteIds) {
    return $.inArray(noteEntity.id, noteIds) === -1;
  }

  /**
   * Check if $note already contains the `noteEntity` content
   */
  static isUpdatedNote(noteEntity, $note) {
    // There can be CRLF vs LF mismatches if we don't sanitize and compare the same way
    const sanitizedNoteEntityText = normalizeNewlines(noteEntity.note.trim());
    const currentNoteText = normalizeNewlines(
F
Fatih Acet 已提交
1413 1414 1415 1416 1417
      $note
        .find('.original-note-content')
        .first()
        .text()
        .trim(),
1418 1419 1420 1421 1422
    );
    return sanitizedNoteEntityText !== currentNoteText;
  }

  static checkMergeRequestStatus() {
1423
    if (getPagePath(1) === 'merge_requests' && gl.mrWidget) {
1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447
      gl.mrWidget.checkStatus();
    }
  }

  static animateAppendNote(noteHtml, $notesList) {
    const $note = $(noteHtml);

    $note.addClass('fade-in-full').renderGFM();
    $notesList.append($note);
    return $note;
  }

  static animateUpdateNote(noteHtml, $note) {
    const $updatedNote = $(noteHtml);

    $updatedNote.addClass('fade-in').renderGFM();
    $note.replaceWith($updatedNote);
    return $updatedNote;
  }

  /**
   * Get data from Form attributes to use for saving/submitting comment.
   */
  getFormData($form) {
1448
    const content = $form.find('.js-note-text').val();
1449
    return {
1450
      // eslint-disable-next-line no-jquery/no-serialize
1451
      formData: $form.serialize(),
1452
      formContent: _.escape(content),
1453
      formAction: $form.attr('action'),
1454
      formContentOriginal: content,
1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478
    };
  }

  /**
   * Identify if comment has any quick actions
   */
  hasQuickActions(formContent) {
    return REGEX_QUICK_ACTIONS.test(formContent);
  }

  /**
   * Remove quick actions and leave comment with pure message
   */
  stripQuickActions(formContent) {
    return formContent.replace(REGEX_QUICK_ACTIONS, '').trim();
  }

  /**
   * Gets appropriate description from quick actions found in provided `formContent`
   */
  getQuickActionDescription(formContent, availableQuickActions = []) {
    let tempFormContent;

    // Identify executed quick actions from `formContent`
1479
    const executedCommands = availableQuickActions.filter(command => {
1480 1481 1482 1483 1484 1485
      const commandRegex = new RegExp(`/${command.name}`);
      return commandRegex.test(formContent);
    });

    if (executedCommands && executedCommands.length) {
      if (executedCommands.length > 1) {
1486
        tempFormContent = __('Applying multiple commands');
1487
      } else {
1488
        const commandDescription = executedCommands[0].description.toLowerCase();
1489 1490 1491
        tempFormContent = sprintf(__('Applying command to %{commandDescription}'), {
          commandDescription,
        });
1492
      }
1493
    } else {
1494
      tempFormContent = __('Applying command');
1495
    }
1496

1497 1498 1499 1500 1501 1502 1503 1504 1505
    return tempFormContent;
  }

  /**
   * Create placeholder note DOM element populated with comment body
   * that we will show while comment is being posted.
   * Once comment is _actually_ posted on server, we will have final element
   * in response that we will show in place of this temporary element.
   */
F
Fatih Acet 已提交
1506 1507 1508 1509 1510 1511 1512 1513
  createPlaceholderNote({
    formContent,
    uniqueId,
    isDiscussionNote,
    currentUsername,
    currentUserFullname,
    currentUserAvatar,
  }) {
1514 1515 1516 1517 1518
    const discussionClass = isDiscussionNote ? 'discussion' : '';
    const $tempNote = $(
      `<li id="${uniqueId}" class="note being-posted fade-in-half timeline-entry">
         <div class="timeline-entry-inner">
            <div class="timeline-icon">
1519
               <a href="/${_.escape(currentUsername)}">
F
Filipa Lacerda 已提交
1520
                 <img class="avatar s40" src="${currentUserAvatar}" />
1521 1522 1523 1524 1525
               </a>
            </div>
            <div class="timeline-content ${discussionClass}">
               <div class="note-header">
                  <div class="note-header-info">
1526
                     <a href="/${_.escape(currentUsername)}">
1527 1528 1529
                       <span class="d-none d-sm-inline-block bold">${_.escape(
                         currentUsername,
                       )}</span>
1530
                       <span class="note-headline-light">${_.escape(currentUsername)}</span>
1531 1532 1533 1534 1535 1536
                     </a>
                  </div>
               </div>
               <div class="note-body">
                 <div class="note-text">
                   <p>${formContent}</p>
K
Kushal Pandya 已提交
1537
                 </div>
1538 1539 1540
               </div>
            </div>
         </div>
F
Fatih Acet 已提交
1541
      </li>`,
1542 1543
    );

1544
    $tempNote.find('.d-none.d-sm-inline-block').text(_.escape(currentUserFullname));
1545
    $tempNote.find('.note-headline-light').text(`@${_.escape(currentUsername)}`);
1546

1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558
    return $tempNote;
  }

  /**
   * Create Placeholder System Note DOM element populated with quick action description
   */
  createPlaceholderSystemNote({ formContent, uniqueId }) {
    const $tempNote = $(
      `<li id="${uniqueId}" class="note system-note timeline-entry being-posted fade-in-half">
         <div class="timeline-entry-inner">
           <div class="timeline-content">
             <i>${formContent}</i>
K
Kushal Pandya 已提交
1559
           </div>
1560
         </div>
F
Fatih Acet 已提交
1561
       </li>`,
1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574
    );

    return $tempNote;
  }

  /**
   * This method does following tasks step-by-step whenever a new comment
   * is submitted by user (both main thread comments as well as discussion comments).
   *
   * 1) Get Form metadata
   * 2) Identify comment type; a) Main thread b) Discussion thread c) Discussion resolve
   * 3) Build temporary placeholder element (using `createPlaceholderNote`)
   * 4) Show placeholder note on UI
P
Phil Hughes 已提交
1575
   * 5) Perform network request to submit the note using `axios.post`
1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590
   *    a) If request is successfully completed
   *        1. Remove placeholder element
   *        2. Show submitted Note element
   *        3. Perform post-submit errands
   *           a. Mark discussion as resolved if comment submission was for resolve.
   *           b. Reset comment form to original state.
   *    b) If request failed
   *        1. Remove placeholder element
   *        2. Show error Flash message about failure
   */
  postComment(e) {
    e.preventDefault();

    // Get Form metadata
    const $submitBtn = $(e.target);
1591
    $submitBtn.prop('disabled', true);
1592 1593
    let $form = $submitBtn.parents('form');
    const $closeBtn = $form.find('.js-note-target-close');
F
Fatih Acet 已提交
1594 1595 1596 1597 1598
    const isDiscussionNote =
      $submitBtn
        .parent()
        .find('li.droplab-item-selected')
        .attr('id') === 'discussion';
1599 1600
    const isMainForm = $form.hasClass('js-main-target-form');
    const isDiscussionForm = $form.hasClass('js-discussion-note-form');
F
Felipe Artur 已提交
1601 1602
    const isDiscussionResolve = $submitBtn.hasClass('js-comment-resolve-button');
    const { formData, formContent, formAction, formContentOriginal } = this.getFormData($form);
1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614
    let noteUniqueId;
    let systemNoteUniqueId;
    let hasQuickActions = false;
    let $notesContainer;
    let tempFormContent;

    // Get reference to notes container based on type of comment
    if (isDiscussionForm) {
      $notesContainer = $form.parent('.discussion-notes').find('.notes');
    } else if (isMainForm) {
      $notesContainer = $('ul.main-notes-list');
    }
1615

1616 1617 1618 1619 1620
    // If comment is to resolve discussion, disable submit buttons while
    // comment posting is finished.
    if (isDiscussionResolve) {
      $form.find('.js-comment-submit-button').disable();
    }
1621

1622 1623 1624 1625 1626
    tempFormContent = formContent;
    if (this.hasQuickActions(formContent)) {
      tempFormContent = this.stripQuickActions(formContent);
      hasQuickActions = true;
    }
K
Kushal Pandya 已提交
1627

1628 1629 1630
    // Show placeholder note
    if (tempFormContent) {
      noteUniqueId = _.uniqueId('tempNote_');
F
Fatih Acet 已提交
1631 1632 1633 1634 1635 1636 1637 1638 1639 1640
      $notesContainer.append(
        this.createPlaceholderNote({
          formContent: tempFormContent,
          uniqueId: noteUniqueId,
          isDiscussionNote,
          currentUsername: gon.current_username,
          currentUserFullname: gon.current_user_fullname,
          currentUserAvatar: gon.current_user_avatar_url,
        }),
      );
1641
    }
K
Kushal Pandya 已提交
1642

1643 1644 1645
    // Show placeholder system note
    if (hasQuickActions) {
      systemNoteUniqueId = _.uniqueId('tempSystemNote_');
F
Fatih Acet 已提交
1646 1647 1648 1649 1650 1651 1652 1653 1654
      $notesContainer.append(
        this.createPlaceholderSystemNote({
          formContent: this.getQuickActionDescription(
            formContent,
            AjaxCache.get(gl.GfmAutoComplete.dataSources.commands),
          ),
          uniqueId: systemNoteUniqueId,
        }),
      );
1655
    }
K
Kushal Pandya 已提交
1656

1657 1658 1659 1660 1661 1662
    // Clear the form textarea
    if ($notesContainer.length) {
      if (isMainForm) {
        this.resetMainTargetForm(e);
      } else if (isDiscussionForm) {
        this.removeDiscussionNoteForm($form);
1663
      }
1664
    }
1665

1666 1667
    $closeBtn.text($closeBtn.data('originalText'));

1668
    // Make request to submit comment on server
1669
    return axios
F
Fatih Acet 已提交
1670 1671
      .post(`${formAction}?html=true`, formData)
      .then(res => {
P
Phil Hughes 已提交
1672 1673
        const note = res.data;

1674
        $submitBtn.prop('disabled', false);
1675 1676
        // Submission successful! remove placeholder
        $notesContainer.find(`#${noteUniqueId}`).remove();
K
Kushal Pandya 已提交
1677

F
Felipe Artur 已提交
1678 1679 1680 1681 1682 1683 1684 1685 1686
        const $diffFile = $form.closest('.diff-file');
        if ($diffFile.length > 0) {
          const blurEvent = new CustomEvent('blur.imageDiff', {
            detail: e,
          });

          $diffFile[0].dispatchEvent(blurEvent);
        }

1687 1688
        // Reset cached commands list when command is applied
        if (hasQuickActions) {
F
Felipe Artur 已提交
1689
          $form.find('textarea.js-note-text').trigger('clear-commands-cache.atwho');
K
Kushal Pandya 已提交
1690
        }
1691

1692 1693
        // Clear previous form errors
        this.clearFlashWrapper();
K
Kushal Pandya 已提交
1694

1695 1696 1697 1698
        // Check if this was discussion comment
        if (isDiscussionForm) {
          // Remove flash-container
          $notesContainer.find('.flash-container').remove();
K
Kushal Pandya 已提交
1699

1700 1701 1702
          // If comment intends to resolve discussion, do the same.
          if (isDiscussionResolve) {
            $form
J
Jacob Schatz 已提交
1703
              .attr('data-discussion-id', $submitBtn.data('discussionId'))
1704
              .attr('data-resolve-all', 'true')
J
Jacob Schatz 已提交
1705
              .attr('data-project-path', $submitBtn.data('projectPath'));
1706
          }
K
Kushal Pandya 已提交
1707

1708
          // Show final note element on UI
F
Felipe Artur 已提交
1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730
          const isNewDiffComment = $notesContainer.length === 0;
          this.addDiscussionNote($form, note, isNewDiffComment);

          if (isNewDiffComment) {
            // Add image badge, avatar badge and toggle discussion badge for new image diffs
            const notePosition = $form.find('#note_position').val();
            if ($diffFile.length > 0 && notePosition.length > 0) {
              const { x, y, width, height } = JSON.parse(notePosition);
              const addBadgeEvent = new CustomEvent('addBadge.imageDiff', {
                detail: {
                  x,
                  y,
                  width,
                  height,
                  noteId: `note_${note.id}`,
                  discussionId: note.discussion_id,
                },
              });

              $diffFile[0].dispatchEvent(addBadgeEvent);
            }
          }
K
Kushal Pandya 已提交
1731

1732 1733
          // append flash-container to the Notes list
          if ($notesContainer.length) {
F
Felipe Artur 已提交
1734
            $notesContainer.append('<div class="flash-container" style="display: none;"></div>');
K
Kushal Pandya 已提交
1735
          }
F
Fatih Acet 已提交
1736 1737
        } else if (isMainForm) {
          // Check if this was main thread comment
1738 1739 1740 1741
          // Show final note element on UI and perform form and action buttons cleanup
          this.addNote($form, note);
          this.reenableTargetFormSubmitButton(e);
        }
K
Kushal Pandya 已提交
1742

1743 1744 1745
        if (note.commands_changes) {
          this.handleQuickActions(note);
        }
K
Kushal Pandya 已提交
1746

1747
        $form.trigger('ajax:success', [note]);
F
Fatih Acet 已提交
1748 1749
      })
      .catch(() => {
1750 1751
        // Submission failed, remove placeholder note and show Flash error message
        $notesContainer.find(`#${noteUniqueId}`).remove();
1752
        $submitBtn.prop('disabled', false);
F
Felipe Artur 已提交
1753 1754 1755 1756 1757 1758 1759 1760 1761 1762
        const blurEvent = new CustomEvent('blur.imageDiff', {
          detail: e,
        });

        const closestDiffFile = $form.closest('.diff-file');

        if (closestDiffFile.length) {
          closestDiffFile[0].dispatchEvent(blurEvent);
        }

1763 1764 1765
        if (hasQuickActions) {
          $notesContainer.find(`#${systemNoteUniqueId}`).remove();
        }
K
Kushal Pandya 已提交
1766

1767 1768
        // Show form again on UI on failure
        if (isDiscussionForm && $notesContainer.length) {
F
Felipe Artur 已提交
1769
          const replyButton = $notesContainer.parent().find('.js-discussion-reply-button');
1770 1771 1772
          this.replyToDiscussionNote(replyButton[0]);
          $form = $notesContainer.parent().find('form');
        }
K
Kushal Pandya 已提交
1773

1774
        $form.find('.js-note-text').val(formContentOriginal);
1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785
        this.reenableTargetFormSubmitButton(e);
        this.addNoteError($form);
      });
  }

  /**
   * This method does following tasks step-by-step whenever an existing comment
   * is updated by user (both main thread comments as well as discussion comments).
   *
   * 1) Get Form metadata
   * 2) Update note element with new content
P
Phil Hughes 已提交
1786
   * 3) Perform network request to submit the updated note using `axios.post`
1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808
   *    a) If request is successfully completed
   *        1. Show submitted Note element
   *    b) If request failed
   *        1. Revert Note element to original content
   *        2. Show error Flash message about failure
   */
  updateComment(e) {
    e.preventDefault();

    // Get Form metadata
    const $submitBtn = $(e.target);
    const $form = $submitBtn.parents('form');
    const $closeBtn = $form.find('.js-note-target-close');
    const $editingNote = $form.parents('.note.is-editing');
    const $noteBody = $editingNote.find('.js-task-list-container');
    const $noteBodyText = $noteBody.find('.note-text');
    const { formData, formContent, formAction } = this.getFormData($form);

    // Cache original comment content
    const cachedNoteBodyText = $noteBodyText.html();

    // Show updated comment content temporarily
1809
    $noteBodyText.html(formContent);
F
Felipe Artur 已提交
1810
    $editingNote.removeClass('is-editing fade-in-full').addClass('being-posted fade-in-half');
F
Fatih Acet 已提交
1811 1812 1813
    $editingNote
      .find('.note-headline-meta a')
      .html(
1814 1815 1816
        `<i class="fa fa-spinner fa-spin" aria-label="${__(
          'Comment is being updated',
        )}" aria-hidden="true"></i>`,
F
Fatih Acet 已提交
1817
      );
1818 1819

    // Make request to update comment on server
F
Fatih Acet 已提交
1820 1821
    axios
      .post(`${formAction}?html=true`, formData)
P
Phil Hughes 已提交
1822
      .then(({ data }) => {
1823
        // Submission successful! render final note element
P
Phil Hughes 已提交
1824
        this.updateNote(data, $editingNote);
1825
      })
P
Phil Hughes 已提交
1826
      .catch(() => {
1827 1828 1829 1830 1831 1832 1833 1834
        // Submission failed, revert back to original note
        $noteBodyText.html(_.escape(cachedNoteBodyText));
        $editingNote.removeClass('being-posted fade-in');
        $editingNote.find('.fa.fa-spinner').remove();

        // Show Flash message about failure
        this.updateNoteError();
      });
K
Kushal Pandya 已提交
1835

J
Jacob Schatz 已提交
1836
    return $closeBtn.text($closeBtn.data('originalText'));
1837 1838
  }
}
K
Kushal Pandya 已提交
1839

1840
window.Notes = Notes;