parent
b1affe07a1
commit
b5b5b4af0c
|
@ -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;
|
||||||
|
|
|
@ -41,8 +41,8 @@ export default {
|
||||||
required: false,
|
required: false,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
showForm: {
|
markdownPreviewUrl: {
|
||||||
type: Boolean,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -71,6 +71,13 @@ export default {
|
||||||
editActions,
|
editActions,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
openForm() {
|
||||||
|
this.showForm = true;
|
||||||
|
this.store.formState = {
|
||||||
|
title: this.state.titleText,
|
||||||
|
description: this.state.descriptionText,
|
||||||
|
};
|
||||||
|
},
|
||||||
updateIssuable() {
|
updateIssuable() {
|
||||||
this.service.updateIssuable(this.formState)
|
this.service.updateIssuable(this.formState)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
@ -96,14 +103,6 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
openForm() {
|
|
||||||
this.showForm = true;
|
|
||||||
this.store.formState = {
|
|
||||||
title: this.state.titleText,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created() {
|
created() {
|
||||||
this.service = new Service(this.endpoint);
|
this.service = new Service(this.endpoint);
|
||||||
this.poll = new Poll({
|
this.poll = new Poll({
|
||||||
|
@ -150,12 +149,14 @@ export default {
|
||||||
:title-html="state.titleHtml"
|
:title-html="state.titleHtml"
|
||||||
:title-text="state.titleText" />
|
:title-text="state.titleText" />
|
||||||
<description-component
|
<description-component
|
||||||
v-if="state.descriptionHtml"
|
:store="store"
|
||||||
|
:show-form="showForm"
|
||||||
: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"
|
||||||
|
:markdown-preview-url="markdownPreviewUrl" />
|
||||||
<edit-actions
|
<edit-actions
|
||||||
v-if="canUpdate && showForm"
|
v-if="canUpdate && showForm"
|
||||||
:can-destroy="canDestroy" />
|
:can-destroy="canDestroy" />
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import animateMixin from '../mixins/animate';
|
import animateMixin from '../mixins/animate';
|
||||||
|
import descriptionField from './fields/description.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [animateMixin],
|
mixins: [animateMixin],
|
||||||
|
@ -24,6 +25,18 @@
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
store: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
showForm: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
markdownPreviewUrl: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -75,6 +88,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
descriptionField,
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.renderGFM();
|
this.renderGFM();
|
||||||
},
|
},
|
||||||
|
@ -82,24 +98,31 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div :class="{ 'common-note-form': showForm }">
|
||||||
class="description"
|
<description-field
|
||||||
:class="{
|
v-if="showForm"
|
||||||
'js-task-list-container': canUpdate
|
:store="store"
|
||||||
}">
|
:markdown-preview-url="markdownPreviewUrl" />
|
||||||
<div
|
<div
|
||||||
class="wiki"
|
v-else-if="descriptionHtml"
|
||||||
|
class="description"
|
||||||
:class="{
|
:class="{
|
||||||
'issue-realtime-pre-pulse': preAnimation,
|
'js-task-list-container': canUpdate
|
||||||
'issue-realtime-trigger-pulse': pulseAnimation
|
}">
|
||||||
}"
|
<div
|
||||||
v-html="descriptionHtml"
|
class="wiki"
|
||||||
ref="gfm-content">
|
:class="{
|
||||||
|
'issue-realtime-pre-pulse': preAnimation,
|
||||||
|
'issue-realtime-trigger-pulse': pulseAnimation
|
||||||
|
}"
|
||||||
|
v-html="descriptionHtml"
|
||||||
|
ref="gfm-content">
|
||||||
|
</div>
|
||||||
|
<textarea
|
||||||
|
class="hidden js-task-list-field"
|
||||||
|
v-if="descriptionText"
|
||||||
|
v-model="descriptionText">
|
||||||
|
</textarea>
|
||||||
</div>
|
</div>
|
||||||
<textarea
|
|
||||||
class="hidden js-task-list-field"
|
|
||||||
v-if="descriptionText"
|
|
||||||
v-model="descriptionText">
|
|
||||||
</textarea>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
<script>
|
||||||
|
/* global Flash */
|
||||||
|
import markdownField from '../../../vue_shared/components/markdown/field.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
store: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
markdownPreviewUrl: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
state: this.store.formState,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
markdownField,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<markdown-field
|
||||||
|
:markdown-preview-url="markdownPreviewUrl">
|
||||||
|
<textarea
|
||||||
|
class="note-textarea js-gfm-input js-autosize markdown-area"
|
||||||
|
data-supports-slash-commands="false"
|
||||||
|
v-model="state.description"
|
||||||
|
ref="textatea"
|
||||||
|
slot="textarea">
|
||||||
|
</textarea>
|
||||||
|
</markdown-field>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -25,6 +25,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
canDestroy,
|
canDestroy,
|
||||||
endpoint,
|
endpoint,
|
||||||
issuableRef,
|
issuableRef,
|
||||||
|
markdownPreviewUrl,
|
||||||
} = issuableElement.dataset;
|
} = issuableElement.dataset;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -35,6 +36,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
initialTitle: issuableTitleElement.innerHTML,
|
initialTitle: issuableTitleElement.innerHTML,
|
||||||
initialDescriptionHtml: issuableDescriptionElement ? issuableDescriptionElement.innerHTML : '',
|
initialDescriptionHtml: issuableDescriptionElement ? issuableDescriptionElement.innerHTML : '',
|
||||||
initialDescriptionText: issuableDescriptionTextarea ? issuableDescriptionTextarea.textContent : '',
|
initialDescriptionText: issuableDescriptionTextarea ? issuableDescriptionTextarea.textContent : '',
|
||||||
|
markdownPreviewUrl,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
render(createElement) {
|
render(createElement) {
|
||||||
|
@ -47,7 +49,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
initialTitle: this.initialTitle,
|
initialTitle: this.initialTitle,
|
||||||
initialDescriptionHtml: this.initialDescriptionHtml,
|
initialDescriptionHtml: this.initialDescriptionHtml,
|
||||||
initialDescriptionText: this.initialDescriptionText,
|
initialDescriptionText: this.initialDescriptionText,
|
||||||
showForm: this.showForm,
|
markdownPreviewUrl: this.markdownPreviewUrl,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -14,6 +14,7 @@ export default class Store {
|
||||||
};
|
};
|
||||||
this.formState = {
|
this.formState = {
|
||||||
title: '',
|
title: '',
|
||||||
|
description: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
<script>
|
||||||
|
/* global Flash */
|
||||||
|
import markdownHeader from './header.vue';
|
||||||
|
import markdownToolbar from './toolbar.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
markdownPreviewUrl: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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 etc.
|
||||||
|
*/
|
||||||
|
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 />
|
||||||
|
</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>
|
|
@ -0,0 +1,97 @@
|
||||||
|
<script>
|
||||||
|
import toolbarButton from './toolbar_button.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
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"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
title="Go full screen"
|
||||||
|
type="button">
|
||||||
|
<i
|
||||||
|
aria-hidden="true"
|
||||||
|
class="fa fa-arrows-alt fa-fw">
|
||||||
|
</i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="comment-toolbar clearfix">
|
||||||
|
<div class="toolbar-text">
|
||||||
|
<a
|
||||||
|
href="/docs"
|
||||||
|
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>
|
|
@ -0,0 +1,48 @@
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="toolbar-btn js-md hidden-xs"
|
||||||
|
tabindex="-1"
|
||||||
|
:data-md-tag="tag"
|
||||||
|
:data-md-block="tagBlock"
|
||||||
|
:data-md-prepend="prepend"
|
||||||
|
data-container="body"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
:title="buttonTitle"
|
||||||
|
:aria-label="buttonTitle">
|
||||||
|
<i
|
||||||
|
aria-hidden="true"
|
||||||
|
class="fa fa-fw"
|
||||||
|
:class="'fa-' + icon">
|
||||||
|
</i>
|
||||||
|
</button>
|
||||||
|
</template>
|
|
@ -55,6 +55,7 @@
|
||||||
"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),
|
||||||
} }
|
} }
|
||||||
%h2.title= markdown_field(@issue, :title)
|
%h2.title= markdown_field(@issue, :title)
|
||||||
- if @issue.description.present?
|
- if @issue.description.present?
|
||||||
|
|
Loading…
Reference in New Issue