BigW Consortium Gitlab

Commit f1b0b4a4 by Phil Hughes

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

parents 47e875ea 4fcff0bf
...@@ -142,7 +142,8 @@ window.DropzoneInput = (function() { ...@@ -142,7 +142,8 @@ window.DropzoneInput = (function() {
$(child).val(beforeSelection + formattedText + afterSelection); $(child).val(beforeSelection + formattedText + afterSelection);
textarea.setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length); textarea.setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length);
textarea.style.height = `${textarea.scrollHeight}px`; textarea.style.height = `${textarea.scrollHeight}px`;
return form_textarea.trigger("input"); form_textarea.trigger("input");
form_textarea.get(0).dispatchEvent(new Event('input'));
}; };
getFilename = function(e) { getFilename = function(e) {
var value; var value;
......
...@@ -46,6 +46,14 @@ export default { ...@@ -46,6 +46,14 @@ export default {
required: false, required: false,
default: () => [], default: () => [],
}, },
markdownPreviewUrl: {
type: String,
required: true,
},
markdownDocs: {
type: String,
required: true,
},
}, },
data() { data() {
const store = new Store({ const store = new Store({
...@@ -75,6 +83,7 @@ export default { ...@@ -75,6 +83,7 @@ export default {
this.showForm = true; this.showForm = true;
this.store.formState = { this.store.formState = {
title: this.state.titleText, title: this.state.titleText,
description: this.state.descriptionText,
}; };
}, },
closeForm() { closeForm() {
...@@ -150,7 +159,9 @@ export default { ...@@ -150,7 +159,9 @@ export default {
v-if="canUpdate && showForm" v-if="canUpdate && showForm"
:form-state="formState" :form-state="formState"
:can-destroy="canDestroy" :can-destroy="canDestroy"
:issuable-templates="issuableTemplates" /> :issuable-templates="issuableTemplates"
:markdown-docs="markdownDocs"
:markdown-preview-url="markdownPreviewUrl" />
<div v-else> <div v-else>
<title-component <title-component
:issuable-ref="issuableRef" :issuable-ref="issuableRef"
......
...@@ -18,11 +18,13 @@ ...@@ -18,11 +18,13 @@
}, },
updatedAt: { updatedAt: {
type: String, type: String,
required: true, required: false,
default: '',
}, },
taskStatus: { taskStatus: {
type: String, type: String,
required: true, required: false,
default: '',
}, },
}, },
data() { data() {
...@@ -83,6 +85,7 @@ ...@@ -83,6 +85,7 @@
<template> <template>
<div <div
v-if="descriptionHtml"
class="description" class="description"
:class="{ :class="{
'js-task-list-container': canUpdate 'js-task-list-container': canUpdate
......
<script>
/* global Flash */
import markdownField from '../../../vue_shared/components/markdown/field.vue';
export default {
props: {
formState: {
type: Object,
required: true,
},
markdownPreviewUrl: {
type: String,
required: true,
},
markdownDocs: {
type: String,
required: true,
},
},
components: {
markdownField,
},
};
</script>
<template>
<div class="common-note-form">
<label
class="sr-only"
for="issue-description">
Description
</label>
<markdown-field
:markdown-preview-url="markdownPreviewUrl"
:markdown-docs="markdownDocs">
<textarea
id="issue-description"
class="note-textarea js-gfm-input js-autosize markdown-area"
data-supports-slash-commands="false"
aria-label="Description"
v-model="formState.description"
ref="textatea"
slot="textarea">
</textarea>
</markdown-field>
</div>
</template>
<script> <script>
import descriptionTemplate from './template.vue';
export default { export default {
props: { props: {
formState: { formState: {
type: Object, type: Object,
required: true, required: true,
}, },
issuableTemplates: {
type: Array,
required: false,
default: () => [],
},
},
components: {
descriptionTemplate,
},
computed: {
hasIssuableTemplates() {
return this.issuableTemplates.length !== 0;
},
}, },
}; };
</script> </script>
<template> <template>
<fieldset class="row"> <fieldset>
<div
class="col-sm-4 col-lg-3"
v-if="hasIssuableTemplates">
<description-template
:issuable-templates="issuableTemplates" />
</div>
<div
:class="{
'col-sm-8 col-lg-9': hasIssuableTemplates,
'col-xs-12': !hasIssuableTemplates,
}">
<label <label
class="sr-only" class="sr-only"
for="issue-title"> for="issue-title">
...@@ -49,6 +23,5 @@ ...@@ -49,6 +23,5 @@
placeholder="Issue title" placeholder="Issue title"
aria-label="Issue title" aria-label="Issue title"
v-model="formState.title" /> v-model="formState.title" />
</div>
</fieldset> </fieldset>
</template> </template>
<script> <script>
import titleField from './fields/title.vue'; import titleField from './fields/title.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';
export default { export default {
props: { props: {
...@@ -17,24 +19,52 @@ ...@@ -17,24 +19,52 @@
required: false, required: false,
default: () => [], default: () => [],
}, },
markdownPreviewUrl: {
type: String,
required: true,
},
markdownDocs: {
type: String,
required: true,
},
}, },
components: { components: {
titleField, titleField,
descriptionField,
descriptionTemplate,
editActions, editActions,
}, },
issuableTemplates: { computed: {
type: Array, hasIssuableTemplates() {
required: true, return this.issuableTemplates.length !== 0;
default: () => [], },
}, },
}; };
</script> </script>
<template> <template>
<form> <form>
<div class="row">
<div
class="col-sm-4 col-lg-3"
v-if="hasIssuableTemplates">
<description-template
:issuable-templates="issuableTemplates" />
</div>
<div
:class="{
'col-sm-8 col-lg-9': hasIssuableTemplates,
'col-xs-12': !hasIssuableTemplates,
}">
<title-field <title-field
:form-state="formState" :form-state="formState"
:issuable-templates="issuableTemplates" /> :issuable-templates="issuableTemplates" />
</div>
</div>
<description-field
:form-state="formState"
:markdown-preview-url="markdownPreviewUrl"
:markdown-docs="markdownDocs" />
<edit-actions <edit-actions
:can-destroy="canDestroy" /> :can-destroy="canDestroy" />
</form> </form>
......
...@@ -27,6 +27,8 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -27,6 +27,8 @@ document.addEventListener('DOMContentLoaded', () => {
canDestroy, canDestroy,
endpoint, endpoint,
issuableRef, issuableRef,
markdownPreviewUrl,
markdownDocs,
} = issuableElement.dataset; } = issuableElement.dataset;
return { return {
...@@ -38,6 +40,8 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -38,6 +40,8 @@ document.addEventListener('DOMContentLoaded', () => {
initialDescriptionHtml: issuableDescriptionElement ? issuableDescriptionElement.innerHTML : '', initialDescriptionHtml: issuableDescriptionElement ? issuableDescriptionElement.innerHTML : '',
initialDescriptionText: issuableDescriptionTextarea ? issuableDescriptionTextarea.textContent : '', initialDescriptionText: issuableDescriptionTextarea ? issuableDescriptionTextarea.textContent : '',
issuableTemplates: initialData.templates, issuableTemplates: initialData.templates,
markdownPreviewUrl,
markdownDocs,
}; };
}, },
render(createElement) { render(createElement) {
...@@ -51,6 +55,8 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -51,6 +55,8 @@ document.addEventListener('DOMContentLoaded', () => {
initialDescriptionHtml: this.initialDescriptionHtml, initialDescriptionHtml: this.initialDescriptionHtml,
initialDescriptionText: this.initialDescriptionText, initialDescriptionText: this.initialDescriptionText,
issuableTemplates: this.issuableTemplates, issuableTemplates: this.issuableTemplates,
markdownPreviewUrl: this.markdownPreviewUrl,
markdownDocs: this.markdownDocs,
}, },
}); });
}, },
......
...@@ -14,6 +14,7 @@ export default class Store { ...@@ -14,6 +14,7 @@ export default class Store {
}; };
this.formState = { this.formState = {
title: '', title: '',
description: '',
}; };
} }
......
<script>
/* global Flash */
import markdownHeader from './header.vue';
import markdownToolbar from './toolbar.vue';
export default {
props: {
markdownPreviewUrl: {
type: String,
required: false,
default: '',
},
markdownDocs: {
type: String,
required: true,
},
},
data() {
return {
markdownPreview: '',
markdownPreviewLoading: false,
previewMarkdown: false,
};
},
components: {
markdownHeader,
markdownToolbar,
},
methods: {
toggleMarkdownPreview() {
this.previewMarkdown = !this.previewMarkdown;
if (!this.previewMarkdown) {
this.markdownPreview = '';
} else {
this.markdownPreviewLoading = true;
this.$http.post(
this.markdownPreviewUrl,
{
/*
Can't use `$refs` as the component is technically in the parent component
so we access the VNode & then get the element
*/
text: this.$slots.textarea[0].elm.value,
},
)
.then((res) => {
const data = res.json();
this.markdownPreviewLoading = false;
this.markdownPreview = data.body;
this.$nextTick(() => {
$(this.$refs['markdown-preview']).renderGFM();
});
})
.catch(() => new Flash('Error loading markdown preview'));
}
},
},
mounted() {
/*
GLForm class handles all the toolbar buttons
*/
return new gl.GLForm($(this.$refs['gl-form']));
},
};
</script>
<template>
<div
class="md-area prepend-top-default append-bottom-default"
ref="gl-form">
<markdown-header
:preview-markdown="previewMarkdown"
@toggle-markdown="toggleMarkdownPreview" />
<div
class="md-write-holder"
v-show="!previewMarkdown">
<div class="zen-backdrop">
<slot name="textarea"></slot>
<a
class="zen-control zen-control-leave js-zen-leave"
href="#"
aria-label="Enter zen mode">
<i
class="fa fa-compress"
aria-hidden="true">
</i>
</a>
<markdown-toolbar
:markdown-docs="markdownDocs" />
</div>
</div>
<div
class="md md-preview-holder md-preview"
v-show="previewMarkdown">
<div
ref="markdown-preview"
v-html="markdownPreview">
</div>
<span v-if="markdownPreviewLoading">
Loading...
</span>
</div>
</div>
</template>
<script>
import tooltipMixin from '../../mixins/tooltip';
import toolbarButton from './toolbar_button.vue';
export default {
mixins: [
tooltipMixin,
],
props: {
previewMarkdown: {
type: Boolean,
required: true,
},
},
components: {
toolbarButton,
},
methods: {
toggleMarkdownPreview(e) {
e.target.blur();
this.$emit('toggle-markdown');
},
},
};
</script>
<template>
<div class="md-header">
<ul class="nav-links clearfix">
<li :class="{ active: !previewMarkdown }">
<a
href="#md-write-holder"
tabindex="-1"
@click.prevent="toggleMarkdownPreview($event)">
Write
</a>
</li>
<li :class="{ active: previewMarkdown }">
<a
href="#md-preview-holder"
tabindex="-1"
@click.prevent="toggleMarkdownPreview($event)">
Preview
</a>
</li>
<li class="pull-right">
<div class="toolbar-group">
<toolbar-button
tag="**"
button-title="Add bold text"
icon="bold" />
<toolbar-button
tag="*"
button-title="Add italic text"
icon="italic" />
<toolbar-button
tag="> "
:prepend="true"
button-title="Insert a quote"
icon="quote-right" />
<toolbar-button
tag="`"
tag-block="```"
button-title="Insert code"
icon="code" />
<toolbar-button
tag="* "
:prepend="true"
button-title="Add a bullet list"
icon="list-ul" />
<toolbar-button
tag="1. "
:prepend="true"
button-title="Add a numbered list"
icon="list-ol" />
<toolbar-button
tag="* [ ] "
:prepend="true"
button-title="Add a task list"
icon="check-square-o" />
</div>
<div class="toolbar-group">
<button
aria-label="Go full screen"
class="toolbar-btn js-zen-enter"
data-container="body"
tabindex="-1"
title="Go full screen"
type="button"
ref="tooltip">
<i
aria-hidden="true"
class="fa fa-arrows-alt fa-fw">
</i>
</button>
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
props: {
markdownDocs: {
type: String,
required: true,
},
},
};
</script>
<template>
<div class="comment-toolbar clearfix">
<div class="toolbar-text">
<a
:href="markdownDocs"
target="_blank"
tabindex="-1">
Markdown is supported
</a>
</div>
<button
class="toolbar-button markdown-selector"
type="button"
tabindex="-1">
<i
class="fa fa-file-image-o toolbar-button-icon"
aria-hidden="true">
</i>
Attach a file
</button>
</div>
</template>
<script>
import tooltipMixin from '../../mixins/tooltip';
export default {
mixins: [
tooltipMixin,
],
props: {
buttonTitle: {
type: String,
required: true,
},
icon: {
type: String,
required: true,
},
tag: {
type: String,
required: true,
},
tagBlock: {
type: String,
required: false,
default: '',
},
prepend: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
iconClass() {
return `fa-${this.icon}`;
},
},
};
</script>
<template>
<button
type="button"
class="toolbar-btn js-md hidden-xs"
tabindex="-1"
ref="tooltip"
data-container="body"
:data-md-tag="tag"
:data-md-block="tagBlock"
:data-md-prepend="prepend"
:title="buttonTitle"
:aria-label="buttonTitle">
<i
aria-hidden="true"
class="fa fa-fw"
:class="iconClass">
</i>
</button>
</template>
export default { export default {
mounted() { mounted() {
this.$nextTick(() => {
$(this.$refs.tooltip).tooltip(); $(this.$refs.tooltip).tooltip();
});
}, },
updated() { updated() {
this.$nextTick(() => {
$(this.$refs.tooltip).tooltip('fixTitle'); $(this.$refs.tooltip).tooltip('fixTitle');
});
},
beforeDestroy() {
$(this.$refs.tooltip).tooltip('destroy');
}, },
}; };
...@@ -56,6 +56,8 @@ ...@@ -56,6 +56,8 @@
"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,
"issuable-ref" => @issue.to_reference, "issuable-ref" => @issue.to_reference,
"markdown-preview-url" => preview_markdown_path(@project),
"markdown-docs" => help_page_path('user/markdown'),
} } } }
%h2.title= markdown_field(@issue, :title) %h2.title= markdown_field(@issue, :title)
- if @issue.description.present? - if @issue.description.present?
......
...@@ -75,18 +75,6 @@ describe('Issuable output', () => { ...@@ -75,18 +75,6 @@ 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) => { it('does not show actions if permissions are incorrect', (done) => {
vm.showForm = true; vm.showForm = true;
vm.canUpdate = false; vm.canUpdate = false;
......
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