diff --git a/packages/application-extension/schema/menus.json b/packages/application-extension/schema/menus.json index bf2bdec29d..2ad34edc14 100644 --- a/packages/application-extension/schema/menus.json +++ b/packages/application-extension/schema/menus.json @@ -14,6 +14,14 @@ "command": "notebook:trust", "rank": 20 }, + { + "type": "separator", + "rank": 30 + }, + { + "command": "filemenu:close-and-cleanup", + "rank": 40 + }, { "command": "application:close", "disabled": true diff --git a/packages/notebook-extension/src/index.ts b/packages/notebook-extension/src/index.ts index ef6612e1d7..52eac92d5c 100644 --- a/packages/notebook-extension/src/index.ts +++ b/packages/notebook-extension/src/index.ts @@ -18,6 +18,8 @@ import { Text, Time } from '@jupyterlab/coreutils'; import { IDocumentManager } from '@jupyterlab/docmanager'; +import { IMainMenu } from '@jupyterlab/mainmenu'; + import { NotebookPanel, INotebookTracker, @@ -26,7 +28,7 @@ import { import { ISettingRegistry } from '@jupyterlab/settingregistry'; -import { ITranslator } from '@jupyterlab/translation'; +import { ITranslator, nullTranslator } from '@jupyterlab/translation'; import { INotebookShell } from '@jupyter-notebook/application'; @@ -126,6 +128,40 @@ const checkpoints: JupyterFrontEndPlugin = { }, }; +/** + * Add a command to close the browser tab when clicking on "Close and Shut Down" + */ +const closeTab: JupyterFrontEndPlugin = { + id: '@jupyter-notebook/notebook-extension:close-tab', + autoStart: true, + requires: [IMainMenu], + optional: [ITranslator], + activate: ( + app: JupyterFrontEnd, + menu: IMainMenu, + translator: ITranslator | null + ) => { + const { commands } = app; + translator = translator ?? nullTranslator; + const trans = translator.load('notebook'); + + const id = 'notebook:close-and-halt'; + commands.addCommand(id, { + label: trans.__('Close and Shut Down Notebook'), + execute: async () => { + await commands.execute('notebook:close-and-shutdown'); + window.close(); + }, + }); + menu.fileMenu.closeAndCleaners.add({ + id, + // use a small rank to it takes precedence over the default + // shut down action for the notebook + rank: 0, + }); + }, +}; + /** * The kernel logo plugin. */ @@ -402,6 +438,7 @@ const trusted: JupyterFrontEndPlugin = { */ const plugins: JupyterFrontEndPlugin[] = [ checkpoints, + closeTab, kernelLogo, kernelStatus, scrollOutput, diff --git a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-chromium-linux.png b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-chromium-linux.png index 65298693b9..99f7a68a5f 100644 Binary files a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-chromium-linux.png and b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-chromium-linux.png differ diff --git a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-firefox-linux.png b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-firefox-linux.png index 245b765140..5501ba9a8f 100644 Binary files a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-firefox-linux.png and b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-firefox-linux.png differ diff --git a/ui-tests/test/notebook.spec.ts b/ui-tests/test/notebook.spec.ts index d83691ee8a..ac28d058fe 100644 --- a/ui-tests/test/notebook.spec.ts +++ b/ui-tests/test/notebook.spec.ts @@ -155,4 +155,24 @@ test.describe('Notebook', () => { const imageName = 'notebooktools-right-panel.png'; expect(await panel.screenshot()).toMatchSnapshot(imageName); }); + + test('Clicking on "Close and Shut Down Notebook" should close the browser tab', async ({ + page, + tmpPath, + }) => { + const notebook = 'simple.ipynb'; + await page.contents.uploadFile( + path.resolve(__dirname, `./notebooks/${notebook}`), + `${tmpPath}/${notebook}` + ); + await page.goto(`notebooks/${tmpPath}/${notebook}`); + + const menuPath = 'File>Close and Halt'; + await page.menu.clickMenuItem(menuPath); + + // Press Enter to confirm the dialog + await page.keyboard.press('Enter'); + + expect(page.isClosed()); + }); });