Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move opening path in new browser tabs to a separate plugin #7056

Merged
merged 25 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading