diff --git a/app/assets/javascripts/pages/projects/edit/index.js b/app/assets/javascripts/pages/projects/edit/index.js index 38351d30a8d..f4beefea90c 100644 --- a/app/assets/javascripts/pages/projects/edit/index.js +++ b/app/assets/javascripts/pages/projects/edit/index.js @@ -9,6 +9,7 @@ import initSearchSettings from '~/search_settings'; import initSettingsPanels from '~/settings_panels'; import setupTransferEdit from '~/transfer_edit'; import UserCallout from '~/user_callout'; +import initTopicsTokenSelector from '~/projects/settings/topics'; import initProjectPermissionsSettings from '../shared/permissions'; import initProjectLoadingSpinner from '../shared/save_project_loader'; @@ -28,3 +29,4 @@ setupTransferEdit('.js-project-transfer-form', 'select.select2'); dirtySubmitFactory(document.querySelectorAll('.js-general-settings-form, .js-mr-settings-form')); initSearchSettings(); +initTopicsTokenSelector(); diff --git a/app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue b/app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue new file mode 100644 index 00000000000..e8b0e95b142 --- /dev/null +++ b/app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue @@ -0,0 +1,92 @@ + + diff --git a/app/assets/javascripts/projects/settings/topics/index.js b/app/assets/javascripts/projects/settings/topics/index.js new file mode 100644 index 00000000000..3fbd1a61abe --- /dev/null +++ b/app/assets/javascripts/projects/settings/topics/index.js @@ -0,0 +1,51 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createDefaultClient from '~/lib/graphql'; +import TopicsTokenSelector from './components/topics_token_selector.vue'; + +Vue.use(VueApollo); + +const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient(), +}); + +export default () => { + const el = document.querySelector('.js-topics-selector'); + + if (!el) return null; + + const { hiddenInputId } = el.dataset; + const hiddenInput = document.getElementById(hiddenInputId); + + const selected = hiddenInput.value + ? hiddenInput.value.split(/,\s*/).map((token, index) => ({ + id: index, + name: token, + })) + : []; + + return new Vue({ + el, + apolloProvider, + render(createElement) { + return createElement(TopicsTokenSelector, { + props: { + selected, + }, + on: { + update(tokens) { + const value = tokens.map(({ name }) => name).join(', '); + hiddenInput.value = value; + // Dispatch `input` event so form submit button becomes active + hiddenInput.dispatchEvent( + new Event('input', { + bubbles: true, + cancelable: true, + }), + ); + }, + }, + }); + }, + }); +}; diff --git a/app/assets/javascripts/projects/settings/topics/queries/project_topics_search.query.graphql b/app/assets/javascripts/projects/settings/topics/queries/project_topics_search.query.graphql new file mode 100644 index 00000000000..b193165062a --- /dev/null +++ b/app/assets/javascripts/projects/settings/topics/queries/project_topics_search.query.graphql @@ -0,0 +1,9 @@ +query searchProjectTopics($search: String) { + topics(search: $search) { + nodes { + id + name + avatarUrl + } + } +} diff --git a/app/assets/javascripts/releases/components/tag_field_new.vue b/app/assets/javascripts/releases/components/tag_field_new.vue index 80f59485426..9e05d00a98d 100644 --- a/app/assets/javascripts/releases/components/tag_field_new.vue +++ b/app/assets/javascripts/releases/components/tag_field_new.vue @@ -95,6 +95,8 @@ export default { noRefSelected: __('No tag selected'), dropdownHeader: __('Tag name'), searchPlaceholder: __('Search or create tag'), + label: __('Tag name'), + labelDescription: __('*Required'), }, createFrom: { noRefSelected: __('No source selected'), @@ -108,11 +110,12 @@ export default {