common_utils.js 14.0 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 */
F
Fatih Acet 已提交
2 3 4
(function() {
  (function(w) {
    var base;
5 6
    const faviconEl = document.getElementById('favicon');
    const originalFavicon = faviconEl ? faviconEl.getAttribute('href') : null;
F
Fatih Acet 已提交
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
    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;
      }
    };
J
José Iván 已提交
29

30 31 32 33 34
    w.gl.utils.isInIssuePage = () => {
      const page = gl.utils.getPagePath(1);
      const action = gl.utils.getPagePath(2);

      return page === 'issues' && action === 'show';
35
    };
36

J
José Iván 已提交
37 38 39 40 41 42 43 44
    w.gl.utils.ajaxGet = function(url) {
      return $.ajax({
        type: "GET",
        url: url,
        dataType: "script"
      });
    };

K
Kushal Pandya 已提交
45 46 47 48 49 50 51 52
    w.gl.utils.ajaxPost = function(url, data) {
      return $.ajax({
        type: 'POST',
        url: url,
        data: data,
      });
    };

J
José Iván 已提交
53 54 55 56 57 58 59 60 61 62 63 64
    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;
      }
    };

B
barthc 已提交
65 66 67 68
    gl.utils.updateTooltipTitle = function($tooltipEl, newTitle) {
      return $tooltipEl.attr('title', newTitle).tooltip('fixTitle');
    };

J
José Iván 已提交
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
    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();
        }
      });
    };

87 88 89
    // 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() {
90 91 92
      var hash = w.gl.utils.getLocationHash();
      if (!hash) return;

93 94 95
      // This is required to handle non-unicode characters in hash
      hash = decodeURIComponent(hash);

P
Phil Hughes 已提交
96 97 98
      const fixedTabs = document.querySelector('.js-tabs-affix');
      const fixedDiffStats = document.querySelector('.js-diff-files-changed.is-stuck');
      const fixedNav = document.querySelector('.navbar-gitlab');
A
Annabel Dunstone Gray 已提交
99 100 101 102

      var adjustment = 0;
      if (fixedNav) adjustment -= fixedNav.offsetHeight;

103 104
      // scroll to user-generated markdown anchor if we cannot find a match
      if (document.getElementById(hash) === null) {
105
        var target = document.getElementById('user-content-' + hash);
106 107
        if (target && target.scrollIntoView) {
          target.scrollIntoView(true);
A
Annabel Dunstone Gray 已提交
108
          window.scrollBy(0, adjustment);
109 110 111 112
        }
      } else {
        // only adjust for fixedTabs when not targeting user-generated content
        if (fixedTabs) {
A
Annabel Dunstone Gray 已提交
113
          adjustment -= fixedTabs.offsetHeight;
114
        }
P
Phil Hughes 已提交
115 116 117 118 119

        if (fixedDiffStats) {
          adjustment -= fixedDiffStats.offsetHeight;
        }

A
Annabel Dunstone Gray 已提交
120
        window.scrollBy(0, adjustment);
121
      }
122
    };
J
José Iván 已提交
123

K
Kushal Pandya 已提交
124 125 126 127 128 129 130 131 132 133 134 135 136
    // 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
      );
    };

137 138 139
    gl.utils.getPagePath = function(index) {
      index = index || 0;
      return $('body').data('page').split(':')[index];
F
Fatih Acet 已提交
140
    };
141

142 143 144 145 146 147 148 149 150 151 152 153 154
    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;
    };

C
Clement Ho 已提交
155 156 157
    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 ?
158 159 160 161
      return window.location.search.slice(1).split('&').map((param) => {
        const split = param.split('=');
        return [decodeURI(split[0]), split[1]].join('=');
      });
C
Clement Ho 已提交
162
    };
C
Clement Ho 已提交
163

164 165 166 167
    gl.utils.isMetaKey = function(e) {
      return e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
    };

168 169 170 171 172 173 174 175
    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;
    };

F
Fatih Acet 已提交
176
    gl.utils.scrollToElement = function($el) {
177 178 179
      const top = $el.offset().top;
      const mrTabsHeight = $('.merge-request-tabs').height() || 0;
      const headerHeight = $('.navbar-gitlab').height() || 0;
180 181

      return $('body, html').animate({
182
        scrollTop: top - mrTabsHeight - headerHeight,
183 184
      }, 200);
    };
F
Fatih Acet 已提交
185

R
Regis 已提交
186 187 188 189 190
    /**
      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
    */
191 192
    w.gl.utils.getParameterByName = (name, parseUrl) => {
      const url = parseUrl || window.location.href;
R
Regis 已提交
193 194 195 196 197 198 199 200
      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, ' '));
    };

D
Douwe Maan 已提交
201
    w.gl.utils.getSelectedFragment = () => {
D
Douwe Maan 已提交
202
      const selection = window.getSelection();
203
      if (selection.rangeCount === 0) return null;
S
Simon Knox 已提交
204 205 206 207
      const documentFragment = document.createDocumentFragment();
      for (let i = 0; i < selection.rangeCount; i += 1) {
        documentFragment.appendChild(selection.getRangeAt(i).cloneContents());
      }
D
Douwe Maan 已提交
208 209 210
      if (documentFragment.textContent.length === 0) return null;

      return documentFragment;
D
Douwe Maan 已提交
211
    };
D
Douwe Maan 已提交
212 213 214 215

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

D
Douwe Maan 已提交
216 217 218
      const selectionStart = target.selectionStart;
      const selectionEnd = target.selectionEnd;
      const value = target.value;
D
Douwe Maan 已提交
219

D
Douwe Maan 已提交
220 221
      const textBefore = value.substring(0, selectionStart);
      const textAfter = value.substring(selectionEnd, value.length);
