diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-20 11:43:02 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-20 11:43:02 +0300 |
commit | d9ab72d6080f594d0b3cae15f14b3ef2c6c638cb (patch) | |
tree | 2341ef426af70ad1e289c38036737e04b0aa5007 /app/assets/javascripts/lib | |
parent | d6e514dd13db8947884cd58fe2a9c2a063400a9b (diff) |
Add latest changes from gitlab-org/gitlab@14-4-stable-eev14.4.0-rc42
Diffstat (limited to 'app/assets/javascripts/lib')
13 files changed, 203 insertions, 17 deletions
diff --git a/app/assets/javascripts/lib/apollo/suppress_network_errors_during_navigation_link.js b/app/assets/javascripts/lib/apollo/suppress_network_errors_during_navigation_link.js new file mode 100644 index 00000000000..ad92bd4de42 --- /dev/null +++ b/app/assets/javascripts/lib/apollo/suppress_network_errors_during_navigation_link.js @@ -0,0 +1,36 @@ +import { Observable } from 'apollo-link'; +import { onError } from 'apollo-link-error'; +import { isNavigatingAway } from '~/lib/utils/is_navigating_away'; + +/** + * Returns an ApolloLink (or null if not enabled) which supresses network + * errors when the browser is navigating away. + * + * @returns {ApolloLink|null} + */ +export const getSuppressNetworkErrorsDuringNavigationLink = () => { + if (!gon.features?.suppressApolloErrorsDuringNavigation) { + return null; + } + + return onError(({ networkError }) => { + if (networkError && isNavigatingAway()) { + // Return an observable that will never notify any subscribers with any + // values, errors, or completions. This ensures that requests aborted due + // to navigating away do not trigger any failure behaviour. + // + // See '../utils/suppress_ajax_errors_during_navigation.js' for an axios + // interceptor that performs a similar role. + return new Observable(() => {}); + } + + // We aren't suppressing anything here, so simply do nothing. + // The onError helper will forward all values/errors/completions from the + // underlying request observable to the next link if you return a falsey + // value. + // + // Note that this return statement is technically redundant, but is kept + // for explicitness. + return undefined; + }); +}; diff --git a/app/assets/javascripts/lib/graphql.js b/app/assets/javascripts/lib/graphql.js index b96a55fe116..39bf804b54e 100644 --- a/app/assets/javascripts/lib/graphql.js +++ b/app/assets/javascripts/lib/graphql.js @@ -11,6 +11,7 @@ import csrf from '~/lib/utils/csrf'; import { objectToQuery, queryToObject } from '~/lib/utils/url_utility'; import PerformanceBarService from '~/performance_bar/services/performance_bar_service'; import { getInstrumentationLink } from './apollo/instrumentation_link'; +import { getSuppressNetworkErrorsDuringNavigationLink } from './apollo/suppress_network_errors_during_navigation_link'; export const fetchPolicies = { CACHE_FIRST: 'cache-first', @@ -143,6 +144,7 @@ export default (resolvers = {}, config = {}) => { new ActionCableLink(), ApolloLink.from( [ + getSuppressNetworkErrorsDuringNavigationLink(), getInstrumentationLink(), requestCounterLink, performanceBarLink, diff --git a/app/assets/javascripts/lib/logger/hello.js b/app/assets/javascripts/lib/logger/hello.js index 18fa35ab55b..ccfdfe91e60 100644 --- a/app/assets/javascripts/lib/logger/hello.js +++ b/app/assets/javascripts/lib/logger/hello.js @@ -1,15 +1,36 @@ +import { s__, sprintf } from '~/locale'; + const HANDSHAKE = String.fromCodePoint(0x1f91d); const MAG = String.fromCodePoint(0x1f50e); +const ROCKET = String.fromCodePoint(0x1f680); export const logHello = () => { // eslint-disable-next-line no-console console.log( - `%cWelcome to GitLab!%c + `%c${s__('HelloMessage|Welcome to GitLab!')}%c -Does this page need fixes or improvements? Open an issue or contribute a merge request to help make GitLab more lovable. At GitLab, everyone can contribute! +${s__( + 'HelloMessage|Does this page need fixes or improvements? Open an issue or contribute a merge request to help make GitLab more lovable. At GitLab, everyone can contribute!', +)} -${HANDSHAKE} Contribute to GitLab: https://about.gitlab.com/community/contribute/ -${MAG} Create a new GitLab issue: https://gitlab.com/gitlab-org/gitlab/-/issues/new`, +${sprintf(s__('HelloMessage|%{handshake_emoji} Contribute to GitLab: %{contribute_link}'), { + handshake_emoji: `${HANDSHAKE}`, + contribute_link: 'https://about.gitlab.com/community/contribute/', +})} +${sprintf(s__('HelloMessage|%{magnifier_emoji} Create a new GitLab issue: %{new_issue_link}'), { + magnifier_emoji: `${MAG}`, + new_issue_link: 'https://gitlab.com/gitlab-org/gitlab/-/issues/new', +})} +${ + window.gon?.dot_com + ? `${sprintf( + s__( + 'HelloMessage|%{rocket_emoji} We like your curiosity! Help us improve GitLab by joining the team: %{jobs_page_link}', + ), + { rocket_emoji: `${ROCKET}`, jobs_page_link: 'https://about.gitlab.com/jobs/' }, + )}` + : '' +}`, `padding-top: 0.5em; font-size: 2em;`, 'padding-bottom: 0.5em;', ); diff --git a/app/assets/javascripts/lib/utils/axios_utils.js b/app/assets/javascripts/lib/utils/axios_utils.js index 0a26f78e253..de6d85b8a18 100644 --- a/app/assets/javascripts/lib/utils/axios_utils.js +++ b/app/assets/javascripts/lib/utils/axios_utils.js @@ -2,6 +2,7 @@ import axios from 'axios'; import { registerCaptchaModalInterceptor } from '~/captcha/captcha_modal_axios_interceptor'; import setupAxiosStartupCalls from './axios_startup_calls'; import csrf from './csrf'; +import { isNavigatingAway } from './is_navigating_away'; import suppressAjaxErrorsDuringNavigation from './suppress_ajax_errors_during_navigation'; axios.defaults.headers.common[csrf.headerKey] = csrf.token; @@ -30,16 +31,11 @@ axios.interceptors.response.use( }, ); -let isUserNavigating = false; -window.addEventListener('beforeunload', () => { - isUserNavigating = true; -}); - // Ignore AJAX errors caused by requests // being cancelled due to browser navigation axios.interceptors.response.use( (response) => response, - (err) => suppressAjaxErrorsDuringNavigation(err, isUserNavigating), + (err) => suppressAjaxErrorsDuringNavigation(err, isNavigatingAway()), ); registerCaptchaModalInterceptor(axios); diff --git a/app/assets/javascripts/lib/utils/color_utils.js b/app/assets/javascripts/lib/utils/color_utils.js index da2c10076b1..66d52051905 100644 --- a/app/assets/javascripts/lib/utils/color_utils.js +++ b/app/assets/javascripts/lib/utils/color_utils.js @@ -1,3 +1,22 @@ +const colorValidatorEl = document.createElement('div'); + +/** + * Validates whether the specified color expression + * is supported by the browser’s DOM API and has a valid form. + * + * This utility assigns the color expression to a detached DOM + * element’s color property. If the color expression is valid, + * the DOM API will accept the value. + * + * @param {String} color color expression rgba, hex, hsla, etc. + */ +export const isValidColorExpression = (colorExpression) => { + colorValidatorEl.style.color = ''; + colorValidatorEl.style.color = colorExpression; + + return colorValidatorEl.style.color.length > 0; +}; + /** * Convert hex color to rgb array * diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index fd9629499b0..813fd3dbb1e 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -6,6 +6,7 @@ import { GlBreakpointInstance as breakpointInstance } from '@gitlab/ui/dist/util import $ from 'jquery'; import Cookies from 'js-cookie'; import { isFunction, defer } from 'lodash'; +import { SCOPED_LABEL_DELIMITER } from '~/vue_shared/components/sidebar/labels_select_widget/constants'; import { convertToCamelCase, convertToSnakeCase } from './text_utility'; import { isObject } from './type_utility'; import { getLocationHash } from './url_utility'; @@ -685,7 +686,7 @@ export const searchBy = (query = '', searchSpace = {}) => { * @param {Object} label * @returns Boolean */ -export const isScopedLabel = ({ title = '' } = {}) => title.indexOf('::') !== -1; +export const isScopedLabel = ({ title = '' } = {}) => title.includes(SCOPED_LABEL_DELIMITER); /** * Returns the base value of the scoped label @@ -696,7 +697,8 @@ export const isScopedLabel = ({ title = '' } = {}) => title.indexOf('::') !== -1 * @param {Object} label * @returns String */ -export const scopedLabelKey = ({ title = '' }) => isScopedLabel({ title }) && title.split('::')[0]; +export const scopedLabelKey = ({ title = '' }) => + isScopedLabel({ title }) && title.split(SCOPED_LABEL_DELIMITER)[0]; // Methods to set and get Cookie export const setCookie = (name, value) => Cookies.set(name, value, { expires: 365 }); diff --git a/app/assets/javascripts/lib/utils/constants.js b/app/assets/javascripts/lib/utils/constants.js index e41de72ded4..0e5a23a5cbb 100644 --- a/app/assets/javascripts/lib/utils/constants.js +++ b/app/assets/javascripts/lib/utils/constants.js @@ -20,3 +20,7 @@ export const BV_DROPDOWN_HIDE = 'bv::dropdown::hide'; export const DEFAULT_TH_CLASSES = 'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-100! gl-p-5! gl-border-b-1!'; + +// We set the drawer's z-index to 252 to clear flash messages that might +// be displayed in the page and that have a z-index of 251. +export const DRAWER_Z_INDEX = 252; 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 0a35efb0ac8..3c446c21865 100644 --- a/app/assets/javascripts/lib/utils/datetime/date_format_utility.js +++ b/app/assets/javascripts/lib/utils/datetime/date_format_utility.js @@ -1,6 +1,8 @@ import dateFormat from 'dateformat'; -import { isString, mapValues, reduce, isDate } from 'lodash'; -import { s__, n__, __ } from '../../../locale'; +import { isString, mapValues, reduce, isDate, unescape } from 'lodash'; +import { roundToNearestHalf } from '~/lib/utils/common_utils'; +import { sanitize } from '~/lib/dompurify'; +import { s__, n__, __, sprintf } from '../../../locale'; /** * Returns i18n month names array. @@ -361,3 +363,26 @@ export const dateToTimeInputValue = (date) => { hour12: false, }); }; + +export const formatTimeAsSummary = ({ seconds, hours, days, minutes, weeks, months }) => { + if (months) { + return sprintf(s__('ValueStreamAnalytics|%{value}M'), { + value: roundToNearestHalf(months), + }); + } else if (weeks) { + return sprintf(s__('ValueStreamAnalytics|%{value}w'), { + value: roundToNearestHalf(weeks), + }); + } else if (days) { + return sprintf(s__('ValueStreamAnalytics|%{value}d'), { + value: roundToNearestHalf(days), + }); + } else if (hours) { + return sprintf(s__('ValueStreamAnalytics|%{value}h'), { value: hours }); + } else if (minutes) { + return sprintf(s__('ValueStreamAnalytics|%{value}m'), { value: minutes }); + } else if (seconds) { + return unescape(sanitize(s__('ValueStreamAnalytics|<1m'), { ALLOWED_TAGS: [] })); + } + return '-'; +}; diff --git a/app/assets/javascripts/lib/utils/datetime_range.js b/app/assets/javascripts/lib/utils/datetime_range.js index a2b161d1446..840cc4600fe 100644 --- a/app/assets/javascripts/lib/utils/datetime_range.js +++ b/app/assets/javascripts/lib/utils/datetime_range.js @@ -26,7 +26,17 @@ const isValidDateString = (dateString) => { return false; } - return !Number.isNaN(Date.parse(dateformat(dateString, 'isoUtcDateTime'))); + let isoFormatted; + try { + isoFormatted = dateformat(dateString, 'isoUtcDateTime'); + } catch (e) { + if (e instanceof TypeError) { + // not a valid date string + return false; + } + throw e; + } + return !Number.isNaN(Date.parse(isoFormatted)); }; const handleRangeDirection = ({ direction = DEFAULT_DIRECTION, anchorDate, minDate, maxDate }) => { diff --git a/app/assets/javascripts/lib/utils/is_navigating_away.js b/app/assets/javascripts/lib/utils/is_navigating_away.js new file mode 100644 index 00000000000..7df00b45379 --- /dev/null +++ b/app/assets/javascripts/lib/utils/is_navigating_away.js @@ -0,0 +1,23 @@ +let navigating = false; + +window.addEventListener('beforeunload', () => { + navigating = true; +}); + +/** + * To only be used for testing purposes. Allows the navigating state to be set + * to a given value. + * + * @param {boolean} value The value to set the navigating flag to. + */ +export const setNavigatingForTestsOnly = (value) => { + navigating = value; +}; + +/** + * Returns a boolean indicating whether the browser is in the process of + * navigating away from the current page. + * + * @returns {boolean} + */ +export const isNavigatingAway = () => navigating; diff --git a/app/assets/javascripts/lib/utils/regexp.js b/app/assets/javascripts/lib/utils/regexp.js index 25b60dcd14a..f212bf80bd7 100644 --- a/app/assets/javascripts/lib/utils/regexp.js +++ b/app/assets/javascripts/lib/utils/regexp.js @@ -1,6 +1,5 @@ /** * Regexp utility for the convenience of working with regular expressions. - * */ // Inspired by https://github.com/mishoo/UglifyJS/blob/2bc1d02363db3798d5df41fb5059a19edca9b7eb/lib/parse-js.js#L203 @@ -8,4 +7,9 @@ const unicodeLetters = '\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u0527\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0620-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u08A0\\u08A2-\\u08AC\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0977\\u0979-\\u097F\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C3D\\u0C58\\u0C59\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0CF1\\u0CF2\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D60\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u16EE-\\u16F0\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191C\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19C1-\\u19C7\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1CE9-\\u1CEC\\u1CEE-\\u1CF1\\u1CF5\\u1CF6\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2160-\\u2188\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005-\\u3007\\u3021-\\u3029\\u3031-\\u3035\\u3038-\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31BA\\u31F0-\\u31FF\\u3400-\\u4DB5\\u4E00-\\u9FCC\\uA000-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA66E\\uA67F-\\uA697\\uA6A0-\\uA6EF\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA78E\\uA790-\\uA793\\uA7A0-\\uA7AA\\uA7F8-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA80-\\uAAAF\\uAAB1\\uAAB5\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uABC0-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC'; -export default { unicodeLetters }; +/** + * A regex that matches all single quotes in a string + */ +export const allSingleQuotes = /'/g; + +export default { unicodeLetters, allSingleQuotes }; diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js index 5ee00464a8b..419afa0a0a9 100644 --- a/app/assets/javascripts/lib/utils/text_utility.js +++ b/app/assets/javascripts/lib/utils/text_utility.js @@ -4,6 +4,7 @@ import { TRUNCATE_WIDTH_DEFAULT_WIDTH, TRUNCATE_WIDTH_DEFAULT_FONT_SIZE, } from '~/lib/utils/constants'; +import { allSingleQuotes } from '~/lib/utils/regexp'; /** * Adds a , to a string composed by numbers, at every 3 chars. @@ -479,3 +480,17 @@ export const markdownConfig = { ALLOWED_ATTR: ['class', 'style', 'href', 'src'], ALLOW_DATA_ATTR: false, }; + +/** + * Escapes a string into a shell string, for example + * when you want to give a user the command to checkout + * a branch. + * + * It replaces all single-quotes with an escaped "'\''" + * that is interpreted by shell as a single-quote. It also + * encapsulates the string in single-quotes. + * + * If the branch is `fix-'bug-behavior'`, that should be + * escaped to `'fix-'\''bug-behavior'\'''`. + */ +export const escapeShellString = (str) => `'${str.replace(allSingleQuotes, () => "'\\''")}'`; diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index bca0e45d98d..1c22d21a313 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -1,3 +1,5 @@ +export const DASH_SCOPE = '-'; + const PATH_SEPARATOR = '/'; const PATH_SEPARATOR_LEADING_REGEX = new RegExp(`^${PATH_SEPARATOR}+`); const PATH_SEPARATOR_ENDING_REGEX = new RegExp(`${PATH_SEPARATOR}+$`); @@ -588,3 +590,30 @@ export function isSameOriginUrl(url) { return false; } } + +/** + * Returns a URL to WebIDE considering the current user's position in + * repository's tree. If not MR `iid` has been passed, the URL is fetched + * from the global `gl.webIDEPath`. + * + * @param sourceProjectFullPath Source project's full path. Used in MRs + * @param targetProjectFullPath Target project's full path. Used in MRs + * @param iid MR iid + * @returns {string} + */ + +export function constructWebIDEPath({ + sourceProjectFullPath, + targetProjectFullPath = '', + iid, +} = {}) { + if (!iid || !sourceProjectFullPath) { + return window.gl?.webIDEPath; + } + return mergeUrlParams( + { + target_project: sourceProjectFullPath !== targetProjectFullPath ? targetProjectFullPath : '', + }, + webIDEUrl(`/${sourceProjectFullPath}/merge_requests/${iid}`), + ); +} |