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/apollo/suppress_network_errors_during_navigation_link.js4
-rw-r--r--app/assets/javascripts/lib/graphql.js5
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js27
-rw-r--r--app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue47
-rw-r--r--app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js55
-rw-r--r--app/assets/javascripts/lib/utils/constants.js2
-rw-r--r--app/assets/javascripts/lib/utils/datetime/date_format_utility.js48
-rw-r--r--app/assets/javascripts/lib/utils/file_upload.js20
-rw-r--r--app/assets/javascripts/lib/utils/rails_ujs.js38
-rw-r--r--app/assets/javascripts/lib/utils/text_markdown.js2
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js6
11 files changed, 202 insertions, 52 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
index ad92bd4de42..9b7901685b6 100644
--- 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
@@ -9,10 +9,6 @@ import { isNavigatingAway } from '~/lib/utils/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
diff --git a/app/assets/javascripts/lib/graphql.js b/app/assets/javascripts/lib/graphql.js
index 39bf804b54e..df2e85afe24 100644
--- a/app/assets/javascripts/lib/graphql.js
+++ b/app/assets/javascripts/lib/graphql.js
@@ -48,7 +48,6 @@ export const stripWhitespaceFromQuery = (url, path) => {
export default (resolvers = {}, config = {}) => {
const {
- assumeImmutableResults,
baseUrl,
batchMax = 10,
cacheConfig,
@@ -161,10 +160,10 @@ export default (resolvers = {}, config = {}) => {
link: appLink,
cache: new InMemoryCache({
...cacheConfig,
- freezeResults: assumeImmutableResults,
+ freezeResults: true,
}),
resolvers,
- assumeImmutableResults,
+ assumeImmutableResults: true,
defaultOptions: {
query: {
fetchPolicy,
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 813fd3dbb1e..a82dad7e2c9 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -220,16 +220,16 @@ export const scrollToElement = (element, options = {}) => {
// 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 { duration = 200, offset = 0, behavior = duration ? 'smooth' : 'auto' } = options;
const y = el.getBoundingClientRect().top + window.pageYOffset + offset - contentTop();
- window.scrollTo({ top: y, behavior: duration ? 'smooth' : 'auto' });
+ window.scrollTo({ top: y, behavior });
});
}
};
-export const scrollToElementWithContext = (element) => {
+export const scrollToElementWithContext = (element, options) => {
const offsetMultiplier = -0.1;
- return scrollToElement(element, { offset: window.innerHeight * offsetMultiplier });
+ return scrollToElement(element, { ...options, offset: window.innerHeight * offsetMultiplier });
};
/**
@@ -688,17 +688,20 @@ export const searchBy = (query = '', searchSpace = {}) => {
*/
export const isScopedLabel = ({ title = '' } = {}) => title.includes(SCOPED_LABEL_DELIMITER);
+const scopedLabelRegex = new RegExp(`(.*)${SCOPED_LABEL_DELIMITER}.*`);
+
/**
- * Returns the base value of the scoped label
- *
- * Expected Label to be an Object with `title` as a key:
- * { title: 'LabelTitle', ...otherProperties };
+ * Returns the key of a scoped label.
+ * For example:
+ * - returns `scoped` if the label is `scoped::value`.
+ * - returns `scoped::label` if the label is `scoped::label::value`.
*
- * @param {Object} label
- * @returns String
+ * @param {Object} label object containing `title` property
+ * @returns String scoped label key, or full label if it is not a scoped label
*/
-export const scopedLabelKey = ({ title = '' }) =>
- isScopedLabel({ title }) && title.split(SCOPED_LABEL_DELIMITER)[0];
+export const scopedLabelKey = ({ title = '' }) => {
+ return title.replace(scopedLabelRegex, '$1');
+};
// 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/confirm_via_gl_modal/confirm_modal.vue b/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue
new file mode 100644
index 00000000000..733d0f69f5d
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue
@@ -0,0 +1,47 @@
+<script>
+import { GlModal } from '@gitlab/ui';
+import { __ } from '~/locale';
+
+export default {
+ cancelAction: { text: __('Cancel') },
+ components: {
+ GlModal,
+ },
+ props: {
+ primaryText: {
+ type: String,
+ required: false,
+ default: __('OK'),
+ },
+ primaryVariant: {
+ type: String,
+ required: false,
+ default: 'confirm',
+ },
+ },
+ computed: {
+ primaryAction() {
+ return { text: this.primaryText, attributes: { variant: this.primaryVariant } };
+ },
+ },
+ mounted() {
+ this.$refs.modal.show();
+ },
+};
+</script>
+
+<template>
+ <gl-modal
+ ref="modal"
+ size="sm"
+ modal-id="confirmationModal"
+ body-class="gl-display-flex"
+ :action-primary="primaryAction"
+ :action-cancel="$options.cancelAction"
+ hide-header
+ @primary="$emit('confirmed')"
+ @hidden="$emit('closed')"
+ >
+ <div class="gl-align-self-center"><slot></slot></div>
+ </gl-modal>
+</template>
diff --git a/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js b/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js
new file mode 100644
index 00000000000..fdd0e045d07
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js
@@ -0,0 +1,55 @@
+import Vue from 'vue';
+
+export function confirmAction(message, { primaryBtnVariant, primaryBtnText } = {}) {
+ return new Promise((resolve) => {
+ let confirmed = false;
+
+ const component = new Vue({
+ components: {
+ ConfirmModal: () => import('./confirm_modal.vue'),
+ },
+ render(h) {
+ return h(
+ 'confirm-modal',
+ {
+ props: {
+ primaryVariant: primaryBtnVariant,
+ primaryText: primaryBtnText,
+ },
+ on: {
+ confirmed() {
+ confirmed = true;
+ },
+ closed() {
+ component.$destroy();
+ resolve(confirmed);
+ },
+ },
+ },
+ [message],
+ );
+ },
+ }).$mount();
+ });
+}
+
+export function confirmViaGlModal(message, element) {
+ const primaryBtnConfig = {};
+
+ const confirmBtnVariant = element.getAttribute('data-confirm-btn-variant');
+
+ if (confirmBtnVariant) {
+ primaryBtnConfig.primaryBtnVariant = confirmBtnVariant;
+ }
+
+ const screenReaderText =
+ element.querySelector('.gl-sr-only')?.textContent ||
+ element.querySelector('.sr-only')?.textContent ||
+ element.getAttribute('aria-label');
+
+ if (screenReaderText) {
+ primaryBtnConfig.primaryBtnText = screenReaderText;
+ }
+
+ return confirmAction(message, primaryBtnConfig);
+}
diff --git a/app/assets/javascripts/lib/utils/constants.js b/app/assets/javascripts/lib/utils/constants.js
index 0e5a23a5cbb..36c6545164e 100644
--- a/app/assets/javascripts/lib/utils/constants.js
+++ b/app/assets/javascripts/lib/utils/constants.js
@@ -24,3 +24,5 @@ export const DEFAULT_TH_CLASSES =
// 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;
+
+export const MIN_USERNAME_LENGTH = 2;
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 3c446c21865..7bff2bf3e47 100644
--- a/app/assets/javascripts/lib/utils/datetime/date_format_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime/date_format_utility.js
@@ -14,33 +14,33 @@ import { s__, n__, __, sprintf } from '../../../locale';
export const getMonthNames = (abbreviated) => {
if (abbreviated) {
return [
- s__('Jan'),
- s__('Feb'),
- s__('Mar'),
- s__('Apr'),
- s__('May'),
- s__('Jun'),
- s__('Jul'),
- s__('Aug'),
- s__('Sep'),
- s__('Oct'),
- s__('Nov'),
- s__('Dec'),
+ __('Jan'),
+ __('Feb'),
+ __('Mar'),
+ __('Apr'),
+ __('May'),
+ __('Jun'),
+ __('Jul'),
+ __('Aug'),
+ __('Sep'),
+ __('Oct'),
+ __('Nov'),
+ __('Dec'),
];
}
return [
- s__('January'),
- s__('February'),
- s__('March'),
- s__('April'),
- s__('May'),
- s__('June'),
- s__('July'),
- s__('August'),
- s__('September'),
- s__('October'),
- s__('November'),
- s__('December'),
+ __('January'),
+ __('February'),
+ __('March'),
+ __('April'),
+ __('May'),
+ __('June'),
+ __('July'),
+ __('August'),
+ __('September'),
+ __('October'),
+ __('November'),
+ __('December'),
];
};
diff --git a/app/assets/javascripts/lib/utils/file_upload.js b/app/assets/javascripts/lib/utils/file_upload.js
index b8b63bf58d4..f99a4927338 100644
--- a/app/assets/javascripts/lib/utils/file_upload.js
+++ b/app/assets/javascripts/lib/utils/file_upload.js
@@ -15,13 +15,17 @@ export default (buttonSelector, fileSelector) => {
});
};
-export const getFilename = ({ clipboardData }) => {
- let value;
- if (window.clipboardData && window.clipboardData.getData) {
- value = window.clipboardData.getData('Text');
- } else if (clipboardData && clipboardData.getData) {
- value = clipboardData.getData('text/plain');
+export const getFilename = (file) => {
+ let fileName;
+ if (file) {
+ fileName = file.name;
}
- value = value.split('\r');
- return value[0];
+
+ return fileName;
+};
+
+export const validateImageName = (file) => {
+ const fileName = file.name ? file.name : 'image.png';
+ const legalImageRegex = /^[\w.\-+]+\.(png|jpg|jpeg|gif|bmp|tiff|ico|webp)$/;
+ return legalImageRegex.test(fileName) ? fileName : 'image.png';
};
diff --git a/app/assets/javascripts/lib/utils/rails_ujs.js b/app/assets/javascripts/lib/utils/rails_ujs.js
index 8b40cc7bd11..6b1985a23ba 100644
--- a/app/assets/javascripts/lib/utils/rails_ujs.js
+++ b/app/assets/javascripts/lib/utils/rails_ujs.js
@@ -1,4 +1,42 @@
import Rails from '@rails/ujs';
+import { confirmViaGlModal } from './confirm_via_gl_modal/confirm_via_gl_modal';
+
+function monkeyPatchConfirmModal() {
+ /**
+ * This function is used to replace the `Rails.confirm` which uses `window.confirm`
+ *
+ * This function opens a confirmation modal which will resolve in a promise.
+ * Because the `Rails.confirm` API is synchronous, we go with a little hack here:
+ *
+ * 1. User clicks on something with `data-confirm`
+ * 2. We open the modal and return `false`, ending the "Rails" event chain
+ * 3. If the modal is closed and the user "confirmed" the action
+ * 1. replace the `Rails.confirm` with a function that always returns `true`
+ * 2. click the same element programmatically
+ *
+ * @param message {String} Message to be shown in the modal
+ * @param element {HTMLElement} Element that was clicked on
+ * @returns {boolean}
+ */
+ function confirmViaModal(message, element) {
+ confirmViaGlModal(message, element)
+ .then((confirmed) => {
+ if (confirmed) {
+ Rails.confirm = () => true;
+ element.click();
+ Rails.confirm = confirmViaModal;
+ }
+ })
+ .catch(() => {});
+ return false;
+ }
+
+ Rails.confirm = confirmViaModal;
+}
+
+if (gon?.features?.bootstrapConfirmationModals) {
+ monkeyPatchConfirmModal();
+}
export const initRails = () => {
// eslint-disable-next-line no-underscore-dangle
diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js
index 0804d792631..40dd29bea76 100644
--- a/app/assets/javascripts/lib/utils/text_markdown.js
+++ b/app/assets/javascripts/lib/utils/text_markdown.js
@@ -233,7 +233,7 @@ export function insertMarkdownText({
}
} else if (tag.indexOf(textPlaceholder) > -1) {
textToInsert = tag.replace(textPlaceholder, () =>
- selected.replace(/\\n/g, '\n').replace('%br', '\\n'),
+ selected.replace(/\\n/g, '\n').replace(/%br/g, '\\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 c70d23d06ec..e53a39cde06 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -5,6 +5,12 @@ const PATH_SEPARATOR_LEADING_REGEX = new RegExp(`^${PATH_SEPARATOR}+`);
const PATH_SEPARATOR_ENDING_REGEX = new RegExp(`${PATH_SEPARATOR}+$`);
const SHA_REGEX = /[\da-f]{40}/gi;
+// About GitLab default host (overwrite in jh)
+export const PROMO_HOST = 'about.gitlab.com';
+
+// About Gitlab default url (overwrite in jh)
+export const PROMO_URL = `https://${PROMO_HOST}`;
+
// Reset the cursor in a Regex so that multiple uses before a recompile don't fail
function resetRegExp(regex) {
regex.lastIndex = 0; /* eslint-disable-line no-param-reassign */