Skip to content

Commit 6246540

Browse files
authored
Move opening path in new browser tabs to a separate plugin (#7056)
* Add `INotebookPathOpener` * Reorg tokens * Use `INotebookPathOpener` * Lint * Rename param * Lint * add docstring * Fix dependency on application package * more tsconfig fixes * fix typo * dedupe * update dev commands on Binder * fix typo * lint * Add missing dependency * tmp * set explicit jupyterlab on Binder * drop link share for now * remove tmp * edit pyproject config * debug * test * test * test * fix typos
1 parent 2b4b422 commit 6246540

File tree

19 files changed

+273
-382
lines changed

19 files changed

+273
-382
lines changed

binder/environment.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ channels:
33
- conda-forge
44
dependencies:
55
- ipywidgets=8
6+
- jupyterlab=4
67
- jupyterlab-language-pack-fr-FR
7-
- jupyterlab-link-share>=0.2
88
- matplotlib
99
- numpy
1010
- nodejs=20

binder/postBuild

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
#!/bin/bash
22
set -euo pipefail
33

4-
python -m pip install -e . --force-reinstall
5-
6-
jlpm && jlpm run build
7-
jlpm run develop
4+
python -m pip install -e ".[dev,test]"
5+
jlpm develop

packages/application-extension/src/index.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ import {
4747
SidePanel,
4848
SidePanelHandler,
4949
SidePanelPalette,
50+
INotebookPathOpener,
51+
defaultNotebookPathOpener,
5052
} from '@jupyter-notebook/application';
5153

5254
import { jupyterIcon } from '@jupyter-notebook/ui-components';
@@ -309,7 +311,7 @@ const pages: JupyterFrontEndPlugin<void> = {
309311
app.commands.addCommand(CommandIDs.openLab, {
310312
label: trans.__('Open JupyterLab'),
311313
execute: () => {
312-
window.open(`${baseUrl}lab`);
314+
window.open(URLExt.join(baseUrl, 'lab'));
313315
},
314316
});
315317
const page = PageConfig.getOption('notebookPage');
@@ -320,7 +322,7 @@ const pages: JupyterFrontEndPlugin<void> = {
320322
if (page === 'tree') {
321323
app.commands.execute('filebrowser:activate');
322324
} else {
323-
window.open(`${baseUrl}tree`);
325+
window.open(URLExt.join(baseUrl, 'tree'));
324326
}
325327
},
326328
});
@@ -332,6 +334,18 @@ const pages: JupyterFrontEndPlugin<void> = {
332334
},
333335
};
334336

337+
/**
338+
* A plugin to open paths in new browser tabs.
339+
*/
340+
const pathOpener: JupyterFrontEndPlugin<INotebookPathOpener> = {
341+
id: '@jupyter-notebook/application-extension:path-opener',
342+
autoStart: true,
343+
provides: INotebookPathOpener,
344+
activate: (app: JupyterFrontEnd): INotebookPathOpener => {
345+
return defaultNotebookPathOpener;
346+
},
347+
};
348+
335349
/**
336350
* The default paths for a Jupyter Notebook app.
337351
*/
@@ -361,16 +375,19 @@ const rendermime: JupyterFrontEndPlugin<IRenderMimeRegistry> = {
361375
ISanitizer,
362376
IMarkdownParser,
363377
ITranslator,
378+
INotebookPathOpener,
364379
],
365380
activate: (
366381
app: JupyterFrontEnd,
367382
docManager: IDocumentManager | null,
368383
latexTypesetter: ILatexTypesetter | null,
369384
sanitizer: IRenderMime.ISanitizer | null,
370385
markdownParser: IMarkdownParser | null,
371-
translator: ITranslator | null
386+
translator: ITranslator | null,
387+
notebookPathOpener: INotebookPathOpener | null
372388
) => {
373389
const trans = (translator ?? nullTranslator).load('jupyterlab');
390+
const opener = notebookPathOpener ?? defaultNotebookPathOpener;
374391
if (docManager) {
375392
app.commands.addCommand(CommandIDs.handleLink, {
376393
label: trans.__('Handle Local Link'),
@@ -382,10 +399,12 @@ const rendermime: JupyterFrontEndPlugin<IRenderMimeRegistry> = {
382399
return docManager.services.contents
383400
.get(path, { content: false })
384401
.then((model) => {
385-
// Open in a new browser tab
386-
const url = PageConfig.getBaseUrl();
387-
const treeUrl = URLExt.join(url, 'tree', model.path);
388-
window.open(treeUrl, '_blank');
402+
const baseUrl = PageConfig.getBaseUrl();
403+
opener.open({
404+
prefix: URLExt.join(baseUrl, 'tree'),
405+
path: model.path,
406+
target: '_blank',
407+
});
389408
});
390409
},
391410
});
@@ -1089,6 +1108,7 @@ const plugins: JupyterFrontEndPlugin<any>[] = [
10891108
menuSpacer,
10901109
opener,
10911110
pages,
1111+
pathOpener,
10921112
paths,
10931113
rendermime,
10941114
shell,

packages/application-extension/tsconfig.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
"references": [
99
{
1010
"path": "../application"
11+
},
12+
{
13+
"path": "../ui-components"
1114
}
1215
]
1316
}

packages/application/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@
44
export * from './app';
55
export * from './shell';
66
export * from './panelhandler';
7+
export * from './pathopener';
8+
export * from './tokens';
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) Jupyter Development Team.
2+
// Distributed under the terms of the Modified BSD License.
3+
4+
import { URLExt } from '@jupyterlab/coreutils';
5+
6+
import { INotebookPathOpener } from './tokens';
7+
8+
/**
9+
* A class to open paths in new browser tabs in the Notebook application.
10+
*/
11+
class DefaultNotebookPathOpener implements INotebookPathOpener {
12+
/**
13+
* Open a path in a new browser tab.
14+
*/
15+
open(options: INotebookPathOpener.IOpenOptions): WindowProxy | null {
16+
const { prefix, path, searchParams, target, features } = options;
17+
const url = new URL(
18+
URLExt.join(prefix, path ?? ''),
19+
window.location.origin
20+
);
21+
if (searchParams) {
22+
url.search = searchParams.toString();
23+
}
24+
return window.open(url, target, features);
25+
}
26+
}
27+
28+
export const defaultNotebookPathOpener = new DefaultNotebookPathOpener();

packages/application/src/tokens.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { Token } from '@lumino/coreutils';
2+
3+
/**
4+
* The INotebookPathOpener interface.
5+
*/
6+
export interface INotebookPathOpener {
7+
/**
8+
* Open a path in the application.
9+
*
10+
* @param options - The options used to open the path.
11+
*/
12+
open: (options: INotebookPathOpener.IOpenOptions) => WindowProxy | null;
13+
}
14+
15+
export namespace INotebookPathOpener {
16+
/**
17+
* The options used to open a path in the application.
18+
*/
19+
export interface IOpenOptions {
20+
/**
21+
* The URL prefix, which should include the base URL
22+
*/
23+
prefix: string;
24+
25+
/**
26+
* The path to open in the application, e.g `setup.py`, or `notebooks/example.ipynb`
27+
*/
28+
path?: string;
29+
30+
/**
31+
* The extra search params to use in the URL.
32+
*/
33+
searchParams?: URLSearchParams;
34+
35+
/**
36+
* Name of the browsing context the resource is being loaded into.
37+
* See https://developer.mozilla.org/en-US/docs/Web/API/Window/open for more details.
38+
*/
39+
target?: string;
40+
41+
/**
42+
*
43+
* See https://developer.mozilla.org/en-US/docs/Web/API/Window/open for more details.
44+
*/
45+
features?: string;
46+
}
47+
}
48+
49+
/**
50+
* The INotebookPathOpener token.
51+
* The main purpose of this token is to allow other extensions or downstream applications
52+
* to override the default behavior of opening a notebook in a new tab.
53+
* It also allows passing the path as a URL search parameter, or other options to the window.open call.
54+
*/
55+
export const INotebookPathOpener = new Token<INotebookPathOpener>(
56+
'@jupyter-notebook/application:INotebookPathOpener'
57+
);

packages/console-extension/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"watch": "tsc -b --watch"
3939
},
4040
"dependencies": {
41+
"@jupyter-notebook/application": "^7.0.3",
4142
"@jupyterlab/application": "^4.0.6",
4243
"@jupyterlab/console": "^4.0.6",
4344
"@jupyterlab/coreutils": "^6.0.6",

packages/console-extension/src/index.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ import {
99

1010
import { IConsoleTracker } from '@jupyterlab/console';
1111

12-
import { PageConfig } from '@jupyterlab/coreutils';
12+
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
13+
14+
import {
15+
INotebookPathOpener,
16+
defaultNotebookPathOpener,
17+
} from '@jupyter-notebook/application';
1318

1419
import { find } from '@lumino/algorithm';
1520

@@ -52,9 +57,16 @@ const opener: JupyterFrontEndPlugin<void> = {
5257
const redirect: JupyterFrontEndPlugin<void> = {
5358
id: '@jupyter-notebook/console-extension:redirect',
5459
requires: [IConsoleTracker],
60+
optional: [INotebookPathOpener],
5561
autoStart: true,
56-
activate: (app: JupyterFrontEnd, tracker: IConsoleTracker) => {
62+
activate: (
63+
app: JupyterFrontEnd,
64+
tracker: IConsoleTracker,
65+
notebookPathOpener: INotebookPathOpener | null
66+
) => {
5767
const baseUrl = PageConfig.getBaseUrl();
68+
const opener = notebookPathOpener ?? defaultNotebookPathOpener;
69+
5870
tracker.widgetAdded.connect(async (send, console) => {
5971
const { sessionContext } = console;
6072
await sessionContext.ready;
@@ -66,7 +78,11 @@ const redirect: JupyterFrontEndPlugin<void> = {
6678
// bail if the console is already added to the main area
6779
return;
6880
}
69-
window.open(`${baseUrl}consoles/${sessionContext.path}`, '_blank');
81+
opener.open({
82+
prefix: URLExt.join(baseUrl, 'consoles'),
83+
path: sessionContext.path,
84+
target: '_blank',
85+
});
7086

7187
// the widget is not needed anymore
7288
console.dispose();

packages/console-extension/tsconfig.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,10 @@
44
"outDir": "lib",
55
"rootDir": "src"
66
},
7-
"include": ["src/**/*"]
7+
"include": ["src/**/*"],
8+
"references": [
9+
{
10+
"path": "../application"
11+
}
12+
]
813
}

0 commit comments

Comments
 (0)