diff options
Diffstat (limited to 'app/assets/javascripts/pages')
14 files changed, 166 insertions, 161 deletions
diff --git a/app/assets/javascripts/pages/dashboard/issues/index.js b/app/assets/javascripts/pages/dashboard/issues/index.js index 9055738f86e..2ffeed8a584 100644 --- a/app/assets/javascripts/pages/dashboard/issues/index.js +++ b/app/assets/javascripts/pages/dashboard/issues/index.js @@ -2,6 +2,7 @@ import projectSelect from '~/project_select'; import initFilteredSearch from '~/pages/search/init_filtered_search'; import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys'; import { FILTERED_SEARCH } from '~/pages/constants'; +import initManualOrdering from '~/manual_ordering'; document.addEventListener('DOMContentLoaded', () => { initFilteredSearch({ @@ -10,4 +11,5 @@ document.addEventListener('DOMContentLoaded', () => { }); projectSelect(); + initManualOrdering(); }); diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js index 35d4b034654..23fb5656008 100644 --- a/app/assets/javascripts/pages/groups/issues/index.js +++ b/app/assets/javascripts/pages/groups/issues/index.js @@ -2,6 +2,7 @@ import projectSelect from '~/project_select'; import initFilteredSearch from '~/pages/search/init_filtered_search'; import { FILTERED_SEARCH } from '~/pages/constants'; import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys'; +import initManualOrdering from '~/manual_ordering'; document.addEventListener('DOMContentLoaded', () => { IssuableFilteredSearchTokenKeys.addExtraTokensForIssues(); @@ -12,4 +13,5 @@ document.addEventListener('DOMContentLoaded', () => { filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys, }); projectSelect(); + initManualOrdering(); }); diff --git a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js index 10cd8ecfbc9..820f0f7f12d 100644 --- a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js +++ b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js @@ -6,9 +6,7 @@ document.addEventListener('DOMContentLoaded', () => { const twoFactorNode = document.querySelector('.js-two-factor-auth'); const skippable = parseBoolean(twoFactorNode.dataset.twoFactorSkippable); if (skippable) { - const button = `<a class="btn btn-sm btn-warning float-right" data-method="patch" href="${ - twoFactorNode.dataset.two_factor_skip_url - }">Configure it later</a>`; + const button = `<a class="btn btn-sm btn-warning float-right" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a>`; const flashAlert = document.querySelector('.flash-alert .container-fluid'); if (flashAlert) flashAlert.insertAdjacentHTML('beforeend', button); } diff --git a/app/assets/javascripts/pages/projects/branches/index/index.js b/app/assets/javascripts/pages/projects/branches/index/index.js index 8fa266a37ce..37e8c75f299 100644 --- a/app/assets/javascripts/pages/projects/branches/index/index.js +++ b/app/assets/javascripts/pages/projects/branches/index/index.js @@ -1,7 +1,9 @@ import AjaxLoadingSpinner from '~/ajax_loading_spinner'; import DeleteModal from '~/branches/branches_delete_modal'; +import initDiverganceGraph from '~/branches/divergence_graph'; document.addEventListener('DOMContentLoaded', () => { AjaxLoadingSpinner.init(); new DeleteModal(); // eslint-disable-line no-new + initDiverganceGraph(document.querySelector('.js-branch-list').dataset.divergingCountsEndpoint); }); diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js index 377dce6c746..506e6075d16 100644 --- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js +++ b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js @@ -124,11 +124,14 @@ export const ContributorsGraph = (function() { }; ContributorsGraph.prototype.draw_x_axis = function() { - return this.svg - .append('g') - .attr('class', 'x axis') - .attr('transform', 'translate(0, ' + this.height + ')') - .call(this.x_axis); + return ( + this.svg + .append('g') + .attr('class', 'x axis') + /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */ + .attr('transform', 'translate(0, ' + this.height + ')') + .call(this.x_axis) + ); }; ContributorsGraph.prototype.draw_y_axis = function() { @@ -205,6 +208,7 @@ export const ContributorsMasterGraph = (function(superClass) { .attr('height', this.height + this.MARGIN.top + this.MARGIN.bottom) .attr('class', 'tint-box') .append('g') + /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */ .attr('transform', 'translate(' + this.MARGIN.left + ',' + this.MARGIN.top + ')'); return this.svg; }; @@ -354,6 +358,7 @@ export const ContributorsAuthorGraph = (function(superClass) { .attr('height', this.height + this.MARGIN.top + this.MARGIN.bottom) .attr('class', 'spark') .append('g') + /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */ .attr('transform', 'translate(' + this.MARGIN.left + ',' + this.MARGIN.top + ')'); return this.svg; }; diff --git a/app/assets/javascripts/pages/projects/issues/index/index.js b/app/assets/javascripts/pages/projects/issues/index/index.js index c34aff02111..c73ebb31eb3 100644 --- a/app/assets/javascripts/pages/projects/issues/index/index.js +++ b/app/assets/javascripts/pages/projects/issues/index/index.js @@ -7,6 +7,7 @@ import initFilteredSearch from '~/pages/search/init_filtered_search'; import { FILTERED_SEARCH } from '~/pages/constants'; import { ISSUABLE_INDEX } from '~/pages/projects/constants'; import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys'; +import initManualOrdering from '~/manual_ordering'; document.addEventListener('DOMContentLoaded', () => { IssuableFilteredSearchTokenKeys.addExtraTokensForIssues(); @@ -19,4 +20,5 @@ document.addEventListener('DOMContentLoaded', () => { new ShortcutsNavigation(); new UsersSelect(); + initManualOrdering(); }); diff --git a/app/assets/javascripts/pages/projects/pages_domains/form.js b/app/assets/javascripts/pages/projects/pages_domains/form.js index 1d0dbfe0406..cef8e92610c 100644 --- a/app/assets/javascripts/pages/projects/pages_domains/form.js +++ b/app/assets/javascripts/pages/projects/pages_domains/form.js @@ -5,14 +5,6 @@ export default () => { if (toggleContainer) { const onToggleButtonClicked = isAutoSslEnabled => { - Array.from(document.querySelectorAll('.js-shown-if-auto-ssl')).forEach(el => { - if (isAutoSslEnabled) { - el.classList.remove('d-none'); - } else { - el.classList.add('d-none'); - } - }); - Array.from(document.querySelectorAll('.js-shown-unless-auto-ssl')).forEach(el => { if (isAutoSslEnabled) { el.classList.add('d-none'); @@ -21,14 +13,6 @@ export default () => { } }); - Array.from(document.querySelectorAll('.js-enabled-if-auto-ssl')).forEach(el => { - if (isAutoSslEnabled) { - el.removeAttribute('disabled'); - } else { - el.setAttribute('disabled', 'disabled'); - } - }); - Array.from(document.querySelectorAll('.js-enabled-unless-auto-ssl')).forEach(el => { if (isAutoSslEnabled) { el.setAttribute('disabled', 'disabled'); diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue index ff6dadeff7d..533065b2d4d 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue +++ b/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue @@ -1,5 +1,6 @@ <script> -import projectFeatureToggle from '../../../../../vue_shared/components/toggle_button.vue'; +import projectFeatureToggle from '~/vue_shared/components/toggle_button.vue'; +import { featureAccessLevelNone } from '../constants'; export default { components: { @@ -43,7 +44,7 @@ export default { if (this.featureEnabled) { return this.options; } - return [[0, 'Enable feature to choose access level']]; + return [featureAccessLevelNone]; }, displaySelectInput() { 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 dea7c586868..b4d24f3aa36 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 @@ -1,16 +1,26 @@ <script> +import settingsMixin from 'ee_else_ce/pages/projects/shared/permissions/mixins/settings_pannel_mixin'; +import { __ } from '~/locale'; import projectFeatureSetting from './project_feature_setting.vue'; -import projectFeatureToggle from '../../../../../vue_shared/components/toggle_button.vue'; +import projectFeatureToggle from '~/vue_shared/components/toggle_button.vue'; import projectSettingRow from './project_setting_row.vue'; -import { visibilityOptions, visibilityLevelDescriptions } from '../constants'; +import { + visibilityOptions, + visibilityLevelDescriptions, + featureAccessLevelMembers, + featureAccessLevelEveryone, +} from '../constants'; import { toggleHiddenClassBySelector } from '../external'; +const PAGE_FEATURE_ACCESS_LEVEL = __('Everyone'); + export default { components: { projectFeatureSetting, projectFeatureToggle, projectSettingRow, }, + mixins: [settingsMixin], props: { currentSettings: { @@ -37,6 +47,11 @@ export default { required: false, default: false, }, + packagesAvailable: { + type: Boolean, + required: false, + default: false, + }, visibilityHelpPath: { type: String, required: false, @@ -67,8 +82,12 @@ export default { required: false, default: '', }, + packagesHelpPath: { + type: String, + required: false, + default: '', + }, }, - data() { const defaults = { visibilityOptions, @@ -91,9 +110,9 @@ export default { computed: { featureAccessLevelOptions() { - const options = [[10, 'Only Project Members']]; + const options = [featureAccessLevelMembers]; if (this.visibilityLevel !== visibilityOptions.PRIVATE) { - options.push([20, 'Everyone With Access']); + options.push(featureAccessLevelEveryone); } return options; }, @@ -106,7 +125,7 @@ export default { pagesFeatureAccessLevelOptions() { if (this.visibilityLevel !== visibilityOptions.PUBLIC) { - return this.featureAccessLevelOptions.concat([[30, 'Everyone']]); + return this.featureAccessLevelOptions.concat([[30, PAGE_FEATURE_ACCESS_LEVEL]]); } return this.featureAccessLevelOptions; }, @@ -148,24 +167,6 @@ export default { } }, - repositoryAccessLevel(value, oldValue) { - if (value < oldValue) { - // sub-features cannot have more premissive access level - this.mergeRequestsAccessLevel = Math.min(this.mergeRequestsAccessLevel, value); - this.buildsAccessLevel = Math.min(this.buildsAccessLevel, value); - - if (value === 0) { - this.containerRegistryEnabled = false; - this.lfsEnabled = false; - } - } else if (oldValue === 0) { - this.mergeRequestsAccessLevel = value; - this.buildsAccessLevel = value; - this.containerRegistryEnabled = true; - this.lfsEnabled = true; - } - }, - issuesAccessLevel(value, oldValue) { if (value === 0) toggleHiddenClassBySelector('.issues-feature', true); else if (oldValue === 0) toggleHiddenClassBySelector('.issues-feature', false); @@ -207,23 +208,20 @@ export default { <option :value="visibilityOptions.PRIVATE" :disabled="!visibilityAllowed(visibilityOptions.PRIVATE)" + >{{ __('Private') }}</option > - Private - </option> <option :value="visibilityOptions.INTERNAL" :disabled="!visibilityAllowed(visibilityOptions.INTERNAL)" + >{{ __('Internal') }}</option > - Internal - </option> <option :value="visibilityOptions.PUBLIC" :disabled="!visibilityAllowed(visibilityOptions.PUBLIC)" + >{{ __('Public') }}</option > - Public - </option> </select> - <i aria-hidden="true" data-hidden="true" class="fa fa-chevron-down"> </i> + <i aria-hidden="true" data-hidden="true" class="fa fa-chevron-down"></i> </div> </div> <span class="form-text text-muted">{{ visibilityLevelDescription }}</span> @@ -299,6 +297,18 @@ export default { name="project[lfs_enabled]" /> </project-setting-row> + <project-setting-row + v-if="packagesAvailable" + :help-path="packagesHelpPath" + label="Packages" + help-text="Every project can have its own space to store its packages" + > + <project-feature-toggle + v-model="packagesEnabled" + :disabled-input="!repositoryEnabled" + name="project[packages_enabled]" + /> + </project-setting-row> </div> <project-setting-row label="Wiki" help-text="Pages for project documentation"> <project-feature-setting diff --git a/app/assets/javascripts/pages/projects/shared/permissions/constants.js b/app/assets/javascripts/pages/projects/shared/permissions/constants.js index ac0dca31c37..73269c6f3ba 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/constants.js +++ b/app/assets/javascripts/pages/projects/shared/permissions/constants.js @@ -15,3 +15,30 @@ export const visibilityLevelDescriptions = { 'The project can be accessed by anyone, regardless of authentication.', ), }; + +const featureAccessLevel = { + NOT_ENABLED: 0, + PROJECT_MEMBERS: 10, + EVERYONE: 20, +}; + +const featureAccessLevelDescriptions = { + [featureAccessLevel.NOT_ENABLED]: __('Enable feature to choose access level'), + [featureAccessLevel.PROJECT_MEMBERS]: __('Only Project Members'), + [featureAccessLevel.EVERYONE]: __('Everyone With Access'), +}; + +export const featureAccessLevelNone = [ + featureAccessLevel.NOT_ENABLED, + featureAccessLevelDescriptions[featureAccessLevel.NOT_ENABLED], +]; + +export const featureAccessLevelMembers = [ + featureAccessLevel.PROJECT_MEMBERS, + featureAccessLevelDescriptions[featureAccessLevel.PROJECT_MEMBERS], +]; + +export const featureAccessLevelEveryone = [ + featureAccessLevel.EVERYONE, + featureAccessLevelDescriptions[featureAccessLevel.EVERYONE], +]; diff --git a/app/assets/javascripts/pages/projects/shared/permissions/mixins/settings_pannel_mixin.js b/app/assets/javascripts/pages/projects/shared/permissions/mixins/settings_pannel_mixin.js new file mode 100644 index 00000000000..fcbd81416f2 --- /dev/null +++ b/app/assets/javascripts/pages/projects/shared/permissions/mixins/settings_pannel_mixin.js @@ -0,0 +1,26 @@ +export default { + data() { + return { + packagesEnabled: false, + }; + }, + watch: { + repositoryAccessLevel(value, oldValue) { + if (value < oldValue) { + // sub-features cannot have more premissive access level + this.mergeRequestsAccessLevel = Math.min(this.mergeRequestsAccessLevel, value); + this.buildsAccessLevel = Math.min(this.buildsAccessLevel, value); + + if (value === 0) { + this.containerRegistryEnabled = false; + this.lfsEnabled = false; + } + } else if (oldValue === 0) { + this.mergeRequestsAccessLevel = value; + this.buildsAccessLevel = value; + this.containerRegistryEnabled = true; + this.lfsEnabled = true; + } + }, + }, +}; diff --git a/app/assets/javascripts/pages/sessions/new/index.js b/app/assets/javascripts/pages/sessions/new/index.js index 3f5a3e15c2c..55bc93a2b13 100644 --- a/app/assets/javascripts/pages/sessions/new/index.js +++ b/app/assets/javascripts/pages/sessions/new/index.js @@ -7,8 +7,8 @@ import OAuthRememberMe from './oauth_remember_me'; import preserveUrlFragment from './preserve_url_fragment'; document.addEventListener('DOMContentLoaded', () => { - new LengthValidator(); // eslint-disable-line no-new new UsernameValidator(); // eslint-disable-line no-new + new LengthValidator(); // eslint-disable-line no-new new SigninTabsMemoizer(); // eslint-disable-line no-new new NoEmojiValidator(); // eslint-disable-line no-new diff --git a/app/assets/javascripts/pages/sessions/new/username_validator.js b/app/assets/javascripts/pages/sessions/new/username_validator.js index 7a41805bada..36d1e773134 100644 --- a/app/assets/javascripts/pages/sessions/new/username_validator.js +++ b/app/assets/javascripts/pages/sessions/new/username_validator.js @@ -1,133 +1,79 @@ -/* eslint-disable consistent-return, class-methods-use-this */ +import InputValidator from '~/validators/input_validator'; -import $ from 'jquery'; import _ from 'underscore'; import axios from '~/lib/utils/axios_utils'; import flash from '~/flash'; import { __ } from '~/locale'; const debounceTimeoutDuration = 1000; +const rootUrl = gon.relative_url_root; const invalidInputClass = 'gl-field-error-outline'; const successInputClass = 'gl-field-success-outline'; -const unavailableMessageSelector = '.username .validation-error'; -const successMessageSelector = '.username .validation-success'; -const pendingMessageSelector = '.username .validation-pending'; -const invalidMessageSelector = '.username .gl-field-error'; +const successMessageSelector = '.validation-success'; +const pendingMessageSelector = '.validation-pending'; +const unavailableMessageSelector = '.validation-error'; -export default class UsernameValidator { - constructor() { - this.inputElement = $('#new_user_username'); - this.inputDomElement = this.inputElement.get(0); - this.state = { - available: false, - valid: false, - pending: false, - empty: true, - }; +export default class UsernameValidator extends InputValidator { + constructor(opts = {}) { + super(); - const debounceTimeout = _.debounce(username => { - this.validateUsername(username); - }, debounceTimeoutDuration); - - this.inputElement.on('keyup.username_check', () => { - const username = this.inputElement.val(); - - this.state.valid = this.inputDomElement.validity.valid; - this.state.empty = !username.length; + const container = opts.container || ''; + const validateLengthElements = document.querySelectorAll(`${container} .js-validate-username`); - if (this.state.valid) { - return debounceTimeout(username); - } - - this.renderState(); - }); + this.debounceValidateInput = _.debounce(inputDomElement => { + UsernameValidator.validateUsernameInput(inputDomElement); + }, debounceTimeoutDuration); - // Override generic field validation - this.inputElement.on('invalid', this.interceptInvalid.bind(this)); + validateLengthElements.forEach(element => + element.addEventListener('input', this.eventHandler.bind(this)), + ); } - renderState() { - // Clear all state - this.clearFieldValidationState(); - - if (this.state.valid && this.state.available) { - return this.setSuccessState(); - } - - if (this.state.empty) { - return this.clearFieldValidationState(); - } - - if (this.state.pending) { - return this.setPendingState(); - } + eventHandler(event) { + const inputDomElement = event.target; - if (!this.state.valid) { - return this.setInvalidState(); - } - - if (!this.state.available) { - return this.setUnavailableState(); - } - } - - interceptInvalid(event) { - event.preventDefault(); - event.stopPropagation(); + UsernameValidator.resetInputState(inputDomElement); + this.debounceValidateInput(inputDomElement); } - validateUsername(username) { - if (this.state.valid) { - this.state.pending = true; - this.state.available = false; - this.renderState(); - axios - .get(`${gon.relative_url_root}/users/${username}/exists`) - .then(({ data }) => this.setAvailabilityState(data.exists)) + static validateUsernameInput(inputDomElement) { + const username = inputDomElement.value; + + if (inputDomElement.checkValidity() && username.length > 0) { + UsernameValidator.setMessageVisibility(inputDomElement, pendingMessageSelector); + UsernameValidator.fetchUsernameAvailability(username) + .then(usernameTaken => { + UsernameValidator.setInputState(inputDomElement, !usernameTaken); + UsernameValidator.setMessageVisibility(inputDomElement, pendingMessageSelector, false); + UsernameValidator.setMessageVisibility( + inputDomElement, + usernameTaken ? unavailableMessageSelector : successMessageSelector, + ); + }) .catch(() => flash(__('An error occurred while validating username'))); } } - setAvailabilityState(usernameTaken) { - if (usernameTaken) { - this.state.available = false; - } else { - this.state.available = true; - } - this.state.pending = false; - this.renderState(); + static fetchUsernameAvailability(username) { + return axios.get(`${rootUrl}/users/${username}/exists`).then(({ data }) => data.exists); } - clearFieldValidationState() { - this.inputElement.siblings('p').hide(); - - this.inputElement.removeClass(invalidInputClass).removeClass(successInputClass); + static setMessageVisibility(inputDomElement, messageSelector, isVisible = true) { + const messageElement = inputDomElement.parentElement.querySelector(messageSelector); + messageElement.classList.toggle('hide', !isVisible); } - setUnavailableState() { - const $usernameUnavailableMessage = this.inputElement.siblings(unavailableMessageSelector); - this.inputElement.addClass(invalidInputClass).removeClass(successInputClass); - $usernameUnavailableMessage.show(); + static setInputState(inputDomElement, success = true) { + inputDomElement.classList.toggle(successInputClass, success); + inputDomElement.classList.toggle(invalidInputClass, !success); } - setSuccessState() { - const $usernameSuccessMessage = this.inputElement.siblings(successMessageSelector); - this.inputElement.addClass(successInputClass).removeClass(invalidInputClass); - $usernameSuccessMessage.show(); - } + static resetInputState(inputDomElement) { + UsernameValidator.setMessageVisibility(inputDomElement, successMessageSelector, false); + UsernameValidator.setMessageVisibility(inputDomElement, unavailableMessageSelector, false); - setPendingState() { - const $usernamePendingMessage = $(pendingMessageSelector); - if (this.state.pending) { - $usernamePendingMessage.show(); - } else { - $usernamePendingMessage.hide(); + if (inputDomElement.checkValidity()) { + inputDomElement.classList.remove(successInputClass, invalidInputClass); } } - - setInvalidState() { - const $inputErrorMessage = $(invalidMessageSelector); - this.inputElement.addClass(invalidInputClass).removeClass(successInputClass); - $inputErrorMessage.show(); - } } diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js index 7f800d20835..1d8b388e935 100644 --- a/app/assets/javascripts/pages/users/user_tabs.js +++ b/app/assets/javascripts/pages/users/user_tabs.js @@ -18,12 +18,12 @@ import UserOverviewBlock from './user_overview_block'; * * <ul class="nav-links"> * <li class="activity-tab active"> - * <a data-action="activity" data-target="#activity" data-toggle="tab" href="/u/username"> + * <a data-action="activity" data-target="#activity" data-toggle="tab" href="/username"> * Activity * </a> * </li> * <li class="groups-tab"> - * <a data-action="groups" data-target="#groups" data-toggle="tab" href="/u/username/groups"> + * <a data-action="groups" data-target="#groups" data-toggle="tab" href="/users/username/groups"> * Groups * </a> * </li> |