Bläddra i källkod

Add "go-up" navigation support in filebrowser, fix other shortcuts behaviour (#6859)

* Add backspace navigation support in filebrowser

* Rework the 'go-up' filebrowser action into a command

* Add temporary workaround for renaming action

* Fix rebase leftover

* Fix #10249 and fix backspace implementation too

* Lint

* Update tests
Michał Krassowski 4 år sedan
förälder
incheckning
2dd172de23

+ 5 - 0
packages/filebrowser-extension/schema/browser.json

@@ -13,6 +13,11 @@
       "command": "filebrowser:toggle-main",
       "keys": ["Accel Shift F"],
       "selector": "body"
+    },
+    {
+      "command": "filebrowser:go-up",
+      "keys": ["Backspace"],
+      "selector": ".jp-DirListing-content .jp-DirListing-itemText"
     }
   ],
   "properties": {

+ 30 - 2
packages/filebrowser-extension/src/index.ts

@@ -98,6 +98,8 @@ namespace CommandIDs {
 
   export const goToPath = 'filebrowser:go-to-path';
 
+  export const goUp = 'filebrowser:go-up';
+
   export const openPath = 'filebrowser:open-path';
 
   export const open = 'filebrowser:open';
@@ -793,6 +795,30 @@ function addCommands(
     }
   });
 
+  commands.addCommand(CommandIDs.goUp, {
+    label: 'go up',
+    execute: async () => {
+      const browserForPath = Private.getBrowserForPath('', factory);
+      if (!browserForPath) {
+        return;
+      }
+      const { model } = browserForPath;
+
+      await model.restored;
+      if (model.path === model.rootPath) {
+        return;
+      }
+      try {
+        await model.cd('..');
+      } catch (reason) {
+        console.warn(
+          `${CommandIDs.goUp} failed to go to parent directory of ${model.path}`,
+          reason
+        );
+      }
+    }
+  });
+
   commands.addCommand(CommandIDs.openPath, {
     label: args =>
       args.path ? trans.__('Open %1', args.path) : trans.__('Open from Path…'),
@@ -1081,8 +1107,10 @@ function addCommands(
     });
   }
 
-  // matches the filebrowser itself
-  const selectorBrowser = '.jp-FileBrowser-listing';
+  // matches the text in the filebrowser; relies on an implementation detail
+  // being the text of the listing element being substituted with input
+  // area to deactivate shortcuts when the file name is being edited.
+  const selectorBrowser = '.jp-DirListing-content .jp-DirListing-itemText';
   // matches anywhere on filebrowser
   const selectorContent = '.jp-DirListing-content';
   // matches all filebrowser items

+ 0 - 3
packages/filebrowser/src/browser.ts

@@ -138,9 +138,6 @@ export class FileBrowser extends Widget {
     this.layout.addWidget(this.crumbs);
     this.layout.addWidget(this.listing);
 
-    // We need to make the FileBrowser focusable so that it receives keyboard events
-    this.node.tabIndex = 0;
-
     if (options.restore !== false) {
       void model.restore(this.id);
     }

+ 15 - 0
packages/filebrowser/src/listing.ts

@@ -814,6 +814,12 @@ export class DirListing extends Widget {
       );
       if (this.selection[item.path]) {
         node.classList.add(SELECTED_CLASS);
+
+        // focus on text to make shortcuts works
+        const text = DOMUtils.findElement(node, ITEM_TEXT_CLASS);
+        if (text) {
+          text.focus();
+        }
         if (this._isCut && this._model.path === this._prevPath) {
           node.classList.add(CUT_CLASS);
         }
@@ -1064,6 +1070,9 @@ export class DirListing extends Widget {
     // Not all browsers support .key, but it discharges us from reconstructing
     // characters from key codes.
     if (!this._inRename && event.key !== undefined && event.key.length === 1) {
+      if (event.ctrlKey || event.shiftKey || event.altKey || event.metaKey) {
+        return;
+      }
       this._searchPrefix += event.key;
 
       clearTimeout(this._searchPrefixTimer);
@@ -1949,6 +1958,12 @@ export namespace DirListing {
       node.appendChild(text);
       node.appendChild(modified);
 
+      // Make the text note focusable so that it receives keyboard events;
+      // text node was specifically chosen to receive shortcuts because
+      // text element gets substituted with input area during file name edits
+      // which conveniently deactivate irrelevant shortcuts.
+      text.tabIndex = 0;
+
       if (hiddenColumns?.has?.('last_modified')) {
         modified.classList.add(MODIFIED_COLUMN_HIDDEN);
       } else {

+ 9 - 3
packages/filebrowser/src/model.ts

@@ -76,10 +76,9 @@ export class FileBrowserModel implements IDisposable {
     this.translator = options.translator || nullTranslator;
     this._trans = this.translator.load('jupyterlab');
     this._driveName = options.driveName || '';
-    const rootPath = this._driveName ? this._driveName + ':' : '';
     this._model = {
-      path: rootPath,
-      name: PathExt.basename(rootPath),
+      path: this.rootPath,
+      name: PathExt.basename(this.rootPath),
       type: 'directory',
       content: undefined,
       writable: false,
@@ -157,6 +156,13 @@ export class FileBrowserModel implements IDisposable {
     return this._model ? this._model.path : '';
   }
 
+  /**
+   * Get the root path
+   */
+  get rootPath(): string {
+    return this._driveName ? this._driveName + ':' : '';
+  }
+
   /**
    * A signal emitted when the path changes.
    */

+ 8 - 0
packages/filebrowser/test/model.spec.ts

@@ -195,6 +195,14 @@ describe('filebrowser/model', () => {
       });
     });
 
+    describe('#rootPath', () => {
+      it('should be and remain the root path of the model', async () => {
+        expect(model.rootPath).toBe('');
+        await model.cd('src/');
+        expect(model.rootPath).toBe('');
+      });
+    });
+
     describe('#items()', () => {
       it('should get an iterator of items in the current path', () => {
         const items = model.items();