summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhil Hughes <me@iamphill.com>2017-05-17 09:59:08 +0100
committerPhil Hughes <me@iamphill.com>2017-05-17 09:59:08 +0100
commit6963442d6719f1428105d39e017438f26eeae928 (patch)
tree1cf16993ccfff3ba50844b544d708a8eb05a0e4b
parent2a9347410263d87c2b79a5facb04826fd8d8dacf (diff)
parent4fcff0bfa2f0d8b0a9f60e93bee807334557918f (diff)
downloadgitlab-ce-issue-edit-inline-confidential.tar.gz
Merge branch 'issue-edit-inline' into issue-edit-inline-confidentialissue-edit-inline-confidential
[ci skip]
-rw-r--r--app/assets/javascripts/dropzone_input.js3
-rw-r--r--app/assets/javascripts/issue_show/components/app.vue13
-rw-r--r--app/assets/javascripts/issue_show/components/description.vue7
-rw-r--r--app/assets/javascripts/issue_show/components/fields/description.vue47
-rw-r--r--app/assets/javascripts/issue_show/components/form.vue14
-rw-r--r--app/assets/javascripts/issue_show/index.js6
-rw-r--r--app/assets/javascripts/issue_show/stores/index.js1
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue107
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue101
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar.vue33
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue58
-rw-r--r--app/assets/javascripts/vue_shared/mixins/tooltip.js12
-rw-r--r--app/views/projects/issues/show.html.haml2
13 files changed, 398 insertions, 6 deletions
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
index b3a76fbb43e..3843539a3b8 100644
--- a/app/assets/javascripts/dropzone_input.js
+++ b/app/assets/javascripts/dropzone_input.js
@@ -142,7 +142,8 @@ window.DropzoneInput = (function() {
$(child).val(beforeSelection + formattedText + afterSelection);
textarea.setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length);
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) {
var value;
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue
index a4d517ddded..87757b1a35d 100644
--- a/app/assets/javascripts/issue_show/components/app.vue
+++ b/app/assets/javascripts/issue_show/components/app.vue
@@ -45,6 +45,14 @@ export default {
type: Boolean,
required: true,
},
+ markdownPreviewUrl: {
+ type: String,
+ required: true,
+ },
+ markdownDocs: {
+ type: String,
+ required: true,
+ },
},
data() {
const store = new Store({
@@ -75,6 +83,7 @@ export default {
this.store.formState = {
title: this.state.titleText,
confidential: this.isConfidential,
+ description: this.state.descriptionText,
};
},
closeForm() {
@@ -155,7 +164,9 @@ export default {
<form-component
v-if="canUpdate && showForm"
:form-state="formState"
- :can-destroy="canDestroy" />
+ :can-destroy="canDestroy"
+ :markdown-docs="markdownDocs"
+ :markdown-preview-url="markdownPreviewUrl" />
<div v-else>
<title-component
:issuable-ref="issuableRef"
diff --git a/app/assets/javascripts/issue_show/components/description.vue b/app/assets/javascripts/issue_show/components/description.vue
index 4ad3eb7dfd7..3281ec6b172 100644
--- a/app/assets/javascripts/issue_show/components/description.vue
+++ b/app/assets/javascripts/issue_show/components/description.vue
@@ -18,11 +18,13 @@
},
updatedAt: {
type: String,
- required: true,
+ required: false,
+ default: '',
},
taskStatus: {
type: String,
- required: true,
+ required: false,
+ default: '',
},
},
data() {
@@ -83,6 +85,7 @@
<template>
<div
+ v-if="descriptionHtml"
class="description"
:class="{
'js-task-list-container': canUpdate
diff --git a/app/assets/javascripts/issue_show/components/fields/description.vue b/app/assets/javascripts/issue_show/components/fields/description.vue
new file mode 100644
index 00000000000..b4c31811a0b
--- /dev/null
+++ b/app/assets/javascripts/issue_show/components/fields/description.vue
@@ -0,0 +1,47 @@
+<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>
diff --git a/app/assets/javascripts/issue_show/components/form.vue b/app/assets/javascripts/issue_show/components/form.vue
index 862558562e5..a653609c78e 100644
--- a/app/assets/javascripts/issue_show/components/form.vue
+++ b/app/assets/javascripts/issue_show/components/form.vue
@@ -1,5 +1,6 @@
<script>
import titleField from './fields/title.vue';
+ import descriptionField from './fields/description.vue';
import editActions from './edit_actions.vue';
import confidentialCheckbox from './fields/confidential_checkbox.vue';
@@ -13,9 +14,18 @@
type: Object,
required: true,
},
+ markdownPreviewUrl: {
+ type: String,
+ required: true,
+ },
+ markdownDocs: {
+ type: String,
+ required: true,
+ },
},
components: {
titleField,
+ descriptionField,
editActions,
confidentialCheckbox,
},
@@ -28,6 +38,10 @@
:form-state="formState" />
<confidential-checkbox
:form-state="formState" />
+ <description-field
+ :form-state="formState"
+ :markdown-preview-url="markdownPreviewUrl"
+ :markdown-docs="markdownDocs" />
<edit-actions
:can-destroy="canDestroy" />
</form>
diff --git a/app/assets/javascripts/issue_show/index.js b/app/assets/javascripts/issue_show/index.js
index b1e8f467979..3b69be05cf3 100644
--- a/app/assets/javascripts/issue_show/index.js
+++ b/app/assets/javascripts/issue_show/index.js
@@ -26,6 +26,8 @@ document.addEventListener('DOMContentLoaded', () => {
endpoint,
issuableRef,
isConfidential,
+ markdownPreviewUrl,
+ markdownDocs,
} = issuableElement.dataset;
return {
@@ -37,6 +39,8 @@ document.addEventListener('DOMContentLoaded', () => {
initialDescriptionHtml: issuableDescriptionElement ? issuableDescriptionElement.innerHTML : '',
initialDescriptionText: issuableDescriptionTextarea ? issuableDescriptionTextarea.textContent : '',
isConfidential: gl.utils.convertPermissionToBoolean(isConfidential),
+ markdownPreviewUrl,
+ markdownDocs,
};
},
render(createElement) {
@@ -50,6 +54,8 @@ document.addEventListener('DOMContentLoaded', () => {
initialDescriptionHtml: this.initialDescriptionHtml,
initialDescriptionText: this.initialDescriptionText,
isConfidential: this.isConfidential,
+ markdownPreviewUrl: this.markdownPreviewUrl,
+ markdownDocs: this.markdownDocs,
},
});
},
diff --git a/app/assets/javascripts/issue_show/stores/index.js b/app/assets/javascripts/issue_show/stores/index.js
index 5af63369211..d90716bef80 100644
--- a/app/assets/javascripts/issue_show/stores/index.js
+++ b/app/assets/javascripts/issue_show/stores/index.js
@@ -15,6 +15,7 @@ export default class Store {
this.formState = {
title: '',
confidential: false,
+ description: '',
};
}
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
new file mode 100644
index 00000000000..68bbd263f02
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -0,0 +1,107 @@
+<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>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
new file mode 100644
index 00000000000..7884b25c5ef
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -0,0 +1,101 @@
+<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>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
new file mode 100644
index 00000000000..93252293ba6
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
@@ -0,0 +1,33 @@
+<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>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
new file mode 100644
index 00000000000..096be507625
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
@@ -0,0 +1,58 @@
+<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>
diff --git a/app/assets/javascripts/vue_shared/mixins/tooltip.js b/app/assets/javascripts/vue_shared/mixins/tooltip.js
index 9bb948bff66..2e3b716a36c 100644
--- a/app/assets/javascripts/vue_shared/mixins/tooltip.js
+++ b/app/assets/javascripts/vue_shared/mixins/tooltip.js
@@ -1,9 +1,17 @@
export default {
mounted() {
- $(this.$refs.tooltip).tooltip();
+ this.$nextTick(() => {
+ $(this.$refs.tooltip).tooltip();
+ });
},
updated() {
- $(this.$refs.tooltip).tooltip('fixTitle');
+ this.$nextTick(() => {
+ $(this.$refs.tooltip).tooltip('fixTitle');
+ });
+ },
+
+ beforeDestroy() {
+ $(this.$refs.tooltip).tooltip('destroy');
},
};
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index c21cea259a1..9afffdba354 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -56,6 +56,8 @@
"can-destroy" => can?(current_user, :destroy_issue, @issue).to_s,
"issuable-ref" => @issue.to_reference,
"is-confidential" => @issue.confidential.to_s,
+ "markdown-preview-url" => preview_markdown_path(@project),
+ "markdown-docs" => help_page_path('user/markdown'),
} }
%h2.title= markdown_field(@issue, :title)
- if @issue.description.present?