diff --git a/builder/index.js b/builder/index.js
index 9091dfcdb..0bfa6af9c 100644
--- a/builder/index.js
+++ b/builder/index.js
@@ -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(
diff --git a/builder/package.json b/builder/package.json
index 26c48f00c..a06fd39f4 100644
--- a/builder/package.json
+++ b/builder/package.json
@@ -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",
diff --git a/builder/style.css b/builder/style.css
index 41f777daa..cc75c1bbf 100644
--- a/builder/style.css
+++ b/builder/style.css
@@ -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');
diff --git a/jupyterlab_classic/app.py b/jupyterlab_classic/app.py
index da4d5c7f3..d8e06f0cd 100644
--- a/jupyterlab_classic/app.py
+++ b/jupyterlab_classic/app.py
@@ -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()
diff --git a/jupyterlab_classic/templates/edit.html b/jupyterlab_classic/templates/edit.html
new file mode 100644
index 000000000..8f4e0f94e
--- /dev/null
+++ b/jupyterlab_classic/templates/edit.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+ {{page_config['appName'] | e}} - Edit
+
+
+
+ {# 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') %}
+
+
+
+
+
+
+
+
diff --git a/packages/application-extension/package.json b/packages/application-extension/package.json
index fd75796dc..6e1bd1578 100644
--- a/packages/application-extension/package.json
+++ b/packages/application-extension/package.json
@@ -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",
diff --git a/packages/application-extension/src/index.ts b/packages/application-extension/src/index.ts
index d8a1d9128..e82afdb1d 100644
--- a/packages/application-extension/src/index.ts
+++ b/packages/application-extension/src/index.ts
@@ -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 = {
}
};
+/**
+ * A plugin to open document in the main area.
+ */
+const opener: JupyterFrontEndPlugin = {
+ 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 = {
}
};
+/**
+ * A plugin to display and rename the title of a file
+ */
+const title: JupyterFrontEndPlugin = {
+ 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 = {
const plugins: JupyterFrontEndPlugin[] = [
logo,
noTabsMenu,
+ opener,
pages,
paths,
router,
sessionDialogs,
shell,
spacer,
+ title,
topVisibility,
translator,
zen
diff --git a/packages/application-extension/style/base.css b/packages/application-extension/style/base.css
index 70f46ed68..49ae28fcf 100644
--- a/packages/application-extension/style/base.css
+++ b/packages/application-extension/style/base.css
@@ -8,3 +8,7 @@
flex-grow: 1;
flex-shrink: 1;
}
+
+.jp-Document {
+ height: 100%;
+}
diff --git a/packages/docmanager-extension/src/index.ts b/packages/docmanager-extension/src/index.ts
index 656556cae..44d1967d7 100644
--- a/packages/docmanager-extension/src/index.ts
+++ b/packages/docmanager-extension/src/index.ts
@@ -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 = {
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;
};
}
diff --git a/packages/notebook-extension/src/index.ts b/packages/notebook-extension/src/index.ts
index 31600b520..c50fcb873 100644
--- a/packages/notebook-extension/src/index.ts
+++ b/packages/notebook-extension/src/index.ts
@@ -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 = {
provides: IClassicShell
};
-/**
- * A plugin to display the title of the notebook
- */
-const title: JupyterFrontEndPlugin = {
- 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 = {
- 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[] = [
checkpoints,
kernelLogo,
- kernelStatus,
- title,
- tree
+ kernelStatus
];
export default plugins;
diff --git a/yarn.lock b/yarn.lock
index c07523441..4244c057e 100644
--- a/yarn.lock
+++ b/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"