diff options
Diffstat (limited to 'app/assets/javascripts/lib/utils')
-rw-r--r-- | app/assets/javascripts/lib/utils/accessor.js | 28 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/common_utils.js | 11 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/datetime/date_format_utility.js | 105 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/dom_utils.js | 12 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/number_utils.js | 9 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/text_markdown.js | 4 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/url_utility.js | 37 |
7 files changed, 147 insertions, 59 deletions
diff --git a/app/assets/javascripts/lib/utils/accessor.js b/app/assets/javascripts/lib/utils/accessor.js index 39cffedcac6..d4a6d70c62c 100644 --- a/app/assets/javascripts/lib/utils/accessor.js +++ b/app/assets/javascripts/lib/utils/accessor.js @@ -1,4 +1,4 @@ -function isPropertyAccessSafe(base, property) { +function canAccessProperty(base, property) { let safe; try { @@ -10,7 +10,7 @@ function isPropertyAccessSafe(base, property) { return safe; } -function isFunctionCallSafe(base, functionName, ...args) { +function canCallFunction(base, functionName, ...args) { let safe = true; try { @@ -22,16 +22,28 @@ function isFunctionCallSafe(base, functionName, ...args) { return safe; } -function isLocalStorageAccessSafe() { +/** + * Determines if `window.localStorage` is available and + * can be written to and read from. + * + * Important: This is not a guarantee that + * `localStorage.setItem` will work in all cases. + * + * `setItem` can still throw exceptions and should be + * surrounded with a try/catch where used. + * + * See: https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#exceptions + */ +function canUseLocalStorage() { let safe; - const TEST_KEY = 'isLocalStorageAccessSafe'; + const TEST_KEY = 'canUseLocalStorage'; const TEST_VALUE = 'true'; - safe = isPropertyAccessSafe(window, 'localStorage'); + safe = canAccessProperty(window, 'localStorage'); if (!safe) return safe; - safe = isFunctionCallSafe(window.localStorage, 'setItem', TEST_KEY, TEST_VALUE); + safe = canCallFunction(window.localStorage, 'setItem', TEST_KEY, TEST_VALUE); if (safe) window.localStorage.removeItem(TEST_KEY); @@ -39,9 +51,7 @@ function isLocalStorageAccessSafe() { } const AccessorUtilities = { - isPropertyAccessSafe, - isFunctionCallSafe, - isLocalStorageAccessSafe, + canUseLocalStorage, }; export default AccessorUtilities; diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 8f86fd55d6e..fd9629499b0 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -117,7 +117,6 @@ export const handleLocationHash = () => { }; // Check if element scrolled into viewport from above or below -// Courtesy http://stackoverflow.com/a/7557433/414749 export const isInViewport = (el, offset = {}) => { const rect = el.getBoundingClientRect(); const { top, left } = offset; @@ -560,11 +559,9 @@ export const addSelectOnFocusBehaviour = (selector = '.js-select-on-focus') => { * Method to round of values with decimal places * with provided precision. * - * Taken from https://stackoverflow.com/a/7343013/414749 - * * Eg; roundOffFloat(3.141592, 3) = 3.142 * - * Refer to spec/javascripts/lib/utils/common_utils_spec.js for + * Refer to spec/frontend/lib/utils/common_utils_spec.js for * more supported examples. * * @param {Float} number @@ -581,7 +578,7 @@ export const roundOffFloat = (number, precision = 0) => { * * Eg; roundToNearestHalf(3.141592) = 3, roundToNearestHalf(3.41592) = 3.5 * - * Refer to spec/javascripts/lib/utils/common_utils_spec.js for + * Refer to spec/frontend/lib/utils/common_utils_spec.js for * more supported examples. * * @param {Float} number @@ -595,7 +592,7 @@ export const roundToNearestHalf = (num) => Math.round(num * 2).toFixed() / 2; * * Eg; roundDownFloat(3.141592, 3) = 3.141 * - * Refer to spec/javascripts/lib/utils/common_utils_spec.js for + * Refer to spec/frontend/lib/utils/common_utils_spec.js for * more supported examples. * * @param {Float} number @@ -645,7 +642,7 @@ export const NavigationType = { * matched with our query. * * You can learn more about behaviour of this method by referring to tests - * within `spec/javascripts/lib/utils/common_utils_spec.js`. + * within `spec/frontend/lib/utils/common_utils_spec.js`. * * @param {string} query String to search for * @param {object} searchSpace Object containing properties to search in for `query` diff --git a/app/assets/javascripts/lib/utils/datetime/date_format_utility.js b/app/assets/javascripts/lib/utils/datetime/date_format_utility.js index 246f290a90a..0a35efb0ac8 100644 --- a/app/assets/javascripts/lib/utils/datetime/date_format_utility.js +++ b/app/assets/javascripts/lib/utils/datetime/date_format_utility.js @@ -1,5 +1,5 @@ import dateFormat from 'dateformat'; -import { isString, mapValues, reduce } from 'lodash'; +import { isString, mapValues, reduce, isDate } from 'lodash'; import { s__, n__, __ } from '../../../locale'; /** @@ -258,3 +258,106 @@ export const parseSeconds = ( return periodCount; }); }; + +/** + * Pads given items with zeros to reach a length of 2 characters. + * + * @param {...any} args Items to be padded. + * @returns {Array<String>} Padded items. + */ +export const padWithZeros = (...args) => args.map((arg) => `${arg}`.padStart(2, '0')); + +/** + * This removes the timezone from an ISO date string. + * This can be useful when populating date/time fields along with a distinct timezone selector, in + * which case we'd want to ignore the timezone's offset when populating the date and time. + * + * Examples: + * stripTimezoneFromISODate('2021-08-16T00:00:00.000-02:00') => '2021-08-16T00:00:00.000' + * stripTimezoneFromISODate('2021-08-16T00:00:00.000Z') => '2021-08-16T00:00:00.000' + * + * @param {String} date The ISO date string representation. + * @returns {String} The ISO date string without the timezone. + */ +export const stripTimezoneFromISODate = (date) => { + if (Number.isNaN(Date.parse(date))) { + return null; + } + return date.replace(/(Z|[+-]\d{2}:\d{2})$/, ''); +}; + +/** + * Extracts the year, month and day from a Date instance and returns them in an object. + * For example: + * dateToYearMonthDate(new Date('2021-08-16')) => { year: '2021', month: '08', day: '16' } + * + * @param {Date} date The date to be parsed + * @returns {Object} An object containing the extracted year, month and day. + */ +export const dateToYearMonthDate = (date) => { + if (!isDate(date)) { + // eslint-disable-next-line @gitlab/require-i18n-strings + throw new Error('Argument should be a Date instance'); + } + const [month, day] = padWithZeros(date.getMonth() + 1, date.getDate()); + return { + year: `${date.getFullYear()}`, + month, + day, + }; +}; + +/** + * Extracts the hours and minutes from a string representing a time. + * For example: + * timeToHoursMinutes('12:46') => { hours: '12', minutes: '46' } + * + * @param {String} time The time to be parsed in the form HH:MM. + * @returns {Object} An object containing the hours and minutes. + */ +export const timeToHoursMinutes = (time = '') => { + if (!time || !time.match(/\d{1,2}:\d{1,2}/)) { + // eslint-disable-next-line @gitlab/require-i18n-strings + throw new Error('Invalid time provided'); + } + const [hours, minutes] = padWithZeros(...time.split(':')); + return { hours, minutes }; +}; + +/** + * This combines a date and a time and returns the computed Date's ISO string representation. + * + * @param {Date} date Date object representing the base date. + * @param {String} time String representing the time to be used, in the form HH:MM. + * @param {String} offset An optional Date-compatible offset. + * @returns {String} The combined Date's ISO string representation. + */ +export const dateAndTimeToISOString = (date, time, offset = '') => { + const { year, month, day } = dateToYearMonthDate(date); + const { hours, minutes } = timeToHoursMinutes(time); + const dateString = `${year}-${month}-${day}T${hours}:${minutes}:00.000${offset || 'Z'}`; + if (Number.isNaN(Date.parse(dateString))) { + // eslint-disable-next-line @gitlab/require-i18n-strings + throw new Error('Could not initialize date'); + } + return dateString; +}; + +/** + * Converts a Date instance to time input-compatible value consisting in a 2-digits hours and + * minutes, separated by a semi-colon, in the 24-hours format. + * + * @param {Date} date Date to be converted + * @returns {String} time input-compatible string in the form HH:MM. + */ +export const dateToTimeInputValue = (date) => { + if (!isDate(date)) { + // eslint-disable-next-line @gitlab/require-i18n-strings + throw new Error('Argument should be a Date instance'); + } + return date.toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit', + hour12: false, + }); +}; diff --git a/app/assets/javascripts/lib/utils/dom_utils.js b/app/assets/javascripts/lib/utils/dom_utils.js index f11c7658a88..f7687a929de 100644 --- a/app/assets/javascripts/lib/utils/dom_utils.js +++ b/app/assets/javascripts/lib/utils/dom_utils.js @@ -77,3 +77,15 @@ export const isElementVisible = (element) => * @returns {Boolean} `true` if the element is currently hidden, otherwise false */ export const isElementHidden = (element) => !isElementVisible(element); + +export const getParents = (element) => { + const parents = []; + let parent = element.parentNode; + + do { + parents.push(parent); + parent = parent.parentNode; + } while (parent); + + return parents; +}; diff --git a/app/assets/javascripts/lib/utils/number_utils.js b/app/assets/javascripts/lib/utils/number_utils.js index f3dedb7726a..f46263c0e4d 100644 --- a/app/assets/javascripts/lib/utils/number_utils.js +++ b/app/assets/javascripts/lib/utils/number_utils.js @@ -69,19 +69,20 @@ export function bytesToGiB(number) { * representation (e.g., giving it 1500 yields 1.5 KB). * * @param {Number} size + * @param {Number} digits - The number of digits to appear after the decimal point * @returns {String} */ -export function numberToHumanSize(size) { +export function numberToHumanSize(size, digits = 2) { const abs = Math.abs(size); if (abs < BYTES_IN_KIB) { return sprintf(__('%{size} bytes'), { size }); } else if (abs < BYTES_IN_KIB ** 2) { - return sprintf(__('%{size} KiB'), { size: bytesToKiB(size).toFixed(2) }); + return sprintf(__('%{size} KiB'), { size: bytesToKiB(size).toFixed(digits) }); } else if (abs < BYTES_IN_KIB ** 3) { - return sprintf(__('%{size} MiB'), { size: bytesToMiB(size).toFixed(2) }); + return sprintf(__('%{size} MiB'), { size: bytesToMiB(size).toFixed(digits) }); } - return sprintf(__('%{size} GiB'), { size: bytesToGiB(size).toFixed(2) }); + return sprintf(__('%{size} GiB'), { size: bytesToGiB(size).toFixed(digits) }); } /** diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js index 6ff2af47dd8..0804d792631 100644 --- a/app/assets/javascripts/lib/utils/text_markdown.js +++ b/app/assets/javascripts/lib/utils/text_markdown.js @@ -232,7 +232,9 @@ export function insertMarkdownText({ .join('\n'); } } else if (tag.indexOf(textPlaceholder) > -1) { - textToInsert = tag.replace(textPlaceholder, () => selected.replace(/\\n/g, '\n')); + textToInsert = tag.replace(textPlaceholder, () => + selected.replace(/\\n/g, '\n').replace('%br', '\\n'), + ); } else { textToInsert = String(startChar) + tag + selected + (wrap ? tag : ''); } diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index e9772232eaf..bca0e45d98d 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -418,43 +418,6 @@ export const urlParamsToArray = (path = '') => export const getUrlParamsArray = () => urlParamsToArray(window.location.search); /** - * Accepts encoding string which includes query params being - * sent to URL. - * - * @param {string} path Query param string - * - * @returns {object} Query params object containing key-value pairs - * with both key and values decoded into plain string. - * - * @deprecated Please use `queryToObject(query, { gatherArrays: true });` instead. See https://gitlab.com/gitlab-org/gitlab/-/issues/328845 - */ -export const urlParamsToObject = (path = '') => - splitPath(path).reduce((dataParam, filterParam) => { - if (filterParam === '') { - return dataParam; - } - - const data = dataParam; - let [key, value] = filterParam.split('='); - key = /%\w+/g.test(key) ? decodeURIComponent(key) : key; - const isArray = key.includes('[]'); - key = key.replace('[]', ''); - value = decodeURIComponent(value.replace(/\+/g, ' ')); - - if (isArray) { - if (!data[key]) { - data[key] = []; - } - - data[key].push(value); - } else { - data[key] = value; - } - - return data; - }, {}); - -/** * Convert search query into an object * * @param {String} query from "document.location.search" |