gitlab-org--gitlab-foss/app/assets/javascripts/merge_request_tabs.js

375 lines
11 KiB
JavaScript
Raw Normal View History

2016-12-02 18:43:20 -05:00
/* eslint-disable no-new, class-methods-use-this */
/* global Breakpoints */
/* global Flash */
2017-03-11 01:45:34 -05:00
import Cookies from 'js-cookie';
import CommitPipelinesTable from './commit/pipelines/pipelines_table';
import './breakpoints';
import './flash';
/* eslint-disable max-len */
// MergeRequestTabs
//
// Handles persisting and restoring the current tab selection and lazily-loading
// content on the MergeRequests#show page.
//
// ### Example Markup
//
// <ul class="nav-links merge-request-tabs">
// <li class="notes-tab active">
// <a data-action="notes" data-target="#notes" data-toggle="tab" href="/foo/bar/merge_requests/1">
// Discussion
// </a>
// </li>
// <li class="commits-tab">
// <a data-action="commits" data-target="#commits" data-toggle="tab" href="/foo/bar/merge_requests/1/commits">
// Commits
// </a>
// </li>
// <li class="diffs-tab">
// <a data-action="diffs" data-target="#diffs" data-toggle="tab" href="/foo/bar/merge_requests/1/diffs">
// Diffs
// </a>
// </li>
// </ul>
//
// <div class="tab-content">
// <div class="notes tab-pane active" id="notes">
// Notes Content
// </div>
// <div class="commits tab-pane" id="commits">
// Commits Content
// </div>
// <div class="diffs tab-pane" id="diffs">
// Diffs Content
// </div>
// </div>
//
// <div class="mr-loading-status">
// <div class="loading">
// Loading Animation
// </div>
// </div>
//
/* eslint-enable max-len */
2016-12-02 18:43:20 -05:00
(() => {
// Store the `location` object, allowing for easier stubbing in tests
let location = window.location;
2016-07-24 16:45:11 -04:00
class MergeRequestTabs {
2016-07-24 16:45:11 -04:00
constructor({ action, setUrl, stubLocation } = {}) {
this.diffsLoaded = false;
this.pipelinesLoaded = false;
this.commitsLoaded = false;
this.fixedLayoutPref = null;
2016-07-24 16:45:11 -04:00
this.setUrl = setUrl !== undefined ? setUrl : true;
this.setCurrentAction = this.setCurrentAction.bind(this);
this.tabShown = this.tabShown.bind(this);
this.showTab = this.showTab.bind(this);
2016-07-24 16:45:11 -04:00
if (stubLocation) {
location = stubLocation;
}
2016-07-24 16:45:11 -04:00
this.bindEvents();
this.activateTab(action);
this.initAffix();
2016-07-24 16:45:11 -04:00
}
bindEvents() {
$(document)
.on('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown)
.on('click', '.js-show-tab', this.showTab);
2017-01-31 04:14:22 -05:00
$('.merge-request-tabs a[data-toggle="tab"]')
.on('click', this.clickTab);
}
2017-04-07 00:19:30 -04:00
// Used in tests
unbindEvents() {
$(document)
.off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown)
.off('click', '.js-show-tab', this.showTab);
2017-01-31 04:14:22 -05:00
$('.merge-request-tabs a[data-toggle="tab"]')
.off('click', this.clickTab);
}
2016-07-24 16:45:11 -04:00
destroyPipelinesView() {
if (this.commitPipelinesTable) {
document.querySelector('#commit-pipeline-table-view')
.removeChild(this.commitPipelinesTable.$el);
2017-04-07 00:19:30 -04:00
this.commitPipelinesTable.$destroy();
}
}
2016-12-01 12:35:26 -05:00
showTab(e) {
e.preventDefault();
this.activateTab($(e.target).data('action'));
}
2016-07-24 16:45:11 -04:00
2017-01-31 04:14:22 -05:00
clickTab(e) {
if (e.currentTarget && gl.utils.isMetaClick(e)) {
const targetLink = e.currentTarget.getAttribute('href');
2017-02-07 11:46:10 -05:00
e.stopImmediatePropagation();
e.preventDefault();
2017-02-07 11:46:10 -05:00
window.open(targetLink, '_blank');
2017-01-31 04:14:22 -05:00
}
}
2016-12-01 12:35:26 -05:00
tabShown(e) {
const $target = $(e.target);
const action = $target.data('action');
2016-07-24 16:45:11 -04:00
if (action === 'commits') {
this.loadCommits($target.attr('href'));
this.expandView();
this.resetViewContainer();
this.destroyPipelinesView();
} else if (this.isDiffAction(action)) {
2016-07-24 16:45:11 -04:00
this.loadDiff($target.attr('href'));
if (Breakpoints.get().getBreakpointSize() !== 'lg') {
2016-07-24 16:45:11 -04:00
this.shrinkView();
}
if (this.diffViewType() === 'parallel') {
this.expandViewContainer();
}
this.destroyPipelinesView();
} else if (action === 'pipelines') {
this.resetViewContainer();
this.mountPipelinesView();
2016-07-24 16:45:11 -04:00
} else {
this.expandView();
this.resetViewContainer();
this.destroyPipelinesView();
2016-07-24 16:45:11 -04:00
}
if (this.setUrl) {
this.setCurrentAction(action);
}
}
2016-07-24 16:45:11 -04:00
scrollToElement(container) {
if (location.hash) {
2017-01-18 16:25:35 -05:00
const offset = -$('.js-tabs-affix').outerHeight();
const $el = $(`${container} ${location.hash}:not(.match)`);
if ($el.length) {
$.scrollTo($el[0], { offset });
2016-07-24 16:45:11 -04:00
}
}
}
2016-07-24 16:45:11 -04:00
// Activate a tab based on the current action
activateTab(action) {
const activate = action === 'show' ? 'notes' : action;
// important note: the .tab('show') method triggers 'shown.bs.tab' event itself
$(`.merge-request-tabs a[data-action='${activate}']`).tab('show');
}
2016-07-24 16:45:11 -04:00
// Replaces the current Merge Request-specific action in the URL with a new one
//
// If the action is "notes", the URL is reset to the standard
// `MergeRequests#show` route.
//
// Examples:
//
// location.pathname # => "/namespace/project/merge_requests/1"
// setCurrentAction('diffs')
// location.pathname # => "/namespace/project/merge_requests/1/diffs"
//
// location.pathname # => "/namespace/project/merge_requests/1/diffs"
// setCurrentAction('notes')
// location.pathname # => "/namespace/project/merge_requests/1"
//
// location.pathname # => "/namespace/project/merge_requests/1/diffs"
// setCurrentAction('commits')
// location.pathname # => "/namespace/project/merge_requests/1/commits"
//
// Returns the new URL String
setCurrentAction(action) {
this.currentAction = action === 'show' ? 'notes' : action;
// Remove a trailing '/commits' '/diffs' '/pipelines' '/new' '/new/diffs'
let newState = location.pathname.replace(/\/(commits|diffs|pipelines|new|new\/diffs)(\.html)?\/?$/, '');
// Append the new action if we're on a tab other than 'notes'
if (this.currentAction !== 'notes') {
newState += `/${this.currentAction}`;
2016-07-24 16:45:11 -04:00
}
// Ensure parameters and hash come along for the ride
newState += location.search + location.hash;
2017-01-13 16:54:16 -05:00
// TODO: Consider refactoring in light of turbolinks removal.
// Replace the current history state with the new one without breaking
// Turbolinks' history.
//
// See https://github.com/rails/turbolinks/issues/363
window.history.replaceState({
url: newState,
}, document.title, newState);
return newState;
}
2016-07-24 16:45:11 -04:00
loadCommits(source) {
2016-07-24 16:45:11 -04:00
if (this.commitsLoaded) {
return;
}
this.ajaxGet({
url: `${source}.json`,
success: (data) => {
document.querySelector('div#commits').innerHTML = data.html;
gl.utils.localTimeAgo($('.js-timeago', 'div#commits'));
this.commitsLoaded = true;
this.scrollToElement('#commits');
},
2016-07-24 16:45:11 -04:00
});
}
2016-07-24 16:45:11 -04:00
mountPipelinesView() {
this.commitPipelinesTable = new CommitPipelinesTable().$mount();
// $mount(el) replaces the el with the new rendered component. We need it in order to mount
// it everytime this tab is clicked - https://vuejs.org/v2/api/#vm-mount
document.querySelector('#commit-pipeline-table-view')
.appendChild(this.commitPipelinesTable.$el);
}
loadDiff(source) {
2016-07-24 16:45:11 -04:00
if (this.diffsLoaded) {
return;
}
// We extract pathname for the current Changes tab anchor href
// some pages like MergeRequestsController#new has query parameters on that anchor
2016-12-03 17:04:21 -05:00
const urlPathname = gl.utils.parseUrlPathname(source);
this.ajaxGet({
url: `${urlPathname}.json${location.search}`,
success: (data) => {
$('#diffs').html(data.html);
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
gl.diffNotesCompileComponents();
}
gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'));
$('#diffs .js-syntax-highlight').syntaxHighlight();
if (this.diffViewType() === 'parallel' && this.isDiffAction(this.currentAction)) {
this.expandViewContainer();
}
this.diffsLoaded = true;
new gl.Diff();
this.scrollToElement('#diffs');
},
2016-07-24 16:45:11 -04:00
});
}
2016-07-25 17:14:14 -04:00
// Show or hide the loading spinner
//
// status - Boolean, true to show, false to hide
toggleLoading(status) {
$('.mr-loading-status .loading').toggle(status);
}
2016-07-24 16:45:11 -04:00
ajaxGet(options) {
const defaults = {
beforeSend: () => this.toggleLoading(true),
error: () => new Flash('An error occurred while fetching this tab.', 'alert'),
complete: () => this.toggleLoading(false),
2016-07-24 16:45:11 -04:00
dataType: 'json',
type: 'GET',
2016-07-24 16:45:11 -04:00
};
$.ajax($.extend({}, defaults, options));
}
2016-07-24 16:45:11 -04:00
diffViewType() {
2016-07-24 16:45:11 -04:00
return $('.inline-parallel-buttons a.active').data('view-type');
}
2016-07-24 16:45:11 -04:00
isDiffAction(action) {
return action === 'diffs' || action === 'new/diffs';
}
expandViewContainer() {
const $wrapper = $('.content-wrapper .container-fluid');
if (this.fixedLayoutPref === null) {
this.fixedLayoutPref = $wrapper.hasClass('container-limited');
}
$wrapper.removeClass('container-limited');
}
resetViewContainer() {
if (this.fixedLayoutPref !== null) {
$('.content-wrapper .container-fluid')
.toggleClass('container-limited', this.fixedLayoutPref);
}
}
2016-07-24 16:45:11 -04:00
shrinkView() {
const $gutterIcon = $('.js-sidebar-toggle i:visible');
// Wait until listeners are set
setTimeout(() => {
// Only when sidebar is expanded
2016-07-24 16:45:11 -04:00
if ($gutterIcon.is('.fa-angle-double-right')) {
$gutterIcon.closest('a').trigger('click', [true]);
2016-07-24 16:45:11 -04:00
}
}, 0);
}
2016-07-24 16:45:11 -04:00
// Expand the issuable sidebar unless the user explicitly collapsed it
expandView() {
if (Cookies.get('collapsed_gutter') === 'true') {
2016-07-24 16:45:11 -04:00
return;
}
const $gutterIcon = $('.js-sidebar-toggle i:visible');
// Wait until listeners are set
setTimeout(() => {
// Only when sidebar is collapsed
2016-07-24 16:45:11 -04:00
if ($gutterIcon.is('.fa-angle-double-left')) {
$gutterIcon.closest('a').trigger('click', [true]);
2016-07-24 16:45:11 -04:00
}
}, 0);
}
2016-07-24 16:45:11 -04:00
initAffix() {
const $tabs = $('.js-tabs-affix');
2016-09-16 09:32:43 -04:00
// Screen space on small screens is usually very sparse
// So we dont affix the tabs on these
2016-09-16 09:32:43 -04:00
if (Breakpoints.get().getBreakpointSize() === 'xs' || !$tabs.length) return;
const $diffTabs = $('#diff-notes-app');
$tabs.off('affix.bs.affix affix-top.bs.affix')
.affix({
offset: {
top: () => (
$diffTabs.offset().top - $tabs.height()
),
},
})
.on('affix.bs.affix', () => $diffTabs.css({ marginTop: $tabs.height() }))
.on('affix-top.bs.affix', () => $diffTabs.css({ marginTop: '' }));
// Fix bug when reloading the page already scrolling
if ($tabs.hasClass('affix')) {
$tabs.trigger('affix.bs.affix');
}
}
}
2016-07-24 16:45:11 -04:00
2016-12-02 18:43:20 -05:00
window.gl = window.gl || {};
window.gl.MergeRequestTabs = MergeRequestTabs;
})();