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