Merge branch 'issue-edit-inline-description-template' into 'issue-edit-inline'

Issue edit inline description template

See merge request !11382
This commit is contained in:
Filipa Lacerda 2017-05-22 14:38:30 +00:00
commit d853d82114
8 changed files with 260 additions and 5 deletions

View file

@ -46,6 +46,11 @@ export default {
required: false,
default: '',
},
issuableTemplates: {
type: Array,
required: false,
default: () => [],
},
isConfidential: {
type: Boolean,
required: true,
@ -58,6 +63,14 @@ export default {
type: String,
required: true,
},
projectPath: {
type: String,
required: true,
},
projectNamespace: {
type: String,
required: true,
},
projectsAutocompleteUrl: {
type: String,
required: true,
@ -186,9 +199,13 @@ export default {
:form-state="formState"
:can-move="canMove"
:can-destroy="canDestroy"
:issuable-templates="issuableTemplates"
:markdown-docs="markdownDocs"
:markdown-preview-url="markdownPreviewUrl"
:projects-autocomplete-url="projectsAutocompleteUrl" />
:project-path="projectPath"
:project-namespace="projectNamespace"
:projects-autocomplete-url="projectsAutocompleteUrl"
/>
<div v-else>
<title-component
:issuable-ref="issuableRef"

View file

@ -0,0 +1,111 @@
<script>
export default {
props: {
formState: {
type: Object,
required: true,
},
issuableTemplates: {
type: Array,
required: false,
default: () => [],
},
projectPath: {
type: String,
required: true,
},
projectNamespace: {
type: String,
required: true,
},
},
computed: {
issuableTemplatesJson() {
return JSON.stringify(this.issuableTemplates);
},
},
mounted() {
// Create the editor for the template
const editor = document.querySelector('.detail-page-description .note-textarea') || {};
editor.setValue = (val) => {
this.formState.description = val;
};
editor.getValue = () => this.formState.description;
this.issuableTemplate = new gl.IssuableTemplateSelectors({
$dropdowns: $(this.$refs.toggle),
editor,
});
},
};
</script>
<template>
<div
class="dropdown js-issuable-selector-wrap"
data-issuable-type="issue">
<button
class="dropdown-menu-toggle js-issuable-selector"
type="button"
ref="toggle"
data-field-name="issuable_template"
data-selected="null"
data-toggle="dropdown"
:data-namespace-path="projectNamespace"
:data-project-path="projectPath"
:data-data="issuableTemplatesJson">
<span class="dropdown-toggle-text">
Choose a template
</span>
<i
aria-hidden="true"
class="fa fa-chevron-down">
</i>
</button>
<div class="dropdown-menu dropdown-select">
<div class="dropdown-title">
Choose a template
<button
class="dropdown-title-button dropdown-menu-close"
aria-label="Close"
type="button">
<i
aria-hidden="true"
class="fa fa-times dropdown-menu-close-icon">
</i>
</button>
</div>
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
placeholder="Filter"
autocomplete="off" />
<i
aria-hidden="true"
class="fa fa-search dropdown-input-search">
</i>
<i
role="button"
aria-label="Clear templates search input"
class="fa fa-times dropdown-input-clear js-dropdown-input-clear">
</i>
</div>
<div class="dropdown-content"></div>
<div class="dropdown-footer">
<ul class="dropdown-footer-list">
<li>
<a class="no-template">
No template
</a>
</li>
<li>
<a class="reset-template">
Reset template
</a>
</li>
</ul>
</div>
</div>
</div>
</template>

View file

@ -3,6 +3,7 @@
import titleField from './fields/title.vue';
import descriptionField from './fields/description.vue';
import editActions from './edit_actions.vue';
import descriptionTemplate from './fields/description_template.vue';
import projectMove from './fields/project_move.vue';
import confidentialCheckbox from './fields/confidential_checkbox.vue';
@ -20,6 +21,11 @@
type: Object,
required: true,
},
issuableTemplates: {
type: Array,
required: false,
default: () => [],
},
markdownPreviewUrl: {
type: String,
required: true,
@ -28,6 +34,14 @@
type: String,
required: true,
},
projectPath: {
type: String,
required: true,
},
projectNamespace: {
type: String,
required: true,
},
projectsAutocompleteUrl: {
type: String,
required: true,
@ -37,24 +51,48 @@
lockedWarning,
titleField,
descriptionField,
descriptionTemplate,
editActions,
projectMove,
confidentialCheckbox,
},
computed: {
hasIssuableTemplates() {
return this.issuableTemplates.length;
},
},
};
</script>
<template>
<form>
<locked-warning v-if="formState.lockedWarningVisible" />
<title-field
:form-state="formState" />
<confidential-checkbox
:form-state="formState" />
<div class="row">
<div
class="col-sm-4 col-lg-3"
v-if="hasIssuableTemplates">
<description-template
:form-state="formState"
:issuable-templates="issuableTemplates"
:project-path="projectPath"
:project-namespace="projectNamespace" />
</div>
<div
:class="{
'col-sm-8 col-lg-9': hasIssuableTemplates,
'col-xs-12': !hasIssuableTemplates,
}">
<title-field
:form-state="formState"
:issuable-templates="issuableTemplates" />
</div>
</div>
<description-field
:form-state="formState"
:markdown-preview-url="markdownPreviewUrl"
:markdown-docs="markdownDocs" />
<confidential-checkbox
:form-state="formState" />
<project-move
v-if="canMove"
:form-state="formState"

View file

@ -4,6 +4,8 @@ import issuableApp from './components/app.vue';
import '../vue_shared/vue_resource_interceptor';
document.addEventListener('DOMContentLoaded', () => {
const initialDataEl = document.getElementById('js-issuable-app-initial-data');
const initialData = JSON.parse(initialDataEl.innerHTML.replace(/&quot;/g, '"'));
$('.issuable-edit').on('click', (e) => {
e.preventDefault();
@ -44,7 +46,10 @@ document.addEventListener('DOMContentLoaded', () => {
isConfidential: gl.utils.convertPermissionToBoolean(isConfidential),
markdownPreviewUrl,
markdownDocs,
projectPath: initialData.project_path,
projectNamespace: initialData.namespace_path,
projectsAutocompleteUrl,
issuableTemplates: initialData.templates,
};
},
render(createElement) {
@ -58,9 +63,12 @@ document.addEventListener('DOMContentLoaded', () => {
initialTitle: this.initialTitle,
initialDescriptionHtml: this.initialDescriptionHtml,
initialDescriptionText: this.initialDescriptionText,
issuableTemplates: this.issuableTemplates,
isConfidential: this.isConfidential,
markdownPreviewUrl: this.markdownPreviewUrl,
markdownDocs: this.markdownDocs,
projectPath: this.projectPath,
projectNamespace: this.projectNamespace,
projectsAutocompleteUrl: this.projectsAutocompleteUrl,
},
});

View file

@ -199,6 +199,14 @@ module IssuablesHelper
issuable_filter_params.any? { |k| params.key?(k) }
end
def issuable_initial_data(issuable)
{
templates: issuable_templates(issuable),
project_path: ref_project.path,
namespace_path: ref_project.namespace.full_path
}.to_json
end
private
def sidebar_gutter_collapsed?

View file

@ -51,6 +51,7 @@
.issue-details.issuable-details
.detail-page-description.content-block
%script#js-issuable-app-initial-data{ type: "application/json" }= issuable_initial_data(@issue)
#js-issuable-app{ "data" => { "endpoint" => namespace_project_issue_path(@project.namespace, @project, @issue),
"can-update" => can?(current_user, :update_issue, @issue).to_s,
"can-destroy" => can?(current_user, :destroy_issue, @issue).to_s,

View file

@ -0,0 +1,49 @@
import Vue from 'vue';
import descriptionTemplate from '~/issue_show/components/fields/description_template.vue';
import '~/templates/issuable_template_selector';
import '~/templates/issuable_template_selectors';
describe('Issue description template component', () => {
let vm;
let formState;
beforeEach((done) => {
const Component = Vue.extend(descriptionTemplate);
formState = {
description: 'test',
};
vm = new Component({
propsData: {
formState,
issuableTemplates: [{ name: 'test' }],
projectPath: '/',
projectNamespace: '/',
},
}).$mount();
Vue.nextTick(done);
});
it('renders templates as JSON array in data attribute', () => {
expect(
vm.$el.querySelector('.js-issuable-selector').getAttribute('data-data'),
).toBe('[{"name":"test"}]');
});
it('updates formState when changing template', () => {
vm.issuableTemplate.editor.setValue('test new template');
expect(
formState.description,
).toBe('test new template');
});
it('returns formState description with editor getValue', () => {
formState.description = 'testing new template';
expect(
vm.issuableTemplate.editor.getValue(),
).toBe('testing new template');
});
});

View file

@ -1,5 +1,7 @@
import Vue from 'vue';
import formComponent from '~/issue_show/components/form.vue';
import '~/templates/issuable_template_selector';
import '~/templates/issuable_template_selectors';
describe('Inline edit form component', () => {
let vm;
@ -19,12 +21,33 @@ describe('Inline edit form component', () => {
markdownPreviewUrl: '/',
markdownDocs: '/',
projectsAutocompleteUrl: '/',
projectPath: '/',
projectNamespace: '/',
},
}).$mount();
Vue.nextTick(done);
});
it('does not render template selector if no templates exist', () => {
expect(
vm.$el.querySelector('.js-issuable-selector-wrap'),
).toBeNull();
});
it('renders template selector when templates exists', (done) => {
spyOn(gl, 'IssuableTemplateSelectors');
vm.issuableTemplates = ['test'];
Vue.nextTick(() => {
expect(
vm.$el.querySelector('.js-issuable-selector-wrap'),
).not.toBeNull();
done();
});
});
it('hides locked warning by default', () => {
expect(
vm.$el.querySelector('.alert'),