gitlab-org--gitlab-foss/app/assets/javascripts/lib/utils/common_utils.js

424 lines
14 KiB
JavaScript
Raw Normal View History

/* 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 */
2016-07-24 16:45:11 -04:00
(function() {
(function(w) {
var base;
const faviconEl = document.getElementById('favicon');
const originalFavicon = faviconEl ? faviconEl.getAttribute('href') : null;
2016-07-24 16:45:11 -04:00
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;
}
};
w.gl.utils.ajaxGet = function(url) {
return $.ajax({
type: "GET",
url: url,
dataType: "script"
});
};
2017-05-05 06:57:29 -04:00
w.gl.utils.ajaxPost = function(url, data) {
return $.ajax({
type: 'POST',
url: url,
data: data,
});
};
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;
}
};
2016-09-08 06:08:09 -04:00
gl.utils.updateTooltipTitle = function($tooltipEl, newTitle) {
return $tooltipEl.attr('title', newTitle).tooltip('fixTitle');
};
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();
}
});
};
// 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() {
var hash = w.gl.utils.getLocationHash();
if (!hash) return;
2017-02-09 14:22:38 -05:00
// This is required to handle non-unicode characters in hash
hash = decodeURIComponent(hash);
2017-06-23 04:24:15 -04:00
var fixedTabs = document.querySelector('.js-tabs-affix');
var fixedNav = document.querySelector('.navbar-gitlab');
var adjustment = 0;
if (fixedNav) adjustment -= fixedNav.offsetHeight;
// scroll to user-generated markdown anchor if we cannot find a match
if (document.getElementById(hash) === null) {
var target = document.getElementById('user-content-' + hash);
if (target && target.scrollIntoView) {
target.scrollIntoView(true);
2017-06-23 04:24:15 -04:00
window.scrollBy(0, adjustment);
}
} else {
// only adjust for fixedTabs when not targeting user-generated content
if (fixedTabs) {
2017-06-23 04:24:15 -04:00
adjustment -= fixedTabs.offsetHeight;
}
2017-06-23 04:24:15 -04:00
window.scrollBy(0, adjustment);
}
};
2016-12-09 09:36:18 -05:00
// 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
);
};
gl.utils.getPagePath = function(index) {
index = index || 0;
return $('body').data('page').split(':')[index];
2016-07-24 16:45:11 -04:00
};
makes system notes less intrusive to a conversation adds dicussion icon and color change in system note links adds discussion icons and sticky note icon for other system notes for now fixes scss lint error adds faded commit lists hides first paragraph in commit list box css tweak for commit list system notes adds commit-list toggle functionality, css tweaks and css classnames more readable small css fix in header. makes links bold in system note renames class no-shade to hide-shade adds entry for this merge request in changelog removes commented line removes the avatar-icon from discussion header minor css tweaks to make the commit list alignment with header text uses monospaced font to make the commit list lined up with all removes icon from system note and align bullet list resolves scss lint warings adds helper function to extract system note message from first p tag adds helper functions to check commit list count and haml cleanup adds changelog entry under 8.14 adds changelog entry with changelog cli removes helper and regex and makes commit list li count using JS makes link in system note normal brakeman build failure resolved fixing rspec test based on new design for discussion shows system note in lowercase removes extra spaces from comments adds code commenting for functions adds semi-colon in some lines fixes rspec given when merge build success removes commented codes rewrite changelog yml file moves isMetaKey to common utils file fixes some indentation issues removes unnecessary variables and resolves some discussions replaces jQuery parent function with siblings fixes scss issues and variable spelling mistake uses constant rather using hardcoded number for visible li count in long commit list makes system note header all lowercase uses color variables and adjust gradient a little some minor changes for adding css classes renames functions name for readability changes changelog title minor scss newline changes makes system note less intrusive to a conversation
2016-10-08 02:50:28 -04:00
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;
};
2016-12-12 22:24:55 -05:00
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 ?
2017-05-15 09:03:52 -04:00
return window.location.search.slice(1).split('&').map((param) => {
const split = param.split('=');
return [decodeURI(split[0]), split[1]].join('=');
});
2016-12-13 09:52:43 -05:00
};
2016-12-12 22:24:55 -05:00
makes system notes less intrusive to a conversation adds dicussion icon and color change in system note links adds discussion icons and sticky note icon for other system notes for now fixes scss lint error adds faded commit lists hides first paragraph in commit list box css tweak for commit list system notes adds commit-list toggle functionality, css tweaks and css classnames more readable small css fix in header. makes links bold in system note renames class no-shade to hide-shade adds entry for this merge request in changelog removes commented line removes the avatar-icon from discussion header minor css tweaks to make the commit list alignment with header text uses monospaced font to make the commit list lined up with all removes icon from system note and align bullet list resolves scss lint warings adds helper function to extract system note message from first p tag adds helper functions to check commit list count and haml cleanup adds changelog entry under 8.14 adds changelog entry with changelog cli removes helper and regex and makes commit list li count using JS makes link in system note normal brakeman build failure resolved fixing rspec test based on new design for discussion shows system note in lowercase removes extra spaces from comments adds code commenting for functions adds semi-colon in some lines fixes rspec given when merge build success removes commented codes rewrite changelog yml file moves isMetaKey to common utils file fixes some indentation issues removes unnecessary variables and resolves some discussions replaces jQuery parent function with siblings fixes scss issues and variable spelling mistake uses constant rather using hardcoded number for visible li count in long commit list makes system note header all lowercase uses color variables and adjust gradient a little some minor changes for adding css classes renames functions name for readability changes changelog title minor scss newline changes makes system note less intrusive to a conversation
2016-10-08 02:50:28 -04:00
gl.utils.isMetaKey = function(e) {
return e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
};
2017-02-09 12:19:26 -05:00
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;
};
2017-01-04 16:44:00 -05:00
gl.utils.scrollToElement = function($el) {
var top = $el.offset().top;
gl.mrTabsHeight = gl.mrTabsHeight || $('.merge-request-tabs').height();
return $('body, html').animate({
2017-02-07 18:20:28 -05:00
scrollTop: top - (gl.mrTabsHeight)
}, 200);
};
2016-07-24 16:45:11 -04:00
2017-01-09 12:26:43 -05:00
/**
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, parseUrl) => {
const url = parseUrl || window.location.href;
2017-01-09 12:26:43 -05:00
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, ' '));
};
2017-01-18 17:19:51 -05:00
w.gl.utils.getSelectedFragment = () => {
2017-01-18 17:38:30 -05:00
const selection = window.getSelection();
if (selection.rangeCount === 0) return null;
2017-04-20 17:40:48 -04:00
const documentFragment = document.createDocumentFragment();
for (let i = 0; i < selection.rangeCount; i += 1) {
documentFragment.appendChild(selection.getRangeAt(i).cloneContents());
}
2017-01-18 17:19:51 -05:00
if (documentFragment.textContent.length === 0) return null;
return documentFragment;
2017-01-18 17:38:30 -05:00
};
2017-01-18 17:19:51 -05:00
w.gl.utils.insertText = (target, text) => {
// Firefox doesn't support `document.execCommand('insertText', false, text)` on textareas
2017-01-18 17:38:30 -05:00
const selectionStart = target.selectionStart;
const selectionEnd = target.selectionEnd;
const value = target.value;
2017-01-18 17:19:51 -05:00
2017-01-18 17:38:30 -05:00
const textBefore = value.substring(0, selectionStart);
const textAfter = value.substring(selectionEnd, value.length);
const insertedText = text instanceof Function ? text(textBefore, textAfter) : text;
const newText = textBefore + insertedText + textAfter;
2017-01-18 17:19:51 -05:00
target.value = newText;
target.selectionStart = target.selectionEnd = selectionStart + insertedText.length;
// Trigger autosave
$(target).trigger('input');
// Trigger autosize
var event = document.createEvent('Event');
event.initEvent('autosize:update', true, false);
target.dispatchEvent(event);
2017-01-18 17:38:30 -05:00
};
2017-01-18 17:19:51 -05:00
w.gl.utils.nodeMatchesSelector = (node, selector) => {
2017-01-18 17:38:30 -05:00
const matches = Element.prototype.matches ||
2017-01-18 17:19:51 -05:00
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);
}
2017-01-18 17:38:30 -05:00
const matchingNodes = parentNode.querySelectorAll(selector);
2017-01-18 17:19:51 -05:00
return Array.prototype.indexOf.call(matchingNodes, node) !== -1;
2017-01-18 17:38:30 -05:00
};
/**
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;
};
2017-02-05 12:36:00 -05:00
2017-03-30 08:14:32 -04:00
/**
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);
};
/**
* 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),
});
/**
* Updates the search parameter of a URL given the parameter and value 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;
2017-02-16 06:29:45 -05:00
const locationSearch = window.location.search;
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;
}, {});
parameters[param] = value;
const toString = Object.keys(parameters)
.map(val => `${val}=${encodeURIComponent(parameters[val])}`)
.join('&');
search = `?${toString}`;
} else {
search = `?${param}=${value}`;
}
return search;
};
/**
* Converts permission provided as strings to booleans.
*
* @param {String} string
* @returns {Boolean}
*/
w.gl.utils.convertPermissionToBoolean = permission => permission === 'true';
2017-02-24 11:28:30 -05:00
/**
* 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) => {
const maxInterval = 32000;
2017-02-24 11:28:30 -05:00
let nextInterval = 2000;
const startTime = Date.now();
2017-02-24 11:28:30 -05:00
return new Promise((resolve, reject) => {
const stop = arg => ((arg instanceof Error) ? reject(arg) : resolve(arg));
const next = () => {
if (Date.now() - startTime < timeout) {
2017-02-24 11:28:30 -05:00
setTimeout(fn.bind(null, next, stop), nextInterval);
nextInterval = Math.min(nextInterval + nextInterval, maxInterval);
2017-02-24 11:28:30 -05:00
} else {
reject(new Error('BACKOFF_TIMEOUT'));
}
};
fn(next, stop);
});
};
2017-04-13 06:09:20 -04:00
w.gl.utils.setFavicon = (faviconPath) => {
if (faviconEl && faviconPath) {
faviconEl.setAttribute('href', faviconPath);
}
};
w.gl.utils.resetFavicon = () => {
if (faviconEl) {
faviconEl.setAttribute('href', originalFavicon);
}
};
w.gl.utils.setCiStatusFavicon = (pageUrl) => {
$.ajax({
url: pageUrl,
dataType: 'json',
success: function(data) {
2017-04-13 07:50:03 -04:00
if (data && data.favicon) {
2017-04-13 06:09:20 -04:00
gl.utils.setFavicon(data.favicon);
} else {
gl.utils.resetFavicon();
}
},
error: function() {
gl.utils.resetFavicon();
}
});
};
})(window);
}).call(window);