Add Edit page

This commit is contained in:
Jeremy Tuloup 2020-12-09 16:31:21 +01:00
parent dfa1cb2461
commit fdc55393cb
11 changed files with 225 additions and 98 deletions

View File

@ -129,6 +129,15 @@ async function main() {
].includes(id)
)
]);
} else if (page === 'edit') {
mods = mods.concat([
require('@jupyterlab/fileeditor-extension').default.filter(({ id }) =>
['@jupyterlab/fileeditor-extension:plugin'].includes(id)
),
require('@jupyterlab-classic/tree-extension').default.filter(({ id }) =>
['@jupyterlab-classic/tree-extension:factory'].includes(id)
)
]);
}
const extension_data = JSON.parse(

View File

@ -20,6 +20,7 @@
"@jupyterlab/codemirror-extension": "^3.0.0-rc.12",
"@jupyterlab/completer-extension": "^3.0.0-rc.12",
"@jupyterlab/docmanager-extension": "^3.0.0-rc.12",
"@jupyterlab/fileeditor-extension": "^3.0.0-rc.12",
"@jupyterlab/mainmenu-extension": "^3.0.0-rc.12",
"@jupyterlab/mathjax2-extension": "^3.0.0-rc.12",
"@jupyterlab/notebook-extension": "^3.0.0-rc.12",

View File

@ -10,6 +10,7 @@
@import url('~@jupyterlab/codemirror-extension/style/index.css');
@import url('~@jupyterlab/docmanager-extension/style/index.css');
@import url('~@jupyterlab/fileeditor-extension/style/index.css');
@import url('~@jupyterlab/mainmenu-extension/style/index.css');
@import url('~@jupyterlab/notebook-extension/style/index.css');
@import url('~@jupyterlab/rendermime-extension/style/index.css');

View File

@ -87,6 +87,19 @@ class ClassicTreeHandler(ClassicPageConfigMixin, ExtensionHandlerJinjaMixin, Ext
)
class ClassicFileHandler(ClassicPageConfigMixin, ExtensionHandlerJinjaMixin, ExtensionHandlerMixin, JupyterHandler):
@web.authenticated
def get(self, path=None):
page_config = self.get_page_config()
return self.write(
self.render_template(
"edit.html",
base_url=self.base_url,
token=self.settings["token"],
page_config=page_config,
)
)
class ClassicNotebookHandler(ClassicPageConfigMixin, ExtensionHandlerJinjaMixin, ExtensionHandlerMixin, JupyterHandler):
@web.authenticated
def get(self, path=None):
@ -118,6 +131,7 @@ class ClassicApp(NBClassicConfigShimMixin, LabServerApp):
super().initialize_handlers()
self.handlers.append(("/classic/tree(.*)", ClassicTreeHandler))
self.handlers.append(("/classic/notebooks(.*)", ClassicNotebookHandler))
self.handlers.append(("/classic/edit(.*)", ClassicFileHandler))
def initialize_templates(self):
super().initialize_templates()

View File

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{page_config['appName'] | e}} - Edit</title>
</head>
<body>
{# Copy so we do not modify the page_config with updates. #}
{% set page_config_full = page_config.copy() %}
{# Set a dummy variable - we just want the side effect of the update. #}
{% set _ = page_config_full.update(baseUrl=base_url, wsUrl=ws_url) %}
{# Sentinel value to say that we are on the tree page #}
{% set _ = page_config_full.update(classicPage='edit') %}
<script id="jupyter-config-data" type="application/json">
{{ page_config_full | tojson }}
</script>
<script src="{{page_config['fullStaticUrl'] | e}}/bundle.js" main="index"></script>
<script type="text/javascript">
/* Remove token from URL. */
(function () {
var parsedUrl = new URL(window.location.href);
if (parsedUrl.searchParams.get('token')) {
parsedUrl.searchParams.delete('token');
window.history.replaceState({ }, '', parsedUrl.href);
}
})();
</script>
</body>
</html>

View File

@ -43,6 +43,7 @@
"@jupyterlab/codeeditor": "^3.0.0-rc.12",
"@jupyterlab/codemirror": "^3.0.0-rc.12",
"@jupyterlab/coreutils": "^5.0.0-rc.12",
"@jupyterlab/docmanager": "^3.0.0-rc.12",
"@jupyterlab/docregistry": "^3.0.0-rc.12",
"@jupyterlab/mainmenu": "^3.0.0-rc.12",
"@jupyterlab/settingregistry": "^3.0.0-rc.12",

View File

@ -15,7 +15,11 @@ import {
ICommandPalette
} from '@jupyterlab/apputils';
import { PageConfig } from '@jupyterlab/coreutils';
import { PageConfig, PathExt } from '@jupyterlab/coreutils';
import { IDocumentManager, renameDialog } from '@jupyterlab/docmanager';
import { DocumentWidget } from '@jupyterlab/docregistry';
import { IMainMenu } from '@jupyterlab/mainmenu';
@ -31,6 +35,16 @@ import { jupyterIcon } from '@jupyterlab-classic/ui-components';
import { Widget } from '@lumino/widgets';
/**
* The default notebook factory.
*/
const NOTEBOOK_FACTORY = 'Notebook';
/**
* The editor factory.
*/
const EDITOR_FACTORY = 'Editor';
/**
* The command IDs used by the application plugin.
*/
@ -81,6 +95,55 @@ const logo: JupyterFrontEndPlugin<void> = {
}
};
/**
* A plugin to open document in the main area.
*/
const opener: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab-classic/application-extension:opener',
autoStart: true,
requires: [IRouter, IDocumentManager],
activate: (
app: JupyterFrontEnd,
router: IRouter,
docManager: IDocumentManager
): void => {
const { commands } = app;
const treePattern = new RegExp('/(notebooks|edit)/(.*)');
const command = 'router:tree';
commands.addCommand(command, {
execute: (args: any) => {
const parsed = args as IRouter.ILocation;
const matches = parsed.path.match(treePattern);
if (!matches) {
return;
}
const [, , file] = matches;
if (!file) {
return;
}
const ext = PathExt.extname(file);
app.restored.then(() => {
// TODO: get factory from file type instead?
if (ext === '.ipynb') {
docManager.open(file, NOTEBOOK_FACTORY, undefined, {
ref: '_noref'
});
} else {
docManager.open(file, EDITOR_FACTORY, undefined, {
ref: '_noref'
});
}
});
}
});
router.register({ command, pattern: treePattern });
}
};
/**
* A plugin to dispose the Tabs menu
*/
@ -220,6 +283,53 @@ const spacer: JupyterFrontEndPlugin<void> = {
}
};
/**
* A plugin to display and rename the title of a file
*/
const title: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab-classic/application-extension:title',
autoStart: true,
requires: [IClassicShell],
optional: [IDocumentManager, IRouter],
activate: (
app: JupyterFrontEnd,
shell: IClassicShell,
docManager: IDocumentManager | null,
router: IRouter | null
) => {
// TODO: this signal might not be needed if we assume there is always only
// one notebook in the main area
const widget = new Widget();
widget.id = 'jp-title';
app.shell.add(widget, 'top', { rank: 10 });
shell.currentChanged.connect(async () => {
const current = shell.currentWidget;
if (!(current instanceof DocumentWidget)) {
return;
}
const h = document.createElement('h1');
h.textContent = current.title.label;
widget.node.appendChild(h);
widget.node.style.marginLeft = '10px';
if (docManager) {
widget.node.onclick = async () => {
const result = await renameDialog(docManager, current.context.path);
if (result) {
h.textContent = result.path;
if (router) {
// TODO: better handle this
router.navigate(`/classic/notebooks/${result.path}`, {
skipRouting: true
});
}
}
};
}
});
}
};
/**
* Plugin to toggle the top header visibility.
*/
@ -328,12 +438,14 @@ const zen: JupyterFrontEndPlugin<void> = {
const plugins: JupyterFrontEndPlugin<any>[] = [
logo,
noTabsMenu,
opener,
pages,
paths,
router,
sessionDialogs,
shell,
spacer,
title,
topVisibility,
translator,
zen

View File

@ -8,3 +8,7 @@
flex-grow: 1;
flex-shrink: 1;
}
.jp-Document {
height: 100%;
}

View File

@ -6,7 +6,7 @@ import {
JupyterFrontEndPlugin
} from '@jupyterlab/application';
import { PageConfig } from '@jupyterlab/coreutils';
import { PageConfig, PathExt } from '@jupyterlab/coreutils';
import { IDocumentManager } from '@jupyterlab/docmanager';
@ -39,7 +39,9 @@ const opener: JupyterFrontEndPlugin<void> = {
docOpen.call(docManager, path, widgetName, kernel, options);
return;
}
window.open(`${baseUrl}classic/notebooks/${path}`);
const ext = PathExt.extname(path);
const route = ext === '.ipynb' ? 'notebooks' : 'edit';
window.open(`${baseUrl}classic/${route}/${path}`);
return undefined;
};
}

View File

@ -2,7 +2,6 @@
// Distributed under the terms of the Modified BSD License.
import {
IRouter,
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';
@ -11,7 +10,7 @@ import { ISessionContext, DOMUtils } from '@jupyterlab/apputils';
import { PageConfig, Text, Time } from '@jupyterlab/coreutils';
import { IDocumentManager, renameDialog } from '@jupyterlab/docmanager';
import { IDocumentManager } from '@jupyterlab/docmanager';
import { NotebookPanel } from '@jupyterlab/notebook';
@ -23,11 +22,6 @@ import {
import { Widget } from '@lumino/widgets';
/**
* The default notebook factory.
*/
const NOTEBOOK_FACTORY = 'Notebook';
/**
* The class for kernel status errors.
*/
@ -240,100 +234,13 @@ const shell: JupyterFrontEndPlugin<IClassicShell> = {
provides: IClassicShell
};
/**
* A plugin to display the title of the notebook
*/
const title: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab-classic/application-extension:title',
autoStart: true,
requires: [IClassicShell],
optional: [IDocumentManager, IRouter],
activate: (
app: JupyterFrontEnd,
shell: IClassicShell,
docManager: IDocumentManager | null,
router: IRouter | null
) => {
// TODO: this signal might not be needed if we assume there is always only
// one notebook in the main area
const widget = new Widget();
widget.id = 'jp-title';
app.shell.add(widget, 'top', { rank: 10 });
shell.currentChanged.connect(async () => {
const current = shell.currentWidget;
if (!(current instanceof NotebookPanel)) {
return;
}
const h = document.createElement('h1');
h.textContent = current.title.label;
widget.node.appendChild(h);
widget.node.style.marginLeft = '10px';
if (docManager) {
widget.node.onclick = async () => {
const result = await renameDialog(
docManager,
current.sessionContext.path
);
if (result) {
h.textContent = result.path;
if (router) {
// TODO: better handle this
router.navigate(`/classic/notebooks/${result.path}`, {
skipRouting: true
});
}
}
};
}
});
}
};
/**
* The default tree route resolver plugin.
*/
const tree: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab-classic/application-extension:tree-resolver',
autoStart: true,
requires: [IRouter, IDocumentManager],
activate: (
app: JupyterFrontEnd,
router: IRouter,
docManager: IDocumentManager
): void => {
const { commands } = app;
const treePattern = new RegExp('/notebooks/(.*)');
const command = 'router:tree';
commands.addCommand(command, {
execute: (args: any) => {
const parsed = args as IRouter.ILocation;
const matches = parsed.path.match(treePattern);
if (!matches) {
return;
}
const [, path] = matches;
app.restored.then(() => {
docManager.open(path, NOTEBOOK_FACTORY, undefined, { ref: '_noref' });
});
}
});
router.register({ command, pattern: treePattern });
}
};
/**
* Export the plugins as default.
*/
const plugins: JupyterFrontEndPlugin<any>[] = [
checkpoints,
kernelLogo,
kernelStatus,
title,
tree
kernelStatus
];
export default plugins;

