Skip to content

Commit

Permalink
Use INotebookPathOpener
Browse files Browse the repository at this point in the history
  • Loading branch information
jtpio committed Sep 18, 2023
1 parent d6bdeae commit 161bc5e
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 61 deletions.
58 changes: 39 additions & 19 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 @@ -301,15 +303,15 @@ const pages: JupyterFrontEndPlugin<void> = {
activate: (
app: JupyterFrontEnd,
translator: ITranslator,
palette: ICommandPalette | null
palette: ICommandPalette | null,
): void => {
const trans = translator.load('notebook');
const baseUrl = PageConfig.getBaseUrl();

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({
route: URLExt.join(baseUrl, 'tree'),
path: model.path,
target: '_blank',
})
});
},
});
Expand All @@ -395,18 +414,18 @@ const rendermime: JupyterFrontEndPlugin<IRenderMimeRegistry> = {
linkHandler: !docManager
? undefined
: {
handleLink: (node: HTMLElement, path: string, id?: string) => {
// If node has the download attribute explicitly set, use the
// default browser downloading behavior.
if (node.tagName === 'A' && node.hasAttribute('download')) {
return;
}
app.commandLinker.connectNode(node, CommandIDs.handleLink, {
path,
id,
});
},
handleLink: (node: HTMLElement, path: string, id?: string) => {
// If node has the download attribute explicitly set, use the
// default browser downloading behavior.
if (node.tagName === 'A' && node.hasAttribute('download')) {
return;
}
app.commandLinker.connectNode(node, CommandIDs.handleLink, {
path,
id,
});
},
},
latexTypesetter: latexTypesetter ?? undefined,
markdownParser: markdownParser ?? undefined,
translator: translator ?? undefined,
Expand Down Expand Up @@ -1089,6 +1108,7 @@ const plugins: JupyterFrontEndPlugin<any>[] = [
menuSpacer,
opener,
pages,
pathOpener,
paths,
rendermime,
shell,
Expand Down
6 changes: 2 additions & 4 deletions packages/application/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ import { IRenderMime } from '@jupyterlab/rendermime-interfaces';

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

import { NotebookShell } from './shell';

import { INotebookShell } from './tokens';
import { INotebookShell, NotebookShell } from './shell';

/**
* App is the main application class. It is instantiated once and shared.
Expand Down Expand Up @@ -165,7 +163,7 @@ export namespace NotebookApp {
*/
export interface IOptions
extends JupyterFrontEnd.IOptions<INotebookShell>,
Partial<IInfo> {}
Partial<IInfo> { }

/**
* The information about a Jupyter Notebook application.
Expand Down
1 change: 1 addition & 0 deletions packages/application/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
export * from './app';
export * from './shell';
export * from './panelhandler';
export * from './pathopener';
export * from './tokens';
25 changes: 25 additions & 0 deletions packages/application/src/pathopener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// 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 path 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 { route, path, searchParams, target, features } = options;
const url = new URL(URLExt.join(route, path ?? ''), window.location.origin);
if (searchParams) {
url.search = searchParams.toString();
}
return window.open(url, target, features);
}
}

export const defaultNotebookPathOpener = new DefaultNotebookPathOpener();
15 changes: 12 additions & 3 deletions packages/application/src/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,23 @@ import { DocumentRegistry } from '@jupyterlab/docregistry';
import { ITranslator, nullTranslator } from '@jupyterlab/translation';

import { find } from '@lumino/algorithm';
import { JSONExt, PromiseDelegate } from '@lumino/coreutils';
import { JSONExt, PromiseDelegate, Token } from '@lumino/coreutils';
import { ISignal, Signal } from '@lumino/signaling';

import { BoxLayout, Panel, SplitPanel, Widget } from '@lumino/widgets';

import { PanelHandler, SidePanelHandler } from './panelhandler';

import { INotebookShell } from './tokens';
/**
* The Jupyter Notebook application shell token.
*/
export const INotebookShell = new Token<INotebookShell>(
'@jupyter-notebook/application:INotebookShell'
);

/**
* The Jupyter Notebook application shell interface.
*/
export interface INotebookShell extends NotebookShell {}

/**
* The namespace for INotebookShell type information.
Expand Down
55 changes: 40 additions & 15 deletions packages/application/src/tokens.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,54 @@
import { Token } from '@lumino/coreutils';

import { NotebookShell } from './shell';

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

export namespace INotebookPathOpener {
export interface IOpenOptions {
/**
* The base route, which should include the base URL
*/
route: 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 application
* to override the default behavior of opening a notebook in a new tab.
* It also allows to pass the path open as a search parame, or other options to the window.open call.
*/
export const INotebookPathOpener = new Token<INotebookPathOpener>(
'@jupyter-notebook/application:INotebookPathOpener'
);

/**
* The Jupyter Notebook application shell interface.
*/
export interface INotebookShell extends NotebookShell {}

/**
* The Jupyter Notebook application shell token.
*/
export const INotebookShell = new Token<INotebookShell>(
'@jupyter-notebook/application:INotebookShell'
);
15 changes: 12 additions & 3 deletions packages/console-extension/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ 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 +54,12 @@ 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 +71,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({
route: URLExt.join(baseUrl, 'consoles'),
path: sessionContext.path,
target: '_blank',
})

// the widget is not needed anymore
console.dispose();
Expand Down
23 changes: 16 additions & 7 deletions packages/docmanager-extension/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ 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 +23,12 @@ 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 +47,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({
route: URLExt.join(baseUrl, route),
path,
searchParams,
});

// dispose the widget since it is not used on this page
widget.dispose();
return;
Expand Down
Loading

0 comments on commit 161bc5e

Please sign in to comment.