text: /* 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, class-methods-use-this */
  /* global Flash */
  /* global Autosave */
  /* global ResolveService */
  /* global mrRefreshWidgetUrl */
  
  import $ from 'jquery';
  import _ from 'underscore';
  import Cookies from 'js-cookie';
  import autosize from 'vendor/autosize';
  import Dropzone from 'dropzone';
  import 'vendor/jquery.caret'; // required by jquery.atwho
  import 'vendor/jquery.atwho';
  import AjaxCache from '~/lib/utils/ajax_cache';
  import CommentTypeToggle from './comment_type_toggle';
  import loadAwardsHandler from './awards_handler';
  import './autosave';
  import './dropzone_input';
  import TaskList from './task_list';
  import { ajaxPost, isInViewport, getPagePath, scrollToElement, isMetaKey } from './lib/utils/common_utils';
  
  window.autosize = autosize;
  window.Dropzone = Dropzone;
  
  function normalizeNewlines(str) {
    return str.replace(/\r\n/g, '\n');
  }
  
  const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
  const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm;
  
  export default class Notes {
    constructor(notes_url, note_ids, last_fetched_at, view, enableGFM = true) {
      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);
      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;
      this.notesCountBadge || (this.notesCountBadge = $('.issuable-details').find('.notes-tab .badge'));
      this.basePollingInterval = 15000;
      this.maxPollingSteps = 4;
  
      this.cleanBinding();
      this.addBinding();
      this.setPollingInterval();
      this.setupMainTargetNoteForm();
      this.taskList = new TaskList({
        dataType: 'note',
        fieldName: 'note',
        selector: '.notes'
      });
      this.collapseLongCommitList();
      this.setViewType(view);
  
      // We are in the Merge Requests page so we need another edit form for Changes tab
      if (getPagePath(1) === 'merge_requests') {
        $('.note-edit-form').clone()
          .addClass('mr-note-edit-form').insertAfter('.note-edit-form');
      }
    }
  
    setViewType(view) {
      this.view = Cookies.get('diff_view') || view;
    }
  
    addBinding() {
      // Edit note link
      $(document).on('click', '.js-note-edit', this.showEditForm.bind(this));
      $(document).on('click', '.note-edit-cancel', this.cancelEdit);
      // Reopen and close actions for Issue/MR combined with note form submit
      $(document).on('click', '.js-comment-submit-button', this.postComment);
      $(document).on('click', '.js-comment-save-button', this.updateComment);
      $(document).on('keyup input', '.js-note-text', this.updateTargetButtons);
      // resolve a discussion
      $(document).on('click', '.js-comment-resolve-button', this.postComment);
      // remove a note (in general)
      $(document).on('click', '.js-note-delete', this.removeNote);
      // delete note attachment
      $(document).on('click', '.js-note-attachment-delete', this.removeAttachment);
      // reset main target form when clicking discard
      $(document).on('click', '.js-note-discard', this.resetMainTargetForm);
      // update the file name when an attachment is selected
      $(document).on('change', '.js-note-attachment-input', this.updateFormAttachment);
      // reply to diff/discussion notes
      $(document).on('click', '.js-discussion-reply-button', this.onReplyToDiscussionNote);
      // add diff note
      $(document).on('click', '.js-add-diff-note-button', this.onAddDiffNote);
      // hide diff note form
      $(document).on('click', '.js-close-discussion-note-form', this.cancelDiscussionForm);
      // toggle commit list
      $(document).on('click', '.system-note-commit-list-toggler', this.toggleCommitList);
      // fetch notes when tab becomes visible
      $(document).on('visibilitychange', this.visibilityChange);
      // when issue status changes, we need to refresh data
      $(document).on('issuable:change', this.refresh);
      // ajax:events that happen on Form when actions like Reopen, Close are performed on Issues and MRs.
      $(document).on('ajax:success', '.js-main-target-form', this.addNote);
      $(document).on('ajax:success', '.js-discussion-note-form', this.addDiscussionNote);
      $(document).on('ajax:success', '.js-main-target-form', this.resetMainTargetForm);
      $(document).on('ajax:complete', '.js-main-target-form', this.reenableTargetFormSubmitButton);
      // when a key is clicked on the notes
      $(document).on('keydown', '.js-note-text', this.keydownNoteText);
      // When the URL fragment/hash has changed, `#note_xxx`
      return $(window).on('hashchange', this.onHashChange);
    }
  
    cleanBinding() {
      $(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('click', '.js-discussion-reply-button');
      $(document).off('click', '.js-add-diff-note-button');
      $(document).off('visibilitychange');
      $(document).off('keyup input', '.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');
      $(document).off('click', '.js-comment-resolve-button');
      $(document).off('click', '.system-note-commit-list-toggler');
      $(document).off('ajax:success', '.js-main-target-form');
      $(document).off('ajax:success', '.js-discussion-note-form');
      $(document).off('ajax:complete', '.js-main-target-form');
      $(window).off('hashchange', this.onHashChange);
    }
  
    static initCommentTypeToggle(form) {
      const dropdownTrigger = form.querySelector('.js-comment-type-dropdown .dropdown-toggle');
      const dropdownList = form.querySelector('.js-comment-type-dropdown .dropdown-menu');
      const noteTypeInput = form.querySelector('#note_type');
      const submitButton = form.querySelector('.js-comment-type-dropdown .js-comment-submit-button');
      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) {
      var $textarea, discussionNoteForm, editNote, myLastNote, myLastNoteEditBtn, newText, originalText;
      if (isMetaKey(e)) {
        return;
      }
  
      $textarea = $(e.target);
      // Edit previous note when UP arrow is hit
      switch (e.which) {
        case 38:
          if ($textarea.val() !== '') {
            return;
          }
          myLastNote = $(`li.note[data-author-id='${gon.current_user_id}'][data-editable]:last`, $textarea.closest('.note, .notes_holder, #notes'));
          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) {
            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);
          }
      }
    }
  
    initRefresh() {
      if (Notes.interval) {
        clearInterval(Notes.interval);
      }
      return Notes.interval = setInterval((function(_this) {
        return function() {
          return _this.refresh();
        };
      })(this), this.pollingInterval);
    }
  
    refresh() {
      if (!document.hidden) {
        return this.getContent();
      }
    }
  
    getContent() {
      if (this.refreshing) {
        return;
      }
      this.refreshing = true;
      return $.ajax({
        url: this.notes_url,
        headers: { 'X-Last-Fetched-At': this.last_fetched_at },
        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) {
              _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.
     *
     * Note: this function is used to gradually increase the polling interval
     * if there aren't new notes coming from the server
     */
    setPollingInterval(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();
    }
  
    handleQuickActions(noteEntity) {
      var votesBlock;
      if (noteEntity.commands_changes) {
        if ('merge' in noteEntity.commands_changes) {
          Notes.checkMergeRequestStatus();
        }
  
        if ('emoji_award' in noteEntity.commands_changes) {
          votesBlock = $('.js-awards-block').eq(0);
  
          loadAwardsHandler().then((awardsHandler) => {
            awardsHandler.addAwardToEmojiBar(votesBlock, noteEntity.commands_changes.emoji_award);
            awardsHandler.scrollToAwards();
          }).catch(() => {
            // ignore
          });
        }
      }
    }
  
    setupNewNote($note) {
      // Update datetime format on the recent note
      gl.utils.localTimeAgo($note.find('.js-timeago'), false);
  
      this.collapseLongCommitList();
      this.taskList.init();
  
      // 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;
    }
  
    onHashChange() {
      if (this.$noteToCleanHighlight) {
        Notes.updateNoteTargetSelector(this.$noteToCleanHighlight);
      }
  
      this.$noteToCleanHighlight = null;
    }
  
    static updateNoteTargetSelector($note) {
      const hash = gl.utils.getLocationHash();
      // 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);
      }
  
      if (!noteEntity.valid) {
        if (noteEntity.errors.commands_only) {
          if (noteEntity.commands_changes &&
              Object.keys(noteEntity.commands_changes).length > 0) {
            $notesList.find('.system-note.being-posted').remove();
          }
          this.addFlash(noteEntity.errors.commands_only, 'notice', this.parentTimeline);
          this.refresh();
        }
        return;
      }
  
      const $note = $notesList.find(`#note_${noteEntity.id}`);
      if (Notes.isNewNote(noteEntity, this.note_ids)) {
        this.note_ids.push(noteEntity.id);
  
        if ($notesList.length) {
          $notesList.find('.system-note.being-posted').remove();
        }
        const $newNote = Notes.animateAppendNote(noteEntity.html, $notesList);
  
        this.setupNewNote($newNote);
        this.refresh();
        return this.updateNotesCount(1);
      }
      // The server can send the same update multiple times so we need to make sure to only update once per actual update.
      else if (Notes.isUpdatedNote(noteEntity, $note)) {
        const isEditing = $note.hasClass('is-editing');
        const initialContent = normalizeNewlines(
          $note.find('.original-note-content').text().trim()
        );
        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);
        const isTextareaUntouched = currentContent === initialContent || currentContent === sanitizedNoteNote;
  
        if (isEditing && isTextareaUntouched) {
          $textarea.val(noteEntity.note);
          this.updatedNotesTrackingMap[noteEntity.id] = noteEntity;
        }
        else if (isEditing && !isTextareaUntouched) {
          this.putConflictEditWarningInPlace(noteEntity, $note);
          this.updatedNotesTrackingMap[noteEntity.id] = noteEntity;
        }
        else {
          const $updatedNote = Notes.animateUpdateNote(noteEntity.html, $note);
          this.setupNewNote($updatedNote);
        }
      }
    }
  
    isParallelView() {
      return Cookies.get('diff_view') === 'parallel';
    }
  
    /**
     * Render note in discussion area.
     *
     * Note: for rendering inline notes use renderDiscussionNote
     */
    renderDiscussionNote(noteEntity, $form) {
      var discussionContainer, form, row, lineType, diffAvatarContainer;
      if (!Notes.isNewNote(noteEntity, this.note_ids)) {
        return;
      }
      this.note_ids.push(noteEntity.id);
      form = $form || $(`.js-discussion-note-form[data-discussion-id="${noteEntity.discussion_id}"]`);
      row = form.closest('tr');
      lineType = this.isParallelView() ? form.find('#line_type').val() : 'old';
      diffAvatarContainer = row.prevAll('.line_holder').first().find('.js-avatar-container.' + lineType + '_line');
      // is this the first note of discussion?
      discussionContainer = $(`.notes[data-discussion-id="${noteEntity.discussion_id}"]`);
      if (!discussionContainer.length) {
        discussionContainer = form.closest('.discussion').find('.notes');
      }
      if (discussionContainer.length === 0) {
        if (noteEntity.diff_discussion_html) {
          var $discussion = $(noteEntity.diff_discussion_html).renderGFM();
  
          if (!this.isParallelView() || row.hasClass('js-temp-notes-holder')) {
            // insert the note and the reply button after the temp row
            row.after($discussion);
          } else {
            // Merge new discussion HTML in
            var $notes = $discussion.find(`.notes[data-discussion-id="${noteEntity.discussion_id}"]`);
            var contentContainerClass = '.' + $notes.closest('.notes_content')
              .attr('class')
              .split(' ')
              .join('.');
  
            row.find(contentContainerClass + ' .content').append($notes.closest('.content').children());
          }
        }
        // Init discussion on 'Discussion' page if it is merge request page
        const page = $('body').attr('data-page');
        if ((page && page.indexOf('projects:merge_request') !== -1) || !noteEntity.diff_discussion_html) {
          Notes.animateAppendNote(noteEntity.discussion_html, $('.main-notes-list'));
        }
      } else {
        // append new note to all matching discussions
        Notes.animateAppendNote(noteEntity.html, discussionContainer);
      }
  
      if (typeof gl.diffNotesCompileComponents !== 'undefined' && noteEntity.discussion_resolvable) {
        gl.diffNotesCompileComponents();
        this.renderDiscussionAvatar(diffAvatarContainer, noteEntity);
      }
  
      gl.utils.localTimeAgo($('.js-timeago'), false);
      Notes.checkMergeRequestStatus();
      return this.updateNotesCount(1);
    }
  
    getLineHolder(changesDiscussionContainer) {
      return $(changesDiscussionContainer).closest('.notes_holder')
        .prevAll('.line_holder')
        .first()
        .get(0);
    }
  
    renderDiscussionAvatar(diffAvatarContainer, noteEntity) {
      var avatarHolder = diffAvatarContainer.find('.diff-comment-avatar-holders');
  
      if (!avatarHolder.length) {
        avatarHolder = document.createElement('diff-note-avatars');
        avatarHolder.setAttribute('discussion-id', noteEntity.discussion_id);
  
        diffAvatarContainer.append(avatarHolder);
  
        gl.diffNotesCompileComponents();
      }
    }
  
    /**
     * Called in response the main target form has been successfully submitted.
     *
     * Removes any errors.
     * Resets text and preview.
     * Resets buttons.
     */
    resetMainTargetForm(e) {
      var form;
      form = $('.js-main-target-form');
      // remove validation errors
      form.find('.js-errors').remove();
      // reset text and preview
      form.find('.js-md-write-button').click();
      form.find('.js-note-text').val('').trigger('input');
      form.find('.js-note-text').data('autosave').reset();
  
      var event = document.createEvent('Event');
      event.initEvent('autosize:update', true, false);
      form.find('.js-autosize')[0].dispatchEvent(event);
  
      this.updateTargetButtons(e);
    }
  
    reenableTargetFormSubmitButton() {
      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.
     *
     * Sets some hidden fields in the form.
     */
    setupMainTargetNoteForm() {
      var form;
      // find the form
      form = $('.js-new-note-form');
      // Set a global clone of the form for later cloning
      this.formClone = form.clone();
      // show the form
      this.setupNoteForm(form);
      // 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('');
      form.find('#note_project_id').remove();
      form.find('#in_reply_to_discussion_id').remove();
      form.find('.js-comment-resolve-button').closest('comment-and-resolve-btn').remove();
      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
     * setup GFM auto complete
     * show the form
     */
    setupNoteForm(form) {
      var textarea, key;
      new gl.GLForm(form, this.enableGFM);
      textarea = form.find('.js-note-text');
      key = [
        '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_project_id').val(),
        form.find('#in_reply_to_discussion_id').val(),
  
        // LegacyDiffNote
        form.find('#note_line_code').val(),
  
        // DiffNote
        form.find('#note_position').val()
      ];
      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');
      }
      return this.addFlash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', formParentTimeline);
    }
  
    updateNoteError($parentTimeline) {
      new Flash('Your comment could not be updated! Please check your network connection and try again.');
    }
  
    /**
     * 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) {
        var projectPath = $form.data('project-path');
        var discussionId = $form.data('discussion-id');
        var mergeRequestId = $form.data('noteable-iid');
  
        if (ResolveService != null) {
          ResolveService.toggleResolveForDiscussion(mergeRequestId, discussionId);
        }
      }
  
      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) {
      var $noteEntityEl, $note_li;
      // Convert returned HTML to a jQuery object so we can modify it further
      $noteEntityEl = $(noteEntity.html);
      $noteEntityEl.addClass('fade-in-full');
      this.revertNoteEditForm($targetNote);
      $noteEntityEl.renderGFM();
      // Find the note's `li` element by ID and replace it with the updated HTML
      $note_li = $('.note-row-' + noteEntity.id);
  
      $note_li.replaceWith($noteEntityEl);
      this.setupNewNote($noteEntityEl);
  
      if (typeof gl.diffNotesCompileComponents !== 'undefined') {
        gl.diffNotesCompileComponents();
      }
    }
  
    checkContentToAllowEditing($el) {
      var initialContent = $el.find('.original-note-content').text().trim();
      var currentContent = $el.find('.js-note-text').val();
      var isAllowed = true;
  
      if (currentContent === initialContent) {
        this.removeNoteEditForm($el);
      }
      else {
        var $buttons = $el.find('.note-form-actions');
        var isWidgetVisible = isInViewport($el.get(0));
  
        if (!isWidgetVisible) {
          scrollToElement($el);
        }
  
        $el.find('.js-finish-edit-warning').show();
        isAllowed = false;
      }
  
      return isAllowed;
    }
  
    /**
     * 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
     */
    showEditForm(e, scrollTo, myLastNote) {
      e.preventDefault();
  
      var $target = $(e.target);
      var $editForm = $(this.getEditFormSelector($target));
      var $note = $target.closest('.note');
      var $currentlyEditing = $('.note.is-editing:visible');
  
      if ($currentlyEditing.length) {
        var isEditAllowed = this.checkContentToAllowEditing($currentlyEditing);
  
        if (!isEditAllowed) {
          return;
        }
      }
  
      $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];
      }
      else {
        $note.find('.js-finish-edit-warning').hide();
        this.removeNoteEditForm($note);
      }
    }
  
    revertNoteEditForm($target) {
      $target = $target || $('.note.is-editing:visible');
      var selector = this.getEditFormSelector($target);
      var $editForm = $(selector);
  
      $editForm.insertBefore('.notes-form');
      $editForm.find('.js-comment-save-button').enable();
      $editForm.find('.js-finish-edit-warning').hide();
    }
  
    getEditFormSelector($el) {
      var selector = '.note-edit-form:not(.mr-note-edit-form)';
  
      if ($el.parents('#diffs').length) {
        selector = '.note-edit-form.mr-note-edit-form';
      }
  
      return selector;
    }
  
    removeNoteEditForm($note) {
      var form = $note.find('.current-note-edit-form');
      $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.
      return form.find('.js-note-text').val(form.find('form.edit-note').data('original-note'));
    }
  
    /**
     * 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) {
      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) {
        // 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.
        return function(i, el) {
          var $note, $notes;
          $note = $(el);
          $notes = $note.closest('.discussion-notes');
          const discussionId = $('.notes', $notes).data('discussion-id');
  
          if (typeof gl.diffNotesCompileComponents !== 'undefined') {
            if (gl.diffNoteApps[noteElId]) {
              gl.diffNoteApps[noteElId].$destroy();
            }
          }
  
          $note.remove();
  
          // check if this is the last note for this line
          if ($notes.find('.note').length === 0) {
            var notesTr = $notes.closest('tr');
  
            // "Discussions" tab
            $notes.closest('.timeline-entry').remove();
  
            $(`.js-diff-avatars-${discussionId}`).trigger('remove.vue');
  
            // The notes tr can contain multiple lists of notes, like on the parallel diff
            if (notesTr.find('.discussion-notes').length > 1) {
              $notes.remove();
            } else {
              notesTr.remove();
            }
          }
        };
      })(this));
  
      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();
      return $note.find('.current-note-edit-form').remove();
    }
  
    /**
     * 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) {
      var form, replyLink;
      form = this.cleanForm(this.formClone.clone());
      replyLink = $(target).closest('.js-discussion-reply-button');
      // 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) {
      // setup note target
      const diffFileData = dataHolder.closest('.text-file');
  
      var discussionID = dataHolder.data('discussionId');
  
      if (discussionID) {
        form.attr('data-discussion-id', discussionID);
        form.find('#in_reply_to_discussion_id').val(discussionID);
      }
  
      form.find('#note_project_id').val(dataHolder.data('discussionProjectId'));
  
      form.attr('data-line-code', dataHolder.data('lineCode'));
      form.find('#line_type').val(dataHolder.data('lineType'));
  
      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'));
  
      form.find('#note_type').val(dataHolder.data('noteType'));
  
      // LegacyDiffNote
      form.find('#note_line_code').val(dataHolder.data('lineCode'));
  
      // DiffNote
      form.find('#note_position').val(dataHolder.attr('data-position'));
  
      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'));
      form.find('.js-note-target-close').remove();
      form.find('.js-note-new-discussion').remove();
      this.setupNoteForm(form);
  
      form
        .removeClass('js-main-target-form')
        .addClass('discussion-form js-discussion-note-form');
  
      if (typeof gl.diffNotesCompileComponents !== 'undefined') {
        var $commentBtn = form.find('comment-and-resolve-btn');
        $commentBtn.attr(':discussion-id', `'${discussionID}'`);
  
        gl.diffNotesCompileComponents();
      }
  
      form.find('.js-note-text').focus();
      form
        .find('.js-comment-resolve-button')
        .attr('data-discussion-id', discussionID);
    }
  
    /**
     * 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,
        showReplyInput
      });
    }
  
    toggleDiffNote({
      target,
      lineType,
      forceShow,
      showReplyInput = false,
    }) {
      var $link, addForm, hasNotes, newForm, noteForm, replyButton, row, rowCssToAdd, targetContent, isDiffCommentAvatar;
      $link = $(target);
      row = $link.closest('tr');
      const nextRow = row.next();
      let targetRow = row;
      if (nextRow.is('.notes_holder')) {
        targetRow = nextRow;
      }
  
      hasNotes = nextRow.is('.notes_holder');
      addForm = false;
      let lineTypeSelector = '';
      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>';
      // In parallel view, look inside the correct left/right pane
      if (this.isParallelView()) {
        lineTypeSelector = `.${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>';
      }
      const notesContentSelector = `.notes_content${lineTypeSelector} .content`;
      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;
            }
          }
        }
      } else if (showReplyInput) {
        // add a notes row and insert the form
        row.after(rowCssToAdd);
        targetRow = row.next();
        notesContent = targetRow.find(notesContentSelector);
        addForm = true;
      } else {
        const isCurrentlyShown = targetRow.find('.content:not(:empty)').is(':visible');
        const isForced = forceShow === true || forceShow === false;
        const showNow = forceShow === true || (!isCurrentlyShown && !isForced);
  
        targetRow.toggle(showNow);
        notesContent.toggle(showNow);
      }
  
      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) {
      var glForm, row;
      row = form.closest('tr');
      glForm = form.data('gl-form');
      glForm.destroy();
      form.find('.js-note-text').data('autosave').reset();
      // show the reply button (will only work for replies)
      form
        .prev('.discussion-reply-holder')
        .show();
      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) {
      var form;
      e.preventDefault();
      form = $(e.target).closest('.js-discussion-note-form');
      return this.removeDiscussionNoteForm(form);
    }
  
    /**
     * Called after an attachment file has been selected.
     *
     * Updates the file name for the selected attachment.
     */
    updateFormAttachment() {
      var filename, form;
      form = $(this).closest('form');
      // get only the basename
      filename = $(this).val().replace(/^.*[\\\/]/, '');
      return form.find('.js-attachment-filename').text(filename);
    }
  
    /**
     * Called when the tab visibility changes
     */
    visibilityChange() {
      return this.refresh();
    }
  
    updateTargetButtons(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.attr('data-alternative-text');
        closetext = closebtn.attr('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();
        }
      }
    }
  
    putEditFormInPlace($el) {
      var $editForm = $(this.getEditFormSelector($el));
      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');
  
      new gl.GLForm($editForm.find('form'), this.enableGFM);
  
      $editForm.find('form')
        .attr('action', postUrl)
        .attr('data-remote', 'true');
      $editForm.find('.js-form-target-id').val(targetId);
      $editForm.find('.js-form-target-type').val(targetType);
      $editForm.find('.js-note-text').focus().val(originalContent);
      $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) {
        const $alert = $(`<div class="js-conflict-edit-warning alert alert-danger">
          This comment has changed since you started editing, please review the
          <a href="#note_${noteEntity.id}" target="_blank" rel="noopener noreferrer">
            updated comment
          </a>
          to ensure information is not lost
        </div>`);
        $alert.insertAfter($note.find('.note-text'));
      }
    }
  
    updateNotesCount(updateCount) {
      return this.notesCountBadge.text(parseInt(this.notesCountBadge.text(), 10) + updateCount);
    }
  
    toggleCommitList(e) {
      const $element = $(e.currentTarget);
      const $closestSystemCommitList = $element.siblings('.system-note-commit-list');
  
      $element.find('.fa').toggleClass('fa-angle-down').toggleClass('fa-angle-up');
      $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.
     */
    collapseLongCommitList() {
      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');
        }
      });
    }
  
    addFlash(...flashParams) {
      this.flashInstance = new Flash(...flashParams);
    }
  
    clearFlash() {
      if (this.flashInstance && this.flashInstance.flashContainer) {
        this.flashInstance.flashContainer.hide();
        this.flashInstance = null;
      }
    }
  
    cleanForm($form) {
      // Remove JS classes that are not needed here
      $form
        .find('.js-comment-type-dropdown')
        .removeClass('btn-group');
  
      // Remove dropdown
      $form
        .find('.dropdown-menu')
        .remove();
  
      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(
        $note.find('.original-note-content').first().text().trim()
      );
      return sanitizedNoteEntityText !== currentNoteText;
    }
  
    static checkMergeRequestStatus() {
      if (getPagePath(1) === 'merge_requests') {
        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) {
      return {
        formData: $form.serialize(),
        formContent: _.escape($form.find('.js-note-text').val()),
        formAction: $form.attr('action'),
      };
    }
  
    /**
     * 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`
      const executedCommands = availableQuickActions.filter((command, index) => {
        const commandRegex = new RegExp(`/${command.name}`);
        return commandRegex.test(formContent);
      });
  
      if (executedCommands && executedCommands.length) {
        if (executedCommands.length > 1) {
          tempFormContent = 'Applying multiple commands';
        } else {
          const commandDescription = executedCommands[0].description.toLowerCase();
          tempFormContent = `Applying command to ${commandDescription}`;
        }
      } else {
        tempFormContent = 'Applying command';
      }
  
      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.
     */
    createPlaceholderNote({ formContent, uniqueId, isDiscussionNote, currentUsername, currentUserFullname, currentUserAvatar }) {
      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">
                 <a href="/${_.escape(currentUsername)}">
                   <img class="avatar s40" src="${currentUserAvatar}" />
                 </a>
              </div>
              <div class="timeline-content ${discussionClass}">
                 <div class="note-header">
                    <div class="note-header-info">
                       <a href="/${_.escape(currentUsername)}">
                         <span class="hidden-xs">${_.escape(currentUsername)}</span>
                         <span class="note-headline-light">${_.escape(currentUsername)}</span>
                       </a>
                    </div>
                 </div>
                 <div class="note-body">
                   <div class="note-text">
                     <p>${formContent}</p>
                   </div>
                 </div>
              </div>
           </div>
        </li>`
      );
  
      $tempNote.find('.hidden-xs').text(_.escape(currentUserFullname));
      $tempNote.find('.note-headline-light').text(`@${_.escape(currentUsername)}`);
  
      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>
             </div>
           </div>
         </li>`
      );
  
      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
     * 5) Perform network request to submit the note using `ajaxPost`
     *    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);
      let $form = $submitBtn.parents('form');
      const $closeBtn = $form.find('.js-note-target-close');
      const isDiscussionNote = $submitBtn.parent().find('li.droplab-item-selected').attr('id') === 'discussion';
      const isMainForm = $form.hasClass('js-main-target-form');
      const isDiscussionForm = $form.hasClass('js-discussion-note-form');
      const isDiscussionResolve = $submitBtn.hasClass('js-comment-resolve-button');
      const { formData, formContent, formAction } = this.getFormData($form);
      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');
      }
  
      // If comment is to resolve discussion, disable submit buttons while
      // comment posting is finished.
      if (isDiscussionResolve) {
        $submitBtn.disable();
        $form.find('.js-comment-submit-button').disable();
      }
  
      tempFormContent = formContent;
      if (this.hasQuickActions(formContent)) {
        tempFormContent = this.stripQuickActions(formContent);
        hasQuickActions = true;
      }
  
      // Show placeholder note
      if (tempFormContent) {
        noteUniqueId = _.uniqueId('tempNote_');
        $notesContainer.append(this.createPlaceholderNote({
          formContent: tempFormContent,
          uniqueId: noteUniqueId,
          isDiscussionNote,
          currentUsername: gon.current_username,
          currentUserFullname: gon.current_user_fullname,
          currentUserAvatar: gon.current_user_avatar_url,
        }));
      }
  
      // Show placeholder system note
      if (hasQuickActions) {
        systemNoteUniqueId = _.uniqueId('tempSystemNote_');
        $notesContainer.append(this.createPlaceholderSystemNote({
          formContent: this.getQuickActionDescription(formContent, AjaxCache.get(gl.GfmAutoComplete.dataSources.commands)),
          uniqueId: systemNoteUniqueId,
        }));
      }
  
      // Clear the form textarea
      if ($notesContainer.length) {
        if (isMainForm) {
          this.resetMainTargetForm(e);
        } else if (isDiscussionForm) {
          this.removeDiscussionNoteForm($form);
        }
      }
  
      /* eslint-disable promise/catch-or-return */
      // Make request to submit comment on server
      ajaxPost(formAction, formData)
        .then((note) => {
          // Submission successful! remove placeholder
          $notesContainer.find(`#${noteUniqueId}`).remove();
  
          // Reset cached commands list when command is applied
          if (hasQuickActions) {
            $form.find('textarea.js-note-text').trigger('clear-commands-cache.atwho');
          }
  
          // Clear previous form errors
          this.clearFlashWrapper();
  
          // Check if this was discussion comment
          if (isDiscussionForm) {
            // Remove flash-container
            $notesContainer.find('.flash-container').remove();
  
            // If comment intends to resolve discussion, do the same.
            if (isDiscussionResolve) {
              $form
                .attr('data-discussion-id', $submitBtn.data('discussion-id'))
                .attr('data-resolve-all', 'true')
                .attr('data-project-path', $submitBtn.data('project-path'));
            }
  
            // Show final note element on UI
            this.addDiscussionNote($form, note, $notesContainer.length === 0);
  
            // append flash-container to the Notes list
            if ($notesContainer.length) {
              $notesContainer.append('<div class="flash-container" style="display: none;"></div>');
            }
          } else if (isMainForm) { // Check if this was main thread comment
            // Show final note element on UI and perform form and action buttons cleanup
            this.addNote($form, note);
            this.reenableTargetFormSubmitButton(e);
          }
  
          if (note.commands_changes) {
            this.handleQuickActions(note);
          }
  
          $form.trigger('ajax:success', [note]);
        }).fail(() => {
          // Submission failed, remove placeholder note and show Flash error message
          $notesContainer.find(`#${noteUniqueId}`).remove();
  
          if (hasQuickActions) {
            $notesContainer.find(`#${systemNoteUniqueId}`).remove();
          }
  
          // Show form again on UI on failure
          if (isDiscussionForm && $notesContainer.length) {
            const replyButton = $notesContainer.parent().find('.js-discussion-reply-button');
            this.replyToDiscussionNote(replyButton[0]);
            $form = $notesContainer.parent().find('form');
          }
  
          $form.find('.js-note-text').val(formContent);
          this.reenableTargetFormSubmitButton(e);
          this.addNoteError($form);
        });
  
      return $closeBtn.text($closeBtn.data('original-text'));
    }
  
    /**
     * 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
     * 3) Perform network request to submit the updated note using `ajaxPost`
     *    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
      $noteBodyText.html(formContent);
      $editingNote.removeClass('is-editing fade-in-full').addClass('being-posted fade-in-half');
      $editingNote.find('.note-headline-meta a').html('<i class="fa fa-spinner fa-spin" aria-label="Comment is being updated" aria-hidden="true"></i>');
  
      /* eslint-disable promise/catch-or-return */
      // Make request to update comment on server
      ajaxPost(formAction, formData)
        .then((note) => {
          // Submission successful! render final note element
          this.updateNote(note, $editingNote);
        })
        .fail(() => {
          // 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();
        });
  
      return $closeBtn.text($closeBtn.data('original-text'));
    }
  }
  
  window.Notes = Notes;
  