Commit 08f7e49d authored by Alfredo Sumaran's avatar Alfredo Sumaran

Merge branch '19742-permalink-blame-button-line-number-hash-links' into 'master'

Update permalink/blame buttons with line number fragment hash

Closes #19742

See merge request !9461
parents cc4ca1f3 c0242485
const lineNumberRe = /^L[0-9]+/;
const updateLineNumbersOnBlobPermalinks = (linksToUpdate) => {
const hash = gl.utils.getLocationHash();
if (hash && lineNumberRe.test(hash)) {
const hashUrlString = `#${hash}`;
[].concat(Array.prototype.slice.call(linksToUpdate)).forEach((permalinkButton) => {
const baseHref = permalinkButton.getAttribute('data-original-href') || (() => {
const href = permalinkButton.getAttribute('href');
permalinkButton.setAttribute('data-original-href', href);
return href;
})();
permalinkButton.setAttribute('href', `${baseHref}${hashUrlString}`);
});
}
};
function BlobLinePermalinkUpdater(blobContentHolder, lineNumberSelector, elementsToUpdate) {
const updateBlameAndBlobPermalinkCb = () => {
// Wait for the hash to update from the LineHighlighter callback
setTimeout(() => {
updateLineNumbersOnBlobPermalinks(elementsToUpdate);
}, 0);
};
blobContentHolder.addEventListener('click', (e) => {
if (e.target.matches(lineNumberSelector)) {
updateBlameAndBlobPermalinkCb();
}
});
updateBlameAndBlobPermalinkCb();
}
export default BlobLinePermalinkUpdater;
...@@ -40,6 +40,7 @@ import BindInOut from './behaviors/bind_in_out'; ...@@ -40,6 +40,7 @@ import BindInOut from './behaviors/bind_in_out';
import GroupsList from './groups_list'; import GroupsList from './groups_list';
import ProjectsList from './projects_list'; import ProjectsList from './projects_list';
import MiniPipelineGraph from './mini_pipeline_graph_dropdown'; import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
const ShortcutsBlob = require('./shortcuts_blob'); const ShortcutsBlob = require('./shortcuts_blob');
const UserCallout = require('./user_callout'); const UserCallout = require('./user_callout');
...@@ -272,6 +273,13 @@ const UserCallout = require('./user_callout'); ...@@ -272,6 +273,13 @@ const UserCallout = require('./user_callout');
break; break;
case 'projects:blame:show': case 'projects:blame:show':
new LineHighlighter(); new LineHighlighter();
new BlobLinePermalinkUpdater(
document.querySelector('#blob-content-holder'),
'.diff-line-num[data-line-number]',
document.querySelectorAll('.js-data-file-blob-permalink-url, .js-blob-blame-link'),
);
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url'); fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href'); fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
......
...@@ -67,17 +67,7 @@ require('vendor/jquery.scrollTo'); ...@@ -67,17 +67,7 @@ require('vendor/jquery.scrollTo');
} }
LineHighlighter.prototype.bindEvents = function() { LineHighlighter.prototype.bindEvents = function() {
$('#blob-content-holder').on('mousedown', 'a[data-line-number]', this.clickHandler); $('#blob-content-holder').on('click', 'a[data-line-number]', this.clickHandler);
// While it may seem odd to bind to the mousedown event and then throw away
// the click event, there is a method to our madness.
//
// If not done this way, the line number anchor will sometimes keep its
// active state even when the event is cancelled, resulting in an ugly border
// around the link and/or a persisted underline text decoration.
$('#blob-content-holder').on('click', 'a[data-line-number]', function(event) {
event.preventDefault();
event.stopPropagation();
});
}; };
LineHighlighter.prototype.clickHandler = function(event) { LineHighlighter.prototype.clickHandler = function(event) {
......
...@@ -57,8 +57,13 @@ ...@@ -57,8 +57,13 @@
visibility: hidden; visibility: hidden;
} }
&:hover i { &:hover,
visibility: visible; &:focus {
outline: none;
& i {
visibility: visible;
}
} }
} }
} }
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
class: 'btn btn-sm' class: 'btn btn-sm'
- else - else
= link_to 'Blame', namespace_project_blame_path(@project.namespace, @project, @id), = link_to 'Blame', namespace_project_blame_path(@project.namespace, @project, @id),
class: 'btn btn-sm' unless @blob.empty? class: 'btn btn-sm js-blob-blame-link' unless @blob.empty?
= link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id),
class: 'btn btn-sm' class: 'btn btn-sm'
= link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project, = link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project,
......
---
title: Update permalink/blame buttons with line number fragment hash
merge_request:
author:
require 'spec_helper'
feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true, js: true do
include TreeHelper
let(:project) { create(:project, :public, :repository) }
let(:path) { 'CHANGELOG' }
let(:sha) { project.repository.commit.sha }
describe 'On a file(blob)' do
def get_absolute_url(path = "")
"http://#{page.server.host}:#{page.server.port}#{path}"
end
def visit_blob(fragment = nil)
visit namespace_project_blob_path(project.namespace, project, tree_join('master', path), anchor: fragment)
end
describe 'Click "Permalink" button' do
it 'works with no initial line number fragment hash' do
visit_blob
expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path))))
end
it 'maintains intitial fragment hash' do
fragment = "L3"
visit_blob(fragment)
expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path), anchor: fragment)))
end
it 'changes fragment hash if line number clicked' do
ending_fragment = "L5"
visit_blob
find('#L3').click
find("##{ending_fragment}").click
expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path), anchor: ending_fragment)))
end
it 'with initial fragment hash, changes fragment hash if line number clicked' do
fragment = "L1"
ending_fragment = "L5"
visit_blob(fragment)
find('#L3').click
find("##{ending_fragment}").click
expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path), anchor: ending_fragment)))
end
end
describe 'Click "Blame" button' do
it 'works with no initial line number fragment hash' do
visit_blob
expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(namespace_project_blame_path(project.namespace, project, tree_join('master', path))))
end
it 'maintains intitial fragment hash' do
fragment = "L3"
visit_blob(fragment)
expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(namespace_project_blame_path(project.namespace, project, tree_join('master', path), anchor: fragment)))
end
it 'changes fragment hash if line number clicked' do
ending_fragment = "L5"
visit_blob
find('#L3').click
find("##{ending_fragment}").click
expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(namespace_project_blame_path(project.namespace, project, tree_join('master', path), anchor: ending_fragment)))
end
it 'with initial fragment hash, changes fragment hash if line number clicked' do
fragment = "L1"
ending_fragment = "L5"
visit_blob(fragment)
find('#L3').click
find("##{ending_fragment}").click
expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(namespace_project_blame_path(project.namespace, project, tree_join('master', path), anchor: ending_fragment)))
end
end
end
end
...@@ -7,16 +7,12 @@ require('~/line_highlighter'); ...@@ -7,16 +7,12 @@ require('~/line_highlighter');
describe('LineHighlighter', function() { describe('LineHighlighter', function() {
var clickLine; var clickLine;
preloadFixtures('static/line_highlighter.html.raw'); preloadFixtures('static/line_highlighter.html.raw');
clickLine = function(number, eventData) { clickLine = function(number, eventData = {}) {
var e;
if (eventData == null) {
eventData = {};
}
if ($.isEmptyObject(eventData)) { if ($.isEmptyObject(eventData)) {
return $("#L" + number).mousedown().click(); return $("#L" + number).click();
} else { } else {
e = $.Event('mousedown', eventData); const e = $.Event('click', eventData);
return $("#L" + number).trigger(e).click(); return $("#L" + number).trigger(e);
} }
}; };
beforeEach(function() { beforeEach(function() {
...@@ -63,12 +59,6 @@ require('~/line_highlighter'); ...@@ -63,12 +59,6 @@ require('~/line_highlighter');
}); });
}); });
describe('#clickHandler', function() { describe('#clickHandler', function() {
it('discards the mousedown event', function() {
var spy;
spy = spyOnEvent('a[data-line-number]', 'mousedown');
clickLine(13);
return expect(spy).toHaveBeenPrevented();
});
it('handles clicking on a child icon element', function() { it('handles clicking on a child icon element', function() {
var spy; var spy;
spy = spyOn(this["class"], 'setHash').and.callThrough(); spy = spyOn(this["class"], 'setHash').and.callThrough();
......
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