render description - tasks work - gfm is juicy

This commit is contained in:
Regis 2017-04-21 14:34:11 -06:00
parent d3a788da5b
commit 76cdb8ee12
7 changed files with 149 additions and 94 deletions

View File

@ -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,
},
}),

View File

@ -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>

View File

@ -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>

View File

@ -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;

View File

@ -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

View File

@ -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) } }

View File

@ -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;