From 76cdb8ee127942a3e5b88eb7e8a9c199bdf3363e Mon Sep 17 00:00:00 2001
From: Regis <boudinot.regis@yahoo.com>
Date: Fri, 21 Apr 2017 14:34:11 -0600
Subject: [PATCH] render description - tasks work - gfm is juicy

---
 app/assets/javascripts/issue_show/index.js    |   6 +-
 .../javascripts/issue_show/issue_title.vue    |  80 -----------
 .../issue_show/issue_title_description.vue    | 128 ++++++++++++++++++
 app/assets/stylesheets/pages/issues.scss      |   9 ++
 app/controllers/projects/issues_controller.rb |   6 +-
 app/views/projects/issues/show.html.haml      |  12 +-
 ...pec.js => issue_title_description_spec.js} |   2 +-
 7 files changed, 149 insertions(+), 94 deletions(-)
 delete mode 100644 app/assets/javascripts/issue_show/issue_title.vue
 create mode 100644 app/assets/javascripts/issue_show/issue_title_description.vue
 rename spec/javascripts/issue_show/{issue_title_spec.js => issue_title_description_spec.js} (88%)

diff --git a/app/assets/javascripts/issue_show/index.js b/app/assets/javascripts/issue_show/index.js
index 4d491e70d83..db1cdb6d498 100644
--- a/app/assets/javascripts/issue_show/index.js
+++ b/app/assets/javascripts/issue_show/index.js
@@ -1,16 +1,16 @@
 import Vue from 'vue';
