浏览代码

Simple mode rename improvements 2.0 (#10518)

* copy output area prep

* added output copy to clipboard

* changed OutputPanel tabIndex to 0

* rename title handler improvements + package integrity

* Lint

* Lint

Co-authored-by: Cameron Toy <cameron-toy@users.noreply.github.com>
Co-authored-by: Jason Grout <jasongrout@users.noreply.github.com>
Co-authored-by: Jason Grout <jgrout6@bloomberg.net>
Cameron Toy 3 年之前
父节点
当前提交
30eba572b9

+ 0 - 1
packages/application/package.json

@@ -45,7 +45,6 @@
     "@fortawesome/fontawesome-free": "^5.12.0",
     "@jupyterlab/apputils": "^3.1.0-beta.1",
     "@jupyterlab/coreutils": "^5.1.0-beta.1",
-    "@jupyterlab/docmanager": "^3.1.0-beta.1",
     "@jupyterlab/docregistry": "^3.1.0-beta.1",
     "@jupyterlab/rendermime": "^3.1.0-beta.1",
     "@jupyterlab/rendermime-interfaces": "^3.1.0-beta.1",

+ 49 - 82
packages/application/src/shell.ts

@@ -1,7 +1,6 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
-import { isValidFileName } from '@jupyterlab/docmanager';
 import { DocumentRegistry, DocumentWidget } from '@jupyterlab/docregistry';
 import { ITranslator, nullTranslator } from '@jupyterlab/translation';
 import {
@@ -383,14 +382,12 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
     this._hsplitPanel.updated.connect(this._onLayoutModified, this);
 
     // Setup single-document-mode title bar
-    const titleWidgetHandler = (this._titleWidgetHandler = new Private.TitleWidgetHandler(
-      this
-    ));
-    this.add(titleWidgetHandler.titleWidget, 'top', { rank: 100 });
+    const titleHandler = (this._titleHandler = new Private.TitleHandler(this));
+    this.add(titleHandler, 'top', { rank: 100 });
 
     if (this._dockPanel.mode === 'multiple-document') {
       this._topHandler.addWidget(this._menuHandler.panel, 100);
-      titleWidgetHandler.hide();
+      titleHandler.hide();
     } else {
       rootLayout.insertWidget(2, this._menuHandler.panel);
     }
@@ -536,7 +533,7 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
 
       // Adjust menu and title
       (this.layout as BoxLayout).insertWidget(2, this._menuHandler.panel);
-      this._titleWidgetHandler.show();
+      this._titleHandler.show();
       this._updateTitlePanelTitle();
     } else {
       // Cache a reference to every widget currently in the dock panel.
@@ -574,7 +571,7 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
       // Adjust menu and title
       this.add(this._menuHandler.panel, 'top', { rank: 100 });
       // this._topHandler.addWidget(this._menuHandler.panel, 100)
-      this._titleWidgetHandler.hide();
+      this._titleHandler.hide();
     }
 
     // Set the mode data attribute on the applications shell node.
@@ -765,7 +762,6 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
       return;
     }
     this._layoutDebouncer.dispose();
-    this._titleWidgetHandler.dispose();
     super.dispose();
   }
 
@@ -1001,8 +997,7 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
    */
   private _updateTitlePanelTitle() {
     let current = this.currentWidget;
-    const inputElement = this._titleWidgetHandler.titleWidget.node
-      .children[0] as HTMLInputElement;
+    const inputElement = this._titleHandler.inputElement;
     inputElement.value = current ? current.title.label : '';
     inputElement.title = current ? current.title.caption : '';
   }
@@ -1395,7 +1390,7 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
   private _topHandler: Private.PanelHandler;
   private _menuHandler: Private.PanelHandler;
   private _skipLinkWidget: Private.SkipLinkWidget;
-  private _titleWidgetHandler: Private.TitleWidgetHandler;
+  private _titleHandler: Private.TitleHandler;
   private _bottomPanel: Panel;
   private _mainOptionsCache = new Map<Widget, DocumentRegistry.IOpenOptions>();
   private _sideOptionsCache = new Map<Widget, DocumentRegistry.IOpenOptions>();
@@ -1785,21 +1780,37 @@ namespace Private {
     }
   }
 
