diff --git a/IPython/html/static/notebook/js/keyboardmanager.js b/IPython/html/static/notebook/js/keyboardmanager.js
index bf35e0f2d..3ff69ba62 100644
--- a/IPython/html/static/notebook/js/keyboardmanager.js
+++ b/IPython/html/static/notebook/js/keyboardmanager.js
@@ -183,6 +183,18 @@ define([
KeyboardManager.prototype.get_default_command_shortcuts = function() {
var that = this;
return {
+ 'space': {
+ help: "Scroll down",
+ handler: function(event) {
+ return that.notebook.scroll_manager.scroll(1);
+ },
+ },
+ 'shift-space': {
+ help: "Scroll up",
+ handler: function(event) {
+ return that.notebook.scroll_manager.scroll(-1);
+ },
+ },
'enter' : {
help : 'edit mode',
help_index : 'aa',
diff --git a/IPython/html/static/notebook/js/maintoolbar.js b/IPython/html/static/notebook/js/maintoolbar.js
index 593704a60..56293b878 100644
--- a/IPython/html/static/notebook/js/maintoolbar.js
+++ b/IPython/html/static/notebook/js/maintoolbar.js
@@ -148,7 +148,6 @@ define([
);
};
-
MainToolBar.prototype.add_celltoolbar_list = function () {
var label = $('').addClass("navbar-text").text('Cell Toolbar:');
var select = $('')
@@ -186,7 +185,6 @@ define([
});
};
-
MainToolBar.prototype.bind_events = function () {
var that = this;
diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js
index 98d6e9a88..8bcf3c9fa 100644
--- a/IPython/html/static/notebook/js/notebook.js
+++ b/IPython/html/static/notebook/js/notebook.js
@@ -18,6 +18,7 @@ define([
'notebook/js/celltoolbarpresets/default',
'notebook/js/celltoolbarpresets/rawcell',
'notebook/js/celltoolbarpresets/slideshow',
+ 'notebook/js/scrollmanager'
], function (
IPython,
$,
@@ -34,7 +35,8 @@ define([
tooltip,
default_celltoolbar,
rawcell_celltoolbar,
- slideshow_celltoolbar
+ slideshow_celltoolbar,
+ scrollmanager
) {
var Notebook = function (selector, options) {
@@ -64,6 +66,10 @@ define([
this.ws_url = options.ws_url;
this._session_starting = false;
this.default_cell_type = this.config.default_cell_type || 'code';
+
+ // Create default scroll manager.
+ this.scroll_manager = new scrollmanager.ScrollManager(this);
+
// default_kernel_name is a temporary measure while we implement proper
// kernel selection and delayed start. Do not rely on it.
this.default_kernel_name = 'python';
@@ -135,7 +141,7 @@ define([
rawcell_celltoolbar.register(this);
slideshow_celltoolbar.register(this);
};
-
+
Notebook.options_default = {
// can be any cell type, or the special values of
// 'above', 'below', or 'selected' to get the value from another cell.
@@ -1620,7 +1626,7 @@ define([
this._session_starting = false;
utils.log_ajax_error(jqxhr, status, error);
};
-
+
/**
* Prompt the user to restart the IPython kernel.
*
diff --git a/IPython/html/static/notebook/js/scrollmanager.js b/IPython/html/static/notebook/js/scrollmanager.js
new file mode 100644
index 000000000..a93dcbcb7
--- /dev/null
+++ b/IPython/html/static/notebook/js/scrollmanager.js
@@ -0,0 +1,193 @@
+// Copyright (c) IPython Development Team.
+// Distributed under the terms of the Modified BSD License.
+define(['jquery'], function($){
+ "use strict";
+
+ var ScrollManager = function(notebook, options) {
+ // Public constructor.
+ this.notebook = notebook;
+ options = options || {};
+ this.animation_speed = options.animation_speed || 250; //ms
+ };
+
+ ScrollManager.prototype.scroll = function (delta) {
+ // Scroll the document.
+ //
+ // Parameters
+ // ----------
+ // delta: integer
+ // direction to scroll the document. Positive is downwards.
+ // Unit is one page length.
+ this.scroll_some(delta);
+ return false;
+ };
+
+ ScrollManager.prototype.scroll_to = function(selector) {
+ // Scroll to an element in the notebook.
+ $('#notebook').animate({'scrollTop': $(selector).offset().top + $('#notebook').scrollTop() - $('#notebook').offset().top}, this.animation_speed);
+ };
+
+ ScrollManager.prototype.scroll_some = function(pages) {
+ // Scroll up or down a given number of pages.
+ //
+ // Parameters
+ // ----------
+ // pages: integer
+ // number of pages to scroll the document, may be positive or negative.
+ $('#notebook').animate({'scrollTop': $('#notebook').scrollTop() + pages * $('#notebook').height()}, this.animation_speed);
+ };
+
+ ScrollManager.prototype.get_first_visible_cell = function() {
+ // Gets the index of the first visible cell in the document.
+
+ // First, attempt to be smart by guessing the index of the cell we are
+ // scrolled to. Then, walk from there up or down until the right cell
+ // is found. To guess the index, get the top of the last cell, and
+ // divide that by the number of cells to get an average cell height.
+ // Then divide the scroll height by the average cell height.
+ var cell_count = this.notebook.ncells();
+ var first_cell_top = this.notebook.get_cell(0).element.offset().top;
+ var last_cell_top = this.notebook.get_cell(cell_count-1).element.offset().top;
+ var avg_cell_height = (last_cell_top - first_cell_top) / cell_count;
+ var notebook = $('#notebook');
+ var i = Math.ceil(notebook.scrollTop() / avg_cell_height);
+ i = Math.min(Math.max(i , 0), cell_count - 1);
+
+ while (this.notebook.get_cell(i).element.offset().top - first_cell_top < notebook.scrollTop() && i < cell_count - 1) {
+ i += 1;
+ }
+
+ while (this.notebook.get_cell(i).element.offset().top - first_cell_top > notebook.scrollTop() - 50 && i >= 0) {
+ i -= 1;
+ }
+ return Math.min(i + 1, cell_count - 1);
+ };
+
+
+ var TargetScrollManager = function(notebook, options) {
+ // Public constructor.
+ ScrollManager.apply(this, [notebook, options]);
+ };
+ TargetScrollManager.prototype = new ScrollManager();
+
+ TargetScrollManager.prototype.is_target = function (index) {
+ // Check if a cell should be a scroll stop.
+ //
+ // Returns `true` if the cell is a cell that the scroll manager
+ // should scroll to. Otherwise, false is returned.
+ //
+ // Parameters
+ // ----------
+ // index: integer
+ // index of the cell to test.
+ return false;
+ };
+
+ TargetScrollManager.prototype.scroll = function (delta) {
+ // Scroll the document.
+ //
+ // Parameters
+ // ----------
+ // delta: integer
+ // direction to scroll the document. Positive is downwards.
+ // Units are targets.
+
+ // Try to scroll to the next slide.
+ var cell_count = this.notebook.ncells();
+ var selected_index = this.get_first_visible_cell() + delta;
+ while (0 <= selected_index && selected_index < cell_count && !this.is_target(selected_index)) {
+ selected_index += delta;
+ }
+
+ if (selected_index < 0 || cell_count <= selected_index) {
+ return ScrollManager.prototype.scroll.apply(this, [delta]);
+ } else {
+ this.scroll_to(this.notebook.get_cell(selected_index).element);
+
+ // Cancel browser keyboard scroll.
+ return false;
+ }
+ };
+
+
+ var SlideScrollManager = function(notebook, options) {
+ // Public constructor.
+ TargetScrollManager.apply(this, [notebook, options]);
+ };
+ SlideScrollManager.prototype = new TargetScrollManager();
+
+ SlideScrollManager.prototype.is_target = function (index) {
+ var cell = this.notebook.get_cell(index);
+ return cell.metadata && cell.metadata.slideshow &&
+ cell.metadata.slideshow.slide_type &&
+ (cell.metadata.slideshow.slide_type === "slide" ||
+ cell.metadata.slideshow.slide_type === "subslide");
+ };
+
+
+ var HeadingScrollManager = function(notebook, options) {
+ // Public constructor.
+ ScrollManager.apply(this, [notebook, options]);
+ options = options || {};
+ this._level = options.heading_level || 1;
+ };
+ HeadingScrollManager.prototype = new ScrollManager();
+
+ HeadingScrollManager.prototype.scroll = function (delta) {
+ // Scroll the document.
+ //
+ // Parameters
+ // ----------
+ // delta: integer
+ // direction to scroll the document. Positive is downwards.
+ // Units are headers.
+
+ // Get all of the header elements that match the heading level or are of
+ // greater magnitude (a smaller header number).
+ var headers = $();
+ var i;
+ for (i = 1; i <= this._level; i++) {
+ headers = headers.add('#notebook-container h' + i);
+ }
+
+ // Find the header the user is on or below.
+ var first_cell_top = this.notebook.get_cell(0).element.offset().top;
+ var notebook = $('#notebook');
+ var current_scroll = notebook.scrollTop();
+ var header_scroll = 0;
+ i = -1;
+ while (current_scroll >= header_scroll && i < headers.length) {
+ if (++i < headers.length) {
+ header_scroll = $(headers[i]).offset().top - first_cell_top;
+ }
+ }
+ i--;
+
+ // Check if the user is below the header.
+ if (i < 0 || current_scroll > $(headers[i]).offset().top - first_cell_top + 30) {
+ // Below the header, count the header as a target.
+ if (delta < 0) {
+ delta += 1;
+ }
+ }
+ i += delta;
+
+ // Scroll!
+ if (0 <= i && i < headers.length) {
+ this.scroll_to(headers[i]);
+ return false;
+ } else {
+ // Default to the base's scroll behavior when target header doesn't
+ // exist.
+ return ScrollManager.prototype.scroll.apply(this, [delta]);
+ }
+ };
+
+ // Return naemspace for require.js loads
+ return {
+ 'ScrollManager': ScrollManager,
+ 'SlideScrollManager': SlideScrollManager,
+ 'HeadingScrollManager': HeadingScrollManager,
+ 'TargetScrollManager': TargetScrollManager
+ };
+});