222 223 224

      const insertedText = text instanceof Function ? text(textBefore, textAfter) : text;
      const newText = textBefore + insertedText + textAfter;
D
Douwe Maan 已提交
225 226

      target.value = newText;
227
      target.selectionStart = target.selectionEnd = selectionStart + insertedText.length;
228 229 230 231 232 233 234 235

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

      // Trigger autosize
      var event = document.createEvent('Event');
      event.initEvent('autosize:update', true, false);
      target.dispatchEvent(event);
D
Douwe Maan 已提交
236
    };
D
Douwe Maan 已提交
237 238

    w.gl.utils.nodeMatchesSelector = (node, selector) => {
D
Douwe Maan 已提交
239
      const matches = Element.prototype.matches ||
D
Douwe Maan 已提交
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
        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);
      }

D
Douwe Maan 已提交
259
      const matchingNodes = parentNode.querySelectorAll(selector);
D
Douwe Maan 已提交
260
      return Array.prototype.indexOf.call(matchingNodes, node) !== -1;
D
Douwe Maan 已提交
261
    };
262

263 264 265 266 267 268 269 270 271 272 273 274 275
    /**
      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;
    };
276

277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
    /**
      this will take in the getAllResponseHeaders result and normalize them
      this way we don't run into production issues when nginx gives us lowercased header keys
    */
    w.gl.utils.normalizeCRLFHeaders = (headers) => {
      const headersObject = {};
      const headersArray = headers.split('\n');

      headersArray.forEach((header) => {
        const keyValue = header.split(': ');
        headersObject[keyValue[0]] = keyValue[1];
      });

      return w.gl.utils.normalizeHeaders(headersObject);
    };

293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
    /**
     * 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),
    });

308
    /**
309
     * Updates the search parameter of a URL given the parameter and value provided.
310 311 312 313 314 315 316 317 318 319 320 321
     *
     * 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;
F
Filipa Lacerda 已提交
322
      const locationSearch = window.location.search;
323

324 325 326 327 328 329 330 331
      if (locationSearch.length) {
        const parameters = locationSearch.substring(1, locationSearch.length)
          .split('&')
          .reduce((acc, element) => {
            const val = element.split('=');
            acc[val[0]] = decodeURIComponent(val[1]);
            return acc;
          }, {});
332

333
        parameters[param] = value;
334

335 336 337 338 339 340 341
        const toString = Object.keys(parameters)
          .map(val => `${val}=${encodeURIComponent(parameters[val])}`)
          .join('&');

        search = `?${toString}`;
      } else {
        search = `?${param}=${value}`;
342 343 344 345 346 347 348 349 350 351 352 353
      }

      return search;
    };

    /**
     * Converts permission provided as strings to booleans.
     *
     * @param  {String} string
     * @returns {Boolean}
     */
    w.gl.utils.convertPermissionToBoolean = permission => permission === 'true';
F
Filipa Lacerda 已提交
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386

    /**
     * Back Off exponential algorithm
     * backOff :: (Function<next, stop>, Number) -> Promise<Any, Error>
     *
     * @param {Function<next, stop>} fn function to be called
     * @param {Number} timeout
     * @return {Promise<Any, Error>}
     * @example
     * ```
     *  backOff(function (next, stop) {
     *    // Let's perform this function repeatedly for 60s or for the timeout provided.
     *
     *    ourFunction()
     *      .then(function (result) {
     *        // continue if result is not what we need
     *        next();
     *
     *        // when result is what we need let's stop with the repetions and jump out of the cycle
     *        stop(result);
     *      })
     *      .catch(function (error) {
     *        // if there is an error, we need to stop this with an error.
     *        stop(error);
     *      })
     *  }, 60000)
     *  .then(function (result) {})
     *  .catch(function (error) {
     *    // deal with errors passed to stop()
     *  })
     * ```
     */
    w.gl.utils.backOff = (fn, timeout = 60000) => {
387
      const maxInterval = 32000;
F
Filipa Lacerda 已提交
388
      let nextInterval = 2000;
389
      let timeElapsed = 0;
F
Filipa Lacerda 已提交
390 391 392 393 394

      return new Promise((resolve, reject) => {
        const stop = arg => ((arg instanceof Error) ? reject(arg) : resolve(arg));

        const next = () => {
395 396 397
          if (timeElapsed < timeout) {
            setTimeout(() => fn(next, stop), nextInterval);
            timeElapsed += nextInterval;
398
            nextInterval = Math.min(nextInterval + nextInterval, maxInterval);
F
Filipa Lacerda 已提交
399 400 401 402 403 404 405 406
          } else {
            reject(new Error('BACKOFF_TIMEOUT'));
          }
        };

        fn(next, stop);
      });
    };
407

408 409 410
    w.gl.utils.setFavicon = (faviconPath) => {
      if (faviconEl && faviconPath) {
        faviconEl.setAttribute('href', faviconPath);
411 412 413 414 415 416 417 418 419 420 421 422 423 424
      }
    };

    w.gl.utils.resetFavicon = () => {
      if (faviconEl) {
        faviconEl.setAttribute('href', originalFavicon);
      }
    };

    w.gl.utils.setCiStatusFavicon = (pageUrl) => {
      $.ajax({
        url: pageUrl,
        dataType: 'json',
        success: function(data) {
L
Luke "Jared" Bennett 已提交
425
          if (data && data.favicon) {
426
            gl.utils.setFavicon(data.favicon);
427 428 429 430 431 432 433 434 435
          } else {
            gl.utils.resetFavicon();
          }
        },
        error: function() {
          gl.utils.resetFavicon();
        }
      });
    };
436
  })(window);
437
}).call(window);