Commit d853d821 authored by Filipa Lacerda's avatar Filipa Lacerda

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

Issue edit inline description template

See merge request !11382
parents 3f996024 21205d1e
...@@ -46,6 +46,11 @@ export default { ...@@ -46,6 +46,11 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
issuableTemplates: {
type: Array,
required: false,
default: () => [],
},
isConfidential: { isConfidential: {
type: Boolean, type: Boolean,
required: true, required: true,
...@@ -58,6 +63,14 @@ export default { ...@@ -58,6 +63,14 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
projectPath: {
type: String,
required: true,
},
projectNamespace: {
type: String,
required: true,
},
projectsAutocompleteUrl: { projectsAutocompleteUrl: {
type: String, type: String,
required: true, required: true,
...@@ -186,9 +199,13 @@ export default { ...@@ -186,9 +199,13 @@ export default {
:form-state="formState" :form-state="formState"
:can-move="canMove" :can-move="canMove"
:can-destroy="canDestroy" :can-destroy="canDestroy"
:issuable-templates="issuableTemplates"
:markdown-docs="markdownDocs" :markdown-docs="markdownDocs"
:markdown-preview-url="markdownPreviewUrl" :markdown-preview-url="markdownPreviewUrl"
:projects-autocomplete-url="projectsAutocompleteUrl" /> :project-path="projectPath"
:project-namespace="projectNamespace"
:projects-autocomplete-url="projectsAutocompleteUrl"
/>
<div v-else> <div v-else>
<title-component <title-component
:issuable-ref="issuableRef" :issuable-ref="issuableRef"
......
<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>
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
import titleField from './fields/title.vue'; import titleField from './fields/title.vue';
import descriptionField from './fields/description.vue'; import descriptionField from './fields/description.vue';
import editActions from './edit_actions.vue'; import editActions from './edit_actions.vue';
import descriptionTemplate from './fields/description_template.vue';
import projectMove from './fields/project_move.vue'; import projectMove from './fields/project_move.vue';
import confidentialCheckbox from './fields/confidential_checkbox.vue'; import confidentialCheckbox from './fields/confidential_checkbox.vue';
...@@ -20,6 +21,11 @@ ...@@ -20,6 +21,11 @@
type: Object, type: Object,
required: true, required: true,
}, },
issuableTemplates: {
type: Array,
required: false,
default: () => [],
},
markdownPreviewUrl: { markdownPreviewUrl: {
type: String, type: String,
required: true, required: true,
...@@ -28,6 +34,14 @@ ...@@ -28,6 +34,14 @@
type: String, type: String,
required: true, required: true,
}, },
projectPath: {
type: String,
required: true,
},
projectNamespace: {
type: String,
required: true,
},
projectsAutocompleteUrl: { projectsAutocompleteUrl: {
type: String, type: String,
required: true, required: true,
...@@ -37,24 +51,48 @@ ...@@ -37,24 +51,48 @@
lockedWarning, lockedWarning,
titleField, titleField,
descriptionField, descriptionField,
descriptionTemplate,
editActions, editActions,
projectMove, projectMove,
confidentialCheckbox, confidentialCheckbox,
}, },
computed: {
hasIssuableTemplates() {
return this.issuableTemplates.length;
},
},
}; };
</script> </script>
<template> <template>
<form> <form>
<locked-warning v-if="formState.lockedWarningVisible" /> <locked-warning v-if="formState.lockedWarningVisible" />
<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 <title-field
:form-state="formState" /> :form-state="formState"
<confidential-checkbox :issuable-templates="issuableTemplates" />
:form-state="formState" /> </div>
</div>
<description-field <description-field
:form-state="formState" :form-state="formState"
:markdown-preview-url="markdownPreviewUrl" :markdown-preview-url="markdownPreviewUrl"
:markdown-docs="markdownDocs" /> :markdown-docs="markdownDocs" />
<confidential-checkbox
:form-state="formState" />
<project-move <project-move
v-if="canMove" v-if="canMove"
:form-state="formState" :form-state="formState"
......
...@@ -4,6 +4,8 @@ import issuableApp from './components/app.vue'; ...@@ -4,6 +4,8 @@ import issuableApp from './components/app.vue';
import '../vue_shared/vue_resource_interceptor'; import '../vue_shared/vue_resource_interceptor';
document.addEventListener('DOMContentLoaded', () => { 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) => { $('.issuable-edit').on('click', (e) => {
e.preventDefault(); e.preventDefault();
...@@ -44,7 +46,10 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -44,7 +46,10 @@ document.addEventListener('DOMContentLoaded', () => {
isConfidential: gl.utils.convertPermissionToBoolean(isConfidential), isConfidential: gl.utils.convertPermissionToBoolean(isConfidential),
markdownPreviewUrl, markdownPreviewUrl,
markdownDocs, markdownDocs,
projectPath: initialData.project_path,
projectNamespace: initialData.namespace_path,
projectsAutocompleteUrl, projectsAutocompleteUrl,
issuableTemplates: initialData.templates,
}; };
}, },
render(createElement) { render(createElement) {
...@@ -58,9 +63,12 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -58,9 +63,12 @@ document.addEventListener('DOMContentLoaded', () => {
initialTitle: this.initialTitle, initialTitle: this.initialTitle,
initialDescriptionHtml: this.initialDescriptionHtml, initialDescriptionHtml: this.initialDescriptionHtml,
initialDescriptionText: this.initialDescriptionText, initialDescriptionText: this.initialDescriptionText,
issuableTemplates: this.issuableTemplates,
isConfidential: this.isConfidential, isConfidential: this.isConfidential,
markdownPreviewUrl: this.markdownPreviewUrl, markdownPreviewUrl: this.markdownPreviewUrl,
markdownDocs: this.markdownDocs, markdownDocs: this.markdownDocs,
projectPath: this.projectPath,
projectNamespace: this.projectNamespace,
projectsAutocompleteUrl: this.projectsAutocompleteUrl, projectsAutocompleteUrl: this.projectsAutocompleteUrl,
}, },
}); });
......
...@@ -199,6 +199,14 @@ module IssuablesHelper ...@@ -199,6 +199,14 @@ module IssuablesHelper
issuable_filter_params.any? { |k| params.key?(k) } issuable_filter_params.any? { |k| params.key?(k) }
end 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 private
def sidebar_gutter_collapsed? def sidebar_gutter_collapsed?
......
...@@ -51,6 +51,7 @@ ...@@ -51,6 +51,7 @@
.issue-details.issuable-details .issue-details.issuable-details
.detail-page-description.content-block .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), #js-issuable-app{ "data" => { "endpoint" => namespace_project_issue_path(@project.namespace, @project, @issue),
"can-update" => can?(current_user, :update_issue, @issue).to_s, "can-update" => can?(current_user, :update_issue, @issue).to_s,
"can-destroy" => can?(current_user, :destroy_issue, @issue).to_s, "can-destroy" => can?(current_user, :destroy_issue, @issue).to_s,
......
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');
});
});
import Vue from 'vue'; import Vue from 'vue';
import formComponent from '~/issue_show/components/form.vue'; import formComponent from '~/issue_show/components/form.vue';
import '~/templates/issuable_template_selector';
import '~/templates/issuable_template_selectors';
describe('Inline edit form component', () => { describe('Inline edit form component', () => {
let vm; let vm;
...@@ -19,12 +21,33 @@ describe('Inline edit form component', () => { ...@@ -19,12 +21,33 @@ describe('Inline edit form component', () => {
markdownPreviewUrl: '/', markdownPreviewUrl: '/',
markdownDocs: '/', markdownDocs: '/',
projectsAutocompleteUrl: '/', projectsAutocompleteUrl: '/',
projectPath: '/',
projectNamespace: '/',
}, },
}).$mount(); }).$mount();
Vue.nextTick(done); 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', () => { it('hides locked warning by default', () => {
expect( expect(
vm.$el.querySelector('.alert'), vm.$el.querySelector('.alert'),
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment