Commit 460f6527 authored by Fatih Acet's avatar Fatih Acet Committed by Alejandro Rodríguez

Merge branch '23696-fix-diff-view-highlighting' into 'master'

Resolve "Highlighting lines is broken"

Add line highlighting back to diff view.  This was working in the MR "changes" tab, but not on a commit page such as https://gitlab.com/winniehell/reproduction-area/commit/9101e66f5761929002956e0f2dd65d7f8643903d

~~This MR also fixes the `scrollToElement` method in `MergeRequestTabs` to account for the extra height of the tab links which are now fixed in place once they are scrolled to the top of the screen.~~ (removed in favor of !7051)

This MR also refactors much of the `Diff` and `MergeRequestTabs` classes to es6 syntax in an effort to increase readability.

Check out both MR "change" tabs and commit diff pages and ensure that line highlighting works and that loading a page with one of these permalink hashes correctly highlights and scrolls to the line.

Ensure I didn't break anything in the transition to es6 syntax.  Check the functionality of the tabs on MR pages, as well as diff page interactivity (unfolding hidden lines in diff files, adding comments to diffs, etc).  I have checked these myself, but another set of eyes would be a good idea.

- [x] [CHANGELOG](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CHANGELOG.md) entry added
- Tests
  - [x] All builds are passing
- [x] Conform by the [merge request performance guides](http://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html)
- [x] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#style-guides)
- [x] Branch has no merge conflicts with `master` (if it does - rebase it please)
- [x] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)

Closes #23696