-import IssueTitle from './issue_title.vue';
+import IssueTitle from './issue_title_description.vue';
 import '../vue_shared/vue_resource_interceptor';
 
 (() => {
   const issueTitleData = document.querySelector('.issue-title-data').dataset;
-  const { initialTitle, endpoint } = issueTitleData;
+  const { candescription, endpoint } = issueTitleData;
 
   const vm = new Vue({
     el: '.issue-title-entrypoint',
     render: createElement => createElement(IssueTitle, {
       props: {
-        initialTitle,
+        candescription,
         endpoint,
       },
     }),
diff --git a/app/assets/javascripts/issue_show/issue_title.vue b/app/assets/javascripts/issue_show/issue_title.vue
deleted file mode 100644
index 00b0e56030a..00000000000
--- a/app/assets/javascripts/issue_show/issue_title.vue
+++ /dev/null
@@ -1,80 +0,0 @@
-<script>
-import Visibility from 'visibilityjs';
-import Poll from './../lib/utils/poll';
-import Service from './services/index';
-
-export default {
-  props: {
-    initialTitle: { required: true, type: String },
-    endpoint: { required: true, type: String },
-  },
-  data() {
-    const resource = new Service(this.$http, this.endpoint);
-
-    const poll = new Poll({
-      resource,
-      method: 'getTitle',
-      successCallback: (res) => {
-        this.renderResponse(res);
-      },
-      errorCallback: (err) => {
-        if (process.env.NODE_ENV !== 'production') {
-          // eslint-disable-next-line no-console
-          console.error('ISSUE SHOW TITLE REALTIME ERROR', err);
-        } else {
-          throw new Error(err);
-        }
-      },
-    });
-
-    return {
-      poll,
-      timeoutId: null,
-      title: this.initialTitle,
-    };
-  },
-  methods: {
-    renderResponse(res) {
-      const body = JSON.parse(res.body);
-      this.triggerAnimation(body);
-    },
-    triggerAnimation(body) {
-      const { title } = body;
-
-      /**
-      * since opacity is changed, even if there is no diff for Vue to update
-      * we must check the title even on a 304 to ensure no visual change
-      */
-      if (this.title === title) return;
-
-      this.$el.style.opacity = 0;
-
-      this.timeoutId = setTimeout(() => {
-        this.title = title;
-
-        this.$el.style.transition = 'opacity 0.2s ease';
-        this.$el.style.opacity = 1;
-
-        clearTimeout(this.timeoutId);
-      }, 100);
-    },
-  },
-  created() {
-    if (!Visibility.hidden()) {
-      this.poll.makeRequest();
-    }
-
-    Visibility.change(() => {
-      if (!Visibility.hidden()) {
-        this.poll.restart();
-      } else {
-        this.poll.stop();
-      }
-    });
-  },
-};
-</script>
-
-<template>
-  <h2 class="title" v-html="title"></h2>
-</template>
diff --git a/app/assets/javascripts/issue_show/issue_title_description.vue b/app/assets/javascripts/issue_show/issue_title_description.vue
new file mode 100644
index 00000000000..4605fdadf8d
--- /dev/null
+++ b/app/assets/javascripts/issue_show/issue_title_description.vue
@@ -0,0 +1,128 @@
+<script>
+import Visibility from 'visibilityjs';
+import Poll from './../lib/utils/poll';
+import Service from './services/index';
+
+export default {
+  props: {
+    endpoint: { required: true, type: String },
+    candescription: { required: true, type: String },
+  },
+  data() {
+    const resource = new Service(this.$http, this.endpoint);
+
+    const poll = new Poll({
+      resource,
+      method: 'getTitle',
+      successCallback: (res) => {
+        this.renderResponse(res);
+      },
+      errorCallback: (err) => {
+        if (process.env.NODE_ENV !== 'production') {
+          // eslint-disable-next-line no-console
+          console.error('ISSUE SHOW TITLE REALTIME ERROR', err);
+        } else {
+          throw new Error(err);
+        }
+      },
+    });
+
+    return {
+      poll,
+      timeoutId: null,
+      title: null,
+      description: null,
+    };
+  },
+  methods: {
+    renderResponse(res) {
+      const body = JSON.parse(res.body);
+      this.triggerAnimation(body);
+    },
+    triggerAnimation(body) {
+      const { title, description } = body;
+
+      this.descriptionText = body.description_text;
+
+      /**
+      * since opacity is changed, even if there is no diff for Vue to update
+      * we must check the title even on a 304 to ensure no visual change
+      */
+      const noTitleChange = this.title === title;
+      const noDescriptionChange = this.description === description;
+
+      if (noTitleChange && noDescriptionChange) return;
+
+      const elementsToVisualize = [];
+
+      if (!noTitleChange) {
+        elementsToVisualize.push(this.$el.querySelector('.title'));
+      } else if (!noDescriptionChange) {
+        elementsToVisualize.push(this.$el.querySelector('.wiki'));
+      }
+
+      elementsToVisualize.forEach((element) => {
+        element.classList.remove('issue-realtime-trigger-pulse');
+        element.classList.add('issue-realtime-pre-pulse');
+      });
+
+      this.timeoutId = setTimeout(() => {
+        this.title = title;
+        this.description = description;
+
+        elementsToVisualize.forEach((element) => {
+          element.classList.remove('issue-realtime-pre-pulse');
+          element.classList.add('issue-realtime-trigger-pulse');
+        });
+
+        clearTimeout(this.timeoutId);
+      }, 100);
+    },
+  },
+  computed: {
+    descriptionClass() {
+      return `description ${this.candescription} is-task-list-enabled`;
+    },
+  },
+  created() {
+    if (!Visibility.hidden()) {
+      this.poll.makeRequest();
+    }
+
+    Visibility.change(() => {
+      if (!Visibility.hidden()) {
+        this.poll.restart();
+      } else {
+        this.poll.stop();
+      }
+    });
+  },
+  updated() {
+    new gl.TaskList({
+      dataType: 'issue',
+      fieldName: 'description',
+      selector: '.detail-page-description',
+    }).init();
+
+    $(this.$refs['issue-content-container-gfm-entry']).renderGFM();
+  },
+};
+</script>
+
+<template>
+  <div>
+    <h2 class="title issue-realtime-trigger-pulse" v-html="title"></h2>
+    <div
+      :class="descriptionClass"
+      v-if="description"
+    >
+      <div
+        class="wiki issue-realtime-trigger-pulse"
+        v-html="description"
+        ref="issue-content-container-gfm-entry"
+      >
+      </div>
+      <textarea class="hidden js-task-list-field" v-if="descriptionText">{{descriptionText}}</textarea>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index b2f45625a2a..13112916615 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -18,6 +18,15 @@
   }
 }
 
+.issue-realtime-pre-pulse {
+  opacity: 0;
+}
+
+.issue-realtime-trigger-pulse {
+  transition: opacity 0.2s ease;
+  opacity: 1;
+}
+
 .check-all-holder {
   line-height: 36px;
   float: left;
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index cbf67137261..b1df50ba849 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -198,7 +198,11 @@ class Projects::IssuesController < Projects::ApplicationController
 
   def rendered_title
     Gitlab::PollingInterval.set_header(response, interval: 3_000)
-    render json: { title: view_context.markdown_field(@issue, :title) }
+    render json: {
+      title: view_context.markdown_field(@issue, :title),
+      description: view_context.markdown_field(@issue, :description),
+      description_text: @issue.description,
+    }
   end
 
   protected
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index fcbd8829595..bf98efbdfdf 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -51,17 +51,11 @@
 
 .issue-details.issuable-details
   .detail-page-description.content-block{ class: ('hide-bottom-border' unless @issue.description.present? ) }
-    .issue-title-data.hidden{ "data" => { "initial-title" => markdown_field(@issue, :title),
-      "endpoint" => rendered_title_namespace_project_issue_path(@project.namespace, @project, @issue),
+    .issue-title-data.hidden{ "data" => { "endpoint" => rendered_title_namespace_project_issue_path(@project.namespace, @project, @issue),
+      "canDescription" => can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : '',
     } }
     .issue-title-entrypoint
-    - if @issue.description.present?
-      .description{ class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : '' }
-        .wiki
-          = preserve do
-            = markdown_field(@issue, :description)
-        %textarea.hidden.js-task-list-field
-          = @issue.description
+
     = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
 
     #merge-requests{ data: { url: referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue) } }
diff --git a/spec/javascripts/issue_show/issue_title_spec.js b/spec/javascripts/issue_show/issue_title_description_spec.js
similarity index 88%
rename from spec/javascripts/issue_show/issue_title_spec.js
rename to spec/javascripts/issue_show/issue_title_description_spec.js
index 03edbf9f947..f351ca7d8e6 100644
--- a/spec/javascripts/issue_show/issue_title_spec.js
+++ b/spec/javascripts/issue_show/issue_title_description_spec.js
@@ -1,5 +1,5 @@
 import Vue from 'vue';
-import issueTitle from '~/issue_show/issue_title.vue';
+import issueTitle from '~/issue_show/issue_title_description.vue';
 
 describe('Issue Title', () => {
   let IssueTitleComponent;
-- 
GitLab