summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/lib
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/lib')
-rw-r--r--app/assets/javascripts/lib/chrome_84_icon_fix.js4
-rw-r--r--app/assets/javascripts/lib/dompurify.js8
-rw-r--r--app/assets/javascripts/lib/graphql.js4
-rw-r--r--app/assets/javascripts/lib/utils/ajax_cache.js2
-rw-r--r--app/assets/javascripts/lib/utils/apollo_startup_js_link.js16
-rw-r--r--app/assets/javascripts/lib/utils/autosave.js4
-rw-r--r--app/assets/javascripts/lib/utils/axios_startup_calls.js8
-rw-r--r--app/assets/javascripts/lib/utils/axios_utils.js10
-rw-r--r--app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js2
-rw-r--r--app/assets/javascripts/lib/utils/chart_utils.js4
-rw-r--r--app/assets/javascripts/lib/utils/color_utils.js4
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js168
-rw-r--r--app/assets/javascripts/lib/utils/css_utils.js2
-rw-r--r--app/assets/javascripts/lib/utils/datetime_range.js18
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js129
-rw-r--r--app/assets/javascripts/lib/utils/dom_utils.js6
-rw-r--r--app/assets/javascripts/lib/utils/favicon.js30
-rw-r--r--app/assets/javascripts/lib/utils/favicon_ci.js16
-rw-r--r--app/assets/javascripts/lib/utils/forms.js12
-rw-r--r--app/assets/javascripts/lib/utils/grammar.js6
-rw-r--r--app/assets/javascripts/lib/utils/headers.js3
-rw-r--r--app/assets/javascripts/lib/utils/icon_utils.js6
-rw-r--r--app/assets/javascripts/lib/utils/notify.js2
-rw-r--r--app/assets/javascripts/lib/utils/number_utils.js2
-rw-r--r--app/assets/javascripts/lib/utils/poll.js4
-rw-r--r--app/assets/javascripts/lib/utils/poll_until_complete.js2
-rw-r--r--app/assets/javascripts/lib/utils/set.js2
-rw-r--r--app/assets/javascripts/lib/utils/simple_poll.js2
-rw-r--r--app/assets/javascripts/lib/utils/sticky.js2
-rw-r--r--app/assets/javascripts/lib/utils/text_markdown.js16
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js28
-rw-r--r--app/assets/javascripts/lib/utils/type_utility.js2
-rw-r--r--app/assets/javascripts/lib/utils/unit_format/formatter_factory.js4
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js16
-rw-r--r--app/assets/javascripts/lib/utils/users_cache.js8
35 files changed, 300 insertions, 252 deletions
diff --git a/app/assets/javascripts/lib/chrome_84_icon_fix.js b/app/assets/javascripts/lib/chrome_84_icon_fix.js
index 60497186c19..20fe9590ce3 100644
--- a/app/assets/javascripts/lib/chrome_84_icon_fix.js
+++ b/app/assets/javascripts/lib/chrome_84_icon_fix.js
@@ -30,7 +30,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const fixSVGs = () => {
requestIdleCallback(() => {
- document.querySelectorAll(`use:not([${SKIP_ATTRIBUTE}])`).forEach(use => {
+ document.querySelectorAll(`use:not([${SKIP_ATTRIBUTE}])`).forEach((use) => {
const href = use?.getAttribute('href') ?? use?.getAttribute('xlink:href') ?? '';
if (href.includes(window.gon.sprite_icons)) {
@@ -60,7 +60,7 @@ document.addEventListener('DOMContentLoaded', async () => {
div.classList.add('hidden');
const result = await fetch(url);
div.innerHTML = await result.text();
- div.querySelectorAll('[id]').forEach(node => {
+ div.querySelectorAll('[id]').forEach((node) => {
node.setAttribute('id', `${prefix}-${node.getAttribute('id')}`);
});
document.body.append(div);
diff --git a/app/assets/javascripts/lib/dompurify.js b/app/assets/javascripts/lib/dompurify.js
index d9ea57fbbce..76624c81ed5 100644
--- a/app/assets/javascripts/lib/dompurify.js
+++ b/app/assets/javascripts/lib/dompurify.js
@@ -11,9 +11,9 @@ const defaultConfig = {
const getAllowedIconUrls = (gon = window.gon) =>
[gon.sprite_file_icons, gon.sprite_icons].filter(Boolean);
-const isUrlAllowed = url => getAllowedIconUrls().some(allowedUrl => url.startsWith(allowedUrl));
+const isUrlAllowed = (url) => getAllowedIconUrls().some((allowedUrl) => url.startsWith(allowedUrl));
-const isHrefSafe = url =>
+const isHrefSafe = (url) =>
isUrlAllowed(url) || isUrlAllowed(relativePathToAbsolute(url, getBaseURL()));
const removeUnsafeHref = (node, attr) => {
@@ -36,7 +36,7 @@ const removeUnsafeHref = (node, attr) => {
*
* @param {Object} node - Node to sanitize
*/
-const sanitizeSvgIcon = node => {
+const sanitizeSvgIcon = (node) => {
removeUnsafeHref(node, 'href');
// Note: `xlink:href` is deprecated, but still in use
@@ -44,7 +44,7 @@ const sanitizeSvgIcon = node => {
removeUnsafeHref(node, 'xlink:href');
};
-addHook('afterSanitizeAttributes', node => {
+addHook('afterSanitizeAttributes', (node) => {
if (node.tagName.toLowerCase() === 'use') {
sanitizeSvgIcon(node);
}
diff --git a/app/assets/javascripts/lib/graphql.js b/app/assets/javascripts/lib/graphql.js
index e0d9a903e0a..5c4bb5ea01f 100644
--- a/app/assets/javascripts/lib/graphql.js
+++ b/app/assets/javascripts/lib/graphql.js
@@ -36,13 +36,13 @@ export default (resolvers = {}, config = {}) => {
};
const uploadsLink = ApolloLink.split(
- operation => operation.getContext().hasUpload || operation.getContext().isSingleRequest,
+ (operation) => operation.getContext().hasUpload || operation.getContext().isSingleRequest,
createUploadLink(httpOptions),
new BatchHttpLink(httpOptions),
);
const performanceBarLink = new ApolloLink((operation, forward) => {
- return forward(operation).map(response => {
+ return forward(operation).map((response) => {
const httpResponse = operation.getContext().response;
if (PerformanceBarService.interceptor) {
diff --git a/app/assets/javascripts/lib/utils/ajax_cache.js b/app/assets/javascripts/lib/utils/ajax_cache.js
index 2d976dbdbbe..935bd0f16e9 100644
--- a/app/assets/javascripts/lib/utils/ajax_cache.js
+++ b/app/assets/javascripts/lib/utils/ajax_cache.js
@@ -25,7 +25,7 @@ class AjaxCache extends Cache {
this.internalStorage[endpoint] = data;
delete this.pendingRequests[endpoint];
})
- .catch(e => {
+ .catch((e) => {
const error = new Error(`${endpoint}: ${e.message}`);
error.textStatus = e.message;
diff --git a/app/assets/javascripts/lib/utils/apollo_startup_js_link.js b/app/assets/javascripts/lib/utils/apollo_startup_js_link.js
index 5c120dd532f..014823f3831 100644
--- a/app/assets/javascripts/lib/utils/apollo_startup_js_link.js
+++ b/app/assets/javascripts/lib/utils/apollo_startup_js_link.js
@@ -7,7 +7,7 @@ import { isEqual, pickBy } from 'lodash';
* @param obj
* @returns {Dictionary<unknown>}
*/
-const pickDefinedValues = obj => pickBy(obj, x => x !== undefined);
+const pickDefinedValues = (obj) => pickBy(obj, (x) => x !== undefined);
/**
* Compares two set of variables, order independent
@@ -28,9 +28,9 @@ export class StartupJSLink extends ApolloLink {
// Extract operationNames from the queries and ensure that we can
// match operationName => element from result array
parseStartupCalls(calls) {
- calls.forEach(call => {
+ calls.forEach((call) => {
const { query, variables, fetchCall } = call;
- const operationName = parse(query)?.definitions?.find(x => x.kind === 'OperationDefinition')
+ const operationName = parse(query)?.definitions?.find((x) => x.kind === 'OperationDefinition')
?.name?.value;
if (operationName) {
@@ -71,9 +71,9 @@ export class StartupJSLink extends ApolloLink {
return forward(operation);
}
- return new Observable(observer => {
+ return new Observable((observer) => {
fetchCall
- .then(response => {
+ .then((response) => {
// Handle HTTP errors
if (!response.ok) {
throw new Error('fetchCall failed');
@@ -81,7 +81,7 @@ export class StartupJSLink extends ApolloLink {
operation.setContext({ response });
return response.json();
})
- .then(result => {
+ .then((result) => {
if (result && (result.errors || !result.data)) {
throw new Error('Received GraphQL error');
}
@@ -92,10 +92,10 @@ export class StartupJSLink extends ApolloLink {
})
.catch(() => {
forward(operation).subscribe({
- next: result => {
+ next: (result) => {
observer.next(result);
},
- error: error => {
+ error: (error) => {
observer.error(error);
},
complete: observer.complete.bind(observer),
diff --git a/app/assets/javascripts/lib/utils/autosave.js b/app/assets/javascripts/lib/utils/autosave.js
index 56df2532528..dac1da743a2 100644
--- a/app/assets/javascripts/lib/utils/autosave.js
+++ b/app/assets/javascripts/lib/utils/autosave.js
@@ -1,6 +1,6 @@
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
-export const clearDraft = autosaveKey => {
+export const clearDraft = (autosaveKey) => {
try {
window.localStorage.removeItem(`autosave/${autosaveKey}`);
} catch (e) {
@@ -9,7 +9,7 @@ export const clearDraft = autosaveKey => {
}
};
-export const getDraft = autosaveKey => {
+export const getDraft = (autosaveKey) => {
try {
return window.localStorage.getItem(`autosave/${autosaveKey}`);
} catch (e) {
diff --git a/app/assets/javascripts/lib/utils/axios_startup_calls.js b/app/assets/javascripts/lib/utils/axios_startup_calls.js
index 7bb1da5aed5..f9d58ff9b1d 100644
--- a/app/assets/javascripts/lib/utils/axios_startup_calls.js
+++ b/app/assets/javascripts/lib/utils/axios_startup_calls.js
@@ -3,9 +3,9 @@ import { mergeUrlParams } from './url_utility';
// We should probably not couple this utility to `gon.gitlab_url`
// Also, this would replace occurrences that aren't at the beginning of the string
-const removeGitLabUrl = url => url.replace(gon.gitlab_url, '');
+const removeGitLabUrl = (url) => url.replace(gon.gitlab_url, '');
-const getFullUrl = req => {
+const getFullUrl = (req) => {
const url = removeGitLabUrl(req.url);
return mergeUrlParams(req.params || {}, url, { sort: true });
};
@@ -36,7 +36,7 @@ const handleStartupCall = async ({ fetchCall }, req) => {
});
};
-const setupAxiosStartupCalls = axios => {
+const setupAxiosStartupCalls = (axios) => {
const { startup_calls: startupCalls } = window.gl || {};
if (!startupCalls || isEmpty(startupCalls)) {
@@ -45,7 +45,7 @@ const setupAxiosStartupCalls = axios => {
const remainingCalls = new Map(Object.entries(startupCalls));
- const interceptor = axios.interceptors.request.use(async req => {
+ const interceptor = axios.interceptors.request.use(async (req) => {
const fullUrl = getFullUrl(req);
const startupCall = remainingCalls.get(fullUrl);
diff --git a/app/assets/javascripts/lib/utils/axios_utils.js b/app/assets/javascripts/lib/utils/axios_utils.js
index 9d517f45caa..cb479e243b2 100644
--- a/app/assets/javascripts/lib/utils/axios_utils.js
+++ b/app/assets/javascripts/lib/utils/axios_utils.js
@@ -9,7 +9,7 @@ axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
// Maintain a global counter for active requests
// see: spec/support/wait_for_requests.rb
-axios.interceptors.request.use(config => {
+axios.interceptors.request.use((config) => {
window.pendingRequests = window.pendingRequests || 0;
window.pendingRequests += 1;
return config;
@@ -19,11 +19,11 @@ setupAxiosStartupCalls(axios);
// Remove the global counter
axios.interceptors.response.use(
- response => {
+ (response) => {
window.pendingRequests -= 1;
return response;
},
- err => {
+ (err) => {
window.pendingRequests -= 1;
return Promise.reject(err);
},
@@ -37,8 +37,8 @@ window.addEventListener('beforeunload', () => {
// Ignore AJAX errors caused by requests
// being cancelled due to browser navigation
axios.interceptors.response.use(
- response => response,
- err => suppressAjaxErrorsDuringNavigation(err, isUserNavigating),
+ (response) => response,
+ (err) => suppressAjaxErrorsDuringNavigation(err, isUserNavigating),
);
export default axios;
diff --git a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
index 28a7ebfdc69..286fc2568b2 100644
--- a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
+++ b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
@@ -68,7 +68,7 @@ export default class LinkedTabs {
// since this is a custom event we need jQuery :(
$(document)
.off('shown.bs.tab', tabSelector)
- .on('shown.bs.tab', tabSelector, e => this.tabShown(e));
+ .on('shown.bs.tab', tabSelector, (e) => this.tabShown(e));
this.activateTab(this.action);
}
diff --git a/app/assets/javascripts/lib/utils/chart_utils.js b/app/assets/javascripts/lib/utils/chart_utils.js
index 4a1e6c5d68c..7da3bab0a4b 100644
--- a/app/assets/javascripts/lib/utils/chart_utils.js
+++ b/app/assets/javascripts/lib/utils/chart_utils.js
@@ -34,7 +34,7 @@ const commonChartOptions = () => ({
legend: false,
});
-export const barChartOptions = shouldAdjustFontSize => ({
+export const barChartOptions = (shouldAdjustFontSize) => ({
...commonChartOptions(),
scales: {
...yAxesConfig(shouldAdjustFontSize),
@@ -89,7 +89,7 @@ export const lineChartOptions = ({ width, numberOfPoints, shouldAdjustFontSize }
* @param {Array} data
* @returns {[*, *]}
*/
-export const firstAndLastY = data => {
+export const firstAndLastY = (data) => {
const [firstEntry] = data;
const [lastEntry] = data.slice(-1);
diff --git a/app/assets/javascripts/lib/utils/color_utils.js b/app/assets/javascripts/lib/utils/color_utils.js
index 07fb2915ca7..a1f56b15631 100644
--- a/app/assets/javascripts/lib/utils/color_utils.js
+++ b/app/assets/javascripts/lib/utils/color_utils.js
@@ -4,7 +4,7 @@
* @param hex string
* @returns array|null
*/
-export const hexToRgb = hex => {
+export const hexToRgb = (hex) => {
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
const fullHex = hex.replace(shorthandRegex, (_m, r, g, b) => r + r + g + g + b + b);
@@ -15,7 +15,7 @@ export const hexToRgb = hex => {
: null;
};
-export const textColorForBackground = backgroundColor => {
+export const textColorForBackground = (backgroundColor) => {
const [r, g, b] = hexToRgb(backgroundColor);
if (r + g + b > 500) {
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index f88a0433535..128ef5b335e 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -4,9 +4,8 @@
import { GlBreakpointInstance as breakpointInstance } from '@gitlab/ui/dist/utils';
import $ from 'jquery';
-import { isFunction } from 'lodash';
+import { isFunction, defer } from 'lodash';
import Cookies from 'js-cookie';
-import axios from './axios_utils';
import { getLocationHash } from './url_utility';
import { convertToCamelCase, convertToSnakeCase } from './text_utility';
import { isObject } from './type_utility';
@@ -54,7 +53,7 @@ export const getCspNonceValue = () => {
return metaTag && metaTag.content;
};
-export const rstrip = val => {
+export const rstrip = (val) => {
if (val) {
return val.replace(/\s+$/, '');
}
@@ -68,7 +67,7 @@ export const disableButtonIfEmptyField = (fieldSelector, buttonSelector, eventNa
closestSubmit.disable();
}
// eslint-disable-next-line func-names
- return field.on(eventName, function() {
+ return field.on(eventName, function () {
if (rstrip($(this).val()) === '') {
return closestSubmit.disable();
}
@@ -149,13 +148,13 @@ export const isInViewport = (el, offset = {}) => {
);
};
-export const parseUrl = url => {
+export const parseUrl = (url) => {
const parser = document.createElement('a');
parser.href = url;
return parser;
};
-export const parseUrlPathname = url => {
+export const parseUrlPathname = (url) => {
const parsedUrl = 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.
@@ -166,8 +165,8 @@ const splitPath = (path = '') => path.replace(/^\?/, '').split('&');
export const urlParamsToArray = (path = '') =>
splitPath(path)
- .filter(param => param.length > 0)
- .map(param => {
+ .filter((param) => param.length > 0)
+ .map((param) => {
const split = param.split('=');
return [decodeURI(split[0]), split[1]].join('=');
});
@@ -209,13 +208,13 @@ export const urlParamsToObject = (path = '') =>
return data;
}, {});
-export const isMetaKey = e => e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
+export const isMetaKey = (e) => e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
// 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)
-export const isMetaClick = e => e.metaKey || e.ctrlKey || e.which === 2;
+export const isMetaClick = (e) => e.metaKey || e.ctrlKey || e.which === 2;
export const contentTop = () => {
const isDesktop = breakpointInstance.isDesktop();
@@ -261,23 +260,26 @@ export const contentTop = () => {
};
export const scrollToElement = (element, options = {}) => {
- let $el = element;
- if (!(element instanceof $)) {
- $el = $(element);
+ let el = element;
+ if (element instanceof $) {
+ // eslint-disable-next-line prefer-destructuring
+ el = element[0];
+ } else if (typeof el === 'string') {
+ el = document.querySelector(element);
}
- const { top } = $el.offset();
- const { offset = 0 } = options;
- // eslint-disable-next-line no-jquery/no-animate
- return $('body, html').animate(
- {
- scrollTop: top - contentTop() + offset,
- },
- 200,
- );
+ if (el && el.getBoundingClientRect) {
+ // In the previous implementation, jQuery naturally deferred this scrolling.
+ // Unfortunately, we're quite coupled to this implementation detail now.
+ defer(() => {
+ const { duration = 200, offset = 0 } = options;
+ const y = el.getBoundingClientRect().top + window.pageYOffset + offset - contentTop();
+ window.scrollTo({ top: y, behavior: duration ? 'smooth' : 'auto' });
+ });
+ }
};
-export const scrollToElementWithContext = element => {
+export const scrollToElementWithContext = (element) => {
const offsetMultiplier = -0.1;
return scrollToElement(element, { offset: window.innerHeight * offsetMultiplier });
};
@@ -287,7 +289,7 @@ export const scrollToElementWithContext = element => {
* each browser screen repaint.
* @param {Function} fn
*/
-export const debounceByAnimationFrame = fn => {
+export const debounceByAnimationFrame = (fn) => {
let requestId;
return function debounced(...args) {
@@ -334,7 +336,7 @@ const handleSelectedRange = (range, restrictToNode) => {
return range.cloneContents();
};
-export const getSelectedFragment = restrictToNode => {
+export const getSelectedFragment = (restrictToNode) => {
const selection = window.getSelection();
if (selection.rangeCount === 0) return null;
// Most usages of the selection only want text from a part of the page (e.g. discussion)
@@ -390,10 +392,10 @@ export const insertText = (target, text) => {
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
*/
-export const normalizeHeaders = headers => {
+export const normalizeHeaders = (headers) => {
const upperCaseHeaders = {};
- Object.keys(headers || {}).forEach(e => {
+ Object.keys(headers || {}).forEach((e) => {
upperCaseHeaders[e.toUpperCase()] = headers[e];
});
@@ -406,7 +408,7 @@ export const normalizeHeaders = headers => {
* @param {Object} paginationInformation
* @returns {Object}
*/
-export const parseIntPagination = paginationInformation => ({
+export const parseIntPagination = (paginationInformation) => ({
perPage: parseInt(paginationInformation['X-PER-PAGE'], 10),
page: parseInt(paginationInformation['X-PAGE'], 10),
total: parseInt(paginationInformation['X-TOTAL'], 10),
@@ -445,10 +447,10 @@ export const parseQueryStringIntoObject = (query = '') => {
*/
export const objectToQueryString = (params = {}) =>
Object.keys(params)
- .map(param => `${param}=${params[param]}`)
+ .map((param) => `${param}=${params[param]}`)
.join('&');
-export const buildUrlWithCurrentLocation = param => {
+export const buildUrlWithCurrentLocation = (param) => {
if (param) return `${window.location.pathname}${param}`;
return window.location.pathname;
@@ -460,7 +462,7 @@ export const buildUrlWithCurrentLocation = param => {
*
* @param {String} param
*/
-export const historyPushState = newUrl => {
+export const historyPushState = (newUrl) => {
window.history.pushState({}, document.title, newUrl);
};
@@ -470,7 +472,7 @@ export const historyPushState = newUrl => {
*
* @param {String} param
*/
-export const historyReplaceState = newUrl => {
+export const historyReplaceState = (newUrl) => {
window.history.replaceState({}, document.title, newUrl);
};
@@ -482,7 +484,7 @@ export const historyReplaceState = newUrl => {
* @param {String} value
* @returns {Boolean}
*/
-export const parseBoolean = value => (value && value.toString()) === 'true';
+export const parseBoolean = (value) => (value && value.toString()) === 'true';
export const BACKOFF_TIMEOUT = 'BACKOFF_TIMEOUT';
@@ -529,7 +531,7 @@ export const backOff = (fn, timeout = 60000) => {
let timeElapsed = 0;
return new Promise((resolve, reject) => {
- const stop = arg => (arg instanceof Error ? reject(arg) : resolve(arg));
+ const stop = (arg) => (arg instanceof Error ? reject(arg) : resolve(arg));
const next = () => {
if (timeElapsed < timeout) {
@@ -545,92 +547,6 @@ export const backOff = (fn, timeout = 60000) => {
});
};
-export const createOverlayIcon = (iconPath, overlayPath) => {
- const faviconImage = document.createElement('img');
-
- return new Promise(resolve => {
- faviconImage.onload = () => {
- const size = 32;
-
- const canvas = document.createElement('canvas');
- canvas.width = size;
- canvas.height = size;
-
- const context = canvas.getContext('2d');
- context.clearRect(0, 0, size, size);
- context.drawImage(
- faviconImage,
- 0,
- 0,
- faviconImage.width,
- faviconImage.height,
- 0,
- 0,
- size,
- size,
- );
-
- const overlayImage = document.createElement('img');
- overlayImage.onload = () => {
- context.drawImage(
- overlayImage,
- 0,
- 0,
- overlayImage.width,
- overlayImage.height,
- 0,
- 0,
- size,
- size,
- );
-
- const faviconWithOverlayUrl = canvas.toDataURL();
-
- resolve(faviconWithOverlayUrl);
- };
- overlayImage.src = overlayPath;
- };
- faviconImage.src = iconPath;
- });
-};
-
-export const setFaviconOverlay = overlayPath => {
- const faviconEl = document.getElementById('favicon');
-
- if (!faviconEl) {
- return null;
- }
-
- const iconPath = faviconEl.getAttribute('data-original-href');
-
- return createOverlayIcon(iconPath, overlayPath).then(faviconWithOverlayUrl =>
- faviconEl.setAttribute('href', faviconWithOverlayUrl),
- );
-};
-
-export const resetFavicon = () => {
- const faviconEl = document.getElementById('favicon');
-
- if (faviconEl) {
- const originalFavicon = faviconEl.getAttribute('data-original-href');
- faviconEl.setAttribute('href', originalFavicon);
- }
-};
-
-export const setCiStatusFavicon = pageUrl =>
- axios
- .get(pageUrl)
- .then(({ data }) => {
- if (data && data.favicon) {
- return setFaviconOverlay(data.favicon);
- }
- return resetFavicon();
- })
- .catch(error => {
- resetFavicon();
- throw error;
- });
-
export const spriteIcon = (icon, className = '') => {
const classAttribute = className.length > 0 ? `class="${className}"` : '';
@@ -728,7 +644,7 @@ export const convertObjectPropsToCamelCase = (obj = {}, options = {}) =>
export const convertObjectPropsToSnakeCase = (obj = {}, options = {}) =>
convertObjectProps(convertToSnakeCase, obj, options);
-export const imagePath = imgUrl =>
+export const imagePath = (imgUrl) =>
`${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/${imgUrl}`;
export const addSelectOnFocusBehaviour = (selector = '.js-select-on-focus') => {
@@ -737,7 +653,7 @@ export const addSelectOnFocusBehaviour = (selector = '.js-select-on-focus') => {
$(selector).on('focusin', function selectOnFocusCallback() {
$(this)
.select()
- .one('mouseup', e => {
+ .one('mouseup', (e) => {
e.preventDefault();
});
});
@@ -833,7 +749,7 @@ export const searchBy = (query = '', searchSpace = {}) => {
const normalizedQuery = query.toLowerCase();
const matches = targetKeys
- .filter(item => {
+ .filter((item) => {
const searchItem = `${searchSpace[item]}`.toLowerCase();
return (
@@ -867,9 +783,9 @@ export const isScopedLabel = ({ title = '' }) => title.indexOf('::') !== -1;
// Methods to set and get Cookie
export const setCookie = (name, value) => Cookies.set(name, value, { expires: 365 });
-export const getCookie = name => Cookies.get(name);
+export const getCookie = (name) => Cookies.get(name);
-export const removeCookie = name => Cookies.remove(name);
+export const removeCookie = (name) => Cookies.remove(name);
/**
* Returns the status of a feature flag.
@@ -884,4 +800,4 @@ export const removeCookie = name => Cookies.remove(name);
* @param {String} flag Feature flag
* @returns {Boolean} on/off
*/
-export const isFeatureFlagEnabled = flag => window.gon.features?.[flag];
+export const isFeatureFlagEnabled = (flag) => window.gon.features?.[flag];
diff --git a/app/assets/javascripts/lib/utils/css_utils.js b/app/assets/javascripts/lib/utils/css_utils.js
index 02f092e73e1..76ac442a470 100644
--- a/app/assets/javascripts/lib/utils/css_utils.js
+++ b/app/assets/javascripts/lib/utils/css_utils.js
@@ -1,5 +1,5 @@
export function loadCSSFile(path) {
- return new Promise(resolve => {
+ return new Promise((resolve) => {
if (!path) resolve();
if (document.querySelector(`link[href="${path}"]`)) {
diff --git a/app/assets/javascripts/lib/utils/datetime_range.js b/app/assets/javascripts/lib/utils/datetime_range.js
index 8efbcb89607..391b685f740 100644
--- a/app/assets/javascripts/lib/utils/datetime_range.js
+++ b/app/assets/javascripts/lib/utils/datetime_range.js
@@ -7,7 +7,7 @@ const MINIMUM_DATE = new Date(0);
const DEFAULT_DIRECTION = 'before';
-const durationToMillis = duration => {
+const durationToMillis = (duration) => {
if (Object.entries(duration).length === 1 && Number.isFinite(duration.seconds)) {
return secondsToMilliseconds(duration.seconds);
}
@@ -19,9 +19,9 @@ const dateMinusDuration = (date, duration) => new Date(date.getTime() - duration
const datePlusDuration = (date, duration) => new Date(date.getTime() + durationToMillis(duration));
-const isValidDuration = duration => Boolean(duration && Number.isFinite(duration.seconds));
+const isValidDuration = (duration) => Boolean(duration && Number.isFinite(duration.seconds));
-const isValidDateString = dateString => {
+const isValidDateString = (dateString) => {
if (typeof dateString !== 'string' || !dateString.trim()) {
return false;
}
@@ -225,7 +225,7 @@ export function getRangeType(range) {
*
* @returns {FixedRange} An object with a start and end in ISO8601 format.
*/
-export const convertToFixedRange = dateTimeRange =>
+export const convertToFixedRange = (dateTimeRange) =>
handlers[getRangeType(dateTimeRange)](dateTimeRange);
/**
@@ -242,7 +242,7 @@ export const convertToFixedRange = dateTimeRange =>
* @param {Object} timeRange - A time range object
* @returns Copy of time range
*/
-const pruneTimeRange = timeRange => {
+const pruneTimeRange = (timeRange) => {
const res = pick(timeRange, ['start', 'end', 'anchor', 'duration', 'direction']);
if (res.direction === DEFAULT_DIRECTION) {
return omit(res, 'direction');
@@ -272,7 +272,7 @@ export const isEqualTimeRanges = (timeRange, other) => {
* @param {Array} timeRanges - Array of time tanges (haystack)
*/
export const findTimeRange = (timeRange, timeRanges) =>
- timeRanges.find(element => isEqualTimeRanges(element, timeRange));
+ timeRanges.find((element) => isEqualTimeRanges(element, timeRange));
// Time Ranges as URL Parameters Utils
@@ -289,11 +289,11 @@ export const timeRangeParamNames = ['start', 'end', 'anchor', 'duration_seconds'
* @param {Object} A time range
* @returns key-value pairs object that can be used as parameters in a URL.
*/
-export const timeRangeToParams = timeRange => {
+export const timeRangeToParams = (timeRange) => {
let params = pruneTimeRange(timeRange);
if (timeRange.duration) {
const durationParms = {};
- Object.keys(timeRange.duration).forEach(key => {
+ Object.keys(timeRange.duration).forEach((key) => {
durationParms[`duration_${key}`] = timeRange.duration[key].toString();
});
params = { ...durationParms, ...params };
@@ -309,7 +309,7 @@ export const timeRangeToParams = timeRange => {
*
* @param {params} params - key-value pairs object.
*/
-export const timeRangeFromParams = params => {
+export const timeRangeFromParams = (params) => {
const timeRangeParams = pick(params, timeRangeParamNames);
let range = Object.entries(timeRangeParams).reduce((acc, [key, val]) => {
// unflatten duration
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index 46b0f0cbc70..15f7c0c874e 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -4,6 +4,8 @@ import * as timeago from 'timeago.js';
import dateFormat from 'dateformat';
import { languageCode, s__, __, n__ } from '../../locale';
+const MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
+
window.timeago = timeago;
/**
@@ -12,7 +14,7 @@ window.timeago = timeago;
*
* @param {Date} date
*/
-export const newDate = date => (date instanceof Date ? new Date(date.getTime()) : new Date());
+export const newDate = (date) => (date instanceof Date ? new Date(date.getTime()) : new Date());
/**
* Returns i18n month names array.
@@ -21,7 +23,7 @@ export const newDate = date => (date instanceof Date ? new Date(date.getTime())
*
* @param {Boolean} abbreviated
*/
-export const getMonthNames = abbreviated => {
+export const getMonthNames = (abbreviated) => {
if (abbreviated) {
return [
s__('Jan'),
@@ -74,7 +76,7 @@ export const getWeekdayNames = () => [
* @param {date} date
* @returns {String}
*/
-export const getDayName = date =>
+export const getDayName = (date) =>
[
__('Sunday'),
__('Monday'),
@@ -242,7 +244,7 @@ export const getDayDifference = (a, b) => {
* @param {Number} seconds
* @return {String}
*/
-export const timeIntervalInWords = intervalInSeconds => {
+export const timeIntervalInWords = (intervalInSeconds) => {
const secondsInteger = parseInt(intervalInSeconds, 10);
const minutes = Math.floor(secondsInteger / 60);
const seconds = secondsInteger - minutes * 60;
@@ -316,7 +318,7 @@ export const monthInWords = (date, abbreviated = false) => {
*
* @param {Date} date
*/
-export const totalDaysInMonth = date => {
+export const totalDaysInMonth = (date) => {
if (!date) {
return 0;
}
@@ -329,7 +331,7 @@ export const totalDaysInMonth = date => {
*
* @param {Array} quarter
*/
-export const totalDaysInQuarter = quarter =>
+export const totalDaysInQuarter = (quarter) =>
quarter.reduce((acc, month) => acc + totalDaysInMonth(month), 0);
/**
@@ -338,7 +340,7 @@ export const totalDaysInQuarter = quarter =>
*
* @param {Date} date
*/
-export const getSundays = date => {
+export const getSundays = (date) => {
if (!date) {
return [];
}
@@ -449,7 +451,7 @@ window.gl.utils = {
* @param milliseconds
* @returns {string}
*/
-export const formatTime = milliseconds => {
+export const formatTime = (milliseconds) => {
const remainingSeconds = Math.floor(milliseconds / 1000) % 60;
const remainingMinutes = Math.floor(milliseconds / 1000 / 60) % 60;
const remainingHours = Math.floor(milliseconds / 1000 / 60 / 60);
@@ -468,7 +470,7 @@ export const formatTime = milliseconds => {
* @param {String} dateString Date in yyyy-mm-dd format
* @return {Date} UTC format
*/
-export const parsePikadayDate = dateString => {
+export const parsePikadayDate = (dateString) => {
const parts = dateString.split('-');
const year = parseInt(parts[0], 10);
const month = parseInt(parts[1] - 1, 10);
@@ -482,7 +484,7 @@ export const parsePikadayDate = dateString => {
* @param {Date} date UTC format
* @return {String} Date formatted in yyyy-mm-dd
*/
-export const pikadayToString = date => {
+export const pikadayToString = (date) => {
const day = pad(date.getDate());
const month = pad(date.getMonth() + 1);
const year = date.getFullYear();
@@ -523,7 +525,7 @@ export const parseSeconds = (
let unorderedMinutes = Math.abs(seconds / SECONDS_PER_MINUTE);
- return mapValues(timePeriodConstraints, minutesPerPeriod => {
+ return mapValues(timePeriodConstraints, (minutesPerPeriod) => {
if (minutesPerPeriod === 0) {
return 0;
}
@@ -567,7 +569,7 @@ export const stringifyTime = (timeObject, fullNameFormat = false) => {
* @param endDate date string that the time difference is calculated for
* @return {Number} number of milliseconds remaining until the given date
*/
-export const calculateRemainingMilliseconds = endDate => {
+export const calculateRemainingMilliseconds = (endDate) => {
const remainingMilliseconds = new Date(endDate).getTime() - Date.now();
return Math.max(remainingMilliseconds, 0);
};
@@ -598,7 +600,7 @@ export const getDateInFuture = (date, daysInFuture) =>
* @param {Date} date
* @returns boolean
*/
-export const isValidDate = date => date instanceof Date && !Number.isNaN(date.getTime());
+export const isValidDate = (date) => date instanceof Date && !Number.isNaN(date.getTime());
/*
* Appending T00:00:00 makes JS assume local time and prevents it from shifting the date
@@ -606,7 +608,7 @@ export const isValidDate = date => date instanceof Date && !Number.isNaN(date.ge
* be consistent with the "edit issue -> due date" UI.
*/
-export const newDateAsLocaleTime = date => {
+export const newDateAsLocaleTime = (date) => {
const suffix = 'T00:00:00';
return new Date(`${date}${suffix}`);
};
@@ -620,7 +622,7 @@ export const endOfDayTime = 'T23:59:59Z';
* @param {Function} formatter
* @return {Any[]} an array of formatted dates between 2 given dates (including start&end date)
*/
-export const getDatesInRange = (d1, d2, formatter = x => x) => {
+export const getDatesInRange = (d1, d2, formatter = (x) => x) => {
if (!(d1 instanceof Date) || !(d2 instanceof Date)) {
return [];
}
@@ -643,7 +645,7 @@ export const getDatesInRange = (d1, d2, formatter = x => x) => {
* @param {Number} seconds
* @return {Number} number of milliseconds
*/
-export const secondsToMilliseconds = seconds => seconds * 1000;
+export const secondsToMilliseconds = (seconds) => seconds * 1000;
/**
* Converts the supplied number of seconds to days.
@@ -651,7 +653,7 @@ export const secondsToMilliseconds = seconds => seconds * 1000;
* @param {Number} seconds
* @return {Number} number of days
*/
-export const secondsToDays = seconds => Math.round(seconds / 86400);
+export const secondsToDays = (seconds) => Math.round(seconds / 86400);
/**
* Converts a numeric utc offset in seconds to +/- hours
@@ -662,7 +664,7 @@ export const secondsToDays = seconds => Math.round(seconds / 86400);
*
* @return {String} the + or - offset in hours
*/
-export const secondsToHours = offset => {
+export const secondsToHours = (offset) => {
const parsed = parseInt(offset, 10);
if (Number.isNaN(parsed) || parsed === 0) {
return `0`;
@@ -682,12 +684,40 @@ export const nDaysAfter = (date, numberOfDays) =>
new Date(newDate(date)).setDate(date.getDate() + numberOfDays);
/**
+ * Returns the date n days before the date provided
+ *
+ * @param {Date} date the initial date
+ * @param {Number} numberOfDays number of days before
+ * @return {Date} the date preceding the date provided
+ */
+export const nDaysBefore = (date, numberOfDays) => nDaysAfter(date, -numberOfDays);
+
+/**
+ * Returns the date n months after the date provided
+ *
+ * @param {Date} date the initial date
+ * @param {Number} numberOfMonths number of months after
+ * @return {Date} the date following the date provided
+ */
+export const nMonthsAfter = (date, numberOfMonths) =>
+ new Date(newDate(date)).setMonth(date.getMonth() + numberOfMonths);
+
+/**
+ * Returns the date n months before the date provided
+ *
+ * @param {Date} date the initial date
+ * @param {Number} numberOfMonths number of months before
+ * @return {Date} the date preceding the date provided
+ */
+export const nMonthsBefore = (date, numberOfMonths) => nMonthsAfter(date, -numberOfMonths);
+
+/**
* Returns the date after the date provided
*
* @param {Date} date the initial date
* @return {Date} the date following the date provided
*/
-export const dayAfter = date => new Date(newDate(date).setDate(date.getDate() + 1));
+export const dayAfter = (date) => new Date(newDate(date).setDate(date.getDate() + 1));
/**
* Mimics the behaviour of the rails distance_of_time_in_words function
@@ -795,7 +825,7 @@ export const differenceInMilliseconds = (startDate, endDate = Date.now()) => {
*
* @return {Date} the date at the first day of the month
*/
-export const dateAtFirstDayOfMonth = date => new Date(newDate(date).setDate(1));
+export const dateAtFirstDayOfMonth = (date) => new Date(newDate(date).setDate(1));
/**
* A utility function which checks if two dates match.
@@ -806,3 +836,62 @@ export const dateAtFirstDayOfMonth = date => new Date(newDate(date).setDate(1));
* @return {Boolean} true if the dates match
*/
export const datesMatch = (date1, date2) => differenceInMilliseconds(date1, date2) === 0;
+
+/**
+ * A utility function which computes a formatted 24 hour
+ * time string from a positive int in the range 0 - 24.
+ *
+ * @param {Int} time a positive Int between 0 and 24
+ *
+ * @returns {String} formatted 24 hour time String
+ */
+export const format24HourTimeStringFromInt = (time) => {
+ if (!Number.isInteger(time) || time < 0 || time > 24) {
+ return '';
+ }
+
+ const formatted24HourString = time > 9 ? `${time}:00` : `0${time}:00`;
+ return formatted24HourString;
+};
+
+/**
+ * A utility function which checks if two date ranges overlap.
+ *
+ * @param {Object} givenPeriodLeft - the first period to compare.
+ * @param {Object} givenPeriodRight - the second period to compare.
+ * @returns {Object} { overlap: number of days the overlap is present, overlapStartDate: the start date of the overlap in time format, overlapEndDate: the end date of the overlap in time format }
+ * @throws {Error} Uncaught Error: Invalid period
+ *
+ * @example
+ * getOverlappingDaysInPeriods(
+ * { start: new Date(2021, 0, 11), end: new Date(2021, 0, 13) },
+ * { start: new Date(2021, 0, 11), end: new Date(2021, 0, 14) }
+ * ) => { daysOverlap: 2, overlapStartDate: 1610323200000, overlapEndDate: 1610496000000 }
+ *
+ */
+export const getOverlappingDaysInPeriods = (givenPeriodLeft = {}, givenPeriodRight = {}) => {
+ const leftStartTime = new Date(givenPeriodLeft.start).getTime();
+ const leftEndTime = new Date(givenPeriodLeft.end).getTime();
+ const rightStartTime = new Date(givenPeriodRight.start).getTime();
+ const rightEndTime = new Date(givenPeriodRight.end).getTime();
+
+ if (!(leftStartTime <= leftEndTime && rightStartTime <= rightEndTime)) {
+ throw new Error(__('Invalid period'));
+ }
+
+ const isOverlapping = leftStartTime < rightEndTime && rightStartTime < leftEndTime;
+
+ if (!isOverlapping) {
+ return { daysOverlap: 0 };
+ }
+
+ const overlapStartDate = Math.max(leftStartTime, rightStartTime);
+ const overlapEndDate = rightEndTime > leftEndTime ? leftEndTime : rightEndTime;
+ const differenceInMs = overlapEndDate - overlapStartDate;
+
+ return {
+ daysOverlap: Math.ceil(differenceInMs / MILLISECONDS_IN_DAY),
+ overlapStartDate,
+ overlapEndDate,
+ };
+};
diff --git a/app/assets/javascripts/lib/utils/dom_utils.js b/app/assets/javascripts/lib/utils/dom_utils.js
index 2f19a0c9b26..f11c7658a88 100644
--- a/app/assets/javascripts/lib/utils/dom_utils.js
+++ b/app/assets/javascripts/lib/utils/dom_utils.js
@@ -6,7 +6,7 @@ import { isInIssuePage, isInMRPage, isInEpicPage } from './common_utils';
*
* @param element DOM element to check
*/
-export const hasHorizontalOverflow = element =>
+export const hasHorizontalOverflow = (element) =>
Boolean(element && element.scrollWidth > element.offsetWidth);
export const addClassIfElementExists = (element, className) => {
@@ -64,7 +64,7 @@ export const parseBooleanDataAttributes = ({ dataset }, names) =>
* @param {HTMLElement} element The element to test
* @returns {Boolean} `true` if the element is currently visible, otherwise false
*/
-export const isElementVisible = element =>
+export const isElementVisible = (element) =>
Boolean(element.offsetWidth || element.offsetHeight || element.getClientRects().length);
/**
@@ -76,4 +76,4 @@ export const isElementVisible = element =>
* @param {HTMLElement} element The element to test
* @returns {Boolean} `true` if the element is currently hidden, otherwise false
*/
-export const isElementHidden = element => !isElementVisible(element);
+export const isElementHidden = (element) => !isElementVisible(element);
diff --git a/app/assets/javascripts/lib/utils/favicon.js b/app/assets/javascripts/lib/utils/favicon.js
new file mode 100644
index 00000000000..47596a76306
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/favicon.js
@@ -0,0 +1,30 @@
+import { FaviconOverlayManager } from '@gitlab/favicon-overlay';
+import { memoize } from 'lodash';
+
+// FaviconOverlayManager is a glorious singleton/static class. Let's start to encapsulate that with this helper.
+const getDefaultFaviconManager = memoize(async () => {
+ await FaviconOverlayManager.initialize({ faviconSelector: '#favicon' });
+
+ return FaviconOverlayManager;
+});
+
+export const setFaviconOverlay = async (path) => {
+ const manager = await getDefaultFaviconManager();
+
+ manager.setFaviconOverlay(path);
+};
+
+export const resetFavicon = async () => {
+ const manager = await getDefaultFaviconManager();
+
+ manager.resetFaviconOverlay();
+};
+
+/**
+ * Clears the cached memoization of the default manager.
+ *
+ * This is needed for determinism in tests.
+ */
+export const clearMemoizeCache = () => {
+ getDefaultFaviconManager.cache.clear();
+};
diff --git a/app/assets/javascripts/lib/utils/favicon_ci.js b/app/assets/javascripts/lib/utils/favicon_ci.js
new file mode 100644
index 00000000000..613e2620e02
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/favicon_ci.js
@@ -0,0 +1,16 @@
+import axios from './axios_utils';
+import { setFaviconOverlay, resetFavicon } from './favicon';
+
+export const setCiStatusFavicon = (pageUrl) =>
+ axios
+ .get(pageUrl)
+ .then(({ data }) => {
+ if (data && data.favicon) {
+ return setFaviconOverlay(data.favicon);
+ }
+ return resetFavicon();
+ })
+ .catch((error) => {
+ resetFavicon();
+ throw error;
+ });
diff --git a/app/assets/javascripts/lib/utils/forms.js b/app/assets/javascripts/lib/utils/forms.js
index 1c5f6cefeda..52e1323412d 100644
--- a/app/assets/javascripts/lib/utils/forms.js
+++ b/app/assets/javascripts/lib/utils/forms.js
@@ -1,14 +1,14 @@
-export const serializeFormEntries = entries =>
+export const serializeFormEntries = (entries) =>
entries.reduce((acc, { name, value }) => Object.assign(acc, { [name]: value }), {});
-export const serializeForm = form => {
+export const serializeForm = (form) => {
const fdata = new FormData(form);
- const entries = Array.from(fdata.keys()).map(key => {
+ const entries = Array.from(fdata.keys()).map((key) => {
let val = fdata.getAll(key);
// Microsoft Edge has a bug in FormData.getAll() that returns an undefined
// value for each form element that does not match the given key:
// https://github.com/jimmywarting/FormData/issues/80
- val = val.filter(n => n);
+ val = val.filter((n) => n);
return { name: key, value: val.length === 1 ? val[0] : val };
});
@@ -27,7 +27,7 @@ export const serializeForm = form => {
* @example
* returns true for '', [], null, undefined
*/
-export const isEmptyValue = value => value == null || value.length === 0;
+export const isEmptyValue = (value) => value == null || value.length === 0;
/**
* A form object serializer
@@ -42,7 +42,7 @@ export const isEmptyValue = value => value == null || value.length === 0;
* Returns
* {"project": "hello", "username": "john"}
*/
-export const serializeFormObject = form =>
+export const serializeFormObject = (form) =>
Object.fromEntries(
Object.entries(form).reduce((acc, [name, { value }]) => {
if (!isEmptyValue(value)) {
diff --git a/app/assets/javascripts/lib/utils/grammar.js b/app/assets/javascripts/lib/utils/grammar.js
index b1f38429369..6d6361d19b6 100644
--- a/app/assets/javascripts/lib/utils/grammar.js
+++ b/app/assets/javascripts/lib/utils/grammar.js
@@ -16,12 +16,12 @@ import { sprintf, s__ } from '~/locale';
*
* @param {String[]} items
*/
-export const toNounSeriesText = items => {
+export const toNounSeriesText = (items, { onlyCommas = false } = {}) => {
if (items.length === 0) {
return '';
} else if (items.length === 1) {
return sprintf(s__(`nounSeries|%{item}`), { item: items[0] }, false);
- } else if (items.length === 2) {
+ } else if (items.length === 2 && !onlyCommas) {
return sprintf(
s__('nounSeries|%{firstItem} and %{lastItem}'),
{
@@ -33,7 +33,7 @@ export const toNounSeriesText = items => {
}
return items.reduce((item, nextItem, idx) =>
- idx === items.length - 1
+ idx === items.length - 1 && !onlyCommas
? sprintf(s__('nounSeries|%{item}, and %{lastItem}'), { item, lastItem: nextItem }, false)
: sprintf(s__('nounSeries|%{item}, %{nextItem}'), { item, nextItem }, false),
);
diff --git a/app/assets/javascripts/lib/utils/headers.js b/app/assets/javascripts/lib/utils/headers.js
new file mode 100644
index 00000000000..80ae3fb146f
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/headers.js
@@ -0,0 +1,3 @@
+export const ContentTypeMultipartFormData = {
+ 'Content-Type': 'multipart/form-data',
+};
diff --git a/app/assets/javascripts/lib/utils/icon_utils.js b/app/assets/javascripts/lib/utils/icon_utils.js
index 043043f2eb5..58274092cf8 100644
--- a/app/assets/javascripts/lib/utils/icon_utils.js
+++ b/app/assets/javascripts/lib/utils/icon_utils.js
@@ -9,7 +9,7 @@ const getSvgDom = memoize(() =>
axios
.get(gon.sprite_icons)
.then(({ data: svgs }) => new DOMParser().parseFromString(svgs, 'text/xml'))
- .catch(e => {
+ .catch((e) => {
getSvgDom.cache.clear();
throw e;
@@ -34,9 +34,9 @@ export const clearSvgIconPathContentCache = () => {
* @param {String} name - Icon name
* @returns A promise that resolves to the svg path
*/
-export const getSvgIconPathContent = name =>
+export const getSvgIconPathContent = (name) =>
getSvgDom()
- .then(doc => {
+ .then((doc) => {
return doc.querySelector(`#${name} path`).getAttribute('d');
})
.catch(() => null);
diff --git a/app/assets/javascripts/lib/utils/notify.js b/app/assets/javascripts/lib/utils/notify.js
index aa7884846a3..2e92c64ab7a 100644
--- a/app/assets/javascripts/lib/utils/notify.js
+++ b/app/assets/javascripts/lib/utils/notify.js
@@ -32,7 +32,7 @@ function notifyMe(message, body, icon, onclick) {
// If it's okay let's create a notification
return notificationGranted(message, opts, onclick);
} else if (Notification.permission !== 'denied') {
- return Notification.requestPermission(permission => {
+ return Notification.requestPermission((permission) => {
// If the user accepts, let's create a notification
if (permission === 'granted') {
return notificationGranted(message, opts, onclick);
diff --git a/app/assets/javascripts/lib/utils/number_utils.js b/app/assets/javascripts/lib/utils/number_utils.js
index bc87232f40b..d49382733c0 100644
--- a/app/assets/javascripts/lib/utils/number_utils.js
+++ b/app/assets/javascripts/lib/utils/number_utils.js
@@ -112,7 +112,7 @@ export const isOdd = (number = 0) => number % 2;
* @param {Array} arr An array of numbers
* @returns {Number} The median of the given array
*/
-export const median = arr => {
+export const median = (arr) => {
const middle = Math.floor(arr.length / 2);
const sorted = arr.sort((a, b) => a - b);
return arr.length % 2 !== 0 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
diff --git a/app/assets/javascripts/lib/utils/poll.js b/app/assets/javascripts/lib/utils/poll.js
index e8583fa951b..6ec1bd206e6 100644
--- a/app/assets/javascripts/lib/utils/poll.js
+++ b/app/assets/javascripts/lib/utils/poll.js
@@ -102,11 +102,11 @@ export default class Poll {
notificationCallback(true);
return resource[method](data)
- .then(response => {
+ .then((response) => {
this.checkConditions(response);
notificationCallback(false);
})
- .catch(error => {
+ .catch((error) => {
notificationCallback(false);
if (error.status === httpStatusCodes.ABORTED) {
return;
diff --git a/app/assets/javascripts/lib/utils/poll_until_complete.js b/app/assets/javascripts/lib/utils/poll_until_complete.js
index 199d0e6f0f7..d3b551ca755 100644
--- a/app/assets/javascripts/lib/utils/poll_until_complete.js
+++ b/app/assets/javascripts/lib/utils/poll_until_complete.js
@@ -29,7 +29,7 @@ export default (url, config = {}) =>
},
data: { url, config },
method: 'axiosGet',
- successCallback: response => {
+ successCallback: (response) => {
if (response.status === httpStatusCodes.OK) {
resolve(response);
eTagPoll.stop();
diff --git a/app/assets/javascripts/lib/utils/set.js b/app/assets/javascripts/lib/utils/set.js
index 541934c4221..a5393bad8c7 100644
--- a/app/assets/javascripts/lib/utils/set.js
+++ b/app/assets/javascripts/lib/utils/set.js
@@ -5,4 +5,4 @@
* @returns {boolean}
*/
export const isSubset = (subset, superset) =>
- Array.from(subset).every(value => superset.has(value));
+ Array.from(subset).every((value) => superset.has(value));
diff --git a/app/assets/javascripts/lib/utils/simple_poll.js b/app/assets/javascripts/lib/utils/simple_poll.js
index e4e9fb2e6fa..42eb93ea16d 100644
--- a/app/assets/javascripts/lib/utils/simple_poll.js
+++ b/app/assets/javascripts/lib/utils/simple_poll.js
@@ -4,7 +4,7 @@ export default (fn, { interval = 2000, timeout = 60000 } = {}) => {
const startTime = Date.now();
return new Promise((resolve, reject) => {
- const stop = arg => (arg instanceof Error ? reject(arg) : resolve(arg));
+ const stop = (arg) => (arg instanceof Error ? reject(arg) : resolve(arg));
const next = () => {
if (timeout === 0 || differenceInMilliseconds(startTime) < timeout) {
setTimeout(fn.bind(null, next, stop), interval);
diff --git a/app/assets/javascripts/lib/utils/sticky.js b/app/assets/javascripts/lib/utils/sticky.js
index f3244301350..6bb7f09b886 100644
--- a/app/assets/javascripts/lib/utils/sticky.js
+++ b/app/assets/javascripts/lib/utils/sticky.js
@@ -67,6 +67,6 @@ export const stickyMonitor = (el, stickyTop, insertPlaceholder = true) => {
* - If the current environment supports `position: sticky`, do nothing.
* - Can receive an iterable element list (NodeList, jQuery collection, etc.) or single HTMLElement.
*/
-export const polyfillSticky = el => {
+export const polyfillSticky = (el) => {
StickyFill.add(el);
};
diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js
index c711c0bd163..2c993c8b128 100644
--- a/app/assets/javascripts/lib/utils/text_markdown.js
+++ b/app/assets/javascripts/lib/utils/text_markdown.js
@@ -14,18 +14,12 @@ function addBlockTags(blockTag, selected) {
}
function lineBefore(text, textarea) {
- const split = text
- .substring(0, textarea.selectionStart)
- .trim()
- .split('\n');
+ const split = text.substring(0, textarea.selectionStart).trim().split('\n');
return split[split.length - 1];
}
function lineAfter(text, textarea) {
- return text
- .substring(textarea.selectionEnd)
- .trim()
- .split('\n')[0];
+ return text.substring(textarea.selectionEnd).trim().split('\n')[0];
}
function convertMonacoSelectionToAceFormat(sel) {
@@ -226,7 +220,7 @@ export function insertMarkdownText({
: blockTagText(text, textArea, blockTag, selected);
} else {
textToInsert = selectedSplit
- .map(val => {
+ .map((val) => {
if (tag.indexOf(textPlaceholder) > -1) {
return tag.replace(textPlaceholder, val);
}
@@ -342,7 +336,7 @@ export function addMarkdownListeners(form) {
// eslint-disable-next-line @gitlab/no-global-event-off
const $allToolbarBtns = $('.js-md', form)
.off('click')
- .on('click', function() {
+ .on('click', function () {
const $toolbarBtn = $(this);
return updateTextForToolbarBtn($toolbarBtn);
@@ -355,7 +349,7 @@ export function addEditorMarkdownListeners(editor) {
// eslint-disable-next-line @gitlab/no-global-event-off
$('.js-md')
.off('click')
- .on('click', e => {
+ .on('click', (e) => {
const { mdTag, mdBlock, mdPrepend, mdSelect } = $(e.currentTarget).data();
insertMarkdownText({
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index c398874db24..eaf396a7a59 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -14,7 +14,7 @@ import {
* @param {String} text
* @returns {String}
*/
-export const addDelimiter = text =>
+export const addDelimiter = (text) =>
text ? text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') : text;
/**
@@ -23,7 +23,7 @@ export const addDelimiter = text =>
* @param {Number} count
* @return {Number|String}
*/
-export const highCountTrim = count => (count > 99 ? '99+' : count);
+export const highCountTrim = (count) => (count > 99 ? '99+' : count);
/**
* Converts first char to uppercase and replaces the given separator with spaces
@@ -43,7 +43,7 @@ export const humanize = (string, separator = '_') => {
* @param {*} str
* @returns {String}
*/
-export const dasherize = str => str.replace(/[_\s]+/g, '-');
+export const dasherize = (str) => str.replace(/[_\s]+/g, '-');
/**
* Replaces whitespace and non-sluggish characters with a given separator
@@ -69,7 +69,7 @@ export const slugify = (str, separator = '-') => {
* @param {String} str
* @returns {String}
*/
-export const slugifyWithUnderscore = str => slugify(str, '_');
+export const slugifyWithUnderscore = (str) => slugify(str, '_');
/**
* Truncates given text
@@ -158,7 +158,7 @@ export const truncateWidth = (string, options = {}) => {
* @param {String} sha
* @returns {String}
*/
-export const truncateSha = sha => sha.substring(0, 8);
+export const truncateSha = (sha) => sha.substring(0, 8);
const ELLIPSIS_CHAR = '…';
export const truncatePathMiddleToLength = (text, maxWidth) => {
@@ -166,7 +166,7 @@ export const truncatePathMiddleToLength = (text, maxWidth) => {
let ellipsisCount = 0;
while (returnText.length >= maxWidth) {
- const textSplit = returnText.split('/').filter(s => s !== ELLIPSIS_CHAR);
+ const textSplit = returnText.split('/').filter((s) => s !== ELLIPSIS_CHAR);
if (textSplit.length === 0) {
// There are n - 1 path separators for n segments, so 2n - 1 <= maxWidth
@@ -243,7 +243,7 @@ export const stripHtml = (string, replace = '') => {
* // returns "trailingUnderscore_"
* convertToCamelCase('trailing_underscore_')
*/
-export const convertToCamelCase = string =>
+export const convertToCamelCase = (string) =>
string.replace(/([a-z0-9])_([a-z0-9])/gi, (match, p1, p2) => `${p1}${p2.toUpperCase()}`);
/**
@@ -251,7 +251,7 @@ export const convertToCamelCase = string =>
*
* @param {*} string
*/
-export const convertToSnakeCase = string =>
+export const convertToSnakeCase = (string) =>
slugifyWithUnderscore((string.match(/([a-zA-Z][^A-Z]*)/g) || [string]).join(' '));
/**
@@ -260,7 +260,7 @@ export const convertToSnakeCase = string =>
*
* @param {*} string
*/
-export const convertToSentenceCase = string => {
+export const convertToSentenceCase = (string) => {
const splitWord = string.split(' ').map((word, index) => (index > 0 ? word.toLowerCase() : word));
return splitWord.join(' ');
@@ -273,7 +273,7 @@ export const convertToSentenceCase = string => {
* @param {String} string
* @returns {String}
*/
-export const convertToTitleCase = string => string.replace(/\b[a-z]/g, s => s.toUpperCase());
+export const convertToTitleCase = (string) => string.replace(/\b[a-z]/g, (s) => s.toUpperCase());
const unicodeConversion = [
[/[ÀÁÂÃÅĀĂĄ]/g, 'A'],
@@ -340,7 +340,7 @@ const unicodeConversion = [
* @param {String} string
* @returns {String}
*/
-export const convertUnicodeToAscii = string => {
+export const convertUnicodeToAscii = (string) => {
let convertedString = string;
unicodeConversion.forEach(([regex, replacer]) => {
@@ -356,7 +356,7 @@ export const convertUnicodeToAscii = string => {
*
* @param {*} string
*/
-export const splitCamelCase = string =>
+export const splitCamelCase = (string) =>
string
.replace(/([A-Z]+)([A-Z][a-z])/g, ' $1 $2')
.replace(/([a-z\d])([A-Z])/g, '$1 $2')
@@ -398,7 +398,7 @@ export const truncateNamespace = (string = '') => {
* @param {String} obj The object to test
* @returns {Boolean}
*/
-export const hasContent = obj => isString(obj) && obj.trim() !== '';
+export const hasContent = (obj) => isString(obj) && obj.trim() !== '';
/**
* A utility function that validates if a
@@ -408,7 +408,7 @@ export const hasContent = obj => isString(obj) && obj.trim() !== '';
*
* @return {Boolean} true if valid
*/
-export const isValidSha1Hash = str => {
+export const isValidSha1Hash = (str) => {
return /^[0-9a-f]{5,40}$/.test(str);
};
diff --git a/app/assets/javascripts/lib/utils/type_utility.js b/app/assets/javascripts/lib/utils/type_utility.js
index 664c0dbbc84..be1911f7c34 100644
--- a/app/assets/javascripts/lib/utils/type_utility.js
+++ b/app/assets/javascripts/lib/utils/type_utility.js
@@ -1 +1 @@
-export const isObject = obj => obj && obj.constructor === Object;
+export const isObject = (obj) => obj && obj.constructor === Object;
diff --git a/app/assets/javascripts/lib/utils/unit_format/formatter_factory.js b/app/assets/javascripts/lib/utils/unit_format/formatter_factory.js
index 5d3dd79850e..9d47a1b7132 100644
--- a/app/assets/javascripts/lib/utils/unit_format/formatter_factory.js
+++ b/app/assets/javascripts/lib/utils/unit_format/formatter_factory.js
@@ -106,7 +106,7 @@ export const scaledSIFormatter = (unit = '', prefixOffset = 0) => {
const multiplicative = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
const symbols = [...fractional, '', ...multiplicative];
- const units = symbols.slice(fractional.length + prefixOffset).map(prefix => {
+ const units = symbols.slice(fractional.length + prefixOffset).map((prefix) => {
return `${prefix}${unit}`;
});
@@ -126,7 +126,7 @@ export const scaledBinaryFormatter = (unit = '', prefixOffset = 0) => {
const multiplicative = ['Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'];
const symbols = ['', ...multiplicative];
- const units = symbols.slice(prefixOffset).map(prefix => {
+ const units = symbols.slice(prefixOffset).map((prefix) => {
return `${prefix}${unit}`;
});
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index a9f6901de32..44d3e78b334 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -112,13 +112,13 @@ export function mergeUrlParams(params, url, options = {}) {
const mergedKeys = sort ? Object.keys(merged).sort() : Object.keys(merged);
const newQuery = mergedKeys
- .filter(key => merged[key] !== null)
- .map(key => {
+ .filter((key) => merged[key] !== null)
+ .map((key) => {
let value = merged[key];
const encodedKey = encodeURIComponent(key);
if (spreadArrays && Array.isArray(value)) {
value = merged[key]
- .map(arrayValue => encodeURIComponent(arrayValue))
+ .map((arrayValue) => encodeURIComponent(arrayValue))
.join(`&${encodedKey}[]=`);
return `${encodedKey}[]=${value}`;
}
@@ -150,11 +150,11 @@ export function removeParams(params, url = window.location.href, skipEncoding =
return url;
}
- const removableParams = skipEncoding ? params : params.map(param => encodeURIComponent(param));
+ const removableParams = skipEncoding ? params : params.map((param) => encodeURIComponent(param));
const updatedQuery = query
.split('&')
- .filter(paramPair => {
+ .filter((paramPair) => {
const [foundParam] = paramPair.split('=');
return removableParams.indexOf(foundParam) < 0;
})
@@ -237,7 +237,7 @@ export function redirectTo(url) {
return window.location.assign(url);
}
-export const escapeFileUrl = fileUrl => encodeURIComponent(fileUrl).replace(/%2F/g, '/');
+export const escapeFileUrl = (fileUrl) => encodeURIComponent(fileUrl).replace(/%2F/g, '/');
export function webIDEUrl(route = undefined) {
let returnUrl = `${gon.relative_url_root || ''}/-/ide/`;
@@ -396,7 +396,7 @@ export function queryToObject(query, options = {}) {
*/
export function objectToQuery(obj) {
return Object.keys(obj)
- .map(k => `${encodeURIComponent(k)}=${encodeURIComponent(obj[k])}`)
+ .map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(obj[k])}`)
.join('&');
}
@@ -420,7 +420,7 @@ export const setUrlParams = (
const queryString = urlObj.search;
const searchParams = clearParams ? new URLSearchParams('') : new URLSearchParams(queryString);
- Object.keys(params).forEach(key => {
+ Object.keys(params).forEach((key) => {
if (params[key] === null || params[key] === undefined) {
searchParams.delete(key);
} else if (Array.isArray(params[key])) {
diff --git a/app/assets/javascripts/lib/utils/users_cache.js b/app/assets/javascripts/lib/utils/users_cache.js
index 9f980fd4899..54f69ef8e1b 100644
--- a/app/assets/javascripts/lib/utils/users_cache.js
+++ b/app/assets/javascripts/lib/utils/users_cache.js
@@ -1,4 +1,4 @@
-import Api from '../../api';
+import { getUsers, getUser, getUserStatus } from '~/rest_api';
import Cache from './cache';
class UsersCache extends Cache {
@@ -7,7 +7,7 @@ class UsersCache extends Cache {
return Promise.resolve(this.get(username));
}
- return Api.users('', { username }).then(({ data }) => {
+ return getUsers('', { username }).then(({ data }) => {
if (!data.length) {
throw new Error(`User "${username}" could not be found!`);
}
@@ -28,7 +28,7 @@ class UsersCache extends Cache {
return Promise.resolve(this.get(userId));
}
- return Api.user(userId).then(({ data }) => {
+ return getUser(userId).then(({ data }) => {
this.internalStorage[userId] = data;
return data;
});
@@ -40,7 +40,7 @@ class UsersCache extends Cache {
return Promise.resolve(this.get(userId).status);
}
- return Api.userStatus(userId).then(({ data }) => {
+ return getUserStatus(userId).then(({ data }) => {
if (!this.hasData(userId)) {
this.internalStorage[userId] = {};
}