All files / app/assets/javascripts gl_form.js

96.49% Statements 55/57
89.65% Branches 26/29
92.3% Functions 12/13
98.14% Lines 53/54

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135                                        497x 497x 497x     497x   497x 57x     497x 5964x 5467x         497x   497x 497x         964x 964x 467x   964x 116x     964x       497x 497x 497x 475x 475x   475x       475x 475x 475x   475x 475x       497x 497x 497x 497x 497x         3x     3x   3x 3x 3x         2x       7x   7x   6x   6x 6x 6x         964x   964x 964x       497x 10x   497x           2x      
import autosize from 'autosize';
import $ from 'jquery';
import { isEmpty } from 'lodash';
import GfmAutoComplete, { defaultAutocompleteConfig } from 'ee_else_ce/gfm_auto_complete';
import { disableButtonIfEmptyField } from '~/lib/utils/common_utils';
import dropzoneInput from './dropzone_input';
import { addMarkdownListeners, removeMarkdownListeners } from './lib/utils/text_markdown';
 
export default class GLForm {
  /**
   * Create a GLForm
   *
   * @param {jQuery} form Root element of the GLForm
   * @param {Object} enableGFM Which autocomplete features should be enabled?
   * @param {Boolean} forceNew If true, treat the element as a **new** form even if `gfm-form` class already exists.
   * @param {Object} gfmDataSources The paths of the autocomplete data sources to use for GfmAutoComplete
   *                                By default, the backend embeds these in the global object gl.GfmAutocomplete.dataSources.
   *                                Use this param to override them.
   */
  constructor(form, enableGFM = {}, forceNew = false, gfmDataSources = {}) {
    this.form = form;
    this.textarea = this.form.find('textarea.js-gfm-input');
    this.enableGFM = { ...defaultAutocompleteConfig, ...enableGFM };
 
    // Disable autocomplete for keywords which do not have dataSources available
    let dataSources = (gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources) || {};
 
    if (!isEmpty(gfmDataSources)) {
      dataSources = gfmDataSources;
    }
 
    Object.keys(this.enableGFM).forEach((item) => {
      if (item !== 'emojis' && !dataSources[item]) {
        this.enableGFM[item] = false;
      }
    });
 
    // Before we start, we should clean up any previous data for this form
    this.destroy();
    // Set up the form
    this.setupForm(dataSources, forceNew);
    this.form.data('glForm', this);
  }
 
  destroy() {
    // Clean form listeners
    this.clearEventListeners();
    if (this.autoComplete) {
      this.autoComplete.destroy();
    }
    if (this.formDropzone) {
      this.formDropzone.destroy();
    }
 
    this.form.data('glForm', null);
  }
 
  setupForm(dataSources, forceNew = false) {
    const isNewForm = this.form.is(':not(.gfm-form)') || forceNew;
    this.form.removeClass('js-new-note-form');
    if (isNewForm) {
      this.form.find('.div-dropzone').remove();
      this.form.addClass('gfm-form');
      // remove notify commit author checkbox for non-commit notes
      disableButtonIfEmptyField(
        this.form.find('.js-note-text'),
        this.form.find('.js-comment-button, .js-note-new-discussion'),
      );
      this.autoComplete = new GfmAutoComplete(dataSources);
      this.autoComplete.setup(this.form.find('.js-gfm-input'), this.enableGFM);
      this.formDropzone = dropzoneInput(this.form, { parallelUploads: 1 });
 
      Eif (this.form.is(':not(.js-no-autosize)')) {
        autosize(this.textarea);
      }
    }
    // form and textarea event listeners
    this.addEventListeners();
    addMarkdownListeners(this.form);
    this.form.show();
    Iif (this.isAutosizeable) this.setupAutosize();
    if (this.textarea.data('autofocus') === true) this.textarea.focus();
  }
 
  setupAutosize() {
    // eslint-disable-next-line @gitlab/no-global-event-off
    this.textarea.off('autosize:resized').on('autosize:resized', this.setHeightData.bind(this));
 
    // eslint-disable-next-line @gitlab/no-global-event-off
    this.textarea.off('mouseup.autosize').on('mouseup.autosize', this.destroyAutosize.bind(this));
 
    setTimeout(() => {
      autosize(this.textarea);
      this.textarea.css('resize', 'vertical');
    }, 0);
  }
 
  setHeightData() {
    this.textarea.data('height', this.textarea.outerHeight());
  }
 
  destroyAutosize() {
    const outerHeight = this.textarea.outerHeight();
 
    if (this.textarea.data('height') === outerHeight) return;
 
    autosize.destroy(this.textarea);
 
    this.textarea.data('height', outerHeight);
    this.textarea.outerHeight(outerHeight);
    this.textarea.css('max-height', window.outerHeight);
  }
 
  clearEventListeners() {
    // eslint-disable-next-line @gitlab/no-global-event-off
    this.textarea.off('focus');
    // eslint-disable-next-line @gitlab/no-global-event-off
    this.textarea.off('blur');
    removeMarkdownListeners(this.form);
  }
 
  addEventListeners() {
    this.textarea.on('focus', function focusTextArea() {
      $(this).closest('.md-area').addClass('is-focused');
    });
    this.textarea.on('blur', function blurTextArea() {
      $(this).closest('.md-area').removeClass('is-focused');
    });
  }
 
  get supportsQuickActions() {
    return Boolean(this.textarea.data('supports-quick-actions'));
  }
}