Skip to content

Commit 7d144fe

Browse files
authored
Improve integration with Notebook 7 (#16)
* Handle opening a file from notebooks * add missing deps * handle edit * fix apputils dep * fix coreutils dep * update binder * fix handling of paths * Add FIXME for Content-Disposition * add config for memory storage * lint
1 parent f216f9d commit 7d144fe

File tree

6 files changed

+152
-15
lines changed

6 files changed

+152
-15
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,7 @@ dmypy.json
120120
# Yarn cache
121121
.yarn/
122122

123+
# Generated
123124
_temp_extension
125+
_output
126+
*.doit.db

binder/environment.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ channels:
1111

1212
dependencies:
1313
# runtime dependencies
14-
- python >=3.8,<3.9.0a0
15-
- jupyterlab >=3,<4.0.0a0
14+
- python >=3.11,<3.12
15+
- jupyterlab >=4.0.10,<5
16+
- notebook >=7.0.6,<8
1617
# labextension build dependencies
17-
- nodejs >=18,<19
18+
- nodejs >=20,<21
1819
- pip
1920
- wheel
2021
# additional packages for demos

docs/jupyter-lite.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"jupyter-lite-schema-version": 0,
3+
"jupyter-config-data": {
4+
"enableMemoryStorage": true,
5+
"settingsStorageDrivers": ["memoryStorageDriver"],
6+
"contentsStorageDrivers": ["memoryStorageDriver"]
7+
}
8+
}

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,11 @@
5555
"watch:src": "tsc -w --sourceMap"
5656
},
5757
"dependencies": {
58-
"@jupyterlab/application": "^4.0.10"
58+
"@jupyterlab/application": "^4.0.10",
59+
"@jupyterlab/apputils": "^4.1.10",
60+
"@jupyterlab/coreutils": "^6.0.10",
61+
"@jupyterlab/filebrowser": "^4.0.10",
62+
"@jupyterlab/translation": "^4.0.10"
5963
},
6064
"devDependencies": {
6165
"@jupyterlab/builder": "^4.0.0",

src/index.ts

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,39 @@ import {
44
JupyterFrontEndPlugin
55
} from '@jupyterlab/application';
66

7-
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
7+
import { showErrorMessage } from '@jupyterlab/apputils';
8+
9+
import { PageConfig, PathExt, URLExt } from '@jupyterlab/coreutils';
10+
11+
import { IDefaultFileBrowser } from '@jupyterlab/filebrowser';
12+
13+
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
814

915
/**
1016
* The regular expression matching the lab URL.
1117
*/
12-
const URL_PATTERN = new RegExp('/lab/?');
18+
const URL_PATTERN = new RegExp('/(lab|notebooks|edit)/?');
1319

1420
/**
1521
* Initialization data for the jupyterlab-open-url-parameter extension.
1622
*/
1723
const plugin: JupyterFrontEndPlugin<void> = {
1824
id: 'jupyterlab-open-url-parameter:plugin',
1925
autoStart: true,
20-
requires: [IRouter],
21-
activate: (app: JupyterFrontEnd, router: IRouter) => {
26+
requires: [IRouter, ITranslator],
27+
optional: [IDefaultFileBrowser],
28+
activate: (
29+
app: JupyterFrontEnd,
30+
router: IRouter,
31+
translator: ITranslator,
32+
browser: IDefaultFileBrowser | null
33+
) => {
2234
const { commands } = app;
35+
const trans = translator.load('jupyterlab') ?? nullTranslator;
2336

2437
const command = 'router:fromUrl';
2538
commands.addCommand(command, {
26-
execute: (args: any) => {
39+
execute: async (args: any) => {
2740
const parsed = args as IRouter.ILocation;
2841
// use request to do the matching
2942
const { request, search } = parsed;
@@ -35,20 +48,73 @@ const plugin: JupyterFrontEndPlugin<void> = {
3548
const urlParams = new URLSearchParams(search);
3649
const paramName = 'fromURL';
3750
const paths = urlParams.getAll(paramName);
38-
if (!paths) {
51+
if (!paths || paths.length === 0) {
3952
return;
4053
}
4154
const urls = paths.map(path => decodeURIComponent(path));
42-
app.restored.then(() => {
43-
// use the JupyterLab command to open the file from the URL
44-
urls.forEach(url => {
45-
void commands.execute('filebrowser:open-url', { url });
46-
});
55+
56+
// handle the route and remove the fromURL parameter
57+
const handleRoute = () => {
4758
const url = new URL(URLExt.join(PageConfig.getBaseUrl(), request));
4859
// only remove the fromURL parameter
4960
url.searchParams.delete(paramName);
5061
const { pathname, search } = url;
5162
router.navigate(`${pathname}${search}`, { skipRouting: true });
63+
};
64+
65+
// fetch the file from the URL and open it with the docmanager
66+
const fetchAndOpen = async (url: string): Promise<void> => {
67+
let type = '';
68+
let blob;
69+
70+
// fetch the file from the URL
71+
try {
72+
const req = await fetch(url);
73+
blob = await req.blob();
74+
type = req.headers.get('Content-Type') ?? '';
75+
} catch (err) {
76+
const reason = err as any;
77+
if (reason.response && reason.response.status !== 200) {
78+
reason.message = trans.__('Could not open URL: %1', url);
79+
}
80+
return showErrorMessage(trans.__('Cannot fetch'), reason);
81+
}
82+
83+
// upload the content of the file to the server
84+
try {
85+
// FIXME: handle Content-Disposition: https://github.com/jupyterlab/jupyterlab/issues/11531
86+
const name = PathExt.basename(url);
87+
const file = new File([blob], name, { type });
88+
const model = await browser?.model.upload(file);
89+
if (!model) {
90+
return;
91+
}
92+
return commands.execute('docmanager:open', {
93+
path: model.path,
94+
options: {
95+
ref: '_noref'
96+
}
97+
});
98+
} catch (error) {
99+
return showErrorMessage(
100+
trans._p('showErrorMessage', 'Upload Error'),
101+
error as Error
102+
);
103+
}
104+
};
105+
106+
const [match] = matches;
107+
// handle opening the URL with the Notebook 7 separately
108+
if (match?.includes('/notebooks') || match?.includes('/edit')) {
109+
const [first] = urls;
110+
await fetchAndOpen(first);
111+
handleRoute();
112+
return;
113+
}
114+
115+
app.restored.then(async () => {
116+
await Promise.all(urls.map(url => fetchAndOpen(url)));
117+
handleRoute();
52118
});
53119
}
54120
});

yarn.lock

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,29 @@ __metadata:
401401
languageName: node
402402
linkType: hard
403403

404+
"@jupyterlab/docmanager@npm:^4.0.10":
405+
version: 4.0.10
406+
resolution: "@jupyterlab/docmanager@npm:4.0.10"
407+
dependencies:
408+
"@jupyterlab/apputils": ^4.1.10
409+
"@jupyterlab/coreutils": ^6.0.10
410+
"@jupyterlab/docregistry": ^4.0.10
411+
"@jupyterlab/services": ^7.0.10
412+
"@jupyterlab/statusbar": ^4.0.10
413+
"@jupyterlab/translation": ^4.0.10
414+
"@jupyterlab/ui-components": ^4.0.10
415+
"@lumino/algorithm": ^2.0.1
416+
"@lumino/coreutils": ^2.1.2
417+
"@lumino/disposable": ^2.1.2
418+
"@lumino/messaging": ^2.0.1
419+
"@lumino/properties": ^2.0.1
420+
"@lumino/signaling": ^2.1.2
421+
"@lumino/widgets": ^2.3.0
422+
react: ^18.2.0
423+
checksum: 8be669130f29f9245c75e6b03f2bf788da17e1026b1117e0bbd8fe84afca5a9fddfa27fa879231979db1a2bf62c3e9b50eb38586cc155a3e64e1c1dd2e528c9b
424+
languageName: node
425+
linkType: hard
426+
404427
"@jupyterlab/docregistry@npm:^4.0.10":
405428
version: 4.0.10
406429
resolution: "@jupyterlab/docregistry@npm:4.0.10"
@@ -426,6 +449,34 @@ __metadata:
426449
languageName: node
427450
linkType: hard
428451

452+
"@jupyterlab/filebrowser@npm:^4.0.10":
453+
version: 4.0.10
454+
resolution: "@jupyterlab/filebrowser@npm:4.0.10"
455+
dependencies:
456+
"@jupyterlab/apputils": ^4.1.10
457+
"@jupyterlab/coreutils": ^6.0.10
458+
"@jupyterlab/docmanager": ^4.0.10
459+
"@jupyterlab/docregistry": ^4.0.10
460+
"@jupyterlab/services": ^7.0.10
461+
"@jupyterlab/statedb": ^4.0.10
462+
"@jupyterlab/statusbar": ^4.0.10
463+
"@jupyterlab/translation": ^4.0.10
464+
"@jupyterlab/ui-components": ^4.0.10
465+
"@lumino/algorithm": ^2.0.1
466+
"@lumino/coreutils": ^2.1.2
467+
"@lumino/disposable": ^2.1.2
468+
"@lumino/domutils": ^2.0.1
469+
"@lumino/dragdrop": ^2.1.4
470+
"@lumino/messaging": ^2.0.1
471+
"@lumino/polling": ^2.1.2
472+
"@lumino/signaling": ^2.1.2
473+
"@lumino/virtualdom": ^2.0.1
474+
"@lumino/widgets": ^2.3.0
475+
react: ^18.2.0
476+
checksum: f19884d40b0b1761e023c939cae1f11112b3b021f75df4806d97d0434e8b25c651bb054a66f563faa99e46c4b2b820b54e6a0e48bffa49d0a0e7bce145c34852
477+
languageName: node
478+
linkType: hard
479+
429480
"@jupyterlab/nbformat@npm:^3.0.0 || ^4.0.0-alpha.21 || ^4.0.0, @jupyterlab/nbformat@npm:^4.0.10":
430481
version: 4.0.10
431482
resolution: "@jupyterlab/nbformat@npm:4.0.10"
@@ -3222,7 +3273,11 @@ __metadata:
32223273
resolution: "jupyterlab-open-url-parameter@workspace:."
32233274
dependencies:
32243275
"@jupyterlab/application": ^4.0.10
3276+
"@jupyterlab/apputils": ^4.1.10
32253277
"@jupyterlab/builder": ^4.0.0
3278+
"@jupyterlab/coreutils": ^6.0.10
3279+
"@jupyterlab/filebrowser": ^4.0.10
3280+
"@jupyterlab/translation": ^4.0.10
32263281
"@types/json-schema": ^7.0.11
32273282
"@types/react": ^18.0.26
32283283
"@types/react-addons-linked-state-mixin": ^0.14.22

0 commit comments

Comments
 (0)