Provide a custom rendermime plugin to handle local links (#6885)

* Provide a custom rendermime plugin

* Lint

* Add UI tests for local links

* Ignore check link in the test notebook

* Improve tests
Jeremy Tuloup 3 years ago committed by GitHub
parent 00eed6292a
commit 9863625809
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -169,6 +169,7 @@ jobs:
- uses: jupyterlab/maintainer-tools/.github/actions/check-links@v1
with:
ignore_links: "https://playwright.dev/docs/test-cli/ https://blog.jupyter.org/the-big-split-9d7b88a031a7 https://blog.jupyter.org/jupyter-ascending-1bf5b362d97e https://mybinder.org/v2/gh/jupyter/notebook/main"
ignore_glob: "ui-tests/test/notebooks/*"
test_lint:
name: Test Lint

@ -71,7 +71,6 @@
"@jupyterlab/outputarea": "~4.0.0",
"@jupyterlab/pdf-extension": "~4.0.0",
"@jupyterlab/rendermime": "~4.0.0",
"@jupyterlab/rendermime-extension": "~4.0.0",
"@jupyterlab/rendermime-interfaces": "~3.8.0",
"@jupyterlab/running-extension": "~4.0.0",
"@jupyterlab/services": "~7.0.0",
@ -151,7 +150,6 @@
"@jupyterlab/metadataform-extension": "^4.0.0",
"@jupyterlab/notebook-extension": "^4.0.0",
"@jupyterlab/pdf-extension": "^4.0.0",
"@jupyterlab/rendermime-extension": "^4.0.0",
"@jupyterlab/running-extension": "^4.0.0",
"@jupyterlab/settingeditor": "^4.0.0",
"@jupyterlab/settingeditor-extension": "^4.0.0",
@ -261,7 +259,6 @@
"@jupyterlab/notebook-extension:tracker",
"@jupyterlab/notebook-extension:widget-factory"
],
"@jupyterlab/rendermime-extension": true,
"@jupyterlab/shortcuts-extension": true,
"@jupyterlab/terminal-extension": true,
"@jupyterlab/theme-light-extension": true,

@ -48,6 +48,7 @@
"@jupyterlab/docmanager": "^4.0.0",
"@jupyterlab/docregistry": "^4.0.0",
"@jupyterlab/mainmenu": "^4.0.0",
"@jupyterlab/rendermime": "^4.0.0",
"@jupyterlab/settingregistry": "^4.0.0",
"@jupyterlab/translation": "^4.0.0",
"@lumino/coreutils": "^2.1.1",

@ -12,6 +12,7 @@ import {
import {
DOMUtils,
ICommandPalette,
ISanitizer,
IToolbarWidgetRegistry,
} from '@jupyterlab/apputils';
@ -25,9 +26,18 @@ import { DocumentWidget } from '@jupyterlab/docregistry';
import { IMainMenu } from '@jupyterlab/mainmenu';
import {
ILatexTypesetter,
IMarkdownParser,
IRenderMime,
IRenderMimeRegistry,
RenderMimeRegistry,
standardRendererFactories,
} from '@jupyterlab/rendermime';
import { ISettingRegistry } from '@jupyterlab/settingregistry';
import { ITranslator } from '@jupyterlab/translation';
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
import {
NotebookApp,
@ -64,6 +74,11 @@ const STRIP_IPYNB = /\.ipynb$/;
* The command IDs used by the application plugin.
*/
namespace CommandIDs {
/**
* Handle local links
*/
export const handleLink = 'application:handle-local-link';
/**
* Toggle Top Bar visibility
*/
@ -295,6 +310,74 @@ const paths: JupyterFrontEndPlugin<JupyterFrontEnd.IPaths> = {
},
};
/**
* A plugin providing a rendermime registry.
*/
const rendermime: JupyterFrontEndPlugin<IRenderMimeRegistry> = {
id: '@jupyter-notebook/application-extension:rendermime',
autoStart: true,
provides: IRenderMimeRegistry,
description: 'Provides the render mime registry.',
optional: [
IDocumentManager,
ILatexTypesetter,
ISanitizer,
IMarkdownParser,
ITranslator,
],
activate: (
app: JupyterFrontEnd,
docManager: IDocumentManager | null,
latexTypesetter: ILatexTypesetter | null,
sanitizer: IRenderMime.ISanitizer | null,
markdownParser: IMarkdownParser | null,
translator: ITranslator | null
) => {
const trans = (translator ?? nullTranslator).load('jupyterlab');
if (docManager) {
app.commands.addCommand(CommandIDs.handleLink, {
label: trans.__('Handle Local Link'),
execute: (args) => {
const path = args['path'] as string | undefined | null;
if (path === undefined || path === null) {
return;
}
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');
});
},
});
}
return new RenderMimeRegistry({
initialFactories: standardRendererFactories,
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,
});
},
},
latexTypesetter: latexTypesetter ?? undefined,
markdownParser: markdownParser ?? undefined,
translator: translator ?? undefined,
sanitizer: sanitizer ?? undefined,
});
},
};
/**
* The default Jupyter Notebook application shell.
*/
@ -919,6 +1002,7 @@ const plugins: JupyterFrontEndPlugin<any>[] = [
opener,
pages,
paths,
rendermime,
shell,
sidePanelVisibility,
status,

@ -0,0 +1,56 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import path from 'path';
import { expect } from '@playwright/test';
import { test } from './fixtures';
const NOTEBOOK = 'local_links.ipynb';
const SUBFOLDER = 'test';
test.describe('Local Links', () => {
test.beforeEach(async ({ page, tmpPath }) => {
await page.contents.uploadFile(
path.resolve(__dirname, `./notebooks/${NOTEBOOK}`),
`${tmpPath}/${NOTEBOOK}`
);
});
test('Open the current directory', async ({ page, tmpPath }) => {
await page.goto(`notebooks/${tmpPath}/${NOTEBOOK}`);
const [current] = await Promise.all([
page.waitForEvent('popup'),
page.getByText('Current Directory').last().click(),
]);
await current.waitForLoadState();
await current.waitForSelector('.jp-DirListing-content');
// Check that the link opened in a new tab
expect(current.url()).toContain(`tree/${tmpPath}`);
await current.close();
});
test('Open a folder', async ({ page, tmpPath }) => {
// Create a test folder
await page.contents.createDirectory(`${tmpPath}/${SUBFOLDER}`);
await page.goto(`notebooks/${tmpPath}/${NOTEBOOK}`);
const [folder] = await Promise.all([
page.waitForEvent('popup'),
page.getByText('Open Test Folder').last().click(),
]);
await folder.waitForLoadState();
await folder.waitForSelector('.jp-DirListing-content');
await folder.close();
// Check that the link opened in a new tab
expect(folder.url()).toContain(`tree/${tmpPath}/${SUBFOLDER}`);
});
});

@ -0,0 +1,49 @@
{
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"# Handle Local Links"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"[Current Directory](./)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"[Open Test Folder](./test)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.3"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

@ -2029,7 +2029,6 @@ __metadata:
"@jupyterlab/metadataform-extension": ^4.0.0
"@jupyterlab/notebook-extension": ^4.0.0
"@jupyterlab/pdf-extension": ^4.0.0
"@jupyterlab/rendermime-extension": ^4.0.0
"@jupyterlab/running-extension": ^4.0.0
"@jupyterlab/settingeditor": ^4.0.0
"@jupyterlab/settingeditor-extension": ^4.0.0
@ -2075,6 +2074,7 @@ __metadata:
"@jupyterlab/docmanager": ^4.0.0
"@jupyterlab/docregistry": ^4.0.0
"@jupyterlab/mainmenu": ^4.0.0
"@jupyterlab/rendermime": ^4.0.0
"@jupyterlab/settingregistry": ^4.0.0
"@jupyterlab/translation": ^4.0.0
"@lumino/coreutils": ^2.1.1
@ -3613,19 +3613,6 @@ __metadata:
languageName: node
linkType: hard
"@jupyterlab/rendermime-extension@npm:^4.0.0":
version: 4.0.0
resolution: "@jupyterlab/rendermime-extension@npm:4.0.0"
dependencies:
"@jupyterlab/application": ^4.0.0
"@jupyterlab/apputils": ^4.0.0
"@jupyterlab/docmanager": ^4.0.0
"@jupyterlab/rendermime": ^4.0.0
"@jupyterlab/translation": ^4.0.0
checksum: 821ca4d0f098430780214d2a8a53fbdc53d60db1f839509c94381a4a4787a8fa7dc218d5e771c14b290e4f3a83c50d8c72d9295b126256af74b41acf8b67f85a
languageName: node
linkType: hard
"@jupyterlab/rendermime-interfaces@npm:^3.8.0":
version: 3.8.0
resolution: "@jupyterlab/rendermime-interfaces@npm:3.8.0"

Loading…
Cancel
Save