View File

@ -1758,6 +1758,30 @@
"@lumino/widgets" "^1.16.1"
react "^17.0.1"
"@jupyterlab/fileeditor-extension@^3.0.0-rc.12":
version "3.0.0-rc.13"
resolved "https://registry.yarnpkg.com/@jupyterlab/fileeditor-extension/-/fileeditor-extension-3.0.0-rc.13.tgz#faa580b0883da4548bbb0b993e2b9db4d1473750"
integrity sha512-YlvZA6R+Mqe+oAeMgfPZwlqCbv8XUNLVVIOz0YNvF8qBhsR8WjvMBYqeUUkPm7cr7kHlnZ+EvWmHwEYl01fqLQ==
dependencies:
"@jupyterlab/application" "^3.0.0-rc.13"
"@jupyterlab/apputils" "^3.0.0-rc.13"
"@jupyterlab/codeeditor" "^3.0.0-rc.13"
"@jupyterlab/codemirror" "^3.0.0-rc.13"
"@jupyterlab/console" "^3.0.0-rc.13"
"@jupyterlab/coreutils" "^5.0.0-rc.13"
"@jupyterlab/docregistry" "^3.0.0-rc.13"
"@jupyterlab/filebrowser" "^3.0.0-rc.13"
"@jupyterlab/fileeditor" "^3.0.0-rc.13"
"@jupyterlab/launcher" "^3.0.0-rc.13"
"@jupyterlab/mainmenu" "^3.0.0-rc.13"
"@jupyterlab/settingregistry" "^3.0.0-rc.13"
"@jupyterlab/statusbar" "^3.0.0-rc.13"
"@jupyterlab/translation" "^3.0.0-rc.13"
"@jupyterlab/ui-components" "^3.0.0-rc.13"
"@lumino/commands" "^1.12.0"
"@lumino/coreutils" "^1.5.3"
"@lumino/widgets" "^1.16.1"
"@jupyterlab/fileeditor@^3.0.0-rc.12":
version "3.0.0-rc.12"
resolved "https://registry.yarnpkg.com/@jupyterlab/fileeditor/-/fileeditor-3.0.0-rc.12.tgz#e95711adfd83f4dcb62d6bf253de386f4823b086"
@ -1806,6 +1830,22 @@
"@lumino/widgets" "^1.16.1"
react "^17.0.1"
"@jupyterlab/launcher@^3.0.0-rc.13":
version "3.0.0-rc.13"
resolved "https://registry.yarnpkg.com/@jupyterlab/launcher/-/launcher-3.0.0-rc.13.tgz#f128c1bbb0d23b44ed38a9ff08c4f7f6e8b4b2fd"
integrity sha512-Rh0tELQhHcxEUtsDPaNLA2GLOBFW9U5kXqrGXs8imLyDoxxfgwjugcfab79IltDWX6c6brTHFu6Uei9zaDwdmQ==
dependencies:
"@jupyterlab/apputils" "^3.0.0-rc.13"
"@jupyterlab/translation" "^3.0.0-rc.13"
"@jupyterlab/ui-components" "^3.0.0-rc.13"
"@lumino/algorithm" "^1.3.3"
"@lumino/commands" "^1.12.0"
"@lumino/coreutils" "^1.5.3"
"@lumino/disposable" "^1.4.3"
"@lumino/properties" "^1.2.3"
"@lumino/widgets" "^1.16.1"
react "^17.0.1"
"@jupyterlab/logconsole@^3.0.0-rc.12":
version "3.0.0-rc.12"
resolved "https://registry.yarnpkg.com/@jupyterlab/logconsole/-/logconsole-3.0.0-rc.12.tgz#cb3b9e48577542bdeeb4221c17218b59909ce5cd"