From 1cf313b55551f18c51a0018d05198c25c3d88596 Mon Sep 17 00:00:00 2001 From: Tim Metzler Date: Fri, 1 Sep 2023 17:32:50 +0200 Subject: [PATCH 1/6] Add shortcut manager --- packages/utils/src/index.js | 1 + packages/utils/src/shortcuts.js | 99 ++++++++++++++++++++++++++++++++ packages/utils/webpack.config.js | 2 +- 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 packages/utils/src/shortcuts.js diff --git a/packages/utils/src/index.js b/packages/utils/src/index.js index 7d7d8099..c73aa981 100644 --- a/packages/utils/src/index.js +++ b/packages/utils/src/index.js @@ -1 +1,2 @@ export * as utils from "./utils"; +export * as shortcuts from "./shortcuts"; diff --git a/packages/utils/src/shortcuts.js b/packages/utils/src/shortcuts.js new file mode 100644 index 00000000..e262fe47 --- /dev/null +++ b/packages/utils/src/shortcuts.js @@ -0,0 +1,99 @@ +import Jupyter from "base/js/namespace"; +import { Notebook } from "notebook/js/notebook"; + +/** + * Defines a new method `execute_cell_and_select_below_without_insert()` for the `Notebook` prototype object, + * which executes the currently selected cell and then selects the cell below it. + */ +function add_Notebook_execute_cell_and_select() { + Notebook.prototype.execute_cell_and_select_below_without_insert = + function () { + let indices = this.get_selected_cells_indices(); + let cell_index; + if (indices.length > 1) { + this.execute_cells(indices); + cell_index = Math.max.apply(Math, indices); + } else { + let cell = this.get_selected_cell(); + cell_index = this.find_cell_index(cell); + cell.execute(); + } + + // If we are at the end do not insert a new cell + cell_index = Math.min(cell_index + 1, this.ncells() - 1); + this.select(cell_index); + this.focus_cell(); + }; +} + +/** + * Returns the keyboard manager object for the specified mode. + * @param {string} mode - The keyboard manager mode to get ("command" or "edit"). + * @throws {Error} If the mode is not "command" or "edit". + * @returns {object} The keyboard manager object for the specified mode. + */ +function get_manager(mode) { + if (mode === "command") { + return Jupyter.keyboard_manager.command_shortcuts; + } else if (mode === "edit") { + return Jupyter.keyboard_manager.edit_shortcuts; + } else { + throw new Error("Mode needs to be either 'command' or 'edit'"); + } +} + +/** + * Removes the specified shortcuts from the keyboard manager for the specified mode. + * @param {string} mode - The keyboard manager mode to remove shortcuts from ("command" or "edit"). + * @param {...string} shortcuts - The shortcut keys to remove. + */ +export function remove_shortcuts(mode, ...shortcuts) { + const manager = get_manager(mode); + for (const shortcut of shortcuts) { + try { + manager.remove_shortcut(shortcut); + } catch (e) { + // Shortcut does not exist and can't be removed; + } + } +} + +/** + * Adds a new shortcut to the keyboard manager for the specified mode. + * @param {string} mode - The keyboard manager mode to add the shortcut to ("command" or "edit"). + * @param {string} key - The key combination for the shortcut. + * @param {function} handler - The function to be called when the shortcut is activated. + * @param {string} help - The help text for the shortcut. + * @param {string} [help_index="zz"] - The help index for the shortcut. + */ +export function add_shortcut(mode, key, handler, help, help_index = "zz") { + const manager = get_manager(mode); + manager.add_shortcut(key, { + help: help, + help_index: help_index, + handler: handler, + }); +} + +/** + * Disables the default behavior of adding a new cell after executing a cell by removing the "alt-enter" and "shift-enter" + * shortcuts for both the command and edit modes, and adding new shortcuts that execute the current cell and select + * the cell below it using the `execute_cell_and_select_below_without_insert()` method. + */ +export function disable_add_cell_on_execute() { + add_Notebook_execute_cell_and_select(); + const shortcut_keys = ["alt-enter", "shift-enter"]; + remove_shortcuts("edit", shortcut_keys); + remove_shortcuts("command", shortcut_keys); + + const help = "run cell"; + const handler = function (event) { + Jupyter.notebook.execute_cell_and_select_below_without_insert(); + return false; + }; + + for (const key of shortcut_keys) { + add_shortcut("edit", key, handler, help); + add_shortcut("command", key, handler, help); + } +} diff --git a/packages/utils/webpack.config.js b/packages/utils/webpack.config.js index c49b846e..16438442 100644 --- a/packages/utils/webpack.config.js +++ b/packages/utils/webpack.config.js @@ -8,7 +8,7 @@ module.exports = { filename: "[name].js", libraryTarget: "commonjs-module", }, - externals: {}, + externals: [/^base\/js*/, /^notebook\/js*/], optimization: { minimize: false, }, From 01a6bc49a9b6fbd8ec839e4304a22dbbee18339a Mon Sep 17 00:00:00 2001 From: Tim Metzler Date: Fri, 1 Sep 2023 17:33:06 +0200 Subject: [PATCH 2/6] Use shortcut manager --- .../src/disable-shortcuts.js | 70 +++---------------- 1 file changed, 11 insertions(+), 59 deletions(-) diff --git a/packages/restricted-exam-notebook/src/disable-shortcuts.js b/packages/restricted-exam-notebook/src/disable-shortcuts.js index 3cf07441..7bb49735 100644 --- a/packages/restricted-exam-notebook/src/disable-shortcuts.js +++ b/packages/restricted-exam-notebook/src/disable-shortcuts.js @@ -1,4 +1,4 @@ -import Jupyter from "base/js/namespace"; +import { shortcuts as utils } from "@e2xgrader/utils"; export function disable_shortcuts() { const shortcuts = [ @@ -12,72 +12,24 @@ export function disable_shortcuts() { "y", "m", "r", - "shift-enter", - "alt-enter", "ctrl-shift-f", "ctrl-shift-p", "p", "d,d", ]; - for (let i in shortcuts) { - try { - Jupyter.keyboard_manager.command_shortcuts.remove_shortcut(shortcuts[i]); - Jupyter.keyboard_manager.edit_shortcuts.remove_shortcut(shortcuts[i]); - } catch (e) { - // Shortcut does not exist - } - } + utils.remove_shortcuts("command", shortcuts); + utils.remove_shortcuts("edit", shortcuts); - try { - Jupyter.keyboard_manager.command_shortcuts.remove_shortcut("ctrl-v"); - } catch (e) { - console.log("Command shortcut " + "ctrl-v" + " does not exist."); - } + utils.remove_shortcuts("command", "crtl-v"); - // Bind all execute cell shortcuts to run cell - - Jupyter.keyboard_manager.command_shortcuts.add_shortcut("alt-enter", { - help: "run cell", - help_index: "zz", - handler: function (event) { - IPython.notebook.execute_cell(); - return false; - }, - }); - - Jupyter.keyboard_manager.command_shortcuts.add_shortcut("shift-enter", { - help: "run cell", - help_index: "zz", - handler: function (event) { - IPython.notebook.execute_cell(); - return false; - }, - }); - - Jupyter.keyboard_manager.edit_shortcuts.add_shortcut("alt-enter", { - help: "run cell", - help_index: "zz", - handler: function (event) { - IPython.notebook.execute_cell(); - return false; - }, - }); - - Jupyter.keyboard_manager.edit_shortcuts.add_shortcut("shift-enter", { - help: "run cell", - help_index: "zz", - handler: function (event) { - IPython.notebook.execute_cell(); - return false; - }, - }); - - Jupyter.keyboard_manager.command_shortcuts.add_shortcut("ctrl-v", { - help: "no action", - help_index: "zz", - handler: function (event) { + utils.disable_add_cell_on_execute(); + utils.add_shortcut( + "command", + "ctrl-v", + function (event) { return false; }, - }); + "no action" + ); } From 63a6aebb0a95240830d5122693c2e623980a2198 Mon Sep 17 00:00:00 2001 From: Tim Metzler Date: Fri, 1 Sep 2023 17:33:30 +0200 Subject: [PATCH 3/6] Add dependency for e2xgrader/utils --- packages/notebook-extensions/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/notebook-extensions/package.json b/packages/notebook-extensions/package.json index 07fe15d0..bb200e75 100644 --- a/packages/notebook-extensions/package.json +++ b/packages/notebook-extensions/package.json @@ -15,7 +15,8 @@ "@e2xgrader/restricted-assignment-notebook": "0.1.1", "@e2xgrader/restricted-exam-notebook": "0.1.1", "@e2xgrader/exam-menubar": "0.1.1", - "@e2xgrader/authoring-menubar": "0.1.1" + "@e2xgrader/authoring-menubar": "0.1.1", + "@e2xgrader/utils": "0.1.1" }, "devDependencies": { "@babel/preset-env": "^7.16.11", From eef7438bfaf88e01052de87290d489d5e7a9d260 Mon Sep 17 00:00:00 2001 From: Tim Metzler Date: Fri, 1 Sep 2023 17:33:56 +0200 Subject: [PATCH 4/6] Remove execute shortcut and remove empty cells on save during authoring --- .../src/teacher-extension.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/notebook-extensions/src/teacher-extension.js b/packages/notebook-extensions/src/teacher-extension.js index 5e5f30a7..15f80e64 100644 --- a/packages/notebook-extensions/src/teacher-extension.js +++ b/packages/notebook-extensions/src/teacher-extension.js @@ -3,6 +3,21 @@ import events from "base/js/events"; import { initialize_cell_extension } from "@e2xgrader/cell-extension"; import { CreateAssignmentToolbar } from "@e2xgrader/create-assignment-celltoolbar"; import { TaskMenubar, TemplateMenubar } from "@e2xgrader/authoring-menubar"; +import { shortcuts, utils } from "@e2xgrader/utils"; + +export function remove_empty_cells() { + let to_remove = []; + for (const [index, cell] of Jupyter.notebook.get_cells().entries()) { + if ( + !utils.is_nbgrader(cell) && + cell.get_text().trim().length === 0 && + Object.keys(cell.attachments).length === 0 + ) { + to_remove.push(index); + } + } + Jupyter.notebook.delete_cells(to_remove); +} function is_taskbook() { let metadata = Jupyter.notebook.metadata; @@ -35,9 +50,13 @@ function initialize() { if (is_taskbook()) { celltoolbar.activate(); new TaskMenubar().activate(); + shortcuts.disable_add_cell_on_execute(); + events.on("before_save.Notebook", remove_empty_cells); } else if (is_templatebook()) { celltoolbar.activate(); new TemplateMenubar().activate(); + shortcuts.disable_add_cell_on_execute(); + events.on("before_save.Notebook", remove_empty_cells); } } From 7ebe744a913905dcb9faeb08e15d1d8f5aef3e59 Mon Sep 17 00:00:00 2001 From: Tim Metzler Date: Fri, 1 Sep 2023 17:34:47 +0200 Subject: [PATCH 5/6] Update package-lock.json --- package-lock.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index c46c28bb..c2afa436 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13873,7 +13873,8 @@ "@e2xgrader/create-assignment-celltoolbar": "0.1.1", "@e2xgrader/exam-menubar": "0.1.1", "@e2xgrader/restricted-assignment-notebook": "0.1.1", - "@e2xgrader/restricted-exam-notebook": "0.1.1" + "@e2xgrader/restricted-exam-notebook": "0.1.1", + "@e2xgrader/utils": "0.1.1" }, "devDependencies": { "@babel/preset-env": "^7.16.11", @@ -15286,6 +15287,7 @@ "@e2xgrader/exam-menubar": "0.1.1", "@e2xgrader/restricted-assignment-notebook": "0.1.1", "@e2xgrader/restricted-exam-notebook": "0.1.1", + "@e2xgrader/utils": "0.1.1", "css-loader": "^6.6.0", "style-loader": "^3.3.1", "webpack": "^5.70.0", From c6158010e4b3d56ea9ab8c14133b833f50058d18 Mon Sep 17 00:00:00 2001 From: Tim Metzler Date: Fri, 1 Sep 2023 17:41:05 +0200 Subject: [PATCH 6/6] Use spread operator --- packages/utils/src/shortcuts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/utils/src/shortcuts.js b/packages/utils/src/shortcuts.js index e262fe47..b8a360c3 100644 --- a/packages/utils/src/shortcuts.js +++ b/packages/utils/src/shortcuts.js @@ -12,7 +12,7 @@ function add_Notebook_execute_cell_and_select() { let cell_index; if (indices.length > 1) { this.execute_cells(indices); - cell_index = Math.max.apply(Math, indices); + cell_index = Math.max(...indices); } else { let cell = this.get_selected_cell(); cell_index = this.find_cell_index(cell);