summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhil Hughes <me@iamphill.com>2017-05-12 12:54:10 +0100
committerPhil Hughes <me@iamphill.com>2017-05-15 11:35:26 +0100
commite61ce83be9aef1f5bf68471c60753ac53ae15e5d (patch)
tree467b98ee541774ed935b898dbedc3d343220e2cf
parent478812543ce82dbfc7a408706aa94ab870443735 (diff)
downloadgitlab-ce-e61ce83be9aef1f5bf68471c60753ac53ae15e5d.tar.gz
Issue inline edit title field
[ci skip]
-rw-r--r--app/assets/javascripts/issue_show/components/app.vue22
-rw-r--r--app/assets/javascripts/issue_show/components/fields/title.vue32
-rw-r--r--app/assets/javascripts/issue_show/components/title.vue36
-rw-r--r--app/assets/javascripts/issue_show/index.js18
-rw-r--r--app/assets/javascripts/issue_show/stores/index.js4
-rw-r--r--spec/javascripts/issue_show/components/app_spec.js12
-rw-r--r--spec/javascripts/issue_show/components/fields/title_spec.js30
-rw-r--r--spec/javascripts/issue_show/components/title_spec.js13
8 files changed, 131 insertions, 36 deletions
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue
index a9418dd0bb2..d07a87ce43f 100644
--- a/app/assets/javascripts/issue_show/components/app.vue
+++ b/app/assets/javascripts/issue_show/components/app.vue
@@ -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" />
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..a2ad1a0e034 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: {
@@ -64,7 +47,6 @@ document.addEventListener('DOMContentLoaded', () => {
initialTitle: this.initialTitle,
initialDescriptionHtml: this.initialDescriptionHtml,
initialDescriptionText: this.initialDescriptionText,
- showForm: this.showForm,
},
});
},
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..ad99249a806 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 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;
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();