Merge branch 'add-inline-edit-button' into 'master'

Add inline edit button to issue_show app

See merge request gitlab-org/gitlab-ce!14875
This commit is contained in:
Filipa Lacerda 2017-10-17 17:04:33 +00:00
commit c3d3103840
6 changed files with 119 additions and 17 deletions

View File

@ -24,6 +24,11 @@ export default {
required: true, required: true,
type: Boolean, type: Boolean,
}, },
showInlineEditButton: {
type: Boolean,
required: false,
default: false,
},
issuableRef: { issuableRef: {
type: String, type: String,
required: true, required: true,
@ -222,20 +227,25 @@ export default {
<div v-else> <div v-else>
<title-component <title-component
:issuable-ref="issuableRef" :issuable-ref="issuableRef"
:can-update="canUpdate"
:title-html="state.titleHtml" :title-html="state.titleHtml"
:title-text="state.titleText" /> :title-text="state.titleText"
:show-inline-edit-button="showInlineEditButton"
/>
<description-component <description-component
v-if="state.descriptionHtml" v-if="state.descriptionHtml"
:can-update="canUpdate" :can-update="canUpdate"
:description-html="state.descriptionHtml" :description-html="state.descriptionHtml"
:description-text="state.descriptionText" :description-text="state.descriptionText"
:updated-at="state.updatedAt" :updated-at="state.updatedAt"
:task-status="state.taskStatus" /> :task-status="state.taskStatus"
/>
<edited-component <edited-component
v-if="hasUpdated" v-if="hasUpdated"
:updated-at="state.updatedAt" :updated-at="state.updatedAt"
:updated-by-name="state.updatedByName" :updated-by-name="state.updatedByName"
:updated-by-path="state.updatedByPath" /> :updated-by-path="state.updatedByPath"
/>
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,5 +1,8 @@
<script> <script>
import animateMixin from '../mixins/animate'; import animateMixin from '../mixins/animate';
import eventHub from '../event_hub';
import tooltip from '../../vue_shared/directives/tooltip';
import { spriteIcon } from '../../lib/utils/common_utils';
export default { export default {
mixins: [animateMixin], mixins: [animateMixin],
@ -15,6 +18,11 @@
type: String, type: String,
required: true, required: true,
}, },
canUpdate: {
required: false,
type: Boolean,
default: false,
},
titleHtml: { titleHtml: {
type: String, type: String,
required: true, required: true,
@ -23,6 +31,14 @@
type: String, type: String,
required: true, required: true,
}, },
showInlineEditButton: {
type: Boolean,
required: false,
default: false,
},
},
directives: {
tooltip,
}, },
watch: { watch: {
titleHtml() { titleHtml() {
@ -30,24 +46,46 @@
this.animateChange(); this.animateChange();
}, },
}, },
computed: {
pencilIcon() {
return spriteIcon('pencil', 'link-highlight');
},
},
methods: { methods: {
setPageTitle() { setPageTitle() {
const currentPageTitleScope = this.titleEl.innerText.split('·'); const currentPageTitleScope = this.titleEl.innerText.split('·');
currentPageTitleScope[0] = `${this.titleText} (${this.issuableRef}) `; currentPageTitleScope[0] = `${this.titleText} (${this.issuableRef}) `;
this.titleEl.textContent = currentPageTitleScope.join('·'); this.titleEl.textContent = currentPageTitleScope.join('·');
}, },
edit() {
eventHub.$emit('open.form');
},
}, },
}; };
</script> </script>
<template> <template>
<h2 <div class="title-container">
class="title" <h2
:class="{ class="title"
'issue-realtime-pre-pulse': preAnimation, :class="{
'issue-realtime-trigger-pulse': pulseAnimation 'issue-realtime-pre-pulse': preAnimation,
}" 'issue-realtime-trigger-pulse': pulseAnimation
v-html="titleHtml" }"
> v-html="titleHtml"
</h2> >
</h2>
<button
v-tooltip
v-if="showInlineEditButton && canUpdate"
type="button"
class="btn-blank btn-edit note-action-button"
v-html="pencilIcon"
title="Edit title and description"
data-placement="bottom"
data-container="body"
@click="edit"
>
</button>
</div>
</template> </template>

