diff options
Diffstat (limited to 'app/assets/javascripts/lib')
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] = {}; } |