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:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-08-18 11:17:02 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-08-18 11:17:02 +0300
commitb39512ed755239198a9c294b6a45e65c05900235 (patch)
treed234a3efade1de67c46b9e5a38ce813627726aa7 /app/assets/javascripts/pages
parentd31474cf3b17ece37939d20082b07f6657cc79a9 (diff)
Add latest changes from gitlab-org/gitlab@15-3-stable-eev15.3.0-rc42
Diffstat (limited to 'app/assets/javascripts/pages')
-rw-r--r--app/assets/javascripts/pages/groups/new/components/app.vue73
-rw-r--r--app/assets/javascripts/pages/groups/new/components/create_group_description_details.vue56
-rw-r--r--app/assets/javascripts/pages/groups/new/index.js11
-rw-r--r--app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue2
-rw-r--r--app/assets/javascripts/pages/profiles/personal_access_tokens/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/ci/secure_files/show/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/graphs/charts/index.js23
-rw-r--r--app/assets/javascripts/pages/projects/graphs/components/code_coverage.vue42
-rw-r--r--app/assets/javascripts/pages/projects/init_blob.js9
-rw-r--r--app/assets/javascripts/pages/projects/issues/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/pages/new/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js41
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue42
-rw-r--r--app/assets/javascripts/pages/projects/tags/releases/index.js6
-rw-r--r--app/assets/javascripts/pages/registrations/new/index.js5
-rw-r--r--app/assets/javascripts/pages/sessions/new/email_format_validator.js46
-rw-r--r--app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue55
17 files changed, 339 insertions, 82 deletions
diff --git a/app/assets/javascripts/pages/groups/new/components/app.vue b/app/assets/javascripts/pages/groups/new/components/app.vue
index 713287f65b4..f01e5e595a3 100644
--- a/app/assets/javascripts/pages/groups/new/components/app.vue
+++ b/app/assets/javascripts/pages/groups/new/components/app.vue
@@ -2,51 +2,74 @@
import importGroupIllustration from '@gitlab/svgs/dist/illustrations/group-import.svg';
import newGroupIllustration from '@gitlab/svgs/dist/illustrations/group-new.svg';
-import { s__ } from '~/locale';
+import { __, s__ } from '~/locale';
import NewNamespacePage from '~/vue_shared/new_namespace/new_namespace_page.vue';
import createGroupDescriptionDetails from './create_group_description_details.vue';
-const PANELS = [
- {
- name: 'create-group-pane',
- selector: '#create-group-pane',
- title: s__('GroupsNew|Create group'),
- description: s__(
- 'GroupsNew|Assemble related projects together and grant members access to several projects at once.',
- ),
- illustration: newGroupIllustration,
- details: createGroupDescriptionDetails,
- },
- {
- name: 'import-group-pane',
- selector: '#import-group-pane',
- title: s__('GroupsNew|Import group'),
- description: s__('GroupsNew|Import a group and related data from another GitLab instance.'),
- illustration: importGroupIllustration,
- details: 'Migrate your existing groups from another instance of GitLab.',
- },
-];
-
export default {
components: {
NewNamespacePage,
},
props: {
+ parentGroupName: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ importExistingGroupPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
hasErrors: {
type: Boolean,
required: false,
default: false,
},
},
- PANELS,
+ computed: {
+ initialBreadcrumb() {
+ return this.parentGroupName || __('New group');
+ },
+ panels() {
+ return [
+ {
+ name: 'create-group-pane',
+ selector: '#create-group-pane',
+ title: this.parentGroupName
+ ? s__('GroupsNew|Create subgroup')
+ : s__('GroupsNew|Create group'),
+ description: s__(
+ 'GroupsNew|Assemble related projects together and grant members access to several projects at once.',
+ ),
+ illustration: newGroupIllustration,
+ details: createGroupDescriptionDetails,
+ detailProps: {
+ parentGroupName: this.parentGroupName,
+ importExistingGroupPath: this.importExistingGroupPath,
+ },
+ },
+ {
+ name: 'import-group-pane',
+ selector: '#import-group-pane',
+ title: s__('GroupsNew|Import group'),
+ description: s__(
+ 'GroupsNew|Import a group and related data from another GitLab instance.',
+ ),
+ illustration: importGroupIllustration,
+ details: 'Migrate your existing groups from another instance of GitLab.',
+ },
+ ];
+ },
+ },
};
</script>
<template>
<new-namespace-page
:jump-to-last-persisted-panel="hasErrors"
- :initial-breadcrumb="__('New group')"
- :panels="$options.PANELS"
+ :initial-breadcrumb="initialBreadcrumb"
+ :panels="panels"
:title="s__('GroupsNew|Create new group')"
persistence-key="new_group_last_active_tab"
/>
diff --git a/app/assets/javascripts/pages/groups/new/components/create_group_description_details.vue b/app/assets/javascripts/pages/groups/new/components/create_group_description_details.vue
index 35193171fb8..be8542628c4 100644
--- a/app/assets/javascripts/pages/groups/new/components/create_group_description_details.vue
+++ b/app/assets/javascripts/pages/groups/new/components/create_group_description_details.vue
@@ -1,6 +1,22 @@
<script>
import { GlSprintf, GlLink } from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
+import { s__ } from '~/locale';
+
+const DESCRIPTION_DETAILS = {
+ group: [
+ s__(
+ 'GroupsNew|%{linkStart}Groups%{linkEnd} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects.',
+ ),
+ s__('GroupsNew|Groups can also be nested by creating %{linkStart}subgroups%{linkEnd}.'),
+ ],
+ subgroup: [
+ s__(
+ 'GroupsNew|%{groupsLinkStart}Groups%{groupsLinkEnd} and %{subgroupsLinkStart}subgroups%{subgroupsLinkEnd} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects.',
+ ),
+ s__('GroupsNew|You can also %{linkStart}import an existing group%{linkEnd}.'),
+ ],
+};
export default {
components: {
@@ -11,30 +27,46 @@ export default {
groupsHelpPath: helpPagePath('user/group/index'),
subgroupsHelpPath: helpPagePath('user/group/subgroups/index'),
},
+ props: {
+ parentGroupName: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ importExistingGroupPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ descriptionDetails: DESCRIPTION_DETAILS,
};
</script>
<template>
<div>
<p>
- <gl-sprintf
- :message="
- s__(
- 'GroupsNew|%{linkStart}Groups%{linkEnd} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects.',
- )
- "
- >
+ <gl-sprintf v-if="parentGroupName" :message="$options.descriptionDetails.subgroup[0]">
+ <template #groupsLink="{ content }">
+ <gl-link :href="$options.paths.groupsHelpPath" target="_blank">{{ content }}</gl-link>
+ </template>
+ <template #subgroupsLink="{ content }">
+ <gl-link :href="$options.paths.subgroupsHelpPath" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ <gl-sprintf v-else :message="$options.descriptionDetails.group[0]">
<template #link="{ content }">
<gl-link :href="$options.paths.groupsHelpPath" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
<p>
- <gl-sprintf
- :message="
- s__('GroupsNew|Groups can also be nested by creating %{linkStart}subgroups%{linkEnd}.')
- "
- >
+ <gl-sprintf v-if="parentGroupName" :message="$options.descriptionDetails.subgroup[1]">
+ <template #link="{ content }">
+ <gl-link :href="importExistingGroupPath">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ <gl-sprintf v-else :message="$options.descriptionDetails.group[1]">
<template #link="{ content }">
<gl-link :href="$options.paths.subgroupsHelpPath" target="_blank">{{ content }}</gl-link>
</template>
diff --git a/app/assets/javascripts/pages/groups/new/index.js b/app/assets/javascripts/pages/groups/new/index.js
index 7c409010510..7dab5258b24 100644
--- a/app/assets/javascripts/pages/groups/new/index.js
+++ b/app/assets/javascripts/pages/groups/new/index.js
@@ -16,9 +16,18 @@ BindInOut.initAll();
initFilePickers();
function initNewGroupCreation(el) {
- const { hasErrors, verificationRequired, verificationFormUrl, subscriptionsUrl } = el.dataset;
+ const {
+ hasErrors,
+ parentGroupName,
+ importExistingGroupPath,
+ verificationRequired,
+ verificationFormUrl,
+ subscriptionsUrl,
+ } = el.dataset;
const props = {
+ parentGroupName,
+ importExistingGroupPath,
hasErrors: parseBoolean(hasErrors),
};
diff --git a/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue b/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue
index 6748a62e777..9cce6723bf7 100644
--- a/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue
+++ b/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue
@@ -68,7 +68,7 @@ export default {
}),
tableCell({
key: 'created_at',
- label: __('Date'),
+ label: __('Start date'),
}),
tableCell({
key: 'status',
diff --git a/app/assets/javascripts/pages/profiles/personal_access_tokens/index.js b/app/assets/javascripts/pages/profiles/personal_access_tokens/index.js
index 3fae9809e51..c520042c172 100644
--- a/app/assets/javascripts/pages/profiles/personal_access_tokens/index.js
+++ b/app/assets/javascripts/pages/profiles/personal_access_tokens/index.js
@@ -2,12 +2,10 @@ import {
initAccessTokenTableApp,
initExpiresAtField,
initNewAccessTokenApp,
- initProjectsField,
initTokensApp,
} from '~/access_tokens';
initAccessTokenTableApp();
initExpiresAtField();
initNewAccessTokenApp();
-initProjectsField();
initTokensApp();
diff --git a/app/assets/javascripts/pages/projects/ci/secure_files/show/index.js b/app/assets/javascripts/pages/projects/ci/secure_files/show/index.js
deleted file mode 100644
index 61486606665..00000000000
--- a/app/assets/javascripts/pages/projects/ci/secure_files/show/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import { initCiSecureFiles } from '~/ci_secure_files';
-
-initCiSecureFiles();
diff --git a/app/assets/javascripts/pages/projects/graphs/charts/index.js b/app/assets/javascripts/pages/projects/graphs/charts/index.js
index c217bc5a727..65e7f48ed24 100644
--- a/app/assets/javascripts/pages/projects/graphs/charts/index.js
+++ b/app/assets/javascripts/pages/projects/graphs/charts/index.js
@@ -55,18 +55,30 @@ waitForCSSLoaded(() => {
},
attrs: {
height: LANGUAGE_CHART_HEIGHT,
+ responsive: true,
},
});
},
});
+ const {
+ graphEndpoint,
+ graphEndDate,
+ graphStartDate,
+ graphRef,
+ graphCsvPath,
+ } = codeCoverageContainer.dataset;
// eslint-disable-next-line no-new
new Vue({
el: codeCoverageContainer,
render(h) {
return h(CodeCoverage, {
props: {
- graphEndpoint: codeCoverageContainer.dataset?.graphEndpoint,
+ graphEndpoint,
+ graphEndDate,
+ graphStartDate,
+ graphRef,
+ graphCsvPath,
},
});
},
@@ -92,6 +104,9 @@ waitForCSSLoaded(() => {
yAxisTitle: __('No. of commits'),
xAxisType: 'category',
},
+ attrs: {
+ responsive: true,
+ },
});
},
});
@@ -125,6 +140,9 @@ waitForCSSLoaded(() => {
yAxisTitle: __('No. of commits'),
xAxisType: 'category',
},
+ attrs: {
+ responsive: true,
+ },
});
},
});
@@ -149,6 +167,9 @@ waitForCSSLoaded(() => {
yAxisTitle: __('No. of commits'),
xAxisType: 'category',
},
+ attrs: {
+ responsive: true,
+ },
});
},
});
diff --git a/app/assets/javascripts/pages/projects/graphs/components/code_coverage.vue b/app/assets/javascripts/pages/projects/graphs/components/code_coverage.vue
index 92ae8128285..d7e68484143 100644
--- a/app/assets/javascripts/pages/projects/graphs/components/code_coverage.vue
+++ b/app/assets/javascripts/pages/projects/graphs/components/code_coverage.vue
@@ -1,5 +1,5 @@
<script>
-import { GlAlert, GlDropdown, GlDropdownItem, GlSprintf } from '@gitlab/ui';
+import { GlAlert, GlButton, GlDropdown, GlDropdownItem, GlSprintf } from '@gitlab/ui';
import { GlAreaChart } from '@gitlab/ui/dist/charts';
import { get } from 'lodash';
import { formatDate } from '~/lib/utils/datetime_utility';
@@ -11,6 +11,7 @@ export default {
components: {
GlAlert,
GlAreaChart,
+ GlButton,
GlDropdown,
GlDropdownItem,
GlSprintf,
@@ -20,6 +21,22 @@ export default {
type: String,
required: true,
},
+ graphEndDate: {
+ type: String,
+ required: true,
+ },
+ graphStartDate: {
+ type: String,
+ required: true,
+ },
+ graphRef: {
+ type: String,
+ required: true,
+ },
+ graphCsvPath: {
+ type: String,
+ required: true,
+ },
},
data() {
return {
@@ -119,6 +136,28 @@ export default {
<template>
<div>
+ <div
+ class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-border-t gl-pt-4 gl-mb-3"
+ >
+ <h4 class="gl-m-0" sub-header>
+ <gl-sprintf
+ :message="__('Code coverage statistics for %{ref} %{start_date} - %{end_date}')"
+ >
+ <template #ref>
+ <strong> {{ graphRef }} </strong>
+ </template>
+ <template #start_date>
+ <strong> {{ graphStartDate }} </strong>
+ </template>
+ <template #end_date>
+ <strong> {{ graphEndDate }} </strong>
+ </template>
+ </gl-sprintf>
+ </h4>
+ <gl-button v-if="canShowData" size="small" data-testid="download-button" :href="graphCsvPath">
+ {{ __('Download raw data (.csv)') }}
+ </gl-button>
+ </div>
<div class="gl-mt-3 gl-mb-3">
<gl-alert
v-if="hasFetchError"
@@ -155,6 +194,7 @@ export default {
:data="chartData"
:option="chartOptions"
:format-tooltip-text="formatTooltipText"
+ responsive
>
<template v-if="canShowData" #tooltip-title>
{{ tooltipTitle }}
diff --git a/app/assets/javascripts/pages/projects/init_blob.js b/app/assets/javascripts/pages/projects/init_blob.js
index 7db34816cfe..f7849e8d588 100644
--- a/app/assets/javascripts/pages/projects/init_blob.js
+++ b/app/assets/javascripts/pages/projects/init_blob.js
@@ -4,6 +4,7 @@ import BlobForkSuggestion from '~/blob/blob_fork_suggestion';
import BlobLinePermalinkUpdater from '~/blob/blob_line_permalink_updater';
import LineHighlighter from '~/blob/line_highlighter';
import initBlobBundle from '~/blob_edit/blob_bundle';
+import addBlobLinksTracking from '~/blob/blob_links_tracking';
export default () => {
new LineHighlighter(); // eslint-disable-line no-new
@@ -11,10 +12,16 @@ export default () => {
// eslint-disable-next-line no-new
new BlobLinePermalinkUpdater(
document.querySelector('#blob-content-holder'),
- '.diff-line-num[data-line-number], .diff-line-num[data-line-number] *',
+ '.file-line-num[data-line-number], .file-line-num[data-line-number] *',
document.querySelectorAll('.js-data-file-blob-permalink-url, .js-blob-blame-link'),
);
+ const eventsToTrack = [
+ { selector: '.file-line-blame', property: 'blame' },
+ { selector: '.file-line-num', property: 'link' },
+ ];
+ addBlobLinksTracking('#blob-content-holder', eventsToTrack);
+
const fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
const fileBlobPermalinkUrl =
fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
diff --git a/app/assets/javascripts/pages/projects/issues/show/index.js b/app/assets/javascripts/pages/projects/issues/show/index.js
index ca2b1a08be8..c92958cd8c7 100644
--- a/app/assets/javascripts/pages/projects/issues/show/index.js
+++ b/app/assets/javascripts/pages/projects/issues/show/index.js
@@ -1,6 +1,6 @@
import { initShow } from '~/issues';
import { store } from '~/notes/stores';
-import initRelatedIssues from '~/related_issues';
+import { initRelatedIssues } from '~/related_issues';
import initSidebarBundle from '~/sidebar/sidebar_bundle';
import initWorkItemLinks from '~/work_items/components/work_item_links';
diff --git a/app/assets/javascripts/pages/projects/pages/new/index.js b/app/assets/javascripts/pages/projects/pages/new/index.js
new file mode 100644
index 00000000000..a5157f5b01b
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/pages/new/index.js
@@ -0,0 +1,3 @@
+import initPages from '~/gitlab_pages/new';
+
+initPages();
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js b/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js
index cd4bc35e74e..9513f42d9c9 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js
@@ -1,4 +1,6 @@
import Vue from 'vue';
+import { BV_SHOW_MODAL } from '~/lib/utils/constants';
+import PipelineSchedulesTakeOwnershipModal from '~/pipeline_schedules/components/take_ownership_modal.vue';
import PipelineSchedulesCallout from '../shared/components/pipeline_schedules_callout.vue';
function initPipelineSchedules() {
@@ -23,4 +25,43 @@ function initPipelineSchedules() {
});
}
+function initTakeownershipModal() {
+ const modalId = 'pipeline-take-ownership-modal';
+ const buttonSelector = 'js-take-ownership-button';
+ const el = document.getElementById(modalId);
+ const takeOwnershipButtons = document.querySelectorAll(`.${buttonSelector}`);
+
+ if (!el) {
+ return;
+ }
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el,
+ data() {
+ return {
+ url: '',
+ };
+ },
+ mounted() {
+ takeOwnershipButtons.forEach((button) => {
+ button.addEventListener('click', () => {
+ const { url } = button.dataset;
+
+ this.url = url;
+ this.$root.$emit(BV_SHOW_MODAL, modalId, `.${buttonSelector}`);
+ });
+ });
+ },
+ render(createElement) {
+ return createElement(PipelineSchedulesTakeOwnershipModal, {
+ props: {
+ ownershipUrl: this.url,
+ },
+ });
+ },
+ });
+}
+
initPipelineSchedules();
+initTakeownershipModal();
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
index f2c30870a68..c7c331c7de5 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
@@ -29,6 +29,10 @@ export default {
lfsLabel: s__('ProjectSettings|Git Large File Storage (LFS)'),
mergeRequestsLabel: s__('ProjectSettings|Merge requests'),
operationsLabel: s__('ProjectSettings|Operations'),
+ environmentsLabel: s__('ProjectSettings|Environments'),
+ environmentsHelpText: s__(
+ 'ProjectSettings|Every project can make deployments to environments either via CI/CD or API calls. Non-project members have read-only access.',
+ ),
packagesHelpText: s__(
'ProjectSettings|Every project can have its own space to store its packages. Note: The Package Registry is always visible when a project is public.',
),
@@ -209,6 +213,7 @@ export default {
requirementsAccessLevel: featureAccessLevel.EVERYONE,
securityAndComplianceAccessLevel: featureAccessLevel.PROJECT_MEMBERS,
operationsAccessLevel: featureAccessLevel.EVERYONE,
+ environmentsAccessLevel: featureAccessLevel.EVERYONE,
containerRegistryAccessLevel: featureAccessLevel.EVERYONE,
warnAboutPotentiallyUnwantedCharacters: true,
lfsEnabled: true,
@@ -282,6 +287,9 @@ export default {
return this.operationsAccessLevel > featureAccessLevel.NOT_ENABLED;
},
+ environmentsEnabled() {
+ return this.environmentsAccessLevel > featureAccessLevel.NOT_ENABLED;
+ },
repositoryEnabled() {
return this.repositoryAccessLevel > featureAccessLevel.NOT_ENABLED;
},
@@ -318,12 +326,8 @@ export default {
packageRegistryAccessLevelEnabled() {
return this.glFeatures.packageRegistryAccessLevel;
},
- showAdditonalSettings() {
- if (this.glFeatures.enforceAuthChecksOnUploads) {
- return true;
- }
-
- return this.visibilityLevel !== this.visibilityOptions.PRIVATE;
+ splitOperationsEnabled() {
+ return this.glFeatures.splitOperationsVisibilityPermissions;
},
},
@@ -381,6 +385,10 @@ export default {
featureAccessLevel.PROJECT_MEMBERS,
this.operationsAccessLevel,
);
+ this.environmentsAccessLevel = Math.min(
+ featureAccessLevel.PROJECT_MEMBERS,
+ this.environmentsAccessLevel,
+ );
this.containerRegistryAccessLevel = Math.min(
featureAccessLevel.PROJECT_MEMBERS,
this.containerRegistryAccessLevel,
@@ -422,6 +430,8 @@ export default {
this.requirementsAccessLevel = featureAccessLevel.EVERYONE;
if (this.operationsAccessLevel === featureAccessLevel.PROJECT_MEMBERS)
this.operationsAccessLevel = featureAccessLevel.EVERYONE;
+ if (this.environmentsAccessLevel === featureAccessLevel.PROJECT_MEMBERS)
+ this.environmentsAccessLevel = featureAccessLevel.EVERYONE;
if (this.containerRegistryAccessLevel === featureAccessLevel.PROJECT_MEMBERS)
this.containerRegistryAccessLevel = featureAccessLevel.EVERYONE;
@@ -545,7 +555,7 @@ export default {
</template>
</gl-sprintf>
</span>
- <div v-if="showAdditonalSettings" class="gl-mt-4">
+ <div class="gl-mt-4">
<strong class="gl-display-block">{{ s__('ProjectSettings|Additional options') }}</strong>
<label
v-if="visibilityLevel !== visibilityOptions.PRIVATE"
@@ -560,9 +570,7 @@ export default {
{{ s__('ProjectSettings|Users can request access') }}
</label>
<label
- v-if="
- visibilityLevel !== visibilityOptions.PUBLIC && glFeatures.enforceAuthChecksOnUploads
- "
+ v-if="visibilityLevel !== visibilityOptions.PUBLIC"
class="gl-line-height-28 gl-font-weight-normal gl-display-block gl-mb-0"
>
<input
@@ -866,6 +874,20 @@ export default {
/>
</project-setting-row>
</div>
+ <template v-if="splitOperationsEnabled">
+ <project-setting-row
+ ref="environments-settings"
+ :label="$options.i18n.environmentsLabel"
+ :help-text="$options.i18n.environmentsHelpText"
+ >
+ <project-feature-setting
+ v-model="environmentsAccessLevel"
+ :label="$options.i18n.environmentsLabel"
+ :options="featureAccessLevelOptions"
+ name="project[project_feature_attributes][environments_access_level]"
+ />
+ </project-setting-row>
+ </template>
</div>
<project-setting-row v-if="canDisableEmails" ref="email-settings" class="mb-3">
<label class="js-emails-disabled">
diff --git a/app/assets/javascripts/pages/projects/tags/releases/index.js b/app/assets/javascripts/pages/projects/tags/releases/index.js
deleted file mode 100644
index cafd880b4be..00000000000
--- a/app/assets/javascripts/pages/projects/tags/releases/index.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import $ from 'jquery';
-import GLForm from '~/gl_form';
-import ZenMode from '~/zen_mode';
-
-new ZenMode(); // eslint-disable-line no-new
-new GLForm($('.release-form')); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/registrations/new/index.js b/app/assets/javascripts/pages/registrations/new/index.js
index 94a5c1cb29b..897acf9b02c 100644
--- a/app/assets/javascripts/pages/registrations/new/index.js
+++ b/app/assets/javascripts/pages/registrations/new/index.js
@@ -3,12 +3,17 @@ import { trackNewRegistrations } from '~/google_tag_manager';
import NoEmojiValidator from '~/emoji/no_emoji_validator';
import LengthValidator from '~/pages/sessions/new/length_validator';
import UsernameValidator from '~/pages/sessions/new/username_validator';
+import EmailFormatValidator from '~/pages/sessions/new/email_format_validator';
import Tracking from '~/tracking';
new UsernameValidator(); // eslint-disable-line no-new
new LengthValidator(); // eslint-disable-line no-new
new NoEmojiValidator(); // eslint-disable-line no-new
+if (gon.features.trialEmailValidation) {
+ new EmailFormatValidator(); // eslint-disable-line no-new
+}
+
trackNewRegistrations();
Tracking.enableFormTracking({
diff --git a/app/assets/javascripts/pages/sessions/new/email_format_validator.js b/app/assets/javascripts/pages/sessions/new/email_format_validator.js
new file mode 100644
index 00000000000..6dcf3b50dca
--- /dev/null
+++ b/app/assets/javascripts/pages/sessions/new/email_format_validator.js
@@ -0,0 +1,46 @@
+import InputValidator from '~/validators/input_validator';
+
+// It checks if email contains at least one character, number or whatever except
+// another "@" or whitespace before "@", at least two characters except
+// another "@" or whitespace after "@" and one dot in between
+const emailRegexPattern = /[^@\s]+@[^@\s]+\.[^@\s]+/;
+const hintMessageSelector = '.validation-hint';
+const warningMessageSelector = '.validation-warning';
+
+export default class EmailFormatValidator extends InputValidator {
+ constructor(opts = {}) {
+ super();
+
+ const container = opts.container || '';
+
+ document
+ .querySelectorAll(`${container} .js-validate-email`)
+ .forEach((element) =>
+ element.addEventListener('keyup', EmailFormatValidator.eventHandler.bind(this)),
+ );
+ }
+
+ static eventHandler(event) {
+ const inputDomElement = event.target;
+
+ EmailFormatValidator.setMessageVisibility(inputDomElement, hintMessageSelector);
+ EmailFormatValidator.setMessageVisibility(inputDomElement, warningMessageSelector);
+ EmailFormatValidator.validateEmailInput(inputDomElement);
+ }
+
+ static validateEmailInput(inputDomElement) {
+ const validEmail = inputDomElement.checkValidity();
+ const validPattern = inputDomElement.value.match(emailRegexPattern);
+
+ EmailFormatValidator.setMessageVisibility(
+ inputDomElement,
+ warningMessageSelector,
+ validEmail && !validPattern,
+ );
+ }
+
+ static setMessageVisibility(inputDomElement, messageSelector, isVisible = false) {
+ const messageElement = inputDomElement.parentElement.querySelector(messageSelector);
+ messageElement.classList.toggle('hide', !isVisible);
+ }
+}
diff --git a/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue b/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue
index 3c22844434d..9d7d9e376cf 100644
--- a/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue
+++ b/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue
@@ -9,6 +9,7 @@ import {
GlFormGroup,
GlFormInput,
GlFormSelect,
+ GlSegmentedControl,
} from '@gitlab/ui';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import axios from '~/lib/utils/axios_utils';
@@ -81,9 +82,11 @@ export default {
newPage: s__('WikiPage|Create page'),
},
cancel: s__('WikiPage|Cancel'),
- editSourceButtonText: s__('WikiPage|Edit source'),
- editRichTextButtonText: s__('WikiPage|Edit rich text'),
},
+ switchEditingControlOptions: [
+ { text: s__('Wiki Page|Source'), value: 'source' },
+ { text: s__('Wiki Page|Rich text'), value: 'richText' },
+ ],
components: {
GlAlert,
GlIcon,
@@ -94,6 +97,7 @@ export default {
GlSprintf,
GlLink,
GlButton,
+ GlSegmentedControl,
MarkdownField,
LocalStorageSync,
ContentEditor: () =>
@@ -105,14 +109,15 @@ export default {
inject: ['formatOptions', 'pageInfo'],
data() {
return {
+ editingMode: 'source',
title: this.pageInfo.title?.trim() || '',
format: this.pageInfo.format || 'markdown',
content: this.pageInfo.content || '',
- useContentEditor: false,
commitMessage: '',
isDirty: false,
contentEditorRenderFailed: false,
contentEditorEmpty: false,
+ switchEditingControlDisabled: false,
};
},
computed: {
@@ -177,6 +182,9 @@ export default {
isContentEditorActive() {
return this.isMarkdownFormat && this.useContentEditor;
},
+ useContentEditor() {
+ return this.editingMode === 'richText';
+ },
},
mounted() {
this.updateCommitMessage();
@@ -193,16 +201,15 @@ export default {
.then(({ data }) => data.body);
},
- toggleEditingMode() {
- if (this.useContentEditor) {
+ toggleEditingMode(editingMode) {
+ this.editingMode = editingMode;
+ if (!this.useContentEditor && this.contentEditor) {
this.content = this.contentEditor.getSerializedContent();
}
-
- this.useContentEditor = !this.useContentEditor;
},
- setUseContentEditor(value) {
- this.useContentEditor = value;
+ setEditingMode(value) {
+ this.editingMode = value;
},
async handleFormSubmit(e) {
@@ -294,6 +301,14 @@ export default {
},
});
},
+
+ enableSwitchEditingControl() {
+ this.switchEditingControlDisabled = false;
+ },
+
+ disableSwitchEditingControl() {
+ this.switchEditingControlDisabled = true;
+ },
},
};
</script>
@@ -372,20 +387,21 @@ export default {
<div class="row" data-testid="wiki-form-content-fieldset">
<div class="col-sm-12 row-sm-5">
<gl-form-group>
- <div v-if="isMarkdownFormat" class="gl-display-flex gl-justify-content-end gl-mb-3">
- <gl-button
+ <div v-if="isMarkdownFormat" class="gl-display-flex gl-justify-content-start gl-mb-3">
+ <gl-segmented-control
data-testid="toggle-editing-mode-button"
data-qa-selector="editing_mode_button"
- :data-qa-mode="toggleEditingModeButtonText"
- variant="link"
- @click="toggleEditingMode"
- >{{ toggleEditingModeButtonText }}</gl-button
- >
+ class="gl-display-flex"
+ :checked="editingMode"
+ :options="$options.switchEditingControlOptions"
+ :disabled="switchEditingControlDisabled"
+ @input="toggleEditingMode"
+ />
</div>
<local-storage-sync
storage-key="gl-wiki-content-editor-enabled"
- :value="useContentEditor"
- @input="setUseContentEditor"
+ :value="editingMode"
+ @input="setEditingMode"
/>
<markdown-field
v-if="!isContentEditorActive"
@@ -422,6 +438,9 @@ export default {
:uploads-path="pageInfo.uploadsPath"
@initialized="loadInitialContent"
@change="handleContentEditorChange"
+ @loading="disableSwitchEditingControl"
+ @loadingSuccess="enableSwitchEditingControl"
+ @loadingError="enableSwitchEditingControl"
/>
<input id="wiki_content" v-model.trim="content" type="hidden" name="wiki[content]" />
</div>