Skip to content

Commit d01f144

Browse files
committed
Use INotebookPathOpener
1 parent 6a9959b commit d01f144

File tree

10 files changed

+173
-61
lines changed

10 files changed

+173
-61
lines changed

packages/application-extension/src/index.ts

+39-19
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';
@@ -301,15 +303,15 @@ const pages: JupyterFrontEndPlugin<void> = {
301303
activate: (
302304
app: JupyterFrontEnd,
303305
translator: ITranslator,
304-
palette: ICommandPalette | null
306+
palette: ICommandPalette | null,
305307
): void => {
306308
const trans = translator.load('notebook');
307309
const baseUrl = PageConfig.getBaseUrl();
308310

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+
route: URLExt.join(baseUrl, 'tree'),
405+
path: model.path,
406+
target: '_blank',
407+
})
389408
});
390409
},
391410
});
@@ -395,18 +414,18 @@ const rendermime: JupyterFrontEndPlugin<IRenderMimeRegistry> = {
395414
linkHandler: !docManager
396415
? undefined
397416
: {
398-
handleLink: (node: HTMLElement, path: string, id?: string) => {
399-
// If node has the download attribute explicitly set, use the
400-
// default browser downloading behavior.
401-
if (node.tagName === 'A' && node.hasAttribute('download')) {
402-
return;
403-
}
404-
app.commandLinker.connectNode(node, CommandIDs.handleLink, {
405-
path,
406-
id,
407-
});
408-
},
417+
handleLink: (node: HTMLElement, path: string, id?: string) => {
418+
// If node has the download attribute explicitly set, use the
419+
// default browser downloading behavior.
420+
if (node.tagName === 'A' && node.hasAttribute('download')) {
421+
return;
422+
}
423+
app.commandLinker.connectNode(node, CommandIDs.handleLink, {
424+
path,
425+
id,
426+
});
409427
},
428+
},
410429
latexTypesetter: latexTypesetter ?? undefined,
411430
markdownParser: markdownParser ?? undefined,
412431
translator: translator ?? undefined,
@@ -1089,6 +1108,7 @@ const plugins: JupyterFrontEndPlugin<any>[] = [
10891108
menuSpacer,
10901109
opener,
10911110
pages,
1111+
pathOpener,
10921112
paths,
10931113
rendermime,
10941114
shell,

packages/application/src/app.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ import { IRenderMime } from '@jupyterlab/rendermime-interfaces';
1818

1919
import { Throttler } from '@lumino/polling';
2020

21-
import { NotebookShell } from './shell';
22-
23-
import { INotebookShell } from './tokens';
21+
import { INotebookShell, NotebookShell } from './shell';
2422

2523
/**
2624
* App is the main application class. It is instantiated once and shared.
@@ -165,7 +163,7 @@ export namespace NotebookApp {
165163
*/
166164
export interface IOptions
167165
extends JupyterFrontEnd.IOptions<INotebookShell>,
168-
Partial<IInfo> {}
166+
Partial<IInfo> { }
169167

170168
/**
171169
* The information about a Jupyter Notebook application.

packages/application/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44
export * from './app';
55
export * from './shell';
66
export * from './panelhandler';
7+
export * from './pathopener';
78
export * from './tokens';
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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 path 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 { route, path, searchParams, target, features } = options;
17+
const url = new URL(URLExt.join(route, path ?? ''), window.location.origin);
18+
if (searchParams) {
19+
url.search = searchParams.toString();
20+
}
21+
return window.open(url, target, features);
22+
}
23+
}
24+
25+
export const defaultNotebookPathOpener = new DefaultNotebookPathOpener();

packages/application/src/shell.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,23 @@ import { DocumentRegistry } from '@jupyterlab/docregistry';
66
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
77

88
import { find } from '@lumino/algorithm';
9-
import { JSONExt, PromiseDelegate } from '@lumino/coreutils';
9+
import { JSONExt, PromiseDelegate, Token } from '@lumino/coreutils';
1010
import { ISignal, Signal } from '@lumino/signaling';
1111

1212
import { BoxLayout, Panel, SplitPanel, Widget } from '@lumino/widgets';
13-
1413
import { PanelHandler, SidePanelHandler } from './panelhandler';
1514

16-
import { INotebookShell } from './tokens';
15+
/**
16+
* The Jupyter Notebook application shell token.
17+
*/
18+
export const INotebookShell = new Token<INotebookShell>(
19+
'@jupyter-notebook/application:INotebookShell'
20+
);
21+
22+
/**
23+
* The Jupyter Notebook application shell interface.
24+
*/
25+
export interface INotebookShell extends NotebookShell {}
1726

1827
/**
1928
* The namespace for INotebookShell type information.

packages/application/src/tokens.ts

+40-15
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,54 @@
11
import { Token } from '@lumino/coreutils';
22

3-
import { NotebookShell } from './shell';
4-
53
/**
64
* The INotebookPathOpener interface.
75
*/
86
export interface INotebookPathOpener {
9-
open: (route: string, path?: string) => void;
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+
export interface IOpenOptions {
17+
/**
18+
* The base route, which should include the base URL
19+
*/
20+
route: string
21+
22+
/**
23+
* The path to open in the application, e.g `setup.py`, or `notebooks/example.ipynb`
24+
*/
25+
path?: string,
26+
27+
/**
28+
* The extra search params to use in the URL.
29+
*/
30+
searchParams?: URLSearchParams;
31+
32+
/**
33+
* Name of the browsing context the resource is being loaded into.
34+
* See https://developer.mozilla.org/en-US/docs/Web/API/Window/open for more details.
35+
*/
36+
target?: string;
37+
38+
/**
39+
*
40+
* See https://developer.mozilla.org/en-US/docs/Web/API/Window/open for more details.
41+
*/
42+
features?: string;
43+
}
1044
}
1145

1246
/**
1347
* The INotebookPathOpener token.
48+
* The main purpose of this token is to allow other extensions or downstream application
49+
* to override the default behavior of opening a notebook in a new tab.
50+
* It also allows to pass the path open as a search parame, or other options to the window.open call.
1451
*/
1552
export const INotebookPathOpener = new Token<INotebookPathOpener>(
1653
'@jupyter-notebook/application:INotebookPathOpener'
1754
);
18-
19-
/**
20-
* The Jupyter Notebook application shell interface.
21-
*/
22-
export interface INotebookShell extends NotebookShell {}
23-
24-
/**
25-
* The Jupyter Notebook application shell token.
26-
*/
27-
export const INotebookShell = new Token<INotebookShell>(
28-
'@jupyter-notebook/application:INotebookShell'
29-
);

packages/console-extension/src/index.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ 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 { INotebookPathOpener, defaultNotebookPathOpener } from '@jupyter-notebook/application';
1315

1416
import { find } from '@lumino/algorithm';
1517

@@ -52,9 +54,12 @@ const opener: JupyterFrontEndPlugin<void> = {
5254
const redirect: JupyterFrontEndPlugin<void> = {
5355
id: '@jupyter-notebook/console-extension:redirect',
5456
requires: [IConsoleTracker],
57+
optional: [INotebookPathOpener],
5558
autoStart: true,
56-
activate: (app: JupyterFrontEnd, tracker: IConsoleTracker) => {
59+
activate: (app: JupyterFrontEnd, tracker: IConsoleTracker, notebookPathOpener: INotebookPathOpener | null) => {
5760
const baseUrl = PageConfig.getBaseUrl();
61+
const opener = notebookPathOpener ?? defaultNotebookPathOpener;
62+
5863
tracker.widgetAdded.connect(async (send, console) => {
5964
const { sessionContext } = console;
6065
await sessionContext.ready;
@@ -66,7 +71,11 @@ const redirect: JupyterFrontEndPlugin<void> = {
6671
// bail if the console is already added to the main area
6772
return;
6873
}
69-
window.open(`${baseUrl}consoles/${sessionContext.path}`, '_blank');
74+
opener.open({
75+
route: URLExt.join(baseUrl, 'consoles'),
76+
path: sessionContext.path,
77+
target: '_blank',
78+
})
7079

7180
// the widget is not needed anymore
7281
console.dispose();

packages/docmanager-extension/src/index.ts

+16-7
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ import {
66
JupyterFrontEndPlugin,
77
} from '@jupyterlab/application';
88

9-
import { PageConfig, PathExt } from '@jupyterlab/coreutils';
9+
import { PageConfig, PathExt, URLExt } from '@jupyterlab/coreutils';
1010

1111
import { IDocumentWidgetOpener } from '@jupyterlab/docmanager';
1212

1313
import { IDocumentWidget, DocumentRegistry } from '@jupyterlab/docregistry';
1414

15-
import { INotebookShell } from '@jupyter-notebook/application';
15+
import { INotebookPathOpener, INotebookShell, defaultNotebookPathOpener } from '@jupyter-notebook/application';
1616

1717
import { Signal } from '@lumino/signaling';
1818

@@ -23,11 +23,12 @@ import { Signal } from '@lumino/signaling';
2323
const opener: JupyterFrontEndPlugin<IDocumentWidgetOpener> = {
2424
id: '@jupyter-notebook/docmanager-extension:opener',
2525
autoStart: true,
26-
optional: [INotebookShell],
26+
optional: [INotebookPathOpener, INotebookShell],
2727
provides: IDocumentWidgetOpener,
28-
activate: (app: JupyterFrontEnd, notebookShell: INotebookShell | null) => {
28+
activate: (app: JupyterFrontEnd, notebookPathOpener: INotebookPathOpener, notebookShell: INotebookShell | null) => {
2929
const baseUrl = PageConfig.getBaseUrl();
3030
const docRegistry = app.docRegistry;
31+
const pathOpener = notebookPathOpener ?? defaultNotebookPathOpener;
3132
let id = 0;
3233
return new (class {
3334
open(widget: IDocumentWidget, options?: DocumentRegistry.IOpenOptions) {
@@ -46,13 +47,21 @@ const opener: JupyterFrontEndPlugin<IDocumentWidgetOpener> = {
4647
) {
4748
route = 'notebooks';
4849
}
49-
let url = `${baseUrl}${route}/${path}`;
5050
// append ?factory only if it's not the default
5151
const defaultFactory = docRegistry.defaultWidgetFactory(path);
52+
let searchParams = undefined;
5253
if (widgetName !== defaultFactory.name) {
53-
url = `${url}?factory=${widgetName}`;
54+
searchParams = new URLSearchParams({
55+
factory: widgetName,
56+
});
5457
}
55-
window.open(url);
58+
59+
pathOpener.open({
60+
route: URLExt.join(baseUrl, route),
61+
path,
62+
searchParams,
63+
});
64+
5665
// dispose the widget since it is not used on this page
5766
widget.dispose();
5867
return;

0 commit comments

Comments
 (0)