diff options
Diffstat (limited to 'app/assets/javascripts/lib/utils/common_utils.js')
-rw-r--r-- | app/assets/javascripts/lib/utils/common_utils.js | 168 |
1 files changed, 42 insertions, 126 deletions
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]; |