common_utils.js.es6 9.86 KB
Newer Older
1
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-param-reassign, no-else-return, quotes, object-shorthand, comma-dangle, camelcase, one-var, vars-on-top, one-var-declaration-per-line, no-return-assign, consistent-return, max-len, prefer-template */
Fatih Acet's avatar
Fatih Acet committed
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
(function() {
  (function(w) {
    var base;
    w.gl || (w.gl = {});
    (base = w.gl).utils || (base.utils = {});
    w.gl.utils.isInGroupsPage = function() {
      return gl.utils.getPagePath() === 'groups';
    };
    w.gl.utils.isInProjectPage = function() {
      return gl.utils.getPagePath() === 'projects';
    };
    w.gl.utils.getProjectSlug = function() {
      if (this.isInProjectPage()) {
        return $('body').data('project');
      } else {
        return null;
      }
    };
    w.gl.utils.getGroupSlug = function() {
      if (this.isInGroupsPage()) {
        return $('body').data('group');
      } else {
        return null;
      }
    };
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

    w.gl.utils.ajaxGet = function(url) {
      return $.ajax({
        type: "GET",
        url: url,
        dataType: "script"
      });
    };

    w.gl.utils.extractLast = function(term) {
      return this.split(term).pop();
    };

    w.gl.utils.rstrip = function rstrip(val) {
      if (val) {
        return val.replace(/\s+$/, '');
      } else {
        return val;
      }
    };

    w.gl.utils.disableButtonIfEmptyField = function(field_selector, button_selector, event_name) {
      event_name = event_name || 'input';
      var closest_submit, field, that;
      that = this;
      field = $(field_selector);
      closest_submit = field.closest('form').find(button_selector);
      if (this.rstrip(field.val()) === "") {
        closest_submit.disable();
      }
      return field.on(event_name, function() {
        if (that.rstrip($(this).val()) === "") {
          return closest_submit.disable();
        } else {
          return closest_submit.enable();
        }
      });
    };

66 67 68
    // automatically adjust scroll position for hash urls taking the height of the navbar into account
    // https://github.com/twitter/bootstrap/issues/1768
    w.gl.utils.handleLocationHash = function() {
69 70 71
      var hash = w.gl.utils.getLocationHash();
      if (!hash) return;

72 73 74
      // This is required to handle non-unicode characters in hash
      hash = decodeURIComponent(hash);

75 76
      // scroll to user-generated markdown anchor if we cannot find a match
      if (document.getElementById(hash) === null) {
77
        var target = document.getElementById('user-content-' + hash);
78 79 80 81 82
        if (target && target.scrollIntoView) {
          target.scrollIntoView(true);
        }
      } else {
        // only adjust for fixedTabs when not targeting user-generated content
83
        var fixedTabs = document.querySelector('.js-tabs-affix');
84
        if (fixedTabs) {
85
          window.scrollBy(0, -fixedTabs.offsetHeight);
86 87
        }
      }
88
    };
89

Kushal Pandya's avatar
Kushal Pandya committed
90 91 92 93 94 95 96 97 98 99 100 101 102
    // Check if element scrolled into viewport from above or below
    // Courtesy http://stackoverflow.com/a/7557433/414749
    w.gl.utils.isInViewport = function(el) {
      var rect = el.getBoundingClientRect();

      return (
        rect.top >= 0 &&
        rect.left >= 0 &&
        rect.bottom <= window.innerHeight &&
        rect.right <= window.innerWidth
      );
    };

103 104 105
    gl.utils.getPagePath = function(index) {
      index = index || 0;
      return $('body').data('page').split(':')[index];
Fatih Acet's avatar
Fatih Acet committed
106
    };
107

108 109 110 111 112 113 114 115 116 117 118 119 120
    gl.utils.parseUrl = function (url) {
      var parser = document.createElement('a');
      parser.href = url;
      return parser;
    };

    gl.utils.parseUrlPathname = function (url) {
      var parsedUrl = gl.utils.parseUrl(url);
      // parsedUrl.pathname will return an absolute path for Firefox and a relative path for IE11
      // We have to make sure we always have an absolute path.
      return parsedUrl.pathname.charAt(0) === '/' ? parsedUrl.pathname : '/' + parsedUrl.pathname;
    };

Clement Ho's avatar
Clement Ho committed
121 122 123 124
    gl.utils.getUrlParamsArray = function () {
      // We can trust that each param has one & since values containing & will be encoded
      // Remove the first character of search as it is always ?
      return window.location.search.slice(1).split('&');
Clement Ho's avatar
Clement Ho committed
125
    };
Clement Ho's avatar
Clement Ho committed
126

127 128 129 130
    gl.utils.isMetaKey = function(e) {
      return e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
    };

131 132 133 134 135 136 137 138
    gl.utils.isMetaClick = function(e) {
      // Identify following special clicks
      // 1) Cmd + Click on Mac (e.metaKey)
      // 2) Ctrl + Click on PC (e.ctrlKey)
      // 3) Middle-click or Mouse Wheel Click (e.which is 2)
      return e.metaKey || e.ctrlKey || e.which === 2;
    };

Fatih Acet's avatar
Fatih Acet committed
139
    gl.utils.scrollToElement = function($el) {
140 141 142 143
      var top = $el.offset().top;
      gl.mrTabsHeight = gl.mrTabsHeight || $('.merge-request-tabs').height();

      return $('body, html').animate({
144
        scrollTop: top - (gl.mrTabsHeight)
145 146
      }, 200);
    };
Fatih Acet's avatar
Fatih Acet committed
147

Regis's avatar
Regis committed
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
    /**
      this will take in the `name` of the param you want to parse in the url
      if the name does not exist this function will return `null`
      otherwise it will return the value of the param key provided
    */
    w.gl.utils.getParameterByName = (name) => {
      const url = window.location.href;
      name = name.replace(/[[\]]/g, '\\$&');
      const regex = new RegExp(`[?&]${name}(=([^&#]*)|&|#|$)`);
      const results = regex.exec(url);
      if (!results) return null;
      if (!results[2]) return '';
      return decodeURIComponent(results[2].replace(/\+/g, ' '));
    };

Douwe Maan's avatar
Douwe Maan committed
163
    w.gl.utils.getSelectedFragment = () => {
Douwe Maan's avatar
Douwe Maan committed
164
      const selection = window.getSelection();
165
      if (selection.rangeCount === 0) return null;
Douwe Maan's avatar
Douwe Maan committed
166
      const documentFragment = selection.getRangeAt(0).cloneContents();
Douwe Maan's avatar
Douwe Maan committed
167 168 169
      if (documentFragment.textContent.length === 0) return null;

      return documentFragment;
Douwe Maan's avatar
Douwe Maan committed
170
    };
Douwe Maan's avatar
Douwe Maan committed
171 172 173 174

    w.gl.utils.insertText = (target, text) => {
      // Firefox doesn't support `document.execCommand('insertText', false, text)` on textareas

Douwe Maan's avatar
Douwe Maan committed
175 176 177
      const selectionStart = target.selectionStart;
      const selectionEnd = target.selectionEnd;
      const value = target.value;
Douwe Maan's avatar
Douwe Maan committed
178

Douwe Maan's avatar
Douwe Maan committed
179 180 181
      const textBefore = value.substring(0, selectionStart);
      const textAfter = value.substring(selectionEnd, value.length);
      const newText = textBefore + text + textAfter;
Douwe Maan's avatar
Douwe Maan committed
182 183 184

      target.value = newText;
      target.selectionStart = target.selectionEnd = selectionStart + text.length;
185 186 187 188 189 190 191 192

      // Trigger autosave
      $(target).trigger('input');

      // Trigger autosize
      var event = document.createEvent('Event');
      event.initEvent('autosize:update', true, false);
      target.dispatchEvent(event);
Douwe Maan's avatar
Douwe Maan committed
193
    };
Douwe Maan's avatar
Douwe Maan committed
194 195

    w.gl.utils.nodeMatchesSelector = (node, selector) => {
Douwe Maan's avatar
Douwe Maan committed
196
      const matches = Element.prototype.matches ||
Douwe Maan's avatar
Douwe Maan committed
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
        Element.prototype.matchesSelector ||
        Element.prototype.mozMatchesSelector ||
        Element.prototype.msMatchesSelector ||
        Element.prototype.oMatchesSelector ||
        Element.prototype.webkitMatchesSelector;

      if (matches) {
        return matches.call(node, selector);
      }

      // IE11 doesn't support `node.matches(selector)`

      let parentNode = node.parentNode;
      if (!parentNode) {
        parentNode = document.createElement('div');
        node = node.cloneNode(true);
        parentNode.appendChild(node);
      }

Douwe Maan's avatar
Douwe Maan committed
216
      const matchingNodes = parentNode.querySelectorAll(selector);
Douwe Maan's avatar
Douwe Maan committed
217
      return Array.prototype.indexOf.call(matchingNodes, node) !== -1;
Douwe Maan's avatar
Douwe Maan committed
218
    };
219

220 221 222 223 224 225 226 227 228 229 230 231 232
    /**
      this will take in the headers from an API response and normalize them
      this way we don't run into production issues when nginx gives us lowercased header keys
    */
    w.gl.utils.normalizeHeaders = (headers) => {
      const upperCaseHeaders = {};

      Object.keys(headers).forEach((e) => {
        upperCaseHeaders[e.toUpperCase()] = headers[e];
      });

      return upperCaseHeaders;
    };
233

234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
    /**
     * Parses pagination object string values into numbers.
     *
     * @param {Object} paginationInformation
     * @returns {Object}
     */
    w.gl.utils.parseIntPagination = paginationInformation => ({
      perPage: parseInt(paginationInformation['X-PER-PAGE'], 10),
      page: parseInt(paginationInformation['X-PAGE'], 10),
      total: parseInt(paginationInformation['X-TOTAL'], 10),
      totalPages: parseInt(paginationInformation['X-TOTAL-PAGES'], 10),
      nextPage: parseInt(paginationInformation['X-NEXT-PAGE'], 10),
      previousPage: parseInt(paginationInformation['X-PREV-PAGE'], 10),
    });

249 250
    /**
     * Transforms a DOMStringMap into a plain object.
251 252 253
     *
     * @param {DOMStringMap} DOMStringMapObject
     * @returns {Object}
254 255 256 257 258
     */
    w.gl.utils.DOMStringMapToObject = DOMStringMapObject => Object.keys(DOMStringMapObject).reduce((acc, element) => {
      acc[element] = DOMStringMapObject[element];
      return acc;
    }, {});
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273

    /**
     * Updates the search parameter of a URL given the parameter and values provided.
     *
     * If no search params are present we'll add it.
     * If param for page is already present, we'll update it
     * If there are params but not for the given one, we'll add it at the end.
     * Returns the new search parameters.
     *
     * @param {String} param
     * @param {Number|String|Undefined|Null} value
     * @return {String}
     */
    w.gl.utils.setParamInURL = (param, value) => {
      let search;
Filipa Lacerda's avatar
Filipa Lacerda committed
274
      const locationSearch = window.location.search;
275

Filipa Lacerda's avatar
Filipa Lacerda committed
276
      if (locationSearch.length === 0) {
277 278 279
        search = `?${param}=${value}`;
      }

Filipa Lacerda's avatar
Filipa Lacerda committed
280
      if (locationSearch.indexOf(param) !== -1) {
281
        const regex = new RegExp(param + '=\\d');
Filipa Lacerda's avatar
Filipa Lacerda committed
282
        search = locationSearch.replace(regex, `${param}=${value}`);
283 284
      }

Filipa Lacerda's avatar
Filipa Lacerda committed
285 286
      if (locationSearch.length && locationSearch.indexOf(param) === -1) {
        search = `${locationSearch}&${param}=${value}`;
287 288 289 290 291 292 293 294 295 296 297 298
      }

      return search;
    };

    /**
     * Converts permission provided as strings to booleans.
     *
     * @param  {String} string
     * @returns {Boolean}
     */
    w.gl.utils.convertPermissionToBoolean = permission => permission === 'true';
299
  })(window);
300
}).call(window);