Commit dad30d6b authored by Sam Rose's avatar Sam Rose Committed by Jacob Schatz

Use PDFLab to render PDFs in GitLab

parent 97667b83
/* eslint-disable no-new */
import Vue from 'vue';
import PDFLab from 'vendor/pdflab';
import workerSrc from 'vendor/pdf.worker';
Vue.use(PDFLab, {
workerSrc,
});
export default () => {
const el = document.getElementById('js-pdf-viewer');
new Vue({
el,
data() {
return {
error: false,
loadError: false,
loading: true,
pdf: el.dataset.endpoint,
};
},
methods: {
onLoad() {
this.loading = false;
},
onError(error) {
this.loading = false;
this.loadError = true;
this.error = error;
},
},
template: `
<div class="container-fluid md prepend-top-default append-bottom-default">
<div
class="text-center loading"
v-if="loading && !error">
<i
class="fa fa-spinner fa-spin"
aria-hidden="true"
aria-label="PDF loading">
</i>
</div>
<pdf-lab
v-if="!loadError"
:pdf="pdf"
@pdflabload="onLoad"
@pdflaberror="onError" />
<p
class="text-center"
v-if="error">
<span v-if="loadError">
An error occured whilst loading the file. Please try again later.
</span>
<span v-else>
An error occured whilst decoding the file.
</span>
</p>
</div>
`,
});
};
import renderPDF from './pdf';
document.addEventListener('DOMContentLoaded', renderPDF);
...@@ -46,6 +46,10 @@ class Blob < SimpleDelegator ...@@ -46,6 +46,10 @@ class Blob < SimpleDelegator
text? && language && language.name == 'SVG' text? && language && language.name == 'SVG'
end end
def pdf?
name && File.extname(name) == '.pdf'
end
def ipython_notebook? def ipython_notebook?
text? && language&.name == 'Jupyter Notebook' text? && language&.name == 'Jupyter Notebook'
end end
...@@ -71,6 +75,8 @@ class Blob < SimpleDelegator ...@@ -71,6 +75,8 @@ class Blob < SimpleDelegator
end end
elsif image? || svg? elsif image? || svg?
'image' 'image'
elsif pdf?
'pdf'
elsif ipython_notebook? elsif ipython_notebook?
'notebook' 'notebook'
elsif sketch? elsif sketch?
......
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('pdf_viewer')
.file-content#js-pdf-viewer{ data: { endpoint: namespace_project_raw_path(@project.namespace, @project, @id) } }
...@@ -38,6 +38,7 @@ var config = { ...@@ -38,6 +38,7 @@ var config = {
network: './network/network_bundle.js', network: './network/network_bundle.js',
notebook_viewer: './blob/notebook_viewer.js', notebook_viewer: './blob/notebook_viewer.js',
sketch_viewer: './blob/sketch_viewer.js', sketch_viewer: './blob/sketch_viewer.js',
pdf_viewer: './blob/pdf_viewer.js',
profile: './profile/profile_bundle.js', profile: './profile/profile_bundle.js',
protected_branches: './protected_branches/protected_branches_bundle.js', protected_branches: './protected_branches/protected_branches_bundle.js',
snippet: './snippet/snippet_bundle.js', snippet: './snippet/snippet_bundle.js',
...@@ -65,7 +66,11 @@ var config = { ...@@ -65,7 +66,11 @@ var config = {
{ {
test: /\.svg$/, test: /\.svg$/,
use: 'raw-loader' use: 'raw-loader'
} }, {
test: /\.(worker.js|pdf)$/,
exclude: /node_modules/,
loader: 'file-loader',
},
] ]
}, },
...@@ -107,6 +112,7 @@ var config = { ...@@ -107,6 +112,7 @@ var config = {
'issuable', 'issuable',
'merge_conflicts', 'merge_conflicts',
'notebook_viewer', 'notebook_viewer',
'pdf_viewer',
'vue_pipelines', 'vue_pipelines',
], ],
minChunks: function(module, count) { minChunks: function(module, count) {
......
import renderPDF from '~/blob/pdf';
import testPDF from './test.pdf';
describe('PDF renderer', () => {
let viewer;
preloadFixtures('static/pdf_viewer.html.raw');
beforeEach(() => {
loadFixtures('static/pdf_viewer.html.raw');
viewer = document.getElementById('js-pdf-viewer');
viewer.dataset.endpoint = testPDF;
});
it('shows loading icon', () => {
renderPDF();
expect(
document.querySelector('.loading'),
).not.toBeNull();
});
describe('successful response', () => {
beforeEach((done) => {
renderPDF();
setTimeout(() => {
done();
}, 500);
});
it('does not show loading icon', () => {
expect(
document.querySelector('.loading'),
).toBeNull();
});
it('renders the PDF', () => {
expect(
document.querySelector('.pdf-viewer'),
).not.toBeNull();
});
it('renders the PDF page', () => {
expect(
document.querySelector('.pdf-page'),
).not.toBeNull();
});
});
describe('error getting file', () => {
beforeEach((done) => {
viewer.dataset.endpoint = 'invalid/endpoint';
renderPDF();
setTimeout(() => {
done();
}, 500);
});
it('does not show loading icon', () => {
expect(
document.querySelector('.loading'),
).toBeNull();
});
it('shows error message', () => {
expect(
document.querySelector('.md').textContent.trim(),
).toBe('An error occured whilst loading the file. Please try again later.');
});
});
});
.file-content#js-pdf-viewer{ data: { endpoint: '/test' } }
...@@ -53,6 +53,20 @@ describe Blob do ...@@ -53,6 +53,20 @@ describe Blob do
end end
end end
describe '#pdf?' do
it 'is falsey when file extension is not .pdf' do
git_blob = double(name: 'git_blob.txt')
expect(described_class.decorate(git_blob)).not_to be_pdf
end
it 'is truthy when file extension is .pdf' do
git_blob = double(name: 'git_blob.pdf')
expect(described_class.decorate(git_blob)).to be_pdf
end
end
describe '#ipython_notebook?' do describe '#ipython_notebook?' do
it 'is falsey when language is not Jupyter Notebook' do it 'is falsey when language is not Jupyter Notebook' do
git_blob = double(text?: true, language: double(name: 'JSON')) git_blob = double(text?: true, language: double(name: 'JSON'))
...@@ -102,6 +116,7 @@ describe Blob do ...@@ -102,6 +116,7 @@ describe Blob do
def stubbed_blob(overrides = {}) def stubbed_blob(overrides = {})
overrides.reverse_merge!( overrides.reverse_merge!(
name: nil,
image?: false, image?: false,
language: nil, language: nil,
lfs_pointer?: false, lfs_pointer?: false,
...@@ -146,6 +161,11 @@ describe Blob do ...@@ -146,6 +161,11 @@ describe Blob do
expect(blob.to_partial_path(project)).to eq 'download' expect(blob.to_partial_path(project)).to eq 'download'
end end
it 'handles PDFs' do
blob = stubbed_blob(name: 'blob.pdf', pdf?: true)
expect(blob.to_partial_path(project)).to eq 'pdf'
end
it 'handles iPython notebooks' do it 'handles iPython notebooks' do
blob = stubbed_blob(text?: true, ipython_notebook?: true) blob = stubbed_blob(text?: true, ipython_notebook?: true)
expect(blob.to_partial_path(project)).to eq 'notebook' expect(blob.to_partial_path(project)).to eq 'notebook'
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -1942,6 +1942,12 @@ file-entry-cache@^2.0.0: ...@@ -1942,6 +1942,12 @@ file-entry-cache@^2.0.0:
flat-cache "^1.2.1" flat-cache "^1.2.1"
object-assign "^4.0.1" object-assign "^4.0.1"
file-loader@^0.11.1:
version "0.11.1"
resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-0.11.1.tgz#6b328ee1234a729e4e47d36375dd6d35c0e1db84"
dependencies:
loader-utils "^1.0.2"
filename-regex@^2.0.0: filename-regex@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.0.tgz#996e3e80479b98b9897f15a8a58b3d084e926775" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.0.tgz#996e3e80479b98b9897f15a8a58b3d084e926775"
...@@ -2923,6 +2929,14 @@ loader-utils@^0.2.11, loader-utils@^0.2.16, loader-utils@^0.2.5: ...@@ -2923,6 +2929,14 @@ loader-utils@^0.2.11, loader-utils@^0.2.16, loader-utils@^0.2.5:
json5 "^0.5.0" json5 "^0.5.0"
object-assign "^4.0.1" object-assign "^4.0.1"
loader-utils@^1.0.2:
version "1.1.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd"
dependencies:
big.js "^3.1.3"
emojis-list "^2.0.0"
json5 "^0.5.0"
locate-path@^2.0.0: locate-path@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
......
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