Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/lib')
-rw-r--r--app/assets/javascripts/lib/graphql.js23
-rw-r--r--app/assets/javascripts/lib/utils/array_utility.js20
-rw-r--r--app/assets/javascripts/lib/utils/axios_utils.js2
-rw-r--r--app/assets/javascripts/lib/utils/color_utils.js20
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js16
-rw-r--r--app/assets/javascripts/lib/utils/constants.js6
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js157
-rw-r--r--app/assets/javascripts/lib/utils/number_utils.js2
-rw-r--r--app/assets/javascripts/lib/utils/poll.js2
-rw-r--r--app/assets/javascripts/lib/utils/poll_until_complete.js2
-rw-r--r--app/assets/javascripts/lib/utils/sticky.js12
-rw-r--r--app/assets/javascripts/lib/utils/text_markdown.js2
-rw-r--r--app/assets/javascripts/lib/utils/unit_format/formatter_factory.js3
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js58
-rw-r--r--app/assets/javascripts/lib/utils/webpack.js5
15 files changed, 283 insertions, 47 deletions
diff --git a/app/assets/javascripts/lib/graphql.js b/app/assets/javascripts/lib/graphql.js
index 5c4bb5ea01f..bda5550a9f4 100644
--- a/app/assets/javascripts/lib/graphql.js
+++ b/app/assets/javascripts/lib/graphql.js
@@ -1,11 +1,11 @@
-import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
-import { createUploadLink } from 'apollo-upload-client';
+import { ApolloClient } from 'apollo-client';
import { ApolloLink } from 'apollo-link';
import { BatchHttpLink } from 'apollo-link-batch-http';
+import { createUploadLink } from 'apollo-upload-client';
+import { StartupJSLink } from '~/lib/utils/apollo_startup_js_link';
import csrf from '~/lib/utils/csrf';
import PerformanceBarService from '~/performance_bar/services/performance_bar_service';
-import { StartupJSLink } from '~/lib/utils/apollo_startup_js_link';
export const fetchPolicies = {
CACHE_FIRST: 'cache-first',
@@ -35,6 +35,16 @@ export default (resolvers = {}, config = {}) => {
batchMax: config.batchMax || 10,
};
+ const requestCounterLink = new ApolloLink((operation, forward) => {
+ window.pendingApolloRequests = window.pendingApolloRequests || 0;
+ window.pendingApolloRequests += 1;
+
+ return forward(operation).map((response) => {
+ window.pendingApolloRequests -= 1;
+ return response;
+ });
+ });
+
const uploadsLink = ApolloLink.split(
(operation) => operation.getContext().hasUpload || operation.getContext().isSingleRequest,
createUploadLink(httpOptions),
@@ -63,7 +73,12 @@ export default (resolvers = {}, config = {}) => {
return new ApolloClient({
typeDefs: config.typeDefs,
- link: ApolloLink.from([performanceBarLink, new StartupJSLink(), uploadsLink]),
+ link: ApolloLink.from([
+ requestCounterLink,
+ performanceBarLink,
+ new StartupJSLink(),
+ uploadsLink,
+ ]),
cache: new InMemoryCache({
...config.cacheConfig,
freezeResults: config.assumeImmutableResults,
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.