diff options
author | Winnie Hellmann <winnie@gitlab.com> | 2018-01-03 10:21:17 +0100 |
---|---|---|
committer | Winnie Hellmann <winnie@gitlab.com> | 2018-01-04 17:45:26 +0100 |
commit | 75f9cf94f6f3fcfddc8efb546599cbf61078a245 (patch) | |
tree | 792048797802dc4d9daa533b6b9873e897dc83af | |
parent | b1e1990ee263bcae73f0e55526a55cff66103220 (diff) | |
download | gitlab-ce-winh-modal-target-id.tar.gz |
Add id to modal.vue to support data-toggle="modal"winh-modal-target-id
20 files changed, 323 insertions, 220 deletions
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 42f61d33f6e..64090695d53 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -158,6 +158,9 @@ import Activities from './activities'; const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search'); switch (page) { + case 'profiles:accounts:show': + import('./pages/profiles/accounts/show').then(m => m.default()).catch(fail); + break; case 'profiles:preferences:show': initExperimentalFlags(); break; diff --git a/app/assets/javascripts/groups/components/item_actions.vue b/app/assets/javascripts/groups/components/item_actions.vue index 58ba5aff7cf..b98cfcf7563 100644 --- a/app/assets/javascripts/groups/components/item_actions.vue +++ b/app/assets/javascripts/groups/components/item_actions.vue @@ -45,11 +45,9 @@ export default { onLeaveGroup() { this.modalStatus = true; }, - leaveGroup(leaveConfirmed) { + leaveGroup() { this.modalStatus = false; - if (leaveConfirmed) { - eventHub.$emit('leaveGroup', this.group, this.parentGroup); - } + eventHub.$emit('leaveGroup', this.group, this.parentGroup); }, }, }; diff --git a/app/assets/javascripts/ide/components/new_dropdown/index.vue b/app/assets/javascripts/ide/components/new_dropdown/index.vue index 6e67e99a70f..d475813c4f7 100644 --- a/app/assets/javascripts/ide/components/new_dropdown/index.vue +++ b/app/assets/javascripts/ide/components/new_dropdown/index.vue @@ -32,10 +32,10 @@ methods: { createNewItem(type) { this.modalType = type; - this.toggleModalOpen(); + this.openModal = true; }, - toggleModalOpen() { - this.openModal = !this.openModal; + hideModal() { + this.openModal = false; }, }, }; @@ -95,7 +95,7 @@ :branch-id="branch" :path="path" :parent="parent" - @toggle="toggleModalOpen" + @hide="hideModal" /> </div> </template> diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue index a0650d37690..0312f56efbd 100644 --- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue +++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue @@ -43,10 +43,10 @@ type: this.type, }); - this.toggleModalOpen(); + this.hideModal(); }, - toggleModalOpen() { - this.$emit('toggle'); + hideModal() { + this.$emit('hide'); }, }, computed: { @@ -86,7 +86,7 @@ :title="modalTitle" :primary-button-label="buttonLabel" kind="success" - @toggle="toggleModalOpen" + @cancel="hideModal" @submit="createEntryInStore" > <form diff --git a/app/assets/javascripts/ide/components/repo_commit_section.vue b/app/assets/javascripts/ide/components/repo_commit_section.vue index 470db2c9650..979721dcb5a 100644 --- a/app/assets/javascripts/ide/components/repo_commit_section.vue +++ b/app/assets/javascripts/ide/components/repo_commit_section.vue @@ -110,7 +110,7 @@ export default { kind="primary" :title="__('Branch has changed')" :text="__('This branch has changed since you started editing. Would you like to create a new branch?')" - @toggle="showNewBranchModal = false" + @cancel="showNewBranchModal = false" @submit="makeCommit(true)" /> <commit-files-list diff --git a/app/assets/javascripts/ide/components/repo_edit_button.vue b/app/assets/javascripts/ide/components/repo_edit_button.vue index 37bd9003e96..42d5d709209 100644 --- a/app/assets/javascripts/ide/components/repo_edit_button.vue +++ b/app/assets/javascripts/ide/components/repo_edit_button.vue @@ -50,7 +50,7 @@ export default { kind="warning" :title="__('Are you sure?')" :text="__('Are you sure you want to discard your changes?')" - @toggle="closeDiscardPopup" + @cancel="closeDiscardPopup" @submit="toggleEditMode(true)" /> </div> diff --git a/app/assets/javascripts/pages/profiles/accounts/show/components/delete_account_modal.vue b/app/assets/javascripts/pages/profiles/accounts/show/components/delete_account_modal.vue new file mode 100644 index 00000000000..1107498c12e --- /dev/null +++ b/app/assets/javascripts/pages/profiles/accounts/show/components/delete_account_modal.vue @@ -0,0 +1,127 @@ +<script> + import Vue from 'vue'; + + import modal from '~/vue_shared/components/modal.vue'; + import { __, s__, sprintf } from '~/locale'; + import csrf from '~/lib/utils/csrf'; + + export default { + props: { + actionUrl: { + type: String, + required: true, + }, + confirmWithPassword: { + type: Boolean, + required: true, + }, + username: { + type: String, + required: true, + }, + }, + data() { + return { + enteredPassword: '', + enteredUsername: '', + }; + }, + components: { + modal, + }, + computed: { + csrfToken() { + return csrf.token; + }, + inputLabel() { + let confirmationValue; + if (this.confirmWithPassword) { + confirmationValue = __('password'); + } else { + confirmationValue = __('username'); + } + + confirmationValue = `<code>${confirmationValue}</code>`; + + return sprintf( + s__('Profiles|Type your %{confirmationValue} to confirm:'), + { confirmationValue }, + false, + ); + }, + text() { + return sprintf( + s__(`Profiles| +You are about to permanently delete %{yourAccount}, and all of the issues, merge requests, and groups linked to your account. +Once you confirm %{deleteAccount}, it cannot be undone or recovered.`), + { + yourAccount: `<strong>${s__('Profiles|your account')}</strong>`, + deleteAccount: `<strong>${s__('Profiles|Delete Account')}</strong>`, + }, + false, + ); + }, + }, + methods: { + canSubmit() { + if (this.confirmWithPassword) { + return this.enteredPassword !== ''; + } + + return this.enteredUsername === this.username; + }, + onSubmit(event) { + this.$refs.form.submit(); + }, + }, + }; +</script> + +<template> + <modal + id="delete-account-modal" + :title="s__('Profiles|Delete your account?')" + :text="text" + kind="danger" + :primary-button-label="s__('Profiles|Delete account')" + @submit="onSubmit" + :submitDisabled="!canSubmit()"> + + <template slot="body" slot-scope="props"> + <p v-html="props.text"></p> + + <form + ref="form" + :action="actionUrl" + method="post"> + + <input + type="hidden" + name="_method" + value="delete" /> + <input + type="hidden" + name="authenticity_token" + :value="csrfToken" /> + + <p id="input-label" v-html="inputLabel"></p> + + <input + v-if="confirmWithPassword" + name="password" + class="form-control" + type="password" + v-model="enteredPassword" + aria-labelledby="input-label" /> + <input + v-else + name="username" + class="form-control" + type="text" + v-model="enteredUsername" + aria-labelledby="input-label" /> + </form> + </template> + + </modal> +</template> diff --git a/app/assets/javascripts/pages/profiles/accounts/show/index.js b/app/assets/javascripts/pages/profiles/accounts/show/index.js new file mode 100644 index 00000000000..2ad89ca6cfa --- /dev/null +++ b/app/assets/javascripts/pages/profiles/accounts/show/index.js @@ -0,0 +1,40 @@ +import Vue from 'vue'; + +import Translate from '~/vue_shared/translate'; + +import deleteAccountModal from './components/delete_account_modal.vue'; + +Vue.use(Translate); + +export function mountDeleteAccountModal() { + const deleteAccountButton = document.getElementById('delete-account-button'); + if (!deleteAccountButton) { + return null; + } + + const deleteAccountModalEl = document.createElement('div'); + deleteAccountButton.parentNode.appendChild(deleteAccountModalEl); + + return new Vue({ + el: deleteAccountModalEl, + components: { + deleteAccountModal, + }, + mounted() { + deleteAccountButton.classList.remove('disabled'); + }, + render(createElement) { + return createElement('delete-account-modal', { + props: { + actionUrl: deleteAccountButton.dataset.actionUrl, + confirmWithPassword: !!deleteAccountButton.dataset.confirmWithPassword, + username: deleteAccountButton.dataset.username, + }, + }); + }, + }); +} + +export default () => { + mountDeleteAccountModal(); +}; diff --git a/app/assets/javascripts/profile/account/components/delete_account_modal.vue b/app/assets/javascripts/profile/account/components/delete_account_modal.vue deleted file mode 100644 index 78be6b6e884..00000000000 --- a/app/assets/javascripts/profile/account/components/delete_account_modal.vue +++ /dev/null @@ -1,146 +0,0 @@ -<script> - import modal from '../../../vue_shared/components/modal.vue'; - import { __, s__, sprintf } from '../../../locale'; - import csrf from '../../../lib/utils/csrf'; - - export default { - props: { - actionUrl: { - type: String, - required: true, - }, - confirmWithPassword: { - type: Boolean, - required: true, - }, - username: { - type: String, - required: true, - }, - }, - data() { - return { - enteredPassword: '', - enteredUsername: '', - isOpen: false, - }; - }, - components: { - modal, - }, - computed: { - csrfToken() { - return csrf.token; - }, - inputLabel() { - let confirmationValue; - if (this.confirmWithPassword) { - confirmationValue = __('password'); - } else { - confirmationValue = __('username'); - } - - confirmationValue = `<code>${confirmationValue}</code>`; - - return sprintf( - s__('Profiles|Type your %{confirmationValue} to confirm:'), - { confirmationValue }, - false, - ); - }, - text() { - return sprintf( - s__(`Profiles| -You are about to permanently delete %{yourAccount}, and all of the issues, merge requests, and groups linked to your account. -Once you confirm %{deleteAccount}, it cannot be undone or recovered.`), - { - yourAccount: `<strong>${s__('Profiles|your account')}</strong>`, - deleteAccount: `<strong>${s__('Profiles|Delete Account')}</strong>`, - }, - false, - ); - }, - }, - methods: { - canSubmit() { - if (this.confirmWithPassword) { - return this.enteredPassword !== ''; - } - - return this.enteredUsername === this.username; - }, - onSubmit(status) { - if (status) { - if (!this.canSubmit()) { - return; - } - - this.$refs.form.submit(); - } - - this.toggleOpen(false); - }, - toggleOpen(isOpen) { - this.isOpen = isOpen; - }, - }, - }; -</script> - -<template> - <div> - <modal - v-if="isOpen" - :title="s__('Profiles|Delete your account?')" - :text="text" - :kind="`danger ${!canSubmit() && 'disabled'}`" - :primary-button-label="s__('Profiles|Delete account')" - @toggle="toggleOpen" - @submit="onSubmit"> - - <template slot="body" slot-scope="props"> - <p v-html="props.text"></p> - - <form - ref="form" - :action="actionUrl" - method="post"> - - <input - type="hidden" - name="_method" - value="delete" /> - <input - type="hidden" - name="authenticity_token" - :value="csrfToken" /> - - <p id="input-label" v-html="inputLabel"></p> - - <input - v-if="confirmWithPassword" - name="password" - class="form-control" - type="password" - v-model="enteredPassword" - aria-labelledby="input-label" /> - <input - v-else - name="username" - class="form-control" - type="text" - v-model="enteredUsername" - aria-labelledby="input-label" /> - </form> - </template> - - </modal> - - <button - type="button" - class="btn btn-danger" - @click="toggleOpen(true)"> - {{ s__('Profiles|Delete account') }} - </button> - </div> -</template> diff --git a/app/assets/javascripts/profile/account/index.js b/app/assets/javascripts/profile/account/index.js deleted file mode 100644 index 635056e0eeb..00000000000 --- a/app/assets/javascripts/profile/account/index.js +++ /dev/null @@ -1,21 +0,0 @@ -import Vue from 'vue'; - -import deleteAccountModal from './components/delete_account_modal.vue'; - -const deleteAccountModalEl = document.getElementById('delete-account-modal'); -// eslint-disable-next-line no-new -new Vue({ - el: deleteAccountModalEl, - components: { - deleteAccountModal, - }, - render(createElement) { - return createElement('delete-account-modal', { - props: { - actionUrl: deleteAccountModalEl.dataset.actionUrl, - confirmWithPassword: !!deleteAccountModalEl.dataset.confirmWithPassword, - username: deleteAccountModalEl.dataset.username, - }, - }); - }, -}); diff --git a/app/assets/javascripts/vue_shared/components/modal.vue b/app/assets/javascripts/vue_shared/components/modal.vue index 55f466b7b41..00089dfef38 100644 --- a/app/assets/javascripts/vue_shared/components/modal.vue +++ b/app/assets/javascripts/vue_shared/components/modal.vue @@ -3,6 +3,10 @@ export default { name: 'modal', props: { + id: { + type: String, + required: false, + }, title: { type: String, required: false, @@ -62,11 +66,11 @@ export default { }, methods: { - close() { - this.$emit('toggle', false); + emitCancel(event) { + this.$emit('cancel', event); }, - emitSubmit(status) { - this.$emit('submit', status); + emitSubmit(event) { + this.$emit('submit', event); }, }, }; @@ -75,7 +79,9 @@ export default { <template> <div class="modal-open"> <div - class="modal show" + :id="id" + class="modal" + :class="id ? '' : 'show'" role="dialog" tabindex="-1" > @@ -93,7 +99,8 @@ export default { <button type="button" class="close pull-right" - @click="close" + @click="emitCancel($event)" + data-dismiss="modal" aria-label="Close" > <span aria-hidden="true">×</span> @@ -110,7 +117,8 @@ export default { type="button" class="btn pull-left" :class="btnCancelKindClass" - @click="close"> + @click="emitCancel($event)" + data-dismiss="modal"> {{ closeButtonLabel }} </button> <button @@ -119,13 +127,17 @@ export default { class="btn pull-right js-primary-button" :disabled="submitDisabled" :class="btnKindClass" - @click="emitSubmit(true)"> + @click="emitSubmit($event)" + data-dismiss="modal"> {{ primaryButtonLabel }} </button> </div> </div> </div> </div> - <div class="modal-backdrop fade in" /> + <div + v-if="!id" + class="modal-backdrop fade in"> + </div> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue index 8053c65d498..16d60bb2876 100644 --- a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue +++ b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue @@ -70,7 +70,7 @@ export default { class="recaptcha-modal js-recaptcha-modal" :hide-footer="true" :title="__('Please solve the reCAPTCHA')" - @toggle="close" + @cancel="close" > <div slot="body"> <p> diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index f1313b79589..ef168b6aff3 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -84,11 +84,14 @@ = s_('Profiles|Deleting an account has the following effects:') = render 'users/deletion_guidance', user: current_user - #delete-account-modal{ data: { action_url: user_registration_path, + %button#delete-account-button.btn.btn-danger.disabled{ data: { toggle: 'modal', + target: '#delete-account-modal', + action_url: user_registration_path, confirm_with_password: ('true' if current_user.confirm_deletion_with_password?), - username: current_user.username } } - %button.btn.btn-danger.disabled - = s_('Profiles|Delete account') + username: current_user.username }, + type: 'button' } + = s_('Profiles|Delete account') + - else - if @user.solo_owned_groups.present? %p @@ -100,6 +103,3 @@ %p = s_("Profiles|You don't have access to delete this user.") .append-bottom-default - -- content_for :page_specific_javascripts do - = webpack_bundle_tag('account') diff --git a/changelogs/unreleased/winh-modal-target-id.yml b/changelogs/unreleased/winh-modal-target-id.yml new file mode 100644 index 00000000000..f8d5b72be50 --- /dev/null +++ b/changelogs/unreleased/winh-modal-target-id.yml @@ -0,0 +1,5 @@ +--- +title: Add id to modal.vue to support data-toggle="modal" +merge_request: 16189 +author: +type: other diff --git a/config/webpack.config.js b/config/webpack.config.js index 5f95255334c..222c5084abf 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -26,7 +26,6 @@ var config = { }, context: path.join(ROOT_PATH, 'app/assets/javascripts'), entry: { - account: './profile/account/index.js', balsamiq_viewer: './blob/balsamiq_viewer.js', blob: './blob_edit/blob_bundle.js', boards: './boards/boards_bundle.js', diff --git a/spec/javascripts/groups/components/item_actions_spec.js b/spec/javascripts/groups/components/item_actions_spec.js index 7a5c1da4d1d..6d6fb410859 100644 --- a/spec/javascripts/groups/components/item_actions_spec.js +++ b/spec/javascripts/groups/components/item_actions_spec.js @@ -47,17 +47,11 @@ describe('ItemActionsComponent', () => { it('should change `modalStatus` prop to `false` and emit `leaveGroup` event with required params when called with `leaveConfirmed` as `true`', () => { spyOn(eventHub, '$emit'); vm.modalStatus = true; - vm.leaveGroup(true); - expect(vm.modalStatus).toBeFalsy(); - expect(eventHub.$emit).toHaveBeenCalledWith('leaveGroup', vm.group, vm.parentGroup); - }); - it('should change `modalStatus` prop to `false` and should NOT emit `leaveGroup` event when called with `leaveConfirmed` as `false`', () => { - spyOn(eventHub, '$emit'); - vm.modalStatus = true; - vm.leaveGroup(false); + vm.leaveGroup(); + expect(vm.modalStatus).toBeFalsy(); - expect(eventHub.$emit).not.toHaveBeenCalled(); + expect(eventHub.$emit).toHaveBeenCalledWith('leaveGroup', vm.group, vm.parentGroup); }); }); }); diff --git a/spec/javascripts/profile/account/components/delete_account_modal_spec.js b/spec/javascripts/pages/profiles/accounts/show/components/delete_account_modal_spec.js index 2e94948cfb2..12ce81719f6 100644 --- a/spec/javascripts/profile/account/components/delete_account_modal_spec.js +++ b/spec/javascripts/pages/profiles/accounts/show/components/delete_account_modal_spec.js @@ -1,8 +1,8 @@ import Vue from 'vue'; -import deleteAccountModal from '~/profile/account/components/delete_account_modal.vue'; +import deleteAccountModal from '~/pages/profiles/accounts/show/components/delete_account_modal.vue'; -import mountComponent from '../../../helpers/vue_mount_component_helper'; +import mountComponent from '../../../../../helpers/vue_mount_component_helper'; describe('DeleteAccountModal component', () => { const actionUrl = `${gl.TEST_HOST}/delete/user`; diff --git a/spec/javascripts/pages/profiles/accounts/show/index_spec.js b/spec/javascripts/pages/profiles/accounts/show/index_spec.js new file mode 100644 index 00000000000..af393893bf5 --- /dev/null +++ b/spec/javascripts/pages/profiles/accounts/show/index_spec.js @@ -0,0 +1,33 @@ +import * as profileAccountModule from '~/pages/profiles/accounts/show'; + +describe('Profile Account bundle', () => { + beforeEach(() => { + setFixtures(` + <div id="container"> + <button + id="delete-account-button" + class="disabled" + data-action-url="dummy-action-url" + data-username="dummy-username"></button> + </div> + `); + }); + + it('mounts the delete account modal', () => { + const container = document.getElementById('container'); + + profileAccountModule.default(); + + expect(container.querySelector('#delete-account-modal')).not.toBeNull(); + }); + + describe('mountDeleteAccountModal', () => { + it('passes the props from delete account button to the modal', () => { + const vm = profileAccountModule.mountDeleteAccountModal(); + const modal = vm.$children[0]; + + expect(modal.actionUrl).toBe('dummy-action-url'); + expect(modal.username).toBe('dummy-username'); + }); + }); +}); diff --git a/spec/javascripts/repo/components/new_dropdown/index_spec.js b/spec/javascripts/repo/components/new_dropdown/index_spec.js index b001c1655b4..6efbbf6d75e 100644 --- a/spec/javascripts/repo/components/new_dropdown/index_spec.js +++ b/spec/javascripts/repo/components/new_dropdown/index_spec.js @@ -57,16 +57,17 @@ describe('new dropdown component', () => { }); }); - describe('toggleModalOpen', () => { + describe('hideModal', () => { + beforeAll((done) => { + vm.openModal = true; + Vue.nextTick(done); + }); + it('closes modal after toggling', (done) => { - vm.toggleModalOpen(); + vm.hideModal(); Vue.nextTick() .then(() => { - expect(vm.$el.querySelector('.modal')).not.toBeNull(); - }) - .then(vm.toggleModalOpen) - .then(() => { expect(vm.$el.querySelector('.modal')).toBeNull(); }) .then(done) diff --git a/spec/javascripts/vue_shared/components/modal_spec.js b/spec/javascripts/vue_shared/components/modal_spec.js index 721f4044659..75b67bcc792 100644 --- a/spec/javascripts/vue_shared/components/modal_spec.js +++ b/spec/javascripts/vue_shared/components/modal_spec.js @@ -2,11 +2,69 @@ import Vue from 'vue'; import modal from '~/vue_shared/components/modal.vue'; import mountComponent from '../../helpers/vue_mount_component_helper'; +const modalComponent = Vue.extend(modal); + describe('Modal', () => { - it('does not render a primary button if no primaryButtonLabel', () => { - const modalComponent = Vue.extend(modal); - const vm = mountComponent(modalComponent); + let vm; + + afterEach(() => { + vm.$destroy(); + }); + + describe('props', () => { + describe('without primaryButtonLabel', () => { + beforeEach(() => { + vm = mountComponent(modalComponent, { + primaryButtonLabel: null, + }); + }); + + it('does not render a primary button', () => { + expect(vm.$el.querySelector('.js-primary-button')).toBeNull(); + }); + }); + + describe('with id', () => { + it('does not render a primary button', () => { + beforeEach(() => { + vm = mountComponent(modalComponent, { + id: 'my-modal', + }); + }); + + it('assigns the id to the modal', () => { + expect(vm.$el.querySelector('#my-modal.modal')).not.toBeNull(); + }); + + it('does not show the modal immediately', () => { + expect(vm.$el.querySelector('#my-modal.modal')).not.toHaveClass('show'); + }); + + it('does not show a backdrop', () => { + expect(vm.$el.querySelector('modal-backdrop')).toBeNull(); + }); + }); + }); + + it('works with data-toggle="modal"', (done) => { + setFixtures(` + <button id="modal-button" data-toggle="modal" data-target="#my-modal"></button> + <div id="modal-container"></div> + `); + + const modalContainer = document.getElementById('modal-container'); + const modalButton = document.getElementById('modal-button'); + vm = mountComponent(modalComponent, { + id: 'my-modal', + }, modalContainer); + const modalElement = vm.$el.querySelector('#my-modal'); + expect(modalElement).not.toHaveClass('in'); + $(modalElement).on('shown.bs.modal', () => { + expect(modalElement).toHaveClass('in'); + done(); + }); - expect(vm.$el.querySelector('.js-primary-button')).toBeNull(); + modalButton.click(); + }); }); }); |