summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/gl_form.js
blob: f4008fe3cc927edfd9623ed1fde393b58f66fec6 (plain)
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
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 });

      if (this.form.is(':not(.js-no-autosize)')) {
        autosize(this.textarea);
      }
    }
    // form and textarea event listeners
    this.addEventListeners();
    addMarkdownListeners(this.form);
    this.form.show();
    if (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'));
  }
}