Commit 347abae0 authored by mfluharty's avatar mfluharty

Turn URLs in blob viewer into clickable links

Use regex from utils file to replace URLs with links
parent 347e530c
// capture anything starting with http:// or https://
// up until a disallowed character or whitespace
export const blobLinkRegex = /https?:\/\/[^"<>\\^`{|}\s]+/g;
export default { blobLinkRegex };
...@@ -4,6 +4,10 @@ import Flash from '../../flash'; ...@@ -4,6 +4,10 @@ import Flash from '../../flash';
import { handleLocationHash } from '../../lib/utils/common_utils'; import { handleLocationHash } from '../../lib/utils/common_utils';
import axios from '../../lib/utils/axios_utils'; import axios from '../../lib/utils/axios_utils';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { blobLinkRegex } from '~/blob/blob_utils';
const SIMPLE_VIEWER_NAME = 'simple';
const RICH_VIEWER_NAME = 'rich';
export default class BlobViewer { export default class BlobViewer {
constructor() { constructor() {
...@@ -21,7 +25,7 @@ export default class BlobViewer { ...@@ -21,7 +25,7 @@ export default class BlobViewer {
} }
static initRichViewer() { static initRichViewer() {
const viewer = document.querySelector('.blob-viewer[data-type="rich"]'); const viewer = document.querySelector(`.blob-viewer[data-type="${RICH_VIEWER_NAME}"]`);
if (!viewer || !viewer.dataset.richType) return; if (!viewer || !viewer.dataset.richType) return;
const initViewer = promise => const initViewer = promise =>
...@@ -61,8 +65,12 @@ export default class BlobViewer { ...@@ -61,8 +65,12 @@ export default class BlobViewer {
this.switcherBtns = document.querySelectorAll('.js-blob-viewer-switch-btn'); this.switcherBtns = document.querySelectorAll('.js-blob-viewer-switch-btn');
this.copySourceBtn = document.querySelector('.js-copy-blob-source-btn'); this.copySourceBtn = document.querySelector('.js-copy-blob-source-btn');
this.simpleViewer = this.$fileHolder[0].querySelector('.blob-viewer[data-type="simple"]'); this.simpleViewer = this.$fileHolder[0].querySelector(
this.richViewer = this.$fileHolder[0].querySelector('.blob-viewer[data-type="rich"]'); `.blob-viewer[data-type="${SIMPLE_VIEWER_NAME}"]`,
);
this.richViewer = this.$fileHolder[0].querySelector(
`.blob-viewer[data-type="${RICH_VIEWER_NAME}"]`,
);
this.initBindings(); this.initBindings();
...@@ -74,7 +82,7 @@ export default class BlobViewer { ...@@ -74,7 +82,7 @@ export default class BlobViewer {
let initialViewerName = initialViewer.getAttribute('data-type'); let initialViewerName = initialViewer.getAttribute('data-type');
if (this.switcher && window.location.hash.indexOf('#L') === 0) { if (this.switcher && window.location.hash.indexOf('#L') === 0) {
initialViewerName = 'simple'; initialViewerName = SIMPLE_VIEWER_NAME;
} }
this.switchToViewer(initialViewerName); this.switchToViewer(initialViewerName);
...@@ -91,11 +99,22 @@ export default class BlobViewer { ...@@ -91,11 +99,22 @@ export default class BlobViewer {
this.copySourceBtn.addEventListener('click', () => { this.copySourceBtn.addEventListener('click', () => {
if (this.copySourceBtn.classList.contains('disabled')) return this.copySourceBtn.blur(); if (this.copySourceBtn.classList.contains('disabled')) return this.copySourceBtn.blur();
return this.switchToViewer('simple'); return this.switchToViewer(SIMPLE_VIEWER_NAME);
}); });
} }
} }
static linkifyURLs(viewer) {
if (viewer.getAttribute('data-linkified')) return;
document.querySelectorAll('.js-blob-content .code .line').forEach(line => {
// eslint-disable-next-line no-param-reassign
line.innerHTML = line.innerHTML.replace(blobLinkRegex, '<a href="$&">$&</a>');
});
viewer.setAttribute('data-linkified', 'true');
}
switchViewHandler(e) { switchViewHandler(e) {
const target = e.currentTarget; const target = e.currentTarget;
...@@ -159,6 +178,8 @@ export default class BlobViewer { ...@@ -159,6 +178,8 @@ export default class BlobViewer {
this.$fileHolder.trigger('highlight:line'); this.$fileHolder.trigger('highlight:line');
handleLocationHash(); handleLocationHash();
if (name === SIMPLE_VIEWER_NAME) BlobViewer.linkifyURLs(viewer);
this.toggleCopyButtonState(); this.toggleCopyButtonState();
}) })
.catch(() => new Flash(__('Error loading viewer'))); .catch(() => new Flash(__('Error loading viewer')));
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
%a.diff-line-num{ href: "#{link}#L#{i}", id: "L#{i}", 'data-line-number' => i } %a.diff-line-num{ href: "#{link}#L#{i}", id: "L#{i}", 'data-line-number' => i }
= link_icon = link_icon
= i = i
.blob-content{ data: { blob_id: blob.id } } .blob-content.js-blob-content{ data: { blob_id: blob.id } }
%pre.code.highlight %pre.code.highlight
%code %code
= blob.present.highlight = blob.present.highlight
...@@ -11,6 +11,13 @@ describe('Blob viewer', () => { ...@@ -11,6 +11,13 @@ describe('Blob viewer', () => {
preloadFixtures('snippets/show.html'); preloadFixtures('snippets/show.html');
const asyncClick = () =>
new Promise(resolve => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
setTimeout(resolve);
});
beforeEach(() => { beforeEach(() => {
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
...@@ -66,13 +73,6 @@ describe('Blob viewer', () => { ...@@ -66,13 +73,6 @@ describe('Blob viewer', () => {
}); });
it('doesnt reload file if already loaded', done => { it('doesnt reload file if already loaded', done => {
const asyncClick = () =>
new Promise(resolve => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
setTimeout(resolve);
});
asyncClick() asyncClick()
.then(() => asyncClick()) .then(() => asyncClick())
.then(() => { .then(() => {
...@@ -177,4 +177,27 @@ describe('Blob viewer', () => { ...@@ -177,4 +177,27 @@ describe('Blob viewer', () => {
expect(axios.get.calls.count()).toBe(1); expect(axios.get.calls.count()).toBe(1);
}); });
}); });
describe('a URL inside the blob content', () => {
beforeEach(() => {
mock.onGet('http://test.host/snippets/1.json?viewer=simple').reply(200, {
html:
'<div class="js-blob-content"><pre class="code"><code><span class="line" lang="yaml"><span class="c1">To install gitlab-shell you also need a Go compiler version 1.8 or newer. https://golang.org/dl/</span></span></code></pre></div>',
});
});
it('is rendered as a link in simple view', done => {
asyncClick()
.then(() => {
expect(document.querySelector('.blob-viewer[data-type="simple"]').innerHTML).toContain(
'<a href="https://golang.org/dl/">https://golang.org/dl/</a>',
);
done();
})
.catch(() => {
fail();
done();
});
});
});
}); });
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