render description - tasks work - gfm is juicy
This commit is contained in:
parent
d3a788da5b
commit
76cdb8ee12
|
@ -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,
|
||||
},
|
||||
}),
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) } }
|
||||
|
|
|
@ -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;
|
Loading…
Reference in New Issue