See merge request !7090
parent fc62daeb
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, max-len, one-var, camelcase, one-var-declaration-per-line, no-unused-vars, no-unused-expressions, no-sequences, object-shorthand, comma-dangle, prefer-arrow-callback, semi, radix, padded-blocks, max-len */
(function() {
this.Diff = (function() {
var UNFOLD_COUNT;
UNFOLD_COUNT = 20;
function Diff() {
$('.files .diff-file').singleFileDiff();
this.filesCommentButton = $('.files .diff-file').filesCommentButton();
if (this.diffViewType() === 'parallel') {
$('.content-wrapper .container-fluid').removeClass('container-limited');
}
$(document).off('click', '.js-unfold');
$(document).on('click', '.js-unfold', (function(_this) {
return function(event) {
var line_number, link, file, offset, old_line, params, prev_new_line, prev_old_line, ref, ref1, since, target, to, unfold, unfoldBottom;
target = $(event.target);
unfoldBottom = target.hasClass('js-unfold-bottom');
unfold = true;
ref = _this.lineNumbers(target.parent()), old_line = ref[0], line_number = ref[1];
offset = line_number - old_line;
if (unfoldBottom) {
line_number += 1;
since = line_number;
to = line_number + UNFOLD_COUNT;
} else {
ref1 = _this.lineNumbers(target.parent().prev()), prev_old_line = ref1[0], prev_new_line = ref1[1];
line_number -= 1;
to = line_number;
if (line_number - UNFOLD_COUNT > prev_new_line + 1) {
since = line_number - UNFOLD_COUNT;
} else {
since = prev_new_line + 1;
unfold = false;
}
}
file = target.parents('.diff-file');
link = file.data('blob-diff-path');
params = {
since: since,
to: to,
bottom: unfoldBottom,
offset: offset,
unfold: unfold,
view: file.data('view')
};
return $.get(link, params, function(response) {
return target.parent().replaceWith(response);
});
};
})(this));
}
Diff.prototype.diffViewType = function() {
return $('.inline-parallel-buttons a.active').data('view-type');
}
Diff.prototype.lineNumbers = function(line) {
if (!line.children().length) {
return [0, 0];
}
return line.find('.diff-line-num').map(function() {
return parseInt($(this).data('linenumber'));
});
};
return Diff;
})();
}).call(this);
/* eslint-disable class-methods-use-this */
(() => {
const UNFOLD_COUNT = 20;
class Diff {
constructor() {
$('.files .diff-file').singleFileDiff();
$('.files .diff-file').filesCommentButton();
if (this.diffViewType() === 'parallel') {
$('.content-wrapper .container-fluid').removeClass('container-limited');
}
$(document)
.off('click', '.js-unfold, .diff-line-num a')
.on('click', '.js-unfold', this.handleClickUnfold.bind(this))
.on('click', '.diff-line-num a', this.handleClickLineNum.bind(this));
this.highlighSelectedLine();
}
handleClickUnfold(e) {
const $target = $(e.target);
// current babel config relies on iterators implementation, so we cannot simply do:
// const [oldLineNumber, newLineNumber] = this.lineNumbers($target.parent());
const ref = this.lineNumbers($target.parent());
const oldLineNumber = ref[0];
const newLineNumber = ref[1];
const offset = newLineNumber - oldLineNumber;
const bottom = $target.hasClass('js-unfold-bottom');
let since;
let to;
let unfold = true;
if (bottom) {
const lineNumber = newLineNumber + 1;
since = lineNumber;
to = lineNumber + UNFOLD_COUNT;
} else {
const lineNumber = newLineNumber - 1;
since = lineNumber - UNFOLD_COUNT;
to = lineNumber;
// make sure we aren't loading more than we need
const prevNewLine = this.lineNumbers($target.parent().prev())[1];
if (since <= prevNewLine + 1) {
since = prevNewLine + 1;
unfold = false;
}
}
const file = $target.parents('.diff-file');
const link = file.data('blob-diff-path');
const view = file.data('view');
const params = { since, to, bottom, offset, unfold, view };
$.get(link, params, response => $target.parent().replaceWith(response));
}
openAnchoredDiff(anchoredDiff, cb) {
const diffTitle = $(`#file-path-${anchoredDiff}`);
const diffFile = diffTitle.closest('.diff-file');
const nothingHereBlock = $('.nothing-here-block:visible', diffFile);
if (nothingHereBlock.length) {
diffFile.singleFileDiff(true, cb);
} else {
cb();
}
}
handleClickLineNum(e) {
const hash = $(e.currentTarget).attr('href');
e.preventDefault();
if (window.history.pushState) {
window.history.pushState(null, null, hash);
} else {
window.location.hash = hash;
}
this.highlighSelectedLine();
}
diffViewType() {
return $('.inline-parallel-buttons a.active').data('view-type');
}
lineNumbers(line) {
if (!line.children().length) {
return [0, 0];
}
return line.find('.diff-line-num').map((i, elm) => parseInt($(elm).data('linenumber'), 10));
}
highlighSelectedLine() {
const $diffFiles = $('.diff-file');
$diffFiles.find('.hll').removeClass('hll');
if (window.location.hash !== '') {
const hash = window.location.hash.replace('#', '');
$diffFiles
.find(`tr#${hash}:not(.match) td, td#${hash}, td[data-line-code="${hash}"]`)
.addClass('hll');
}
}
}
window.gl = window.gl || {};
window.gl.Diff = Diff;
})();
...@@ -61,7 +61,7 @@ ...@@ -61,7 +61,7 @@
new ZenMode(); new ZenMode();
break; break;
case 'projects:compare:show': case 'projects:compare:show':
new Diff(); new gl.Diff();
break; break;
case 'projects:issues:new': case 'projects:issues:new':
case 'projects:issues:edit': case 'projects:issues:edit':
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
break; break;
case 'projects:merge_requests:new': case 'projects:merge_requests:new':
case 'projects:merge_requests:edit': case 'projects:merge_requests:edit':
new Diff(); new gl.Diff();
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
new GLForm($('.merge-request-form')); new GLForm($('.merge-request-form'));
new IssuableForm($('.merge-request-form')); new IssuableForm($('.merge-request-form'));
...@@ -91,7 +91,7 @@ ...@@ -91,7 +91,7 @@
new GLForm($('.release-form')); new GLForm($('.release-form'));
break; break;
case 'projects:merge_requests:show': case 'projects:merge_requests:show':
new Diff(); new gl.Diff();
shortcut_handler = new ShortcutsIssuable(true); shortcut_handler = new ShortcutsIssuable(true);
new ZenMode(); new ZenMode();
new MergedButtons(); new MergedButtons();
...@@ -101,7 +101,7 @@ ...@@ -101,7 +101,7 @@
new MergedButtons(); new MergedButtons();
break; break;
case "projects:merge_requests:diffs": case "projects:merge_requests:diffs":
new Diff(); new gl.Diff();
new ZenMode(); new ZenMode();
new MergedButtons(); new MergedButtons();
break; break;
...@@ -117,7 +117,7 @@ ...@@ -117,7 +117,7 @@
break; break;
case 'projects:commit:show': case 'projects:commit:show':
new Commit(); new Commit();
new Diff(); new gl.Diff();
new ZenMode(); new ZenMode();
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
break; break;
......
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
if (window.mrTabs) { if (window.mrTabs) {
window.mrTabs.unbindEvents(); window.mrTabs.unbindEvents();
} }
window.mrTabs = new MergeRequestTabs(this.opts); window.mrTabs = new gl.MergeRequestTabs(this.opts);
}; };
MergeRequest.prototype.showAllCommits = function() { MergeRequest.prototype.showAllCommits = function() {
......
/* eslint-disable max-len, func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, no-underscore-dangle, no-undef, one-var, one-var-declaration-per-line, quotes, comma-dangle, consistent-return, prefer-template, no-param-reassign, camelcase, vars-on-top, space-in-parens, curly, prefer-arrow-callback, no-unused-vars, no-return-assign, semi, object-shorthand, operator-assignment, padded-blocks, max-len */ /* eslint-disable no-new, class-methods-use-this */
/* global Breakpoints */
/* global Cookies */
/* global DiffNotesApp */
/* global Flash */
/*= require js.cookie */
/*= require breakpoints */
/* eslint-disable max-len */
// MergeRequestTabs // MergeRequestTabs
// //
// Handles persisting and restoring the current tab selection and lazily-loading // Handles persisting and restoring the current tab selection and lazily-loading
// content on the MergeRequests#show page. // content on the MergeRequests#show page.
//
/*= require js.cookie */
// //
// ### Example Markup // ### Example Markup
// //
...@@ -45,70 +51,73 @@ ...@@ -45,70 +51,73 @@
// </div> // </div>
// </div> // </div>
// //
(function() { /* eslint-enable max-len */
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
this.MergeRequestTabs = (function() {
MergeRequestTabs.prototype.diffsLoaded = false;
MergeRequestTabs.prototype.buildsLoaded = false; (() => {
// Store the `location` object, allowing for easier stubbing in tests
let location = window.location;
MergeRequestTabs.prototype.pipelinesLoaded = false; class MergeRequestTabs {
MergeRequestTabs.prototype.commitsLoaded = false; constructor({ action, setUrl, buildsLoaded, stubLocation } = {}) {
this.diffsLoaded = false;
this.buildsLoaded = false;
this.pipelinesLoaded = false;
this.commitsLoaded = false;
this.fixedLayoutPref = null;
MergeRequestTabs.prototype.fixedLayoutPref = null; this.setUrl = setUrl !== undefined ? setUrl : true;
this.buildsLoaded = buildsLoaded || false;
function MergeRequestTabs(opts) { this.setCurrentAction = this.setCurrentAction.bind(this);
this.opts = opts != null ? opts : {}; this.tabShown = this.tabShown.bind(this);
this.opts.setUrl = this.opts.setUrl !== undefined ? this.opts.setUrl : true; this.showTab = this.showTab.bind(this);
this.buildsLoaded = this.opts.buildsLoaded || false; if (stubLocation) {
location = stubLocation;
}
this.setCurrentAction = bind(this.setCurrentAction, this);
this.tabShown = bind(this.tabShown, this);
this.showTab = bind(this.showTab, this);
// Store the `location` object, allowing for easier stubbing in tests
this._location = location;
this.bindEvents(); this.bindEvents();
this.activateTab(this.opts.action); this.activateTab(action);
this.initAffix(); this.initAffix();
} }
MergeRequestTabs.prototype.bindEvents = function() { bindEvents() {
$(document).on('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown); $(document)
$(document).on('click', '.js-show-tab', this.showTab); .on('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown)
}; .on('click', '.js-show-tab', this.showTab);
}
MergeRequestTabs.prototype.unbindEvents = function() {
$(document).off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown); unbindEvents() {
$(document).off('click', '.js-show-tab', this.showTab); $(document)
}; .off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown)
.off('click', '.js-show-tab', this.showTab);
MergeRequestTabs.prototype.showTab = function(event) { }
event.preventDefault();
return this.activateTab($(event.target).data('action')); showTab(e) {
}; e.preventDefault();
this.activateTab($(e.target).data('action'));
MergeRequestTabs.prototype.tabShown = function(event) { }
var $target, action, navBarHeight;
$target = $(event.target); tabShown(e) {
action = $target.data('action'); const $target = $(e.target);
const action = $target.data('action');
if (action === 'commits') { if (action === 'commits') {
this.loadCommits($target.attr('href')); this.loadCommits($target.attr('href'));
this.expandView(); this.expandView();
this.resetViewContainer(); this.resetViewContainer();
} else if (this.isDiffAction(action)) { } else if (this.isDiffAction(action)) {
this.loadDiff($target.attr('href')); this.loadDiff($target.attr('href'));
if ((typeof bp !== "undefined" && bp !== null) && bp.getBreakpointSize() !== 'lg') { if (Breakpoints.get().getBreakpointSize() !== 'lg') {
this.shrinkView(); this.shrinkView();
} }
if (this.diffViewType() === 'parallel') { if (this.diffViewType() === 'parallel') {
this.expandViewContainer(); this.expandViewContainer();
} }
navBarHeight = $('.navbar-gitlab').outerHeight(); const navBarHeight = $('.navbar-gitlab').outerHeight();
$.scrollTo(".merge-request-details .merge-request-tabs", { $.scrollTo('.merge-request-details .merge-request-tabs', {
offset: -navBarHeight offset: -navBarHeight,
}); });
} else if (action === 'builds') { } else if (action === 'builds') {
this.loadBuilds($target.attr('href')); this.loadBuilds($target.attr('href'));
...@@ -122,32 +131,31 @@ ...@@ -122,32 +131,31 @@
this.expandView(); this.expandView();
this.resetViewContainer(); this.resetViewContainer();
} }
if (this.opts.setUrl) { if (this.setUrl) {
this.setCurrentAction(action); this.setCurrentAction(action);
} }
}; }
MergeRequestTabs.prototype.scrollToElement = function(container) { scrollToElement(container) {
var $el, navBarHeight; if (location.hash) {
if (window.location.hash) { const offset = 0 - (
navBarHeight = $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight() + document.querySelector('.js-tabs-affix').offsetHeight; $('.navbar-gitlab').outerHeight() +
$el = $(container + " " + window.location.hash + ":not(.match)"); $('.layout-nav').outerHeight() +
$('.js-tabs-affix').outerHeight()
);
const $el = $(`${container} ${location.hash}:not(.match)`);
if ($el.length) { if ($el.length) {
return $.scrollTo(container + " " + window.location.hash + ":not(.match)", { $.scrollTo($el[0], { offset });
offset: -navBarHeight
});
} }
} }
}; }
// Activate a tab based on the current action // Activate a tab based on the current action
MergeRequestTabs.prototype.activateTab = function(action) { activateTab(action) {
if (action === 'show') { const activate = action === 'show' ? 'notes' : action;
action = 'notes';
}
// important note: the .tab('show') method triggers 'shown.bs.tab' event itself // important note: the .tab('show') method triggers 'shown.bs.tab' event itself
$(".merge-request-tabs a[data-action='" + action + "']").tab('show'); $(`.merge-request-tabs a[data-action='${activate}']`).tab('show');
}; }
// Replaces the current Merge Request-specific action in the URL with a new one // Replaces the current Merge Request-specific action in the URL with a new one
// //
...@@ -169,274 +177,214 @@ ...@@ -169,274 +177,214 @@
// location.pathname # => "/namespace/project/merge_requests/1/commits" // location.pathname # => "/namespace/project/merge_requests/1/commits"
// //
// Returns the new URL String // Returns the new URL String
MergeRequestTabs.prototype.setCurrentAction = function(action) { setCurrentAction(action) {
var new_state; this.currentAction = action === 'show' ? 'notes' : action;
// Normalize action, just to be safe
if (action === 'show') {
action = 'notes';
}
this.currentAction = action;
// Remove a trailing '/commits' '/diffs' '/builds' '/pipelines' '/new' '/new/diffs' // Remove a trailing '/commits' '/diffs' '/builds' '/pipelines' '/new' '/new/diffs'
new_state = this._location.pathname.replace(/\/(commits|diffs|builds|pipelines|new|new\/diffs)(\.html)?\/?$/, ''); let newState = location.pathname.replace(/\/(commits|diffs|builds|pipelines|new|new\/diffs)(\.html)?\/?$/, '');
// Append the new action if we're on a tab other than 'notes' // Append the new action if we're on a tab other than 'notes'
if (action !== 'notes') { if (this.currentAction !== 'notes') {
new_state += "/" + action; newState += `/${this.currentAction}`;
} }
// Ensure parameters and hash come along for the ride // Ensure parameters and hash come along for the ride
new_state += this._location.search + this._location.hash; newState += location.search + location.hash;
history.replaceState({
turbolinks: true,
url: new_state
// Replace the current history state with the new one without breaking // Replace the current history state with the new one without breaking
// Turbolinks' history. // Turbolinks' history.
// //
// See https://github.com/rails/turbolinks/issues/363 // See https://github.com/rails/turbolinks/issues/363
}, document.title, new_state); window.history.replaceState({
return new_state; turbolinks: true,
}; url: newState,
}, document.title, newState);
MergeRequestTabs.prototype.loadCommits = function(source) { return newState;
}
loadCommits(source) {
if (this.commitsLoaded) { if (this.commitsLoaded) {
return; return;
} }
return this._get({ this.ajaxGet({
url: source + ".json", url: `${source}.json`,
success: (function(_this) { success: (data) => {
return function(data) { document.querySelector('div#commits').innerHTML = data.html;
document.querySelector("div#commits").innerHTML = data.html; gl.utils.localTimeAgo($('.js-timeago', 'div#commits'));
gl.utils.localTimeAgo($('.js-timeago', 'div#commits')); this.commitsLoaded = true;
_this.commitsLoaded = true; this.scrollToElement('#commits');
return _this.scrollToElement("#commits"); },
};
})(this)
}); });
}; }
MergeRequestTabs.prototype.loadDiff = function(source) { loadDiff(source) {
if (this.diffsLoaded) { if (this.diffsLoaded) {
return; return;
} }
// We extract pathname for the current Changes tab anchor href // We extract pathname for the current Changes tab anchor href
// some pages like MergeRequestsController#new has query parameters on that anchor // some pages like MergeRequestsController#new has query parameters on that anchor
var url = gl.utils.parseUrl(source); const url = document.createElement('a');
url.href = source;
return this._get({
url: (url.pathname + ".json") + this._location.search, this.ajaxGet({
success: (function(_this) { url: `${url.pathname}.json${location.search}`,
return function(data) { success: (data) => {
$('#diffs').html(data.html); $('#diffs').html(data.html);
if (typeof gl.diffNotesCompileComponents !== 'undefined') { if (typeof gl.diffNotesCompileComponents !== 'undefined') {
gl.diffNotesCompileComponents(); gl.diffNotesCompileComponents();
} }
gl.utils.localTimeAgo($('.js-timeago', 'div#diffs')); gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'));
$('#diffs .js-syntax-highlight').syntaxHighlight(); $('#diffs .js-syntax-highlight').syntaxHighlight();
$('#diffs .diff-file').singleFileDiff();
if (_this.diffViewType() === 'parallel' && (_this.isDiffAction(_this.currentAction)) ) { if (this.diffViewType() === 'parallel' && this.isDiffAction(this.currentAction)) {
_this.expandViewContainer(); this.expandViewContainer();
} }
_this.diffsLoaded = true; this.diffsLoaded = true;
var anchoredDiff = gl.utils.getLocationHash();
if (anchoredDiff) _this.openAnchoredDiff(anchoredDiff, function() { const diffPage = new gl.Diff();
_this.scrollToElement("#diffs");
_this.highlighSelectedLine(); const locationHash = gl.utils.getLocationHash();
}); const anchoredDiff = locationHash && locationHash.split('_')[0];
_this.filesCommentButton = $('.files .diff-file').filesCommentButton(); if (anchoredDiff) {
return $(document).off('click', '.diff-line-num a').on('click', '.diff-line-num a', function(e) { diffPage.openAnchoredDiff(anchoredDiff, () => this.scrollToElement('#diffs'));
e.preventDefault(); }
window.location.hash = $(e.currentTarget).attr('href'); },
_this.highlighSelectedLine();
return _this.scrollToElement("#diffs");
});
};
})(this)
}); });
}; }
MergeRequestTabs.prototype.openAnchoredDiff = function(anchoredDiff, cb) {
var diffTitle = $('#file-path-' + anchoredDiff);
var diffFile = diffTitle.closest('.diff-file');
var nothingHereBlock = $('.nothing-here-block:visible', diffFile);
if (nothingHereBlock.length) {
diffFile.singleFileDiff(true, cb);
} else {
cb();
}
};
MergeRequestTabs.prototype.highlighSelectedLine = function() {
var $diffLine, diffLineTop, hashClassString, locationHash, navBarHeight;
$('.hll').removeClass('hll');
locationHash = window.location.hash;
if (locationHash !== '') {
dataLineString = '[data-line-code="' + locationHash.replace('#', '') + '"]';
$diffLine = $(locationHash + ":not(.match)", $('#diffs'));
if (!$diffLine.is('tr')) {
$diffLine = $('#diffs').find("td" + locationHash + ", td" + dataLineString);
} else {
$diffLine = $diffLine.find('td');
}
if ($diffLine.length) {
$diffLine.addClass('hll');
diffLineTop = $diffLine.offset().top;
return navBarHeight = $('.navbar-gitlab').outerHeight();
}
}
};
MergeRequestTabs.prototype.loadBuilds = function(source) { loadBuilds(source) {
if (this.buildsLoaded) { if (this.buildsLoaded) {
return; return;
} }
return this._get({ this.ajaxGet({
url: source + ".json", url: `${source}.json`,
success: (function(_this) { success: (data) => {
return function(data) { document.querySelector('div#builds').innerHTML = data.html;
document.querySelector("div#builds").innerHTML = data.html; gl.utils.localTimeAgo($('.js-timeago', 'div#builds'));
gl.utils.localTimeAgo($('.js-timeago', 'div#builds')); this.buildsLoaded = true;
_this.buildsLoaded = true; new gl.Pipelines();
if (!this.pipelines) this.pipelines = new gl.Pipelines(); this.scrollToElement('#builds');
return _this.scrollToElement("#builds"); },
};
})(this)
}); });
}; }
MergeRequestTabs.prototype.loadPipelines = function(source) { loadPipelines(source) {
if (this.pipelinesLoaded) { if (this.pipelinesLoaded) {
return; return;
} }
return this._get({ this.ajaxGet({
url: source + ".json", url: `${source}.json`,
success: function(data) { success: (data) => {
$('#pipelines').html(data.html); $('#pipelines').html(data.html);
gl.utils.localTimeAgo($('.js-timeago', '#pipelines')); gl.utils.localTimeAgo($('.js-timeago', '#pipelines'));
this.pipelinesLoaded = true; this.pipelinesLoaded = true;
return this.scrollToElement("#pipelines"); this.scrollToElement('#pipelines');
}.bind(this) },
}); });
}; }
// Show or hide the loading spinner // Show or hide the loading spinner
// //
// status - Boolean, true to show, false to hide // status - Boolean, true to show, false to hide
MergeRequestTabs.prototype.toggleLoading = function(status) { toggleLoading(status) {
return $('.mr-loading-status .loading').toggle(status); $('.mr-loading-status .loading').toggle(status);
}; }
MergeRequestTabs.prototype._get = function(options) { ajaxGet(options) {
var defaults; const defaults = {
defaults = { beforeSend: () => this.toggleLoading(true),
beforeSend: (function(_this) { error: () => new Flash('An error occurred while fetching this tab.', 'alert'),
return function() { complete: () => this.toggleLoading(false),
return _this.toggleLoading(true);
};
})(this),
complete: (function(_this) {
return function() {
return _this.toggleLoading(false);
};
})(this),
dataType: 'json', dataType: 'json',
type: 'GET' type: 'GET',
}; };
options = $.extend({}, defaults, options); $.ajax($.extend({}, defaults, options));
return $.ajax(options); }
};
MergeRequestTabs.prototype.diffViewType = function() { diffViewType() {
return $('.inline-parallel-buttons a.active').data('view-type'); return $('.inline-parallel-buttons a.active').data('view-type');
}; }
MergeRequestTabs.prototype.isDiffAction = function(action) { isDiffAction(action) {
return action === 'diffs' || action === 'new/diffs' return action === 'diffs' || action === 'new/diffs';
}; }
MergeRequestTabs.prototype.expandViewContainer = function() { expandViewContainer() {
var $wrapper = $('.content-wrapper .container-fluid'); const $wrapper = $('.content-wrapper .container-fluid');
if (this.fixedLayoutPref === null) { if (this.fixedLayoutPref === null) {
this.fixedLayoutPref = $wrapper.hasClass('container-limited'); this.fixedLayoutPref = $wrapper.hasClass('container-limited');
} }
$wrapper.removeClass('container-limited'); $wrapper.removeClass('container-limited');
}; }
MergeRequestTabs.prototype.resetViewContainer = function() { resetViewContainer() {
if (this.fixedLayoutPref !== null) { if (this.fixedLayoutPref !== null) {
$('.content-wrapper .container-fluid') $('.content-wrapper .container-fluid')
.toggleClass('container-limited', this.fixedLayoutPref); .toggleClass('container-limited', this.fixedLayoutPref);
} }
}; }
shrinkView() {
const $gutterIcon = $('.js-sidebar-toggle i:visible');
MergeRequestTabs.prototype.shrinkView = function() { // Wait until listeners are set
var $gutterIcon; setTimeout(() => {
$gutterIcon = $('.js-sidebar-toggle i:visible'); // Only when sidebar is expanded
return setTimeout(function() {
if ($gutterIcon.is('.fa-angle-double-right')) { if ($gutterIcon.is('.fa-angle-double-right')) {
return $gutterIcon.closest('a').trigger('click', [true]); $gutterIcon.closest('a').trigger('click', [true]);
} }
// Wait until listeners are set
// Only when sidebar is expanded
}, 0); }, 0);
}; }
MergeRequestTabs.prototype.expandView = function() { // Expand the issuable sidebar unless the user explicitly collapsed it
var $gutterIcon; expandView() {
if (Cookies.get('collapsed_gutter') === 'true') { if (Cookies.get('collapsed_gutter') === 'true') {
return; return;
} }
$gutterIcon = $('.js-sidebar-toggle i:visible'); const $gutterIcon = $('.js-sidebar-toggle i:visible');
return setTimeout(function() {
// Wait until listeners are set
setTimeout(() => {
// Only when sidebar is collapsed
if ($gutterIcon.is('.fa-angle-double-left')) { if ($gutterIcon.is('.fa-angle-double-left')) {
return $gutterIcon.closest('a').trigger('click', [true]); $gutterIcon.closest('a').trigger('click', [true]);
} }
}, 0); }, 0);
// Expand the issuable sidebar unless the user explicitly collapsed it }
// Wait until listeners are set
// Only when sidebar is collapsed
};
MergeRequestTabs.prototype.initAffix = function () { initAffix() {
var $tabs = $('.js-tabs-affix'); const $tabs = $('.js-tabs-affix');
// Screen space on small screens is usually very sparse // Screen space on small screens is usually very sparse
// So we dont affix the tabs on these // So we dont affix the tabs on these
if (Breakpoints.get().getBreakpointSize() === 'xs' || !$tabs.length) return; if (Breakpoints.get().getBreakpointSize() === 'xs' || !$tabs.length) return;
var $diffTabs = $('#diff-notes-app'), const $diffTabs = $('#diff-notes-app');
$fixedNav = $('.navbar-fixed-top'), const $fixedNav = $('.navbar-fixed-top');
$layoutNav = $('.layout-nav'); const $layoutNav = $('.layout-nav');
$tabs.off('affix.bs.affix affix-top.bs.affix') $tabs.off('affix.bs.affix affix-top.bs.affix')
.affix({ .affix({
offset: { offset: {
top: function () { top: () => (
var tabsTop = $diffTabs.offset().top - $tabs.height(); $diffTabs.offset().top - $tabs.height() - $fixedNav.height() - $layoutNav.height()
tabsTop = tabsTop - ($fixedNav.height() + $layoutNav.height()); ),
},
return tabsTop; })
} .on('affix.bs.affix', () => $diffTabs.css({ marginTop: $tabs.height() }))
} .on('affix-top.bs.affix', () => $diffTabs.css({ marginTop: '' }));
}).on('affix.bs.affix', function () {
$diffTabs.css({
marginTop: $tabs.height()
});
}).on('affix-top.bs.affix', function () {
$diffTabs.css({
marginTop: ''
});
});
// Fix bug when reloading the page already scrolling // Fix bug when reloading the page already scrolling
if ($tabs.hasClass('affix')) { if ($tabs.hasClass('affix')) {
$tabs.trigger('affix.bs.affix'); $tabs.trigger('affix.bs.affix');
} }
}; }
}
return MergeRequestTabs;
})();
}).call(this); window.gl = window.gl || {};
window.gl.MergeRequestTabs = MergeRequestTabs;
})();
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>'; COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>';
function SingleFileDiff(file, forceLoad, cb) { function SingleFileDiff(file, forceLoad, cb) {
var clickTarget;
this.file = file; this.file = file;
this.toggleDiff = bind(this.toggleDiff, this); this.toggleDiff = bind(this.toggleDiff, this);
this.content = $('.diff-content', this.file); this.content = $('.diff-content', this.file);
...@@ -31,9 +32,9 @@ ...@@ -31,9 +32,9 @@
this.content.after(this.collapsedContent); this.content.after(this.collapsedContent);
this.$toggleIcon.addClass('fa-caret-down'); this.$toggleIcon.addClass('fa-caret-down');
} }
$('.file-title, .click-to-expand', this.file).on('click', this.toggleDiff); clickTarget = $('.file-title, .click-to-expand', this.file).on('click', this.toggleDiff);
if (forceLoad) { if (forceLoad) {
this.toggleDiff(null, cb); this.toggleDiff({ target: clickTarget }, cb);
} }
} }
......
---
title: Fix diff view permalink highlighting
merge_request: 7090
author:
/* eslint-disable space-before-function-paren, no-var, comma-dangle, dot-notation, quotes, no-undef, no-return-assign, no-underscore-dangle, camelcase, padded-blocks, max-len */ /* eslint-disable no-var, comma-dangle, object-shorthand */
/*= require merge_request_tabs */ /*= require merge_request_tabs */
//= require breakpoints //= require breakpoints
(function() { (function () {
describe('MergeRequestTabs', function() { describe('MergeRequestTabs', function () {
var stubLocation; var stubLocation = {};
stubLocation = function(stubs) { var setLocation = function (stubs) {
var defaults; var defaults = {
defaults = {
pathname: '', pathname: '',
search: '', search: '',
hash: '' hash: ''
}; };
return $.extend(defaults, stubs); $.extend(stubLocation, defaults, stubs || {});
}; };
fixture.preload('merge_request_tabs.html'); fixture.preload('merge_request_tabs.html');
beforeEach(function() {
this["class"] = new MergeRequestTabs(); beforeEach(function () {
return this.spies = { this.class = new gl.MergeRequestTabs({ stubLocation: stubLocation });
ajax: spyOn($, 'ajax').and.callFake(function() {}), setLocation();
history: spyOn(history, 'replaceState').and.callFake(function() {})
this.spies = {
ajax: spyOn($, 'ajax').and.callFake(function () {}),
history: spyOn(window.history, 'replaceState').and.callFake(function () {})
}; };
}); });
describe('#activateTab', function() {
beforeEach(function() { describe('#activateTab', function () {
beforeEach(function () {
fixture.load('merge_request_tabs.html'); fixture.load('merge_request_tabs.html');
return this.subject = this["class"].activateTab; this.subject = this.class.activateTab;
}); });
it('shows the first tab when action is show', function() { it('shows the first tab when action is show', function () {
this.subject('show'); this.subject('show');
return expect($('#notes')).toHaveClass('active'); expect($('#notes')).toHaveClass('active');
}); });
it('shows the notes tab when action is notes', function() { it('shows the notes tab when action is notes', function () {
this.subject('notes'); this.subject('notes');
return expect($('#notes')).toHaveClass('active'); expect($('#notes')).toHaveClass('active');
}); });
it('shows the commits tab when action is commits', function() { it('shows the commits tab when action is commits', function () {
this.subject('commits'); this.subject('commits');
return expect($('#commits')).toHaveClass('active'); expect($('#commits')).toHaveClass('active');
}); });
return it('shows the diffs tab when action is diffs', function() { it('shows the diffs tab when action is diffs', function () {
this.subject('diffs'); this.subject('diffs');
return expect($('#diffs')).toHaveClass('active'); expect($('#diffs')).toHaveClass('active');
}); });
}); });
return describe('#setCurrentAction', function() {
beforeEach(function() { describe('#setCurrentAction', function () {
return this.subject = this["class"].setCurrentAction; beforeEach(function () {
this.subject = this.class.setCurrentAction;
}); });
it('changes from commits', function() { it('changes from commits', function () {
this["class"]._location = stubLocation({ setLocation({
pathname: '/foo/bar/merge_requests/1/commits' pathname: '/foo/bar/merge_requests/1/commits'
}); });
expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1'); expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1');
return expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs'); expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs');
}); });
it('changes from diffs', function() { it('changes from diffs', function () {
this["class"]._location = stubLocation({ setLocation({
pathname: '/foo/bar/merge_requests/1/diffs' pathname: '/foo/bar/merge_requests/1/diffs'
}); });
expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1'); expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1');
return expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits'); expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
}); });
it('changes from diffs.html', function() { it('changes from diffs.html', function () {
this["class"]._location = stubLocation({ setLocation({
pathname: '/foo/bar/merge_requests/1/diffs.html' pathname: '/foo/bar/merge_requests/1/diffs.html'
}); });
expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1'); expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1');
return expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits'); expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
}); });
it('changes from notes', function() { it('changes from notes', function () {
this["class"]._location = stubLocation({ setLocation({
pathname: '/foo/bar/merge_requests/1' pathname: '/foo/bar/merge_requests/1'
}); });
expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs'); expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs');
return expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits'); expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
}); });
it('includes search parameters and hash string', function() { it('includes search parameters and hash string', function () {
this["class"]._location = stubLocation({ setLocation({
pathname: '/foo/bar/merge_requests/1/diffs', pathname: '/foo/bar/merge_requests/1/diffs',
search: '?view=parallel', search: '?view=parallel',
hash: '#L15-35' hash: '#L15-35'
}); });
return expect(this.subject('show')).toBe('/foo/bar/merge_requests/1?view=parallel#L15-35'); expect(this.subject('show')).toBe('/foo/bar/merge_requests/1?view=parallel#L15-35');
}); });
it('replaces the current history state', function() { it('replaces the current history state', function () {
var new_state; var newState;
this["class"]._location = stubLocation({ setLocation({
pathname: '/foo/bar/merge_requests/1' pathname: '/foo/bar/merge_requests/1'
}); });
new_state = this.subject('commits'); newState = this.subject('commits');
return expect(this.spies.history).toHaveBeenCalledWith({ expect(this.spies.history).toHaveBeenCalledWith({
turbolinks: true, turbolinks: true,
url: new_state url: newState
}, document.title, new_state); }, document.title, newState);
}); });
return it('treats "show" like "notes"', function() { it('treats "show" like "notes"', function () {
this["class"]._location = stubLocation({ setLocation({
pathname: '/foo/bar/merge_requests/1/commits' pathname: '/foo/bar/merge_requests/1/commits'
}); });
return expect(this.subject('show')).toBe('/foo/bar/merge_requests/1'); expect(this.subject('show')).toBe('/foo/bar/merge_requests/1');
}); });
}); });
}); });
}).call(this); }).call(this);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment