Skip to content

Commit

Permalink
Move opening path in new browser tabs to a separate plugin (#7056)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
jtpio committed Sep 20, 2023
1 parent 2b4b422 commit 6246540
Show file tree
Hide file tree
Showing 19 changed files with 273 additions and 382 deletions.
2 changes: 1 addition & 1 deletion binder/environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ channels:
- conda-forge
dependencies:
- ipywidgets=8
- jupyterlab=4
- jupyterlab-language-pack-fr-FR
- jupyterlab-link-share>=0.2
- matplotlib
- numpy
- nodejs=20
Expand Down
6 changes: 2 additions & 4 deletions binder/postBuild
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#!/bin/bash
set -euo pipefail

python -m pip install -e . --force-reinstall

jlpm && jlpm run build
jlpm run develop
python -m pip install -e ".[dev,test]"
jlpm develop
34 changes: 27 additions & 7 deletions packages/application-extension/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ import {
SidePanel,
SidePanelHandler,
SidePanelPalette,
INotebookPathOpener,
defaultNotebookPathOpener,
} from '@jupyter-notebook/application';

import { jupyterIcon } from '@jupyter-notebook/ui-components';
Expand Down Expand Up @@ -309,7 +311,7 @@ const pages: JupyterFrontEndPlugin<void> = {
app.commands.addCommand(CommandIDs.openLab, {
label: trans.__('Open JupyterLab'),
execute: () => {
window.open(`${baseUrl}lab`);
window.open(URLExt.join(baseUrl, 'lab'));
},
});
const page = PageConfig.getOption('notebookPage');
Expand All @@ -320,7 +322,7 @@ const pages: JupyterFrontEndPlugin<void> = {
if (page === 'tree') {
app.commands.execute('filebrowser:activate');
} else {
window.open(`${baseUrl}tree`);
window.open(URLExt.join(baseUrl, 'tree'));
}
},
});
Expand All @@ -332,6 +334,18 @@ const pages: JupyterFrontEndPlugin<void> = {
},
};

/**
* A plugin to open paths in new browser tabs.
*/
const pathOpener: JupyterFrontEndPlugin<INotebookPathOpener> = {
id: '@jupyter-notebook/application-extension:path-opener',
autoStart: true,
provides: INotebookPathOpener,
activate: (app: JupyterFrontEnd): INotebookPathOpener => {
return defaultNotebookPathOpener;
},
};