View File

@ -72,12 +72,22 @@
} }
} }
.title-container {
display: flex;
}
.title { .title {
padding: 0; padding: 0;
margin-bottom: 16px; margin-bottom: 16px;
border-bottom: none; border-bottom: none;
} }
.btn-edit {
margin-left: auto;
// Set height to match title height
height: 2em;
}
// Border around images in issue and MR descriptions. // Border around images in issue and MR descriptions.
.description img:not(.emoji) { .description img:not(.emoji) {
border: 1px solid $white-normal; border: 1px solid $white-normal;

View File

@ -531,14 +531,13 @@ ul.notes {
padding: 0; padding: 0;
min-width: 16px; min-width: 16px;
color: $gray-darkest; color: $gray-darkest;
fill: $gray-darkest;
.fa { .fa {
position: relative; position: relative;
font-size: 16px; font-size: 16px;
} }
svg { svg {
height: 16px; height: 16px;
width: 16px; width: 16px;
@ -566,6 +565,7 @@ ul.notes {
.link-highlight { .link-highlight {
color: $gl-link-color; color: $gl-link-color;
fill: $gl-link-color;
svg { svg {
fill: $gl-link-color; fill: $gl-link-color;

View File

@ -332,4 +332,15 @@ describe('Issuable output', () => {
.catch(done.fail); .catch(done.fail);
}); });
}); });
describe('show inline edit button', () => {
it('should not render by default', () => {
expect(vm.$el.querySelector('.title-container .note-action-button')).toBeDefined();
});
it('should render if showInlineEditButton', () => {
vm.showInlineEditButton = true;
expect(vm.$el.querySelector('.title-container .note-action-button')).toBeDefined();
});
});
}); });

View File

@ -1,6 +1,7 @@
import Vue from 'vue'; import Vue from 'vue';
import Store from '~/issue_show/stores'; import Store from '~/issue_show/stores';
import titleComponent from '~/issue_show/components/title.vue'; import titleComponent from '~/issue_show/components/title.vue';
import eventHub from '~/issue_show/event_hub';
describe('Title component', () => { describe('Title component', () => {
let vm; let vm;
@ -25,7 +26,7 @@ describe('Title component', () => {
it('renders title HTML', () => { it('renders title HTML', () => {
expect( expect(
vm.$el.innerHTML.trim(), vm.$el.querySelector('.title').innerHTML.trim(),
).toBe('Testing <img>'); ).toBe('Testing <img>');
}); });
@ -47,12 +48,12 @@ describe('Title component', () => {
Vue.nextTick(() => { Vue.nextTick(() => {
expect( expect(
vm.$el.classList.contains('issue-realtime-pre-pulse'), vm.$el.querySelector('.title').classList.contains('issue-realtime-pre-pulse'),
).toBeTruthy(); ).toBeTruthy();
setTimeout(() => { setTimeout(() => {
expect( expect(
vm.$el.classList.contains('issue-realtime-trigger-pulse'), vm.$el.querySelector('.title').classList.contains('issue-realtime-trigger-pulse'),
).toBeTruthy(); ).toBeTruthy();
done(); done();
@ -72,4 +73,36 @@ describe('Title component', () => {
done(); done();
}); });
}); });
describe('inline edit button', () => {
beforeEach(() => {
spyOn(eventHub, '$emit');
});
it('should not show by default', () => {
expect(vm.$el.querySelector('.note-action-button')).toBeNull();
});
it('should not show if canUpdate is false', () => {
vm.showInlineEditButton = true;
vm.canUpdate = false;
expect(vm.$el.querySelector('.note-action-button')).toBeNull();
});
it('should show if showInlineEditButton and canUpdate', () => {
vm.showInlineEditButton = true;
vm.canUpdate = true;
expect(vm.$el.querySelector('.note-action-button')).toBeDefined();
});
it('should trigger open.form event when clicked', () => {
vm.showInlineEditButton = true;
vm.canUpdate = true;
Vue.nextTick(() => {
vm.$el.querySelector('.note-action-button').click();
expect(eventHub.$emit).toHaveBeenCalledWith('open.form');
});
});
});
}); });