mirror of
https://github.com/jupyter/notebook.git
synced 2024-12-15 04:00:34 +08:00
Add Edit page
This commit is contained in:
parent
dfa1cb2461
commit
fdc55393cb
@ -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(
|
||||
|
@ -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",
|
||||
|
@ -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');
|
||||
|
@ -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()
|
||||
|
36
jupyterlab_classic/templates/edit.html
Normal file
36
jupyterlab_classic/templates/edit.html
Normal 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>
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -8,3 +8,7 @@
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
.jp-Document {
|
||||
height: 100%;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
|
40
yarn.lock
40
yarn.lock
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user