@jupyterlab/pdf-extension 4.0.0-alpha.8 → 4.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -8
- package/src/index.ts +198 -0
- package/style/base.css +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jupyterlab/pdf-extension",
|
|
3
|
-
"version": "4.0.0-
|
|
3
|
+
"version": "4.0.0-beta.0",
|
|
4
4
|
"description": "JupyterLab - PDF Viewer",
|
|
5
5
|
"homepage": "https://github.com/jupyterlab/jupyterlab",
|
|
6
6
|
"bugs": {
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"lib/*.js.map",
|
|
28
28
|
"lib/*.js",
|
|
29
29
|
"style/*.css",
|
|
30
|
-
"style/index.js"
|
|
30
|
+
"style/index.js",
|
|
31
|
+
"src/**/*.{ts,tsx}"
|
|
31
32
|
],
|
|
32
33
|
"scripts": {
|
|
33
34
|
"build": "tsc -b",
|
|
@@ -36,15 +37,15 @@
|
|
|
36
37
|
"watch": "tsc -b --watch"
|
|
37
38
|
},
|
|
38
39
|
"dependencies": {
|
|
39
|
-
"@jupyterlab/rendermime-interfaces": "^
|
|
40
|
-
"@lumino/coreutils": "^
|
|
41
|
-
"@lumino/disposable": "^
|
|
42
|
-
"@lumino/widgets": "^
|
|
40
|
+
"@jupyterlab/rendermime-interfaces": "^3.8.0-beta.0",
|
|
41
|
+
"@lumino/coreutils": "^2.0.0",
|
|
42
|
+
"@lumino/disposable": "^2.0.0",
|
|
43
|
+
"@lumino/widgets": "^2.0.0"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
46
|
"rimraf": "~3.0.0",
|
|
46
|
-
"typedoc": "~0.
|
|
47
|
-
"typescript": "~
|
|
47
|
+
"typedoc": "~0.23.25",
|
|
48
|
+
"typescript": "~5.0.2"
|
|
48
49
|
},
|
|
49
50
|
"publishConfig": {
|
|
50
51
|
"access": "public"
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// Copyright (c) Jupyter Development Team.
|
|
2
|
+
// Distributed under the terms of the Modified BSD License.
|
|
3
|
+
/**
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
* @module pdf-extension
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { IRenderMime } from '@jupyterlab/rendermime-interfaces';
|
|
9
|
+
import { PromiseDelegate } from '@lumino/coreutils';
|
|
10
|
+
import { DisposableDelegate } from '@lumino/disposable';
|
|
11
|
+
import { Widget } from '@lumino/widgets';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The MIME type for PDF.
|
|
15
|
+
*/
|
|
16
|
+
const MIME_TYPE = 'application/pdf';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A class for rendering a PDF document.
|
|
20
|
+
*/
|
|
21
|
+
export class RenderedPDF extends Widget implements IRenderMime.IRenderer {
|
|
22
|
+
constructor() {
|
|
23
|
+
super();
|
|
24
|
+
this.addClass('jp-PDFContainer');
|
|
25
|
+
// We put the object in an iframe, which seems to have a better chance
|
|
26
|
+
// of retaining its scroll position upon tab focusing, moving around etc.
|
|
27
|
+
const iframe = document.createElement('iframe');
|
|
28
|
+
this.node.appendChild(iframe);
|
|
29
|
+
// The iframe content window is not available until the onload event.
|
|
30
|
+
iframe.onload = () => {
|
|
31
|
+
const body = iframe.contentWindow!.document.createElement('body');
|
|
32
|
+
body.style.margin = '0px';
|
|
33
|
+
iframe.contentWindow!.document.body = body;
|
|
34
|
+
this._object = iframe.contentWindow!.document.createElement('object');
|
|
35
|
+
// work around for https://discussions.apple.com/thread/252247740
|
|
36
|
+
// Detect if running on Desktop Safari
|
|
37
|
+
if (!(window as any).safari) {
|
|
38
|
+
this._object.type = MIME_TYPE;
|
|
39
|
+
}
|
|
40
|
+
this._object.width = '100%';
|
|
41
|
+
this._object.height = '100%';
|
|
42
|
+
body.appendChild(this._object);
|
|
43
|
+
this._ready.resolve(void 0);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Render PDF into this widget's node.
|
|
49
|
+
*/
|
|
50
|
+
async renderModel(model: IRenderMime.IMimeModel): Promise<void> {
|
|
51
|
+
await this._ready.promise;
|
|
52
|
+
const data = model.data[MIME_TYPE] as string | undefined;
|
|
53
|
+
if (
|
|
54
|
+
!data ||
|
|
55
|
+
(data.length === this._base64.length && data === this._base64)
|
|
56
|
+
) {
|
|
57
|
+
// If there is no data, or if the string has not changed, we do not
|
|
58
|
+
// need to re-parse the data and rerender. We do, however, check
|
|
59
|
+
// for a fragment if the user wants to scroll the output.
|
|
60
|
+
if (model.metadata.fragment && this._object.data) {
|
|
61
|
+
const url = this._object.data;
|
|
62
|
+
this._object.data = `${url.split('#')[0]}${model.metadata.fragment}`;
|
|
63
|
+
}
|
|
64
|
+
// For some opaque reason, Firefox seems to loose its scroll position
|
|
65
|
+
// upon unhiding a PDF. But triggering a refresh of the URL makes it
|
|
66
|
+
// find it again. No idea what the reason for this is.
|
|
67
|
+
if (Private.IS_FIREFOX) {
|
|
68
|
+
this._object.data = this._object.data; // eslint-disable-line
|
|
69
|
+
}
|
|
70
|
+
return Promise.resolve(void 0);
|
|
71
|
+
}
|
|
72
|
+
this._base64 = data;
|
|
73
|
+
const blob = Private.b64toBlob(data, MIME_TYPE);
|
|
74
|
+
|
|
75
|
+
// Release reference to any previous object url.
|
|
76
|
+
if (this._disposable) {
|
|
77
|
+
this._disposable.dispose();
|
|
78
|
+
}
|
|
79
|
+
let objectUrl = URL.createObjectURL(blob);
|
|
80
|
+
if (model.metadata.fragment) {
|
|
81
|
+
objectUrl += model.metadata.fragment;
|
|
82
|
+
}
|
|
83
|
+
this._object.data = objectUrl;
|
|
84
|
+
|
|
85
|
+
// Set the disposable release the object URL.
|
|
86
|
+
this._disposable = new DisposableDelegate(() => {
|
|
87
|
+
try {
|
|
88
|
+
URL.revokeObjectURL(objectUrl);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
/* no-op */
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Handle a `before-hide` message.
|
|
98
|
+
*/
|
|
99
|
+
protected onBeforeHide(): void {
|
|
100
|
+
// Dispose of any URL fragment before hiding the widget
|
|
101
|
+
// so that it is not remembered upon show. Only Firefox
|
|
102
|
+
// seems to have a problem with this.
|
|
103
|
+
if (Private.IS_FIREFOX) {
|
|
104
|
+
this._object.data = this._object.data.split('#')[0];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Dispose of the resources held by the pdf widget.
|
|
110
|
+
*/
|
|
111
|
+
dispose(): void {
|
|
112
|
+
if (this._disposable) {
|
|
113
|
+
this._disposable.dispose();
|
|
114
|
+
}
|
|
115
|
+
super.dispose();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private _base64 = '';
|
|
119
|
+
private _disposable: DisposableDelegate | null = null;
|
|
120
|
+
private _object: HTMLObjectElement;
|
|
121
|
+
private _ready = new PromiseDelegate<void>();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* A mime renderer factory for PDF data.
|
|
126
|
+
*/
|
|
127
|
+
export const rendererFactory: IRenderMime.IRendererFactory = {
|
|
128
|
+
safe: false,
|
|
129
|
+
mimeTypes: [MIME_TYPE],
|
|
130
|
+
defaultRank: 100,
|
|
131
|
+
createRenderer: options => new RenderedPDF()
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const extensions: IRenderMime.IExtension | IRenderMime.IExtension[] = [
|
|
135
|
+
{
|
|
136
|
+
id: '@jupyterlab/pdf-extension:factory',
|
|
137
|
+
rendererFactory,
|
|
138
|
+
dataType: 'string',
|
|
139
|
+
documentWidgetFactoryOptions: {
|
|
140
|
+
name: 'PDF',
|
|
141
|
+
// TODO: translate label
|
|
142
|
+
modelName: 'base64',
|
|
143
|
+
primaryFileType: 'PDF',
|
|
144
|
+
fileTypes: ['PDF'],
|
|
145
|
+
defaultFor: ['PDF']
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
export default extensions;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* A namespace for PDF widget private data.
|
|
154
|
+
*/
|
|
155
|
+
namespace Private {
|
|
156
|
+
/**
|
|
157
|
+
* A flag for determining whether the user is using Firefox.
|
|
158
|
+
* There are some different PDF viewer behaviors on Firefox,
|
|
159
|
+
* and we try to address them with this. User agent string parsing
|
|
160
|
+
* is *not* reliable, so this should be considered a best-effort test.
|
|
161
|
+
*/
|
|
162
|
+
export const IS_FIREFOX: boolean = /Firefox/.test(navigator.userAgent);
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Convert a base64 encoded string to a Blob object.
|
|
166
|
+
* Modified from a snippet found here:
|
|
167
|
+
* https://stackoverflow.com/questions/16245767/creating-a-blob-from-a-base64-string-in-javascript
|
|
168
|
+
*
|
|
169
|
+
* @param b64Data - The base64 encoded data.
|
|
170
|
+
*
|
|
171
|
+
* @param contentType - The mime type of the data.
|
|
172
|
+
*
|
|
173
|
+
* @param sliceSize - The size to chunk the data into for processing.
|
|
174
|
+
*
|
|
175
|
+
* @returns a Blob for the data.
|
|
176
|
+
*/
|
|
177
|
+
export function b64toBlob(
|
|
178
|
+
b64Data: string,
|
|
179
|
+
contentType: string = '',
|
|
180
|
+
sliceSize: number = 512
|
|
181
|
+
): Blob {
|
|
182
|
+
const byteCharacters = atob(b64Data);
|
|
183
|
+
const byteArrays: Uint8Array[] = [];
|
|
184
|
+
|
|
185
|
+
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
|
|
186
|
+
const slice = byteCharacters.slice(offset, offset + sliceSize);
|
|
187
|
+
|
|
188
|
+
const byteNumbers = new Array(slice.length);
|
|
189
|
+
for (let i = 0; i < slice.length; i++) {
|
|
190
|
+
byteNumbers[i] = slice.charCodeAt(i);
|
|
191
|
+
}
|
|
192
|
+
const byteArray = new Uint8Array(byteNumbers);
|
|
193
|
+
byteArrays.push(byteArray);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return new Blob(byteArrays, { type: contentType });
|
|
197
|
+
}
|
|
198
|
+
}
|
package/style/base.css
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/*
|
|
20
|
-
When drag events occur, `
|
|
20
|
+
When drag events occur, `lm-mod-override-cursor` is added to the body.
|
|
21
21
|
This reuses the same CSS selector logic as jp-IFrame to prevent embedded
|
|
22
22
|
PDFs from swallowing cursor events.
|
|
23
23
|
*/
|