-  export class TitleWidgetHandler {
+  export class TitleHandler extends Widget {
     /**
-     * Construct a new title widget handler.
+     * Construct a new title handler.
      */
     constructor(shell: ILabShell) {
+      super();
+      const inputElement = document.createElement('input');
+      inputElement.type = 'text';
+      this.node.appendChild(inputElement);
       this._shell = shell;
-      const titleWidget = (this._titleWidget = new Widget());
-      titleWidget.id = 'jp-title-panel-title';
-      const titleInput = document.createElement('input');
-      titleInput.type = 'text';
-      titleInput.addEventListener('keyup', this);
-      titleInput.addEventListener('click', this);
-      titleInput.addEventListener('blur', this);
+      this.id = 'jp-title-panel-title';
+    }
 
-      titleWidget.node.appendChild(titleInput);
+    /**
+     * Handle `after-attach` messages for the widget.
+     */
+    protected onAfterAttach(msg: Message): void {
+      super.onAfterAttach(msg);
+      this.inputElement.addEventListener('keyup', this);
+      this.inputElement.addEventListener('click', this);
+      this.inputElement.addEventListener('blur', this);
+    }
+
+    /**
+     * Handle `before-detach` messages for the widget.
+     */
+    protected onBeforeDetach(msg: Message): void {
+      super.onBeforeDetach(msg);
+      this.inputElement.removeEventListener('keyup', this);
+      this.inputElement.removeEventListener('click', this);
+      this.inputElement.removeEventListener('blur', this);
     }
 
     handleEvent(event: Event): void {
@@ -1822,17 +1833,18 @@ namespace Private {
     private async _evtKeyUp(event: KeyboardEvent): Promise<void> {
       if (event.key == 'Enter') {
         const widget = this._shell.currentWidget;
-        if (widget instanceof DocumentWidget) {
-          const oldName = widget.context.path.split('/').pop()!;
-          const inputElement = this.titleWidget.node
-            .children[0] as HTMLInputElement;
-          const newName = inputElement.value;
-          inputElement.blur();
-          if (newName !== oldName && isValidFileName(newName)) {
-            await widget.context.rename(newName);
-          } else {
-            inputElement.value = oldName;
-          }
+        if (widget == null) {
+          return;
+        }
+        const oldName = widget.title.label;
+        const inputElement = this.inputElement;
+        const newName = inputElement.value;
+        inputElement.blur();
+
+        if (newName !== oldName) {
+          widget.title.label = newName;
+        } else {
+          inputElement.value = oldName;
         }
       }
     }
@@ -1846,15 +1858,7 @@ namespace Private {
         return;
       }
 
-      const currentWidget = this._shell.currentWidget;
-      const inputElement = this.titleWidget.node
-        .children[0] as HTMLInputElement;
-      if (currentWidget == null || !(currentWidget instanceof DocumentWidget)) {
-        inputElement.readOnly = true;
-        return;
-      } else {
-        inputElement.removeAttribute('readOnly');
-      }
+      const inputElement = this.inputElement;
 
       event.preventDefault();
       event.stopPropagation();
@@ -1870,50 +1874,13 @@ namespace Private {
     }
 
     /**
-     * Get the input element managed by the handler.
-     */
-    get titleWidget(): Widget {
-      return this._titleWidget;
-    }
-
-    /**
-     * Dispose of the handler and the resources it holds.
-     */
-    dispose(): void {
-      if (this.isDisposed) {
-        return;
-      }
-      this._isDisposed = true;
-      this._titleWidget.node.removeEventListener('keyup', this);
-      this._titleWidget.node.removeEventListener('click', this);
-      this._titleWidget.node.removeEventListener('blur', this);
-      this._titleWidget.dispose();
-    }
-
-    /**
-     * Hide the title widget.
-     */
-    hide(): void {
-      this._titleWidget.hide();
-    }
-
-    /**
-     * Show the title widget.
-     */
-    show(): void {
-      this._titleWidget.show();
-    }
-
-    /**
-     * Test whether the handler has been disposed.
+     * The input element containing the parent widget's title.
      */
-    get isDisposed(): boolean {
-      return this._isDisposed;
+    get inputElement(): HTMLInputElement {
+      return this.node.children[0] as HTMLInputElement;
     }
 
-    private _titleWidget: Widget;
     private _shell: ILabShell;
-    private _isDisposed: boolean = false;
     private _selected: boolean = false;
   }
 

+ 0 - 1
packages/application/style/index.css

@@ -10,6 +10,5 @@
 @import url('~@jupyterlab/ui-components/style/index.css');
 @import url('~@jupyterlab/apputils/style/index.css');
 @import url('~@jupyterlab/docregistry/style/index.css');
-@import url('~@jupyterlab/docmanager/style/index.css');
 
 @import url('./base.css');

+ 0 - 1
packages/application/style/index.js

@@ -10,6 +10,5 @@ import '@lumino/widgets/style/index.js';
 import '@jupyterlab/ui-components/style/index.js';
 import '@jupyterlab/apputils/style/index.js';
 import '@jupyterlab/docregistry/style/index.js';
-import '@jupyterlab/docmanager/style/index.js';
 
 import './base.css';

+ 0 - 3
packages/application/tsconfig.json

@@ -12,9 +12,6 @@
     {
       "path": "../coreutils"
     },
-    {
-      "path": "../docmanager"
-    },
     {
       "path": "../docregistry"
     },

+ 0 - 6
packages/application/tsconfig.test.json

@@ -8,9 +8,6 @@
     {
       "path": "../coreutils"
     },
-    {
-      "path": "../docmanager"
-    },
     {
       "path": "../docregistry"
     },
@@ -44,9 +41,6 @@
     {
       "path": "../coreutils"
     },
-    {
-      "path": "../docmanager"
-    },
     {
       "path": "../docregistry"
     },

+ 1 - 1
packages/docmanager-extension/style/index.css

@@ -8,5 +8,5 @@
 @import url('~@jupyterlab/apputils/style/index.css');
 @import url('~@jupyterlab/statusbar/style/index.css');
 @import url('~@jupyterlab/docregistry/style/index.css');
-@import url('~@jupyterlab/docmanager/style/index.css');
 @import url('~@jupyterlab/application/style/index.css');
+@import url('~@jupyterlab/docmanager/style/index.css');

+ 1 - 1
packages/docmanager-extension/style/index.js

@@ -8,5 +8,5 @@ import '@lumino/widgets/style/index.js';
 import '@jupyterlab/apputils/style/index.js';
 import '@jupyterlab/statusbar/style/index.js';
 import '@jupyterlab/docregistry/style/index.js';
-import '@jupyterlab/docmanager/style/index.js';
 import '@jupyterlab/application/style/index.js';
+import '@jupyterlab/docmanager/style/index.js';

+ 27 - 1
packages/docregistry/src/default.ts

@@ -11,7 +11,7 @@ import * as models from '@jupyterlab/shared-models';
 import { ITranslator, nullTranslator } from '@jupyterlab/translation';
 import { PartialJSONValue } from '@lumino/coreutils';
 import { ISignal, Signal } from '@lumino/signaling';
-import { Widget } from '@lumino/widgets';
+import { Title, Widget } from '@lumino/widgets';
 import { DocumentRegistry, IDocumentWidget } from './index';
 
 /**
@@ -486,6 +486,9 @@ export class DocumentWidget<
     void this.context.ready.then(() => {
       this._handleDirtyState();
     });
+
+    // listen for changes to the title object
+    this.title.changed.connect(this._onTitleChanged, this);
   }
 
   /**
@@ -495,6 +498,29 @@ export class DocumentWidget<
     /* no-op */
   }
 
+  /**
+   * Handle a title change.
+   */
+  private async _onTitleChanged(_sender: Title<this>) {
+    const validNameExp = /[\/\\:]/;
+    const name = this.title.label;
+    const filename = this.context.path.split('/').pop()!;
+
+    if (name === filename) {
+      return;
+    }
+    if (name.length > 0 && !validNameExp.test(name)) {
+      const oldPath = this.context.path;
+      await this.context.rename(name);
+      if (this.context.path !== oldPath) {
+        // Rename succeeded
+        return;
+      }
+    }
+    // Reset title if name is invalid or rename fails
+    this.title.label = filename;
+  }
+
   /**
    * Handle a path change.
    */

+ 1 - 1
packages/filebrowser-extension/style/index.css

@@ -9,8 +9,8 @@
 @import url('~@jupyterlab/apputils/style/index.css');
 @import url('~@jupyterlab/statusbar/style/index.css');
 @import url('~@jupyterlab/docregistry/style/index.css');
-@import url('~@jupyterlab/docmanager/style/index.css');
 @import url('~@jupyterlab/application/style/index.css');
+@import url('~@jupyterlab/docmanager/style/index.css');
 @import url('~@jupyterlab/filebrowser/style/index.css');
 @import url('~@jupyterlab/launcher/style/index.css');
 

+ 1 - 1
packages/filebrowser-extension/style/index.js

@@ -9,8 +9,8 @@ import '@jupyterlab/ui-components/style/index.js';
 import '@jupyterlab/apputils/style/index.js';
 import '@jupyterlab/statusbar/style/index.js';
 import '@jupyterlab/docregistry/style/index.js';
-import '@jupyterlab/docmanager/style/index.js';
 import '@jupyterlab/application/style/index.js';
+import '@jupyterlab/docmanager/style/index.js';
 import '@jupyterlab/filebrowser/style/index.js';
 import '@jupyterlab/launcher/style/index.js';
 

+ 1 - 1
packages/notebook-extension/style/index.css

@@ -10,8 +10,8 @@
 @import url('~@jupyterlab/codeeditor/style/index.css');
 @import url('~@jupyterlab/statusbar/style/index.css');
 @import url('~@jupyterlab/rendermime/style/index.css');
-@import url('~@jupyterlab/docmanager/style/index.css');
 @import url('~@jupyterlab/application/style/index.css');
+@import url('~@jupyterlab/docmanager/style/index.css');
 @import url('~@jupyterlab/filebrowser/style/index.css');
 @import url('~@jupyterlab/cells/style/index.css');
 @import url('~@jupyterlab/launcher/style/index.css');

+ 1 - 1
packages/notebook-extension/style/index.js

@@ -10,8 +10,8 @@ import '@jupyterlab/apputils/style/index.js';
 import '@jupyterlab/codeeditor/style/index.js';
 import '@jupyterlab/statusbar/style/index.js';
 import '@jupyterlab/rendermime/style/index.js';
-import '@jupyterlab/docmanager/style/index.js';
 import '@jupyterlab/application/style/index.js';
+import '@jupyterlab/docmanager/style/index.js';
 import '@jupyterlab/filebrowser/style/index.js';
 import '@jupyterlab/cells/style/index.js';
 import '@jupyterlab/launcher/style/index.js';

+ 1 - 1
packages/rendermime-extension/style/index.css

@@ -6,5 +6,5 @@
 /* This file was auto-generated by ensurePackage() in @jupyterlab/buildutils */
 @import url('~@jupyterlab/apputils/style/index.css');
 @import url('~@jupyterlab/rendermime/style/index.css');
-@import url('~@jupyterlab/docmanager/style/index.css');
 @import url('~@jupyterlab/application/style/index.css');
+@import url('~@jupyterlab/docmanager/style/index.css');

+ 1 - 1
packages/rendermime-extension/style/index.js

@@ -6,5 +6,5 @@
 /* This file was auto-generated by ensurePackage() in @jupyterlab/buildutils */
 import '@jupyterlab/apputils/style/index.js';
 import '@jupyterlab/rendermime/style/index.js';
-import '@jupyterlab/docmanager/style/index.js';
 import '@jupyterlab/application/style/index.js';
+import '@jupyterlab/docmanager/style/index.js';

+ 1 - 1
packages/toc-extension/style/index.css

@@ -6,8 +6,8 @@
 /* This file was auto-generated by ensurePackage() in @jupyterlab/buildutils */
 @import url('~@jupyterlab/ui-components/style/index.css');
 @import url('~@jupyterlab/rendermime/style/index.css');
-@import url('~@jupyterlab/docmanager/style/index.css');
 @import url('~@jupyterlab/application/style/index.css');
+@import url('~@jupyterlab/docmanager/style/index.css');
 @import url('~@jupyterlab/fileeditor/style/index.css');
 @import url('~@jupyterlab/markdownviewer/style/index.css');
 @import url('~@jupyterlab/notebook/style/index.css');

+ 1 - 1
packages/toc-extension/style/index.js

@@ -6,8 +6,8 @@
 /* This file was auto-generated by ensurePackage() in @jupyterlab/buildutils */
 import '@jupyterlab/ui-components/style/index.js';
 import '@jupyterlab/rendermime/style/index.js';
-import '@jupyterlab/docmanager/style/index.js';
 import '@jupyterlab/application/style/index.js';
+import '@jupyterlab/docmanager/style/index.js';
 import '@jupyterlab/fileeditor/style/index.js';
 import '@jupyterlab/markdownviewer/style/index.js';
 import '@jupyterlab/notebook/style/index.js';