/**
* The default paths for a Jupyter Notebook app.
*/
Expand Down Expand Up @@ -361,16 +375,19 @@ const rendermime: JupyterFrontEndPlugin<IRenderMimeRegistry> = {
ISanitizer,
IMarkdownParser,
ITranslator,
INotebookPathOpener,
],
activate: (
app: JupyterFrontEnd,
docManager: IDocumentManager | null,
latexTypesetter: ILatexTypesetter | null,
sanitizer: IRenderMime.ISanitizer | null,
markdownParser: IMarkdownParser | null,
translator: ITranslator | null
translator: ITranslator | null,
notebookPathOpener: INotebookPathOpener | null
) => {
const trans = (translator ?? nullTranslator).load('jupyterlab');
const opener = notebookPathOpener ?? defaultNotebookPathOpener;
if (docManager) {
app.commands.addCommand(CommandIDs.handleLink, {
label: trans.__('Handle Local Link'),
Expand All @@ -382,10 +399,12 @@ const rendermime: JupyterFrontEndPlugin<IRenderMimeRegistry> = {
return docManager.services.contents
.get(path, { content: false })
.then((model) => {
// Open in a new browser tab
const url = PageConfig.getBaseUrl();
const treeUrl = URLExt.join(url, 'tree', model.path);
window.open(treeUrl, '_blank');
const baseUrl = PageConfig.getBaseUrl();
opener.open({
prefix: URLExt.join(baseUrl, 'tree'),
path: model.path,
target: '_blank',
});
});
},
});
Expand Down Expand Up @@ -1089,6 +1108,7 @@ const plugins: JupyterFrontEndPlugin<any>[] = [
menuSpacer,
opener,
pages,
pathOpener,
paths,
rendermime,
shell,
Expand Down
3 changes: 3 additions & 0 deletions packages/application-extension/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
"references": [
{
"path": "../application"
},
{
"path": "../ui-components"
}
]
}
2 changes: 2 additions & 0 deletions packages/application/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@
export * from './app';
export * from './shell';
export * from './panelhandler';
export * from './pathopener';
export * from './tokens';
28 changes: 28 additions & 0 deletions packages/application/src/pathopener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

import { URLExt } from '@jupyterlab/coreutils';

import { INotebookPathOpener } from './tokens';

/**
* A class to open paths in new browser tabs in the Notebook application.
*/
class DefaultNotebookPathOpener implements INotebookPathOpener {
/**
* Open a path in a new browser tab.
*/
open(options: INotebookPathOpener.IOpenOptions): WindowProxy | null {
const { prefix, path, searchParams, target, features } = options;
const url = new URL(
URLExt.join(prefix, path ?? ''),
window.location.origin
);
if (searchParams) {
url.search = searchParams.toString();
}
return window.open(url, target, features);
}
}

export const defaultNotebookPathOpener = new DefaultNotebookPathOpener();
57 changes: 57 additions & 0 deletions packages/application/src/tokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Token } from '@lumino/coreutils';

/**
* The INotebookPathOpener interface.
*/
export interface INotebookPathOpener {
/**
* Open a path in the application.
*
* @param options - The options used to open the path.
*/
open: (options: INotebookPathOpener.IOpenOptions) => WindowProxy | null;
}

export namespace INotebookPathOpener {
/**
* The options used to open a path in the application.
*/
export interface IOpenOptions {
/**
* The URL prefix, which should include the base URL
*/
prefix: string;

/**
* The path to open in the application, e.g `setup.py`, or `notebooks/example.ipynb`
*/
path?: string;

/**
* The extra search params to use in the URL.
*/
searchParams?: URLSearchParams;

/**
* Name of the browsing context the resource is being loaded into.
* See https://developer.mozilla.org/en-US/docs/Web/API/Window/open for more details.
*/
target?: string;

/**
*
* See https://developer.mozilla.org/en-US/docs/Web/API/Window/open for more details.
*/
features?: string;
}
}

/**
* The INotebookPathOpener token.
* The main purpose of this token is to allow other extensions or downstream applications
* to override the default behavior of opening a notebook in a new tab.
* It also allows passing the path as a URL search parameter, or other options to the window.open call.
*/
export const INotebookPathOpener = new Token<INotebookPathOpener>(
'@jupyter-notebook/application:INotebookPathOpener'
);
1 change: 1 addition & 0 deletions packages/console-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"watch": "tsc -b --watch"
},
"dependencies": {
"@jupyter-notebook/application": "^7.0.3",
"@jupyterlab/application": "^4.0.6",
"@jupyterlab/console": "^4.0.6",
"@jupyterlab/coreutils": "^6.0.6",
Expand Down
22 changes: 19 additions & 3 deletions packages/console-extension/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ import {

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

import { PageConfig } from '@jupyterlab/coreutils';
import { PageConfig, URLExt } from '@jupyterlab/coreutils';

import {
INotebookPathOpener,
defaultNotebookPathOpener,
} from '@jupyter-notebook/application';

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

Expand Down Expand Up @@ -52,9 +57,16 @@ const opener: JupyterFrontEndPlugin<void> = {
const redirect: JupyterFrontEndPlugin<void> = {
id: '@jupyter-notebook/console-extension:redirect',
requires: [IConsoleTracker],
optional: [INotebookPathOpener],
autoStart: true,
activate: (app: JupyterFrontEnd, tracker: IConsoleTracker) => {
activate: (
app: JupyterFrontEnd,
tracker: IConsoleTracker,
notebookPathOpener: INotebookPathOpener | null
) => {
const baseUrl = PageConfig.getBaseUrl();
const opener = notebookPathOpener ?? defaultNotebookPathOpener;

tracker.widgetAdded.connect(async (send, console) => {
const { sessionContext } = console;
await sessionContext.ready;
Expand All @@ -66,7 +78,11 @@ const redirect: JupyterFrontEndPlugin<void> = {
// bail if the console is already added to the main area
return;
}
window.open(`${baseUrl}consoles/${sessionContext.path}`, '_blank');
opener.open({
prefix: URLExt.join(baseUrl, 'consoles'),
path: sessionContext.path,
target: '_blank',
});

// the widget is not needed anymore
console.dispose();
Expand Down
7 changes: 6 additions & 1 deletion packages/console-extension/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,10 @@
"outDir": "lib",
"rootDir": "src"
},
"include": ["src/**/*"]
"include": ["src/**/*"],
"references": [
{
"path": "../application"
}
]
}
31 changes: 24 additions & 7 deletions packages/docmanager-extension/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ import {
JupyterFrontEndPlugin,
} from '@jupyterlab/application';

import { PageConfig, PathExt } from '@jupyterlab/coreutils';
import { PageConfig, PathExt, URLExt } from '@jupyterlab/coreutils';

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

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

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

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

Expand All @@ -23,11 +27,16 @@ import { Signal } from '@lumino/signaling';
const opener: JupyterFrontEndPlugin<IDocumentWidgetOpener> = {
id: '@jupyter-notebook/docmanager-extension:opener',
autoStart: true,
optional: [INotebookShell],
optional: [INotebookPathOpener, INotebookShell],
provides: IDocumentWidgetOpener,
activate: (app: JupyterFrontEnd, notebookShell: INotebookShell | null) => {
activate: (
app: JupyterFrontEnd,
notebookPathOpener: INotebookPathOpener,
notebookShell: INotebookShell | null
) => {
const baseUrl = PageConfig.getBaseUrl();
const docRegistry = app.docRegistry;
const pathOpener = notebookPathOpener ?? defaultNotebookPathOpener;
let id = 0;
return new (class {
open(widget: IDocumentWidget, options?: DocumentRegistry.IOpenOptions) {
Expand All @@ -46,13 +55,21 @@ const opener: JupyterFrontEndPlugin<IDocumentWidgetOpener> = {
) {
route = 'notebooks';
}
let url = `${baseUrl}${route}/${path}`;
// append ?factory only if it's not the default
const defaultFactory = docRegistry.defaultWidgetFactory(path);
let searchParams = undefined;
if (widgetName !== defaultFactory.name) {
url = `${url}?factory=${widgetName}`;
searchParams = new URLSearchParams({
factory: widgetName,
});
}
window.open(url);

pathOpener.open({
prefix: URLExt.join(baseUrl, route),
path,
searchParams,
});

// dispose the widget since it is not used on this page
widget.dispose();
return;
Expand Down
7 changes: 6 additions & 1 deletion packages/help-extension/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,10 @@
"outDir": "lib",
"rootDir": "src"
},
"include": ["src/**/*"]
"include": ["src/**/*"],
"references": [
{
"path": "../ui-components"
}
]
}
Loading

0 comments on commit 6246540

Please sign in to comment.