diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-18 10:34:06 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-18 10:34:06 +0000 |
commit | 859a6fb938bb9ee2a317c46dfa4fcc1af49608f0 (patch) | |
tree | d7f2700abe6b4ffcb2dcfc80631b2d87d0609239 /app/assets/javascripts/lib/utils | |
parent | 446d496a6d000c73a304be52587cd9bbc7493136 (diff) | |
download | gitlab-ce-859a6fb938bb9ee2a317c46dfa4fcc1af49608f0.tar.gz |
Add latest changes from gitlab-org/gitlab@13-9-stable-eev13.9.0-rc42
Diffstat (limited to 'app/assets/javascripts/lib/utils')
-rw-r--r-- | app/assets/javascripts/lib/utils/array_utility.js | 20 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/axios_utils.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/color_utils.js | 20 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/common_utils.js | 16 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/constants.js | 6 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/datetime_utility.js | 157 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/number_utils.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/poll.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/poll_until_complete.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/sticky.js | 12 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/text_markdown.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/unit_format/formatter_factory.js | 3 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/url_utility.js | 58 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/webpack.js | 5 |
14 files changed, 264 insertions, 43 deletions
diff --git a/app/assets/javascripts/lib/utils/array_utility.js b/app/assets/javascripts/lib/utils/array_utility.js new file mode 100644 index 00000000000..197e7790ed7 --- /dev/null +++ b/app/assets/javascripts/lib/utils/array_utility.js @@ -0,0 +1,20 @@ +/** + * Return a shallow copy of an array with two items swapped. + * + * @param {Array} array - The source array + * @param {Number} leftIndex - Index of the first item + * @param {Number} rightIndex - Index of the second item + * @returns {Array} new array with the left and right items swapped + */ +export const swapArrayItems = (array, leftIndex = 0, rightIndex = 0) => { + const copy = array.slice(); + + if (leftIndex >= array.length || leftIndex < 0 || rightIndex >= array.length || rightIndex < 0) { + return copy; + } + + const temp = copy[leftIndex]; + copy[leftIndex] = copy[rightIndex]; + copy[rightIndex] = temp; + return copy; +}; diff --git a/app/assets/javascripts/lib/utils/axios_utils.js b/app/assets/javascripts/lib/utils/axios_utils.js index cb479e243b2..204c84b879e 100644 --- a/app/assets/javascripts/lib/utils/axios_utils.js +++ b/app/assets/javascripts/lib/utils/axios_utils.js @@ -1,7 +1,7 @@ import axios from 'axios'; +import setupAxiosStartupCalls from './axios_startup_calls'; import csrf from './csrf'; import suppressAjaxErrorsDuringNavigation from './suppress_ajax_errors_during_navigation'; -import setupAxiosStartupCalls from './axios_startup_calls'; axios.defaults.headers.common[csrf.headerKey] = csrf.token; // Used by Rails to check if it is a valid XHR request diff --git a/app/assets/javascripts/lib/utils/color_utils.js b/app/assets/javascripts/lib/utils/color_utils.js index a1f56b15631..ff176f11867 100644 --- a/app/assets/javascripts/lib/utils/color_utils.js +++ b/app/assets/javascripts/lib/utils/color_utils.js @@ -23,3 +23,23 @@ export const textColorForBackground = (backgroundColor) => { } return '#FFFFFF'; }; + +/** + * Check whether a color matches the expected hex format + * + * This matches any hex (0-9 and A-F) value which is either 3 or 6 characters in length + * + * An empty string will return `null` which means that this is neither valid nor invalid. + * This is useful for forms resetting the validation state + * + * @param color string = '' + * + * @returns {null|boolean} + */ +export const validateHexColor = (color = '') => { + if (!color) { + return null; + } + + return /^#([0-9A-F]{3}){1,2}$/i.test(color); +}; diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 128ef5b335e..73eadfe3cbe 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -4,11 +4,11 @@ import { GlBreakpointInstance as breakpointInstance } from '@gitlab/ui/dist/utils'; import $ from 'jquery'; -import { isFunction, defer } from 'lodash'; import Cookies from 'js-cookie'; -import { getLocationHash } from './url_utility'; +import { isFunction, defer } from 'lodash'; import { convertToCamelCase, convertToSnakeCase } from './text_utility'; import { isObject } from './type_utility'; +import { getLocationHash } from './url_utility'; export const getPagePath = (index = 0) => { const page = $('body').attr('data-page') || ''; @@ -45,6 +45,7 @@ export const checkPageAndAction = (page, action) => { export const isInIncidentPage = () => checkPageAndAction('incidents', 'show'); export const isInIssuePage = () => checkPageAndAction('issues', 'show'); +export const isInDesignPage = () => checkPageAndAction('issues', 'designs'); export const isInMRPage = () => checkPageAndAction('merge_requests', 'show'); export const isInEpicPage = () => checkPageAndAction('epics', 'show'); @@ -122,7 +123,7 @@ export const handleLocationHash = () => { } if (isInIssuePage()) { - adjustment -= fixedIssuableTitle.offsetHeight; + adjustment -= fixedIssuableTitle?.offsetHeight; } if (isInMRPage()) { @@ -801,3 +802,12 @@ export const removeCookie = (name) => Cookies.remove(name); * @returns {Boolean} on/off */ export const isFeatureFlagEnabled = (flag) => window.gon.features?.[flag]; + +/** + * This method takes in array with snake_case strings + * and returns a new array with camelCase strings + * + * @param {Array[String]} array - Array to be converted + * @returns {Array[String]} Converted array + */ +export const convertArrayToCamelCase = (array) => array.map((i) => convertToCamelCase(i)); diff --git a/app/assets/javascripts/lib/utils/constants.js b/app/assets/javascripts/lib/utils/constants.js index 993d51370ec..b19a4a01a5f 100644 --- a/app/assets/javascripts/lib/utils/constants.js +++ b/app/assets/javascripts/lib/utils/constants.js @@ -10,3 +10,9 @@ export const DATETIME_RANGE_TYPES = { open: 'open', invalid: 'invalid', }; + +export const BV_SHOW_MODAL = 'bv::show::modal'; +export const BV_HIDE_MODAL = 'bv::hide::modal'; +export const BV_HIDE_TOOLTIP = 'bv::hide::tooltip'; +export const BV_DROPDOWN_SHOW = 'bv::dropdown::show'; +export const BV_DROPDOWN_HIDE = 'bv::dropdown::hide'; diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index 15f7c0c874e..38b3b26dc44 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -1,10 +1,12 @@ +import dateFormat from 'dateformat'; import $ from 'jquery'; import { isString, mapValues, isNumber, reduce } from 'lodash'; import * as timeago from 'timeago.js'; -import dateFormat from 'dateformat'; import { languageCode, s__, __, n__ } from '../../locale'; -const MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000; +const MILLISECONDS_IN_HOUR = 60 * 60 * 1000; +const MILLISECONDS_IN_DAY = 24 * MILLISECONDS_IN_HOUR; +const DAYS_IN_WEEK = 7; window.timeago = timeago; @@ -674,50 +676,127 @@ export const secondsToHours = (offset) => { }; /** - * Returns the date n days after the date provided + * Returns the date `n` days after the date provided * * @param {Date} date the initial date * @param {Number} numberOfDays number of days after - * @return {Date} the date following the date provided + * @param {Object} [options={}] Additional options for this calculation + * @param {boolean} [options.utc=false] Performs the calculation using UTC dates. + * This will cause Daylight Saving Time to be ignored. Defaults to `false` + * if not provided, which causes the calculation to be performed in the + * user's timezone. + * + * @return {Date} A `Date` object `n` days after the provided `Date` */ -export const nDaysAfter = (date, numberOfDays) => - new Date(newDate(date)).setDate(date.getDate() + numberOfDays); +export const nDaysAfter = (date, numberOfDays, { utc = false } = {}) => { + const clone = newDate(date); + + const cloneValue = utc + ? clone.setUTCDate(date.getUTCDate() + numberOfDays) + : clone.setDate(date.getDate() + numberOfDays); + + return new Date(cloneValue); +}; /** - * Returns the date n days before the date provided + * 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 + * @param {Object} [options={}] Additional options for this calculation + * @param {boolean} [options.utc=false] Performs the calculation using UTC dates. + * This will cause Daylight Saving Time to be ignored. Defaults to `false` + * if not provided, which causes the calculation to be performed in the + * user's timezone. + * @return {Date} A `Date` object `n` days before the provided `Date` */ -export const nDaysBefore = (date, numberOfDays) => nDaysAfter(date, -numberOfDays); +export const nDaysBefore = (date, numberOfDays, options) => + nDaysAfter(date, -numberOfDays, options); /** - * Returns the date n months after the date provided + * Returns the date `n` weeks after the date provided + * + * @param {Date} date the initial date + * @param {Number} numberOfWeeks number of weeks after + * @param {Object} [options={}] Additional options for this calculation + * @param {boolean} [options.utc=false] Performs the calculation using UTC dates. + * This will cause Daylight Saving Time to be ignored. Defaults to `false` + * if not provided, which causes the calculation to be performed in the + * user's timezone. + * + * @return {Date} A `Date` object `n` weeks after the provided `Date` + */ +export const nWeeksAfter = (date, numberOfWeeks, options) => + nDaysAfter(date, DAYS_IN_WEEK * numberOfWeeks, options); + +/** + * Returns the date `n` weeks before the date provided + * + * @param {Date} date the initial date + * @param {Number} numberOfWeeks number of weeks before + * @param {Object} [options={}] Additional options for this calculation + * @param {boolean} [options.utc=false] Performs the calculation using UTC dates. + * This will cause Daylight Saving Time to be ignored. Defaults to `false` + * if not provided, which causes the calculation to be performed in the + * user's timezone. + * + * @return {Date} A `Date` object `n` weeks before the provided `Date` + */ +export const nWeeksBefore = (date, numberOfWeeks, options) => + nWeeksAfter(date, -numberOfWeeks, options); + +/** + * 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 + * @param {Object} [options={}] Additional options for this calculation + * @param {boolean} [options.utc=false] Performs the calculation using UTC dates. + * This will cause Daylight Saving Time to be ignored. Defaults to `false` + * if not provided, which causes the calculation to be performed in the + * user's timezone. + * + * @return {Date} A `Date` object `n` months after the provided `Date` */ -export const nMonthsAfter = (date, numberOfMonths) => - new Date(newDate(date)).setMonth(date.getMonth() + numberOfMonths); +export const nMonthsAfter = (date, numberOfMonths, { utc = false } = {}) => { + const clone = newDate(date); + + const cloneValue = utc + ? clone.setUTCMonth(date.getUTCMonth() + numberOfMonths) + : clone.setMonth(date.getMonth() + numberOfMonths); + + return new Date(cloneValue); +}; /** - * Returns the date n months before the date provided + * 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 + * @param {Object} [options={}] Additional options for this calculation + * @param {boolean} [options.utc=false] Performs the calculation using UTC dates. + * This will cause Daylight Saving Time to be ignored. Defaults to `false` + * if not provided, which causes the calculation to be performed in the + * user's timezone. + * + * @return {Date} A `Date` object `n` months before the provided `Date` */ -export const nMonthsBefore = (date, numberOfMonths) => nMonthsAfter(date, -numberOfMonths); +export const nMonthsBefore = (date, numberOfMonths, options) => + nMonthsAfter(date, -numberOfMonths, options); /** * Returns the date after the date provided * * @param {Date} date the initial date + * @param {Object} [options={}] Additional options for this calculation + * @param {boolean} [options.utc=false] Performs the calculation using UTC dates. + * This will cause Daylight Saving Time to be ignored. Defaults to `false` + * if not provided, which causes the calculation to be performed in the + * user's timezone. + * * @return {Date} the date following the date provided */ -export const dayAfter = (date) => new Date(newDate(date).setDate(date.getDate() + 1)); +export const dayAfter = (date, options) => nDaysAfter(date, 1, options); /** * Mimics the behaviour of the rails distance_of_time_in_words function @@ -859,17 +938,17 @@ export const format24HourTimeStringFromInt = (time) => { * * @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 } + * @returns {Object} { daysOverlap: number of days the overlap is present, hoursOverlap: number of hours 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( + * getOverlapDateInPeriods( * { 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 } + * ) => { daysOverlap: 2, hoursOverlap: 48, overlapStartDate: 1610323200000, overlapEndDate: 1610496000000 } * */ -export const getOverlappingDaysInPeriods = (givenPeriodLeft = {}, givenPeriodRight = {}) => { +export const getOverlapDateInPeriods = (givenPeriodLeft = {}, givenPeriodRight = {}) => { const leftStartTime = new Date(givenPeriodLeft.start).getTime(); const leftEndTime = new Date(givenPeriodLeft.end).getTime(); const rightStartTime = new Date(givenPeriodRight.start).getTime(); @@ -890,8 +969,44 @@ export const getOverlappingDaysInPeriods = (givenPeriodLeft = {}, givenPeriodRig const differenceInMs = overlapEndDate - overlapStartDate; return { + hoursOverlap: Math.ceil(differenceInMs / MILLISECONDS_IN_HOUR), daysOverlap: Math.ceil(differenceInMs / MILLISECONDS_IN_DAY), overlapStartDate, overlapEndDate, }; }; + +/** + * A utility function that checks that the date is today + * + * @param {Date} date + * + * @return {Boolean} true if provided date is today + */ +export const isToday = (date) => { + const today = new Date(); + return ( + date.getDate() === today.getDate() && + date.getMonth() === today.getMonth() && + date.getFullYear() === today.getFullYear() + ); +}; + +/** + * Returns the start of the provided day + * + * @param {Object} [options={}] Additional options for this calculation + * @param {boolean} [options.utc=false] Performs the calculation using UTC time. + * If `true`, the time returned will be midnight UTC. If `false` (the default) + * the time returned will be midnight in the user's local time. + * + * @returns {Date} A new `Date` object that represents the start of the day + * of the provided date + */ +export const getStartOfDay = (date, { utc = false } = {}) => { + const clone = newDate(date); + + const cloneValue = utc ? clone.setUTCHours(0, 0, 0, 0) : clone.setHours(0, 0, 0, 0); + + return new Date(cloneValue); +}; diff --git a/app/assets/javascripts/lib/utils/number_utils.js b/app/assets/javascripts/lib/utils/number_utils.js index d49382733c0..0f29f538b07 100644 --- a/app/assets/javascripts/lib/utils/number_utils.js +++ b/app/assets/javascripts/lib/utils/number_utils.js @@ -1,5 +1,5 @@ -import { BYTES_IN_KIB } from './constants'; import { sprintf, __ } from '~/locale'; +import { BYTES_IN_KIB } from './constants'; /** * Function that allows a number with an X amount of decimals diff --git a/app/assets/javascripts/lib/utils/poll.js b/app/assets/javascripts/lib/utils/poll.js index 6ec1bd206e6..71782c9a4ce 100644 --- a/app/assets/javascripts/lib/utils/poll.js +++ b/app/assets/javascripts/lib/utils/poll.js @@ -1,5 +1,5 @@ -import httpStatusCodes, { successCodes } from './http_status'; import { normalizeHeaders } from './common_utils'; +import httpStatusCodes, { successCodes } from './http_status'; /** * Polling utility for handling realtime updates. diff --git a/app/assets/javascripts/lib/utils/poll_until_complete.js b/app/assets/javascripts/lib/utils/poll_until_complete.js index d3b551ca755..3545db3a227 100644 --- a/app/assets/javascripts/lib/utils/poll_until_complete.js +++ b/app/assets/javascripts/lib/utils/poll_until_complete.js @@ -1,6 +1,6 @@ import axios from '~/lib/utils/axios_utils'; -import Poll from './poll'; import httpStatusCodes from './http_status'; +import Poll from './poll'; /** * Polls an endpoint until it returns either a 200 OK or a error status. diff --git a/app/assets/javascripts/lib/utils/sticky.js b/app/assets/javascripts/lib/utils/sticky.js index 6bb7f09b886..a6d53358cb8 100644 --- a/app/assets/javascripts/lib/utils/sticky.js +++ b/app/assets/javascripts/lib/utils/sticky.js @@ -1,5 +1,3 @@ -import StickyFill from 'stickyfilljs'; - export const createPlaceholder = () => { const placeholder = document.createElement('div'); placeholder.classList.add('sticky-placeholder'); @@ -60,13 +58,3 @@ export const stickyMonitor = (el, stickyTop, insertPlaceholder = true) => { }, ); }; - -/** - * Polyfill the `position: sticky` behavior. - * - * - 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) => { - StickyFill.add(el); -}; diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js index 2c993c8b128..5e66aa05218 100644 --- a/app/assets/javascripts/lib/utils/text_markdown.js +++ b/app/assets/javascripts/lib/utils/text_markdown.js @@ -1,7 +1,7 @@ /* eslint-disable func-names, no-param-reassign, operator-assignment, consistent-return */ import $ from 'jquery'; -import { insertText } from '~/lib/utils/common_utils'; import Shortcuts from '~/behaviors/shortcuts/shortcuts'; +import { insertText } from '~/lib/utils/common_utils'; const LINK_TAG_PATTERN = '[{text}](url)'; 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 9d47a1b7132..15f9512fe92 100644 --- a/app/assets/javascripts/lib/utils/unit_format/formatter_factory.js +++ b/app/assets/javascripts/lib/utils/unit_format/formatter_factory.js @@ -20,8 +20,9 @@ function formatNumber( return ''; } + const locale = document.documentElement.lang || undefined; const num = value * valueFactor; - const formatted = num.toLocaleString(undefined, { + const formatted = num.toLocaleString(locale, { minimumFractionDigits: fractionDigits, maximumFractionDigits: fractionDigits, style, diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index 44d3e78b334..cc2cf787a8f 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -16,6 +16,50 @@ function decodeUrlParameter(val) { return decodeURIComponent(val.replace(/\+/g, '%20')); } +/** + * Safely encodes a string to be used as a path + * + * Note: This function DOES encode typical URL parts like ?, =, &, #, and + + * If you need to use search parameters or URL fragments, they should be + * added AFTER calling this function, not before. + * + * Usecase: An image filename is stored verbatim, and you need to load it in + * the browser. + * + * Example: /some_path/file #1.jpg ==> /some_path/file%20%231.jpg + * Example: /some-path/file! Final!.jpg ==> /some-path/file%21%20Final%21.jpg + * + * Essentially, if a character *could* present a problem in a URL, it's escaped + * to the hexadecimal representation instead. This means it's a bit more + * aggressive than encodeURIComponent: that built-in function doesn't + * encode some characters that *could* be problematic, so this function + * adds them (#, !, ~, *, ', (, and )). + * Additionally, encodeURIComponent *does* encode `/`, but we want safer + * URLs, not non-functional URLs, so this function DEcodes slashes ('%2F'). + * + * @param {String} potentiallyUnsafePath + * @returns {String} + */ +export function encodeSaferUrl(potentiallyUnsafePath) { + const unencode = ['%2F']; + const encode = ['#', '!', '~', '\\*', "'", '\\(', '\\)']; + let saferPath = encodeURIComponent(potentiallyUnsafePath); + + unencode.forEach((code) => { + saferPath = saferPath.replace(new RegExp(code, 'g'), decodeURIComponent(code)); + }); + encode.forEach((code) => { + const encodedValue = code + .codePointAt(code.length - 1) + .toString(16) + .toUpperCase(); + + saferPath = saferPath.replace(new RegExp(code, 'g'), `%${encodedValue}`); + }); + + return saferPath; +} + export function cleanLeadingSeparator(path) { return path.replace(PATH_SEPARATOR_LEADING_REGEX, ''); } @@ -310,6 +354,20 @@ export function isAbsoluteOrRootRelative(url) { } /** + * Returns true if url is an external URL + * + * @param {String} url + * @returns {Boolean} + */ +export function isExternal(url) { + if (isRootRelative(url)) { + return false; + } + + return !url.includes(gon.gitlab_url); +} + +/** * Converts a relative path to an absolute or a root relative path depending * on what is passed as a basePath. * diff --git a/app/assets/javascripts/lib/utils/webpack.js b/app/assets/javascripts/lib/utils/webpack.js index 622c40e0f35..07a4d2deb0b 100644 --- a/app/assets/javascripts/lib/utils/webpack.js +++ b/app/assets/javascripts/lib/utils/webpack.js @@ -1,6 +1,9 @@ import { joinPaths } from '~/lib/utils/url_utility'; -// tell webpack to load assets from origin so that web workers don't break +/** + * Tell webpack to load assets from origin so that web workers don't break + * See https://gitlab.com/gitlab-org/gitlab/-/issues/321656 for a fix + */ export function resetServiceWorkersPublicPath() { // __webpack_public_path__ is a global variable that can be used to adjust // the webpack publicPath setting at runtime. |