diff options
author | Phil Hughes <me@iamphill.com> | 2017-05-12 12:54:10 +0100 |
---|---|---|
committer | Phil Hughes <me@iamphill.com> | 2017-05-15 12:15:56 +0100 |
commit | b1affe07a1ff0b7f10cdc94b91dfea97d92b015c (patch) | |
tree | 68c11c33cceedb2761fcead85ab0b0d1d70a036c | |
parent | 478812543ce82dbfc7a408706aa94ab870443735 (diff) | |
download | gitlab-ce-b1affe07a1ff0b7f10cdc94b91dfea97d92b015c.tar.gz |
Issue inline edit title field
[ci skip]
8 files changed, 133 insertions, 31 deletions
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue index a9418dd0bb2..5c253bfd0c1 100644 --- a/app/assets/javascripts/issue_show/components/app.vue +++ b/app/assets/javascripts/issue_show/components/app.vue @@ -57,8 +57,14 @@ export default { store, state: store.state, formState: store.formState, + showForm: false, }; }, + computed: { + elementType() { + return this.showForm ? 'form' : 'div'; + }, + }, components: { descriptionComponent, titleComponent, @@ -90,6 +96,14 @@ export default { }); }, }, + methods: { + openForm() { + this.showForm = true; + this.store.formState = { + title: this.state.titleText, + }; + }, + }, created() { this.service = new Service(this.endpoint); this.poll = new Poll({ @@ -117,17 +131,21 @@ export default { eventHub.$on('delete.issuable', this.deleteIssuable); eventHub.$on('update.issuable', this.updateIssuable); + eventHub.$on('open.form', this.openForm); }, beforeDestroy() { eventHub.$off('delete.issuable', this.deleteIssuable); eventHub.$off('update.issuable', this.updateIssuable); + eventHub.$on('open.form', this.openForm); }, }; </script> <template> - <div> + <div :is="elementType"> <title-component + :store="store" + :show-form="showForm" :issuable-ref="issuableRef" :title-html="state.titleHtml" :title-text="state.titleText" /> diff --git a/app/assets/javascripts/issue_show/components/fields/title.vue b/app/assets/javascripts/issue_show/components/fields/title.vue new file mode 100644 index 00000000000..732a4021205 --- /dev/null +++ b/app/assets/javascripts/issue_show/components/fields/title.vue @@ -0,0 +1,32 @@ +<script> + export default { + props: { + store: { + type: Object, + required: true, + }, + }, + data() { + return { + state: this.store.formState, + }; + }, + }; +</script> + +<template> + <fieldset> + <label + class="sr-only" + for="issue-title"> + Title + </label> + <input + id="issue-title" + class="form-control" + type="text" + placeholder="Issue title" + aria-label="Issue title" + v-model="state.title" /> + </fieldset> +</template> diff --git a/app/assets/javascripts/issue_show/components/title.vue b/app/assets/javascripts/issue_show/components/title.vue index a9dabd4cff1..a61ce414891 100644 --- a/app/assets/javascripts/issue_show/components/title.vue +++ b/app/assets/javascripts/issue_show/components/title.vue @@ -1,8 +1,12 @@ <script> import animateMixin from '../mixins/animate'; + import titleField from './fields/title.vue'; export default { mixins: [animateMixin], + components: { + titleField, + }, data() { return { preAnimation: false, @@ -23,6 +27,14 @@ type: String, required: true, }, + store: { + type: Object, + required: true, + }, + showForm: { + type: Boolean, + required: true, + }, }, watch: { titleHtml() { @@ -41,13 +53,19 @@ </script> <template> - <h2 - class="title" - :class="{ - 'issue-realtime-pre-pulse': preAnimation, - 'issue-realtime-trigger-pulse': pulseAnimation - }" - v-html="titleHtml" - > - </h2> + <div> + <title-field + v-if="showForm" + :store="store" /> + <h2 + v-else + class="title" + :class="{ + 'issue-realtime-pre-pulse': preAnimation, + 'issue-realtime-trigger-pulse': pulseAnimation + }" + v-html="titleHtml" + > + </h2> + </div> </template> diff --git a/app/assets/javascripts/issue_show/index.js b/app/assets/javascripts/issue_show/index.js index 246ccf66323..58a8d0590e1 100644 --- a/app/assets/javascripts/issue_show/index.js +++ b/app/assets/javascripts/issue_show/index.js @@ -35,25 +35,8 @@ document.addEventListener('DOMContentLoaded', () => { initialTitle: issuableTitleElement.innerHTML, initialDescriptionHtml: issuableDescriptionElement ? issuableDescriptionElement.innerHTML : '', initialDescriptionText: issuableDescriptionTextarea ? issuableDescriptionTextarea.textContent : '', - showForm: false, }; }, - methods: { - openForm() { - this.showForm = true; - }, - closeForm() { - this.showForm = false; - }, - }, - created() { - eventHub.$on('open.form', this.openForm); - eventHub.$on('close.form', this.closeForm); - }, - beforeDestroy() { - eventHub.$off('open.form', this.openForm); - eventHub.$off('close.form', this.closeForm); - }, render(createElement) { return createElement('issuable-app', { props: { diff --git a/app/assets/javascripts/issue_show/stores/index.js b/app/assets/javascripts/issue_show/stores/index.js index b2c1b9d1c6e..0ab52c307a0 100644 --- a/app/assets/javascripts/issue_show/stores/index.js +++ b/app/assets/javascripts/issue_show/stores/index.js @@ -12,7 +12,9 @@ export default class Store { taskStatus: '', updatedAt: '', }; - this.formState = {}; + this.formState = { + title: '', + }; } updateState(data) { diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js index 1f46d836e1e..1c47ba49b18 100644 --- a/spec/javascripts/issue_show/components/app_spec.js +++ b/spec/javascripts/issue_show/components/app_spec.js @@ -75,6 +75,18 @@ describe('Issuable output', () => { }); }); + it('changes element to `form` when open', (done) => { + vm.showForm = true; + + Vue.nextTick(() => { + expect( + vm.$el.tagName, + ).toBe('FORM'); + + done(); + }); + }); + it('does not show actions if permissions are incorrect', (done) => { vm.showForm = true; vm.canUpdate = false; diff --git a/spec/javascripts/issue_show/components/fields/title_spec.js b/spec/javascripts/issue_show/components/fields/title_spec.js new file mode 100644 index 00000000000..69afcd24df9 --- /dev/null +++ b/spec/javascripts/issue_show/components/fields/title_spec.js @@ -0,0 +1,30 @@ +import Vue from 'vue'; +import Store from '~/issue_show/stores'; +import titleField from '~/issue_show/components/fields/title.vue'; + +describe('Title field component', () => { + let vm; + let store; + + beforeEach(() => { + const Component = Vue.extend(titleField); + store = new Store({ + titleHtml: '', + descriptionHtml: '', + issuableRef: '', + }); + store.formState.title = 'test'; + + vm = new Component({ + propsData: { + store, + }, + }).$mount(); + }); + + it('renders form control with formState title', () => { + expect( + vm.$el.querySelector('.form-control').value, + ).toBe('test'); + }); +}); diff --git a/spec/javascripts/issue_show/components/title_spec.js b/spec/javascripts/issue_show/components/title_spec.js index 2f953e7e92e..45ec8718eb6 100644 --- a/spec/javascripts/issue_show/components/title_spec.js +++ b/spec/javascripts/issue_show/components/title_spec.js @@ -1,4 +1,5 @@ import Vue from 'vue'; +import Store from '~/issue_show/stores'; import titleComponent from '~/issue_show/components/title.vue'; describe('Title component', () => { @@ -11,13 +12,19 @@ describe('Title component', () => { issuableRef: '#1', titleHtml: 'Testing <img />', titleText: 'Testing', + showForm: false, + store: new Store({ + titleHtml: '', + descriptionHtml: '', + issuableRef: '', + }), }, }).$mount(); }); it('renders title HTML', () => { expect( - vm.$el.innerHTML.trim(), + vm.$el.querySelector('h2').innerHTML.trim(), ).toBe('Testing <img>'); }); @@ -39,12 +46,12 @@ describe('Title component', () => { Vue.nextTick(() => { expect( - vm.$el.classList.contains('issue-realtime-pre-pulse'), + vm.$el.querySelector('h2').classList.contains('issue-realtime-pre-pulse'), ).toBeTruthy(); setTimeout(() => { expect( - vm.$el.classList.contains('issue-realtime-trigger-pulse'), + vm.$el.querySelector('h2').classList.contains('issue-realtime-trigger-pulse'), ).toBeTruthy(); done(); |