diff options
author | Bryce Johnson <bryce@gitlab.com> | 2017-09-07 14:46:23 -0400 |
---|---|---|
committer | Bryce Johnson <bryce@gitlab.com> | 2017-09-07 14:46:23 -0400 |
commit | 3d9b6bc2b98583a5220870025e942077c9303eaf (patch) | |
tree | c938afc9a0e169ab8ff0b6b55b96c2a5b365efee /app/assets/javascripts | |
parent | e4348ae8c221a63d0e2c4e428fcae8c3bca0eb2f (diff) | |
parent | bc955cfc8e75e17897ab25717176209fefbba915 (diff) | |
download | gitlab-ce-3d9b6bc2b98583a5220870025e942077c9303eaf.tar.gz |
Merge branch 'master' into backport-issues-controller-changesbackport-issues-controller-changes
Diffstat (limited to 'app/assets/javascripts')
20 files changed, 658 insertions, 29 deletions
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js index 687f09882a7..16c5d0fa344 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js +++ b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js @@ -35,6 +35,7 @@ document.addEventListener('DOMContentLoaded', () => { propsData: { endpoint: pipelineTableViewEl.dataset.endpoint, helpPagePath: pipelineTableViewEl.dataset.helpPagePath, + autoDevopsHelpPath: pipelineTableViewEl.dataset.helpAutoDevopsPath, }, }).$mount(); pipelineTableViewEl.appendChild(table.$el); diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue index dd751ec97a8..c931e1e0ea5 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_table.vue +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue @@ -13,6 +13,10 @@ type: String, required: true, }, + autoDevopsHelpPath: { + type: String, + required: true, + }, }, mixins: [ pipelinesMixin, @@ -95,6 +99,7 @@ <pipelines-table-component :pipelines="state.pipelines" :update-graph-dropdown="updateGraphDropdown" + :auto-devops-help-path="autoDevopsHelpPath" /> </div> </div> diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 6db0b18ae5a..f3b537c83e2 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -160,6 +160,9 @@ import initChangesDropdown from './init_changes_dropdown'; const filteredSearchManager = new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests'); filteredSearchManager.setup(); } + if (page === 'projects:merge_requests:index') { + new UserCallout({ setCalloutPerProject: true }); + } const pagePrefix = page === 'projects:merge_requests:index' ? 'merge_request_' : 'issue_'; IssuableIndex.init(pagePrefix); @@ -342,6 +345,7 @@ import initChangesDropdown from './init_changes_dropdown'; case 'projects:show': shortcut_handler = new ShortcutsNavigation(); new NotificationsForm(); + new UserCallout({ setCalloutPerProject: true }); if ($('#tree-slider').length) new TreeView(); if ($('.blob-viewer').length) new BlobViewer(); @@ -361,6 +365,9 @@ import initChangesDropdown from './init_changes_dropdown'; case 'projects:pipelines:new': new NewBranchForm($('.js-new-pipeline-form')); break; + case 'projects:pipelines:index': + new UserCallout({ setCalloutPerProject: true }); + break; case 'projects:pipelines:builds': case 'projects:pipelines:failures': case 'projects:pipelines:show': @@ -418,6 +425,7 @@ import initChangesDropdown from './init_changes_dropdown'; new TreeView(); new BlobViewer(); new NewCommitForm($('.js-create-dir-form')); + new UserCallout({ setCalloutPerProject: true }); $('#tree-slider').waitForImages(function() { gl.utils.ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath); }); @@ -569,6 +577,9 @@ import initChangesDropdown from './init_changes_dropdown'; case 'edit': shortcut_handler = new ShortcutsNavigation(); new ProjectNew(); + import(/* webpackChunkName: 'project_permissions' */ './projects/permissions') + .then(permissions => permissions.default()) + .catch(() => {}); break; case 'new': new ProjectNew(); diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 3b3620fe61b..0c3c877ff15 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -243,6 +243,7 @@ import bp from './breakpoints'; propsData: { endpoint: pipelineTableViewEl.dataset.endpoint, helpPagePath: pipelineTableViewEl.dataset.helpPagePath, + autoDevopsHelpPath: pipelineTableViewEl.dataset.helpAutoDevopsPath, }, }).$mount(); diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue index 2ca5ac2912f..f0b44dfa6d8 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_url.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue @@ -1,29 +1,45 @@ <script> -import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; -import tooltip from '../../vue_shared/directives/tooltip'; + import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; + import tooltip from '../../vue_shared/directives/tooltip'; + import popover from '../../vue_shared/directives/popover'; -export default { - props: { - pipeline: { - type: Object, - required: true, + export default { + props: { + pipeline: { + type: Object, + required: true, + }, + autoDevopsHelpPath: { + type: String, + required: true, + }, }, - }, - components: { - userAvatarLink, - }, - directives: { - tooltip, - }, - computed: { - user() { - return this.pipeline.user; + components: { + userAvatarLink, }, - }, -}; + directives: { + tooltip, + popover, + }, + computed: { + user() { + return this.pipeline.user; + }, + popoverOptions() { + return { + html: true, + delay: { hide: 600 }, + trigger: 'hover', + placement: 'top', + title: '<div class="autodevops-title">This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b></div>', + content: `<a class="autodevops-link" href="${this.autoDevopsHelpPath}" target="_blank" rel="noopener noreferrer nofollow">Learn more about Auto DevOps</a>`, + }; + }, + }, + }; </script> <template> - <div class="table-section section-15 hidden-xs hidden-sm"> + <div class="table-section section-15 hidden-xs hidden-sm pipeline-tags"> <a :href="pipeline.path" class="js-pipeline-url-link"> @@ -57,6 +73,13 @@ export default { :title="pipeline.yaml_errors"> yaml invalid </span> + <a + v-if="pipeline.flags.auto_devops" + class="js-pipeline-url-autodevops label label-info autodevops-badge" + v-popover="popoverOptions" + role="button"> + Auto DevOps + </a> <span v-if="pipeline.flags.stuck" class="js-pipeline-url-stuck label label-warning"> diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue index 010063a0240..5e6d6b2fbdc 100644 --- a/app/assets/javascripts/pipelines/components/pipelines.vue +++ b/app/assets/javascripts/pipelines/components/pipelines.vue @@ -25,8 +25,8 @@ return { endpoint: pipelinesData.endpoint, - cssClass: pipelinesData.cssClass, helpPagePath: pipelinesData.helpPagePath, + autoDevopsPath: pipelinesData.helpAutoDevopsPath, newPipelinePath: pipelinesData.newPipelinePath, canCreatePipeline: pipelinesData.canCreatePipeline, allPath: pipelinesData.allPath, @@ -139,9 +139,7 @@ }; </script> <template> - <div - class="pipelines-container" - :class="cssClass"> + <div class="pipelines-container"> <div class="top-area scrolling-tabs-container inner-page-scroll-tabs" v-if="!isLoading && !shouldRenderEmptyState"> @@ -200,6 +198,7 @@ <pipelines-table-component :pipelines="state.pipelines" :update-graph-dropdown="updateGraphDropdown" + :auto-devops-help-path="autoDevopsPath" /> </div> diff --git a/app/assets/javascripts/pipelines/components/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_table.vue index 5088d92209f..7aa0c0e8a7f 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_table.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_table.vue @@ -17,6 +17,10 @@ required: false, default: false, }, + autoDevopsHelpPath: { + type: String, + required: true, + }, }, components: { pipelinesTableRowComponent, @@ -54,6 +58,7 @@ :key="model.id" :pipeline="model" :update-graph-dropdown="updateGraphDropdown" + :auto-devops-help-path="autoDevopsHelpPath" /> </div> </template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue index c3f1c426d8a..5b9bb6c3750 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue @@ -25,6 +25,10 @@ export default { required: false, default: false, }, + autoDevopsHelpPath: { + type: String, + required: true, + }, }, components: { asyncButtonComponent, @@ -218,7 +222,10 @@ export default { </div> </div> - <pipeline-url :pipeline="pipeline" /> + <pipeline-url + :pipeline="pipeline" + :auto-devops-help-path="autoDevopsHelpPath" + /> <div class="table-section section-25"> <div diff --git a/app/assets/javascripts/projects/permissions/components/project_feature_setting.vue b/app/assets/javascripts/projects/permissions/components/project_feature_setting.vue new file mode 100644 index 00000000000..80c5d39f736 --- /dev/null +++ b/app/assets/javascripts/projects/permissions/components/project_feature_setting.vue @@ -0,0 +1,104 @@ +<script> +import projectFeatureToggle from './project_feature_toggle.vue'; + +export default { + props: { + name: { + type: String, + required: false, + default: '', + }, + options: { + type: Array, + required: false, + default: () => [], + }, + value: { + type: Number, + required: false, + default: 0, + }, + disabledInput: { + type: Boolean, + required: false, + default: false, + }, + }, + + components: { + projectFeatureToggle, + }, + + computed: { + featureEnabled() { + return this.value !== 0; + }, + + displayOptions() { + if (this.featureEnabled) { + return this.options; + } + return [ + [0, 'Enable feature to choose access level'], + ]; + }, + + displaySelectInput() { + return this.disabledInput || !this.featureEnabled || this.displayOptions.length < 2; + }, + }, + + model: { + prop: 'value', + event: 'change', + }, + + methods: { + toggleFeature(featureEnabled) { + if (featureEnabled === false || this.options.length < 1) { + this.$emit('change', 0); + } else { + const [firstOptionValue] = this.options[this.options.length - 1]; + this.$emit('change', firstOptionValue); + } + }, + + selectOption(e) { + this.$emit('change', Number(e.target.value)); + }, + }, +}; +</script> + +<template> + <div class="project-feature-controls" :data-for="name"> + <input + v-if="name" + type="hidden" + :name="name" + :value="value" + /> + <project-feature-toggle + :value="featureEnabled" + @change="toggleFeature" + :disabledInput="disabledInput" + /> + <div class="select-wrapper"> + <select + class="form-control project-repo-select select-control" + @change="selectOption" + :disabled="displaySelectInput" + > + <option + v-for="[optionValue, optionName] in displayOptions" + :key="optionValue" + :value="optionValue" + :selected="optionValue === value" + > + {{optionName}} + </option> + </select> + <i aria-hidden="true" class="fa fa-chevron-down"></i> + </div> + </div> +</template> diff --git a/app/assets/javascripts/projects/permissions/components/project_feature_toggle.vue b/app/assets/javascripts/projects/permissions/components/project_feature_toggle.vue new file mode 100644 index 00000000000..2403c60186a --- /dev/null +++ b/app/assets/javascripts/projects/permissions/components/project_feature_toggle.vue @@ -0,0 +1,51 @@ +<script> +export default { + props: { + name: { + type: String, + required: false, + default: '', + }, + value: { + type: Boolean, + required: true, + }, + disabledInput: { + type: Boolean, + required: false, + default: false, + }, + }, + + model: { + prop: 'value', + event: 'change', + }, + + methods: { + toggleFeature() { + if (!this.disabledInput) this.$emit('change', !this.value); + }, + }, +}; +</script> + +<template> + <label class="toggle-wrapper"> + <input + v-if="name" + type="hidden" + :name="name" + :value="value" + /> + <button + type="button" + aria-label="Toggle" + class="project-feature-toggle" + data-enabled-text="Enabled" + data-disabled-text="Disabled" + :class="{ checked: value, disabled: disabledInput }" + @click="toggleFeature" + /> + </label> +</template> diff --git a/app/assets/javascripts/projects/permissions/components/project_setting_row.vue b/app/assets/javascripts/projects/permissions/components/project_setting_row.vue new file mode 100644 index 00000000000..6140d74fea8 --- /dev/null +++ b/app/assets/javascripts/projects/permissions/components/project_setting_row.vue @@ -0,0 +1,36 @@ +<script> +export default { + props: { + label: { + type: String, + required: false, + default: null, + }, + helpPath: { + type: String, + required: false, + default: null, + }, + helpText: { + type: String, + required: false, + default: null, + }, + }, +}; +</script> + +<template> + <div class="project-feature-row"> + <label v-if="label" class="label-light"> + {{label}} + <a v-if="helpPath" :href="helpPath" target="_blank"> + <i aria-hidden="true" data-hidden="true" class="fa fa-question-circle"></i> + </a> + </label> + <span v-if="helpText" class="help-block"> + {{helpText}} + </span> + <slot /> + </div> +</template> diff --git a/app/assets/javascripts/projects/permissions/components/settings_panel.vue b/app/assets/javascripts/projects/permissions/components/settings_panel.vue new file mode 100644 index 00000000000..326d9105666 --- /dev/null +++ b/app/assets/javascripts/projects/permissions/components/settings_panel.vue @@ -0,0 +1,312 @@ +<script> +import projectFeatureSetting from './project_feature_setting.vue'; +import projectFeatureToggle from './project_feature_toggle.vue'; +import projectSettingRow from './project_setting_row.vue'; +import { visibilityOptions, visibilityLevelDescriptions } from '../constants'; +import { toggleHiddenClassBySelector } from '../external'; + +export default { + props: { + currentSettings: { + type: Object, + required: true, + }, + canChangeVisibilityLevel: { + type: Boolean, + required: false, + default: false, + }, + allowedVisibilityOptions: { + type: Array, + required: false, + default: () => [0, 10, 20], + }, + lfsAvailable: { + type: Boolean, + required: false, + default: false, + }, + registryAvailable: { + type: Boolean, + required: false, + default: false, + }, + visibilityHelpPath: { + type: String, + required: false, + }, + lfsHelpPath: { + type: String, + required: false, + }, + registryHelpPath: { + type: String, + required: false, + }, + }, + + data() { + const defaults = { + visibilityOptions, + visibilityLevel: visibilityOptions.PUBLIC, + issuesAccessLevel: 20, + repositoryAccessLevel: 20, + mergeRequestsAccessLevel: 20, + buildsAccessLevel: 20, + wikiAccessLevel: 20, + snippetsAccessLevel: 20, + containerRegistryEnabled: true, + lfsEnabled: true, + requestAccessEnabled: true, + highlightChangesClass: false, + }; + + return { ...defaults, ...this.currentSettings }; + }, + + components: { + projectFeatureSetting, + projectFeatureToggle, + projectSettingRow, + }, + + computed: { + featureAccessLevelOptions() { + const options = [ + [10, 'Only Project Members'], + ]; + if (this.visibilityLevel !== visibilityOptions.PRIVATE) { + options.push([20, 'Everyone With Access']); + } + return options; + }, + + repoFeatureAccessLevelOptions() { + return this.featureAccessLevelOptions.filter( + ([value]) => value <= this.repositoryAccessLevel, + ); + }, + + repositoryEnabled() { + return this.repositoryAccessLevel > 0; + }, + + visibilityLevelDescription() { + return visibilityLevelDescriptions[this.visibilityLevel]; + }, + }, + + methods: { + highlightChanges() { + this.highlightChangesClass = true; + this.$nextTick(() => { + this.highlightChangesClass = false; + }); + }, + + visibilityAllowed(option) { + return this.allowedVisibilityOptions.includes(option); + }, + }, + + watch: { + visibilityLevel(value, oldValue) { + if (value === visibilityOptions.PRIVATE) { + // when private, features are restricted to "only team members" + this.issuesAccessLevel = Math.min(10, this.issuesAccessLevel); + this.repositoryAccessLevel = Math.min(10, this.repositoryAccessLevel); + this.mergeRequestsAccessLevel = Math.min(10, this.mergeRequestsAccessLevel); + this.buildsAccessLevel = Math.min(10, this.buildsAccessLevel); + this.wikiAccessLevel = Math.min(10, this.wikiAccessLevel); + this.snippetsAccessLevel = Math.min(10, this.snippetsAccessLevel); + this.highlightChanges(); + } else if (oldValue === visibilityOptions.PRIVATE) { + // if changing away from private, make enabled features more permissive + if (this.issuesAccessLevel > 0) this.issuesAccessLevel = 20; + if (this.repositoryAccessLevel > 0) this.repositoryAccessLevel = 20; + if (this.mergeRequestsAccessLevel > 0) this.mergeRequestsAccessLevel = 20; + if (this.buildsAccessLevel > 0) this.buildsAccessLevel = 20; + if (this.wikiAccessLevel > 0) this.wikiAccessLevel = 20; + if (this.snippetsAccessLevel > 0) this.snippetsAccessLevel = 20; + this.highlightChanges(); + } + }, + + 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); + }, + + mergeRequestsAccessLevel(value, oldValue) { + if (value === 0) toggleHiddenClassBySelector('.merge-requests-feature', true); + else if (oldValue === 0) toggleHiddenClassBySelector('.merge-requests-feature', false); + }, + + buildsAccessLevel(value, oldValue) { + if (value === 0) toggleHiddenClassBySelector('.builds-feature', true); + else if (oldValue === 0) toggleHiddenClassBySelector('.builds-feature', false); + }, + }, +}; + +</script> + +<template> + <div> + <div class="project-visibility-setting"> + <project-setting-row + label="Project visibility" + :help-path="visibilityHelpPath" + > + <div class="project-feature-controls"> + <div class="select-wrapper"> + <select + name="project[visibility_level]" + v-model="visibilityLevel" + class="form-control select-control" + :disabled="!canChangeVisibilityLevel" + > + <option + :value="visibilityOptions.PRIVATE" + :disabled="!visibilityAllowed(visibilityOptions.PRIVATE)" + > + Private + </option> + <option + :value="visibilityOptions.INTERNAL" + :disabled="!visibilityAllowed(visibilityOptions.INTERNAL)" + > + Internal + </option> + <option + :value="visibilityOptions.PUBLIC" + :disabled="!visibilityAllowed(visibilityOptions.PUBLIC)" + > + Public + </option> + </select> + <i aria-hidden="true" data-hidden="true" class="fa fa-chevron-down"></i> + </div> + </div> + <span class="help-block">{{ visibilityLevelDescription }}</span> + <label v-if="visibilityLevel !== visibilityOptions.PUBLIC" class="request-access"> + <input + type="hidden" + name="project[request_access_enabled]" + :value="requestAccessEnabled" + /> + <input type="checkbox" v-model="requestAccessEnabled" /> + Allow users to request access + </label> + </project-setting-row> + </div> + <div class="project-feature-settings" :class="{ 'highlight-changes': highlightChangesClass }"> + <project-setting-row + label="Issues" + help-text="Lightweight issue tracking system for this project" + > + <project-feature-setting + name="project[project_feature_attributes][issues_access_level]" + :options="featureAccessLevelOptions" + v-model="issuesAccessLevel" + /> + </project-setting-row> + <project-setting-row + label="Repository" + help-text="View and edit files in this project" + > + <project-feature-setting + name="project[project_feature_attributes][repository_access_level]" + :options="featureAccessLevelOptions" + v-model="repositoryAccessLevel" + /> + </project-setting-row> + <div class="project-feature-setting-group"> + <project-setting-row + label="Merge requests" + help-text="Submit changes to be merged upstream" + > + <project-feature-setting + name="project[project_feature_attributes][merge_requests_access_level]" + :options="repoFeatureAccessLevelOptions" + v-model="mergeRequestsAccessLevel" + :disabledInput="!repositoryEnabled" + /> + </project-setting-row> + <project-setting-row + label="Pipelines" + help-text="Build, test, and deploy your changes" + > + <project-feature-setting + name="project[project_feature_attributes][builds_access_level]" + :options="repoFeatureAccessLevelOptions" + v-model="buildsAccessLevel" + :disabledInput="!repositoryEnabled" + /> + </project-setting-row> + <project-setting-row + v-if="registryAvailable" + label="Container registry" + :help-path="registryHelpPath" + help-text="Every project can have its own space to store its Docker images" + > + <project-feature-toggle + name="project[container_registry_enabled]" + v-model="containerRegistryEnabled" + :disabledInput="!repositoryEnabled" + /> + </project-setting-row> + <project-setting-row + v-if="lfsAvailable" + label="Git Large File Storage" + :help-path="lfsHelpPath" + help-text="Manages large files such as audio, video, and graphics files" + > + <project-feature-toggle + name="project[lfs_enabled]" + v-model="lfsEnabled" + :disabledInput="!repositoryEnabled" + /> + </project-setting-row> + </div> + <project-setting-row + label="Wiki" + help-text="Pages for project documentation" + > + <project-feature-setting + name="project[project_feature_attributes][wiki_access_level]" + :options="featureAccessLevelOptions" + v-model="wikiAccessLevel" + /> + </project-setting-row> + <project-setting-row + label="Snippets" + help-text="Share code pastes with others out of Git repository" + > + <project-feature-setting + name="project[project_feature_attributes][snippets_access_level]" + :options="featureAccessLevelOptions" + v-model="snippetsAccessLevel" + /> + </project-setting-row> + </div> + </div> +</template> diff --git a/app/assets/javascripts/projects/permissions/constants.js b/app/assets/javascripts/projects/permissions/constants.js new file mode 100644 index 00000000000..ce47562f259 --- /dev/null +++ b/app/assets/javascripts/projects/permissions/constants.js @@ -0,0 +1,11 @@ +export const visibilityOptions = { + PRIVATE: 0, + INTERNAL: 10, + PUBLIC: 20, +}; + +export const visibilityLevelDescriptions = { + [visibilityOptions.PRIVATE]: 'The project is accessible only by members of the project. Access must be granted explicitly to each user.', + [visibilityOptions.INTERNAL]: 'The project can be accessed by any user who is logged in.', + [visibilityOptions.PUBLIC]: 'The project can be accessed by anyone, regardless of authentication.', +}; diff --git a/app/assets/javascripts/projects/permissions/external.js b/app/assets/javascripts/projects/permissions/external.js new file mode 100644 index 00000000000..460af4a2111 --- /dev/null +++ b/app/assets/javascripts/projects/permissions/external.js @@ -0,0 +1,18 @@ +const selectorCache = []; + +// workaround since we don't have a polyfill for classList.toggle 2nd parameter +export function toggleHiddenClass(element, hidden) { + if (hidden) { + element.classList.add('hidden'); + } else { + element.classList.remove('hidden'); + } +} + +// hide external feature-specific settings when a given feature is disabled +export function toggleHiddenClassBySelector(selector, hidden) { + if (!selectorCache[selector]) { + selectorCache[selector] = document.querySelectorAll(selector); + } + selectorCache[selector].forEach(elm => toggleHiddenClass(elm, hidden)); +} diff --git a/app/assets/javascripts/projects/permissions/index.js b/app/assets/javascripts/projects/permissions/index.js new file mode 100644 index 00000000000..dbde8dda634 --- /dev/null +++ b/app/assets/javascripts/projects/permissions/index.js @@ -0,0 +1,13 @@ +import Vue from 'vue'; +import settingsPanel from './components/settings_panel.vue'; + +export default function initProjectPermissionsSettings() { + const mountPoint = document.querySelector('.js-project-permissions-form'); + const componentPropsEl = document.querySelector('.js-project-permissions-form-data'); + const componentProps = JSON.parse(componentPropsEl.innerHTML); + + return new Vue({ + el: mountPoint, + render: createElement => createElement(settingsPanel, { props: { ...componentProps } }), + }); +} diff --git a/app/assets/javascripts/projects_dropdown/components/projects_list_search.vue b/app/assets/javascripts/projects_dropdown/components/projects_list_search.vue index fa5efef2919..8d0c29177e6 100644 --- a/app/assets/javascripts/projects_dropdown/components/projects_list_search.vue +++ b/app/assets/javascripts/projects_dropdown/components/projects_list_search.vue @@ -27,7 +27,7 @@ export default { listEmptyMessage() { return this.searchFailed ? s__('ProjectsDropdown|Something went wrong on our end.') : - s__('ProjectsDropdown|No projects matched your query'); + s__('ProjectsDropdown|Sorry, no projects matched your search'); }, }, }; diff --git a/app/assets/javascripts/projects_dropdown/components/search.vue b/app/assets/javascripts/projects_dropdown/components/search.vue index b71997234e5..53bc76d0f2d 100644 --- a/app/assets/javascripts/projects_dropdown/components/search.vue +++ b/app/assets/javascripts/projects_dropdown/components/search.vue @@ -53,7 +53,7 @@ export default { class="form-control" ref="search" v-model="searchQuery" - :placeholder="s__('ProjectsDropdown|Search projects')" + :placeholder="s__('ProjectsDropdown|Search your projects')" /> <i v-if="!searchQuery" diff --git a/app/assets/javascripts/settings_panels.js b/app/assets/javascripts/settings_panels.js index 7fa5996d600..8635ccece6e 100644 --- a/app/assets/javascripts/settings_panels.js +++ b/app/assets/javascripts/settings_panels.js @@ -41,4 +41,8 @@ export default function initSettingsPanels() { $section.on('click.toggleSection', '.js-settings-toggle', () => toggleSection($section)); $section.find('.settings-content:not(.expanded)').on('scroll.expandSection', () => expandSection($section)); }); + + if (location.hash) { + expandSection($(location.hash)); + } } diff --git a/app/assets/javascripts/user_callout.js b/app/assets/javascripts/user_callout.js index ff2208baeab..a45b22f3084 100644 --- a/app/assets/javascripts/user_callout.js +++ b/app/assets/javascripts/user_callout.js @@ -1,7 +1,11 @@ import Cookies from 'js-cookie'; export default class UserCallout { - constructor(className = 'user-callout') { + constructor(options = {}) { + this.options = options; + + const className = this.options.className || 'user-callout'; + this.userCalloutBody = $(`.${className}`); this.cookieName = this.userCalloutBody.data('uid'); this.isCalloutDismissed = Cookies.get(this.cookieName); @@ -17,7 +21,11 @@ export default class UserCallout { dismissCallout(e) { const $currentTarget = $(e.currentTarget); - Cookies.set(this.cookieName, 'true', { expires: 365 }); + if (this.options.setCalloutPerProject) { + Cookies.set(this.cookieName, 'true', { expires: 365, path: this.userCalloutBody.data('project-path') }); + } else { + Cookies.set(this.cookieName, 'true', { expires: 365 }); + } if ($currentTarget.hasClass('close')) { this.userCalloutBody.remove(); diff --git a/app/assets/javascripts/vue_shared/directives/popover.js b/app/assets/javascripts/vue_shared/directives/popover.js new file mode 100644 index 00000000000..05fa563cbd0 --- /dev/null +++ b/app/assets/javascripts/vue_shared/directives/popover.js @@ -0,0 +1,20 @@ +/** + * Helper to user bootstrap popover in vue.js. + * Follow docs for html attributes: https://getbootstrap.com/docs/3.3/javascript/#static-popover + * + * @example + * import popover from 'vue_shared/directives/popover.js'; + * { + * directives: [popover] + * } + * <a v-popover="{options}">popover</a> + */ +export default { + bind(el, binding) { + $(el).popover(binding.value); + }, + + unbind(el) { + $(el).popover('destroy'); + }, +}; |