diff --git a/packages/notebook-extension/schema/close-tab.json b/packages/notebook-extension/schema/close-tab.json new file mode 100644 index 0000000000..b57268a3b3 --- /dev/null +++ b/packages/notebook-extension/schema/close-tab.json @@ -0,0 +1,16 @@ +{ + "jupyter.lab.setting-icon": "notebook-ui-components:jupyter", + "jupyter.lab.setting-icon-label": "Jupyter Notebook Close Tab", + "title": "Jupyter Notebook Close Tab", + "description": "Jupyter Notebook Close Tab settings", + "properties": { + "confirmClosingNotebook": { + "type": "boolean", + "title": "Confirm Closing Notebook", + "description": "Whether to show a confirmation dialog when closing and shutting down a notebook", + "default": true + } + }, + "additionalProperties": false, + "type": "object" +} diff --git a/packages/notebook-extension/src/index.ts b/packages/notebook-extension/src/index.ts index e0fafbb08f..4c80fb621d 100644 --- a/packages/notebook-extension/src/index.ts +++ b/packages/notebook-extension/src/index.ts @@ -11,6 +11,8 @@ import { DOMUtils, IToolbarWidgetRegistry, ICommandPalette, + Dialog, + showDialog, } from '@jupyterlab/apputils'; import { Cell, CodeCell } from '@jupyterlab/cells'; @@ -231,21 +233,46 @@ const closeTab: JupyterFrontEndPlugin = { 'Add a command to close the browser tab when clicking on "Close and Shut Down".', autoStart: true, requires: [IMainMenu], - optional: [ITranslator], + optional: [ITranslator, ISettingRegistry], activate: ( app: JupyterFrontEnd, menu: IMainMenu, - translator: ITranslator | null + translator: ITranslator | null, + settingRegistry: ISettingRegistry | null ) => { const { commands } = app; translator = translator ?? nullTranslator; const trans = translator.load('notebook'); + let confirmClosing = true; // Default to showing confirmation + const id = 'notebook:close-and-halt'; commands.addCommand(id, { - label: trans.__('Close and Shut Down Notebook'), + label: () => { + // Add ellipsis when confirmation is enabled + return confirmClosing + ? trans.__('Close and Shut Down Notebook…') + : trans.__('Close and Shut Down Notebook'); + }, execute: async () => { - // Shut the kernel down, without confirmation + if (confirmClosing) { + const result = await showDialog({ + title: trans.__('Shut down notebook?'), + body: trans.__( + 'The notebook kernel will be shut down. Any unsaved changes will be lost.' + ), + buttons: [ + Dialog.cancelButton({ label: trans.__('Cancel') }), + Dialog.warnButton({ label: trans.__('Shut Down') }), + ], + }); + + if (!result.button.accept) { + return; + } + } + + // Shut the kernel down await commands.execute('notebook:shutdown-kernel', { activate: false }); window.close(); }, @@ -256,6 +283,26 @@ const closeTab: JupyterFrontEndPlugin = { // shut down action for the notebook rank: 0, }); + + // Load settings + if (settingRegistry) { + const loadSettings = settingRegistry.load(closeTab.id); + const updateSettings = (settings: ISettingRegistry.ISettings): void => { + confirmClosing = settings.get('confirmClosingNotebook') + .composite as boolean; + }; + + Promise.all([loadSettings, app.restored]) + .then(([settings]) => { + updateSettings(settings); + settings.changed.connect(updateSettings); + }) + .catch((reason: Error) => { + console.error( + `Failed to load settings for ${closeTab.id}: ${reason.message}` + ); + }); + } }, }; diff --git a/tsconfigbase.json b/tsconfigbase.json index b2b9304791..b9d4ac924a 100644 --- a/tsconfigbase.json +++ b/tsconfigbase.json @@ -13,6 +13,7 @@ "noUnusedLocals": true, "preserveWatchOutput": true, "resolveJsonModule": true, + "skipLibCheck": true, "strict": true, "strictNullChecks": true, "target": "ES2018",