Issue inline edit title field

[ci skip]
This commit is contained in:
Phil Hughes 2017-05-12 12:54:10 +01:00
parent 478812543c
commit e61ce83be9
8 changed files with 131 additions and 36 deletions

View File

@ -41,10 +41,6 @@ export default {
required: false,
default: '',
},
showForm: {
type: Boolean,
required: true,
},
},
data() {
const store = new Store({
@ -57,14 +53,26 @@ export default {
store,
state: store.state,
formState: store.formState,
showForm: false,
};
},
computed: {
elementType() {
return this.showForm ? 'form' : 'div';
},
},
components: {
descriptionComponent,
titleComponent,
editActions,
},
methods: {
openForm() {
this.showForm = true;
this.store.formState = {
title: this.state.titleText,
};
},
updateIssuable() {
this.service.updateIssuable(this.formState)
.then(() => {
@ -117,17 +125,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.$off('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" />

View File

@ -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>

View File

@ -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>

View File

@ -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: {
@ -64,7 +47,6 @@ document.addEventListener('DOMContentLoaded', () => {
initialTitle: this.initialTitle,
initialDescriptionHtml: this.initialDescriptionHtml,
initialDescriptionText: this.initialDescriptionText,
showForm: this.showForm,
},
});
},

View File

@ -12,7 +12,9 @@ export default class Store {
taskStatus: '',
updatedAt: '',
};
this.formState = {};
this.formState = {
title: '',
};
}
updateState(data) {

View File

@ -75,6 +75,18 @@ describe('Issuable output', () => {
});
});
it('changes element for `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;

View File

@ -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');
});
});

View File

@ -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();