##// END OF EJS Templates
Move saving to contentmanager...
Jeff Hemmelgarn -
Show More
@@ -1,196 +1,197 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/dialog',
8 'base/js/dialog',
9 ], function(IPython, $, utils, dialog) {
9 ], function(IPython, $, utils, dialog) {
10 var ContentManager = function(options) {
10 var ContentManager = function(options) {
11 // Constructor
11 // Constructor
12 //
12 //
13 // A contentmanager handles passing file operations
13 // A contentmanager handles passing file operations
14 // to the back-end. This includes checkpointing
14 // to the back-end. This includes checkpointing
15 // with the normal file operations.
15 // with the normal file operations.
16 //
16 //
17 // Parameters:
17 // Parameters:
18 // options: dictionary
18 // options: dictionary
19 // Dictionary of keyword arguments.
19 // Dictionary of keyword arguments.
20 // events: $(Events) instance
20 // events: $(Events) instance
21 // base_url: string
21 // base_url: string
22 this.version = 0.1;
22 this.version = 0.1;
23 this.events = options.events;
23 this.events = options.events;
24 this.base_url = options.base_url;
24 this.base_url = options.base_url;
25 };
25 };
26
26
27 /**
27 /**
28 * Creates a new notebook file at the specified path, and
28 * Creates a new notebook file at the specified path, and
29 * opens that notebook in a new window.
29 * opens that notebook in a new window.
30 *
30 *
31 * @method scroll_to_cell
31 * @method scroll_to_cell
32 * @param {String} path The path to create the new notebook at
32 * @param {String} path The path to create the new notebook at
33 */
33 */
34 ContentManager.prototype.new_notebook = function(path) {
34 ContentManager.prototype.new_notebook = function(path) {
35 var base_url = this.base_url;
35 var base_url = this.base_url;
36 var settings = {
36 var settings = {
37 processData : false,
37 processData : false,
38 cache : false,
38 cache : false,
39 type : "POST",
39 type : "POST",
40 dataType : "json",
40 dataType : "json",
41 async : false,
41 async : false,
42 success : function (data, status, xhr){
42 success : function (data, status, xhr){
43 var notebook_name = data.name;
43 var notebook_name = data.name;
44 window.open(
44 window.open(
45 utils.url_join_encode(
45 utils.url_join_encode(
46 base_url,
46 base_url,
47 'notebooks',
47 'notebooks',
48 path,
48 path,
49 notebook_name
49 notebook_name
50 ),
50 ),
51 '_blank'
51 '_blank'
52 );
52 );
53 },
53 },
54 error : function(xhr, status, error) {
54 error : function(xhr, status, error) {
55 utils.log_ajax_error(xhr, status, error);
55 utils.log_ajax_error(xhr, status, error);
56 var msg;
56 var msg;
57 if (xhr.responseJSON && xhr.responseJSON.message) {
57 if (xhr.responseJSON && xhr.responseJSON.message) {
58 msg = xhr.responseJSON.message;
58 msg = xhr.responseJSON.message;
59 } else {
59 } else {
60 msg = xhr.statusText;
60 msg = xhr.statusText;
61 }
61 }
62 dialog.modal({
62 dialog.modal({
63 title : 'Creating Notebook Failed',
63 title : 'Creating Notebook Failed',
64 body : "The error was: " + msg,
64 body : "The error was: " + msg,
65 buttons : {'OK' : {'class' : 'btn-primary'}}
65 buttons : {'OK' : {'class' : 'btn-primary'}}
66 });
66 });
67 }
67 }
68 };
68 };
69 var url = utils.url_join_encode(
69 var url = utils.url_join_encode(
70 base_url,
70 base_url,
71 'api/notebooks',
71 'api/notebooks',
72 path
72 path
73 );
73 );
74 $.ajax(url,settings);
74 $.ajax(url,settings);
75 };
75 };
76
76
77 ContentManager.prototype.delete_notebook = function(name, path, base_url) {
77 ContentManager.prototype.delete_notebook = function(name, path, base_url) {
78 var settings = {
78 var settings = {
79 processData : false,
79 processData : false,
80 cache : false,
80 cache : false,
81 type : "DELETE",
81 type : "DELETE",
82 dataType: "json",
82 dataType: "json",
83 error : utils.log_ajax_error,
83 error : utils.log_ajax_error,
84 };
84 };
85 var url = utils.url_join_encode(
85 var url = utils.url_join_encode(
86 base_url,
86 base_url,
87 'api/notebooks',
87 'api/notebooks',
88 path,
88 path,
89 name
89 name
90 );
90 );
91 $.ajax(url, settings);
91 $.ajax(url, settings);
92 };
92 };
93
93
94 ContentManager.prototype.rename_notebook = function(notebook, nbname) {
94 ContentManager.prototype.rename_notebook = function(notebook, nbname) {
95 var that = notebook;
95 var that = notebook;
96 if (!nbname.match(/\.ipynb$/)) {
96 if (!nbname.match(/\.ipynb$/)) {
97 nbname = nbname + ".ipynb";
97 nbname = nbname + ".ipynb";
98 }
98 }
99 var data = {name: nbname};
99 var data = {name: nbname};
100 var settings = {
100 var settings = {
101 processData : false,
101 processData : false,
102 cache : false,
102 cache : false,
103 type : "PATCH",
103 type : "PATCH",
104 data : JSON.stringify(data),
104 data : JSON.stringify(data),
105 dataType: "json",
105 dataType: "json",
106 contentType: 'application/json',
106 contentType: 'application/json',
107 success : $.proxy(that.rename_success, that),
107 success : $.proxy(that.rename_success, that),
108 error : $.proxy(that.rename_error, that)
108 error : $.proxy(that.rename_error, that)
109 };
109 };
110 this.events.trigger('rename_notebook.Notebook', data);
110 this.events.trigger('rename_notebook.Notebook', data);
111 var url = utils.url_join_encode(
111 var url = utils.url_join_encode(
112 that.base_url,
112 that.base_url,
113 'api/notebooks',
113 'api/notebooks',
114 that.notebook_path,
114 that.notebook_path,
115 that.notebook_name
115 that.notebook_name
116 );
116 );
117 $.ajax(url, settings);
117 $.ajax(url, settings);
118 };
118 };
119
119
120 ContentManager.prototype.save_notebook = function(notebook, extra_settings) {
120 ContentManager.prototype.save_notebook = function(notebook, extra_settings) {
121 var that = notebook;
121 // Create a JSON model to be sent to the server.
122 // Create a JSON model to be sent to the server.
122 var model = {};
123 var model = {};
123 model.name = notebook.notebook_name;
124 model.name = notebook.notebook_name;
124 model.path = notebook.notebook_path;
125 model.path = notebook.notebook_path;
125 model.content = notebook.toJSON();
126 model.content = notebook.toJSON();
126 model.content.nbformat = notebook.nbformat;
127 model.content.nbformat = notebook.nbformat;
127 model.content.nbformat_minor = notebook.nbformat_minor;
128 model.content.nbformat_minor = notebook.nbformat_minor;
128 // time the ajax call for autosave tuning purposes.
129 // time the ajax call for autosave tuning purposes.
129 var start = new Date().getTime();
130 var start = new Date().getTime();
130 // We do the call with settings so we can set cache to false.
131 // We do the call with settings so we can set cache to false.
131 var settings = {
132 var settings = {
132 processData : false,
133 processData : false,
133 cache : false,
134 cache : false,
134 type : "PUT",
135 type : "PUT",
135 data : JSON.stringify(model),
136 data : JSON.stringify(model),
136 contentType: 'application/json',
137 contentType: 'application/json',
137 success : $.proxy(notebook.save_notebook_success, this, start),
138 success : $.proxy(notebook.save_notebook_success, that, start),
138 error : $.proxy(notebook.save_notebook_error, this)
139 error : $.proxy(notebook.save_notebook_error, that)
139 };
140 };
140 if (extra_settings) {
141 if (extra_settings) {
141 for (var key in extra_settings) {
142 for (var key in extra_settings) {
142 settings[key] = extra_settings[key];
143 settings[key] = extra_settings[key];
143 }
144 }
144 }
145 }
145 notebook.events.trigger('notebook_saving.Notebook');
146 notebook.events.trigger('notebook_saving.Notebook');
146 var url = utils.url_join_encode(
147 var url = utils.url_join_encode(
147 notebook.base_url,
148 notebook.base_url,
148 'api/notebooks',
149 'api/notebooks',
149 notebook.notebook_path,
150 notebook.notebook_path,
150 notebook.notebook_name
151 notebook.notebook_name
151 );
152 );
152 $.ajax(url, settings);
153 $.ajax(url, settings);
153 };
154 };
154
155
155 ContentManager.prototype.save_checkpoint = function() {
156 ContentManager.prototype.save_checkpoint = function() {
156 // This is not necessary - integrated into save
157 // This is not necessary - integrated into save
157 };
158 };
158
159
159 ContentManager.prototype.restore_checkpoint = function(notebook, id) {
160 ContentManager.prototype.restore_checkpoint = function(notebook, id) {
160 that = notebook;
161 that = notebook;
161 this.events.trigger('notebook_restoring.Notebook', checkpoint);
162 this.events.trigger('notebook_restoring.Notebook', checkpoint);
162 var url = utils.url_join_encode(
163 var url = utils.url_join_encode(
163 this.base_url,
164 this.base_url,
164 'api/notebooks',
165 'api/notebooks',
165 this.notebook_path,
166 this.notebook_path,
166 this.notebook_name,
167 this.notebook_name,
167 'checkpoints',
168 'checkpoints',
168 checkpoint
169 checkpoint
169 );
170 );
170 $.post(url).done(
171 $.post(url).done(
171 $.proxy(that.restore_checkpoint_success, that)
172 $.proxy(that.restore_checkpoint_success, that)
172 ).fail(
173 ).fail(
173 $.proxy(that.restore_checkpoint_error, that)
174 $.proxy(that.restore_checkpoint_error, that)
174 );
175 );
175 };
176 };
176
177
177 ContentManager.prototype.list_checkpoints = function(notebook) {
178 ContentManager.prototype.list_checkpoints = function(notebook) {
178 that = notebook;
179 that = notebook;
179 var url = utils.url_join_encode(
180 var url = utils.url_join_encode(
180 that.base_url,
181 that.base_url,
181 'api/notebooks',
182 'api/notebooks',
182 that.notebook_path,
183 that.notebook_path,
183 that.notebook_name,
184 that.notebook_name,
184 'checkpoints'
185 'checkpoints'
185 );
186 );
186 $.get(url).done(
187 $.get(url).done(
187 $.proxy(that.list_checkpoints_success, that)
188 $.proxy(that.list_checkpoints_success, that)
188 ).fail(
189 ).fail(
189 $.proxy(that.list_checkpoints_error, that)
190 $.proxy(that.list_checkpoints_error, that)
190 );
191 );
191 };
192 };
192
193
193 IPython.ContentManager = ContentManager;
194 IPython.ContentManager = ContentManager;
194
195
195 return {'ContentManager': ContentManager};
196 return {'ContentManager': ContentManager};
196 });
197 });
@@ -1,2614 +1,2580 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/dialog',
8 'base/js/dialog',
9 'notebook/js/textcell',
9 'notebook/js/textcell',
10 'notebook/js/codecell',
10 'notebook/js/codecell',
11 'services/sessions/session',
11 'services/sessions/session',
12 'notebook/js/celltoolbar',
12 'notebook/js/celltoolbar',
13 'components/marked/lib/marked',
13 'components/marked/lib/marked',
14 'highlight',
14 'highlight',
15 'notebook/js/mathjaxutils',
15 'notebook/js/mathjaxutils',
16 'base/js/keyboard',
16 'base/js/keyboard',
17 'notebook/js/tooltip',
17 'notebook/js/tooltip',
18 'notebook/js/celltoolbarpresets/default',
18 'notebook/js/celltoolbarpresets/default',
19 'notebook/js/celltoolbarpresets/rawcell',
19 'notebook/js/celltoolbarpresets/rawcell',
20 'notebook/js/celltoolbarpresets/slideshow',
20 'notebook/js/celltoolbarpresets/slideshow',
21 'notebook/js/scrollmanager'
21 'notebook/js/scrollmanager'
22 ], function (
22 ], function (
23 IPython,
23 IPython,
24 $,
24 $,
25 utils,
25 utils,
26 dialog,
26 dialog,
27 textcell,
27 textcell,
28 codecell,
28 codecell,
29 session,
29 session,
30 celltoolbar,
30 celltoolbar,
31 marked,
31 marked,
32 hljs,
32 hljs,
33 mathjaxutils,
33 mathjaxutils,
34 keyboard,
34 keyboard,
35 tooltip,
35 tooltip,
36 default_celltoolbar,
36 default_celltoolbar,
37 rawcell_celltoolbar,
37 rawcell_celltoolbar,
38 slideshow_celltoolbar,
38 slideshow_celltoolbar,
39 scrollmanager
39 scrollmanager
40 ) {
40 ) {
41
41
42 var Notebook = function (selector, options) {
42 var Notebook = function (selector, options) {
43 // Constructor
43 // Constructor
44 //
44 //
45 // A notebook contains and manages cells.
45 // A notebook contains and manages cells.
46 //
46 //
47 // Parameters:
47 // Parameters:
48 // selector: string
48 // selector: string
49 // options: dictionary
49 // options: dictionary
50 // Dictionary of keyword arguments.
50 // Dictionary of keyword arguments.
51 // events: $(Events) instance
51 // events: $(Events) instance
52 // keyboard_manager: KeyboardManager instance
52 // keyboard_manager: KeyboardManager instance
53 // save_widget: SaveWidget instance
53 // save_widget: SaveWidget instance
54 // config: dictionary
54 // config: dictionary
55 // base_url : string
55 // base_url : string
56 // notebook_path : string
56 // notebook_path : string
57 // notebook_name : string
57 // notebook_name : string
58 this.config = utils.mergeopt(Notebook, options.config);
58 this.config = utils.mergeopt(Notebook, options.config);
59 this.base_url = options.base_url;
59 this.base_url = options.base_url;
60 this.notebook_path = options.notebook_path;
60 this.notebook_path = options.notebook_path;
61 this.notebook_name = options.notebook_name;
61 this.notebook_name = options.notebook_name;
62 this.events = options.events;
62 this.events = options.events;
63 this.keyboard_manager = options.keyboard_manager;
63 this.keyboard_manager = options.keyboard_manager;
64 this.content_manager = options.content_manager;
64 this.content_manager = options.content_manager;
65 this.save_widget = options.save_widget;
65 this.save_widget = options.save_widget;
66 this.tooltip = new tooltip.Tooltip(this.events);
66 this.tooltip = new tooltip.Tooltip(this.events);
67 this.ws_url = options.ws_url;
67 this.ws_url = options.ws_url;
68 this._session_starting = false;
68 this._session_starting = false;
69 this.default_cell_type = this.config.default_cell_type || 'code';
69 this.default_cell_type = this.config.default_cell_type || 'code';
70
70
71 // Create default scroll manager.
71 // Create default scroll manager.
72 this.scroll_manager = new scrollmanager.ScrollManager(this);
72 this.scroll_manager = new scrollmanager.ScrollManager(this);
73
73
74 // TODO: This code smells (and the other `= this` line a couple lines down)
74 // TODO: This code smells (and the other `= this` line a couple lines down)
75 // We need a better way to deal with circular instance references.
75 // We need a better way to deal with circular instance references.
76 this.keyboard_manager.notebook = this;
76 this.keyboard_manager.notebook = this;
77 this.save_widget.notebook = this;
77 this.save_widget.notebook = this;
78
78
79 mathjaxutils.init();
79 mathjaxutils.init();
80
80
81 if (marked) {
81 if (marked) {
82 marked.setOptions({
82 marked.setOptions({
83 gfm : true,
83 gfm : true,
84 tables: true,
84 tables: true,
85 langPrefix: "language-",
85 langPrefix: "language-",
86 highlight: function(code, lang) {
86 highlight: function(code, lang) {
87 if (!lang) {
87 if (!lang) {
88 // no language, no highlight
88 // no language, no highlight
89 return code;
89 return code;
90 }
90 }
91 var highlighted;
91 var highlighted;
92 try {
92 try {
93 highlighted = hljs.highlight(lang, code, false);
93 highlighted = hljs.highlight(lang, code, false);
94 } catch(err) {
94 } catch(err) {
95 highlighted = hljs.highlightAuto(code);
95 highlighted = hljs.highlightAuto(code);
96 }
96 }
97 return highlighted.value;
97 return highlighted.value;
98 }
98 }
99 });
99 });
100 }
100 }
101
101
102 this.element = $(selector);
102 this.element = $(selector);
103 this.element.scroll();
103 this.element.scroll();
104 this.element.data("notebook", this);
104 this.element.data("notebook", this);
105 this.next_prompt_number = 1;
105 this.next_prompt_number = 1;
106 this.session = null;
106 this.session = null;
107 this.kernel = null;
107 this.kernel = null;
108 this.clipboard = null;
108 this.clipboard = null;
109 this.undelete_backup = null;
109 this.undelete_backup = null;
110 this.undelete_index = null;
110 this.undelete_index = null;
111 this.undelete_below = false;
111 this.undelete_below = false;
112 this.paste_enabled = false;
112 this.paste_enabled = false;
113 // It is important to start out in command mode to match the intial mode
113 // It is important to start out in command mode to match the intial mode
114 // of the KeyboardManager.
114 // of the KeyboardManager.
115 this.mode = 'command';
115 this.mode = 'command';
116 this.set_dirty(false);
116 this.set_dirty(false);
117 this.metadata = {};
117 this.metadata = {};
118 this._checkpoint_after_save = false;
118 this._checkpoint_after_save = false;
119 this.last_checkpoint = null;
119 this.last_checkpoint = null;
120 this.checkpoints = [];
120 this.checkpoints = [];
121 this.autosave_interval = 0;
121 this.autosave_interval = 0;
122 this.autosave_timer = null;
122 this.autosave_timer = null;
123 // autosave *at most* every two minutes
123 // autosave *at most* every two minutes
124 this.minimum_autosave_interval = 120000;
124 this.minimum_autosave_interval = 120000;
125 this.notebook_name_blacklist_re = /[\/\\:]/;
125 this.notebook_name_blacklist_re = /[\/\\:]/;
126 this.nbformat = 4; // Increment this when changing the nbformat
126 this.nbformat = 4; // Increment this when changing the nbformat
127 this.nbformat_minor = 0; // Increment this when changing the nbformat
127 this.nbformat_minor = 0; // Increment this when changing the nbformat
128 this.codemirror_mode = 'ipython';
128 this.codemirror_mode = 'ipython';
129 this.create_elements();
129 this.create_elements();
130 this.bind_events();
130 this.bind_events();
131 this.save_notebook = function() { // don't allow save until notebook_loaded
131 this.save_notebook = function() { // don't allow save until notebook_loaded
132 this.save_notebook_error(null, null, "Load failed, save is disabled");
132 this.save_notebook_error(null, null, "Load failed, save is disabled");
133 };
133 };
134
134
135 // Trigger cell toolbar registration.
135 // Trigger cell toolbar registration.
136 default_celltoolbar.register(this);
136 default_celltoolbar.register(this);
137 rawcell_celltoolbar.register(this);
137 rawcell_celltoolbar.register(this);
138 slideshow_celltoolbar.register(this);
138 slideshow_celltoolbar.register(this);
139 };
139 };
140
140
141 Notebook.options_default = {
141 Notebook.options_default = {
142 // can be any cell type, or the special values of
142 // can be any cell type, or the special values of
143 // 'above', 'below', or 'selected' to get the value from another cell.
143 // 'above', 'below', or 'selected' to get the value from another cell.
144 Notebook: {
144 Notebook: {
145 default_cell_type: 'code',
145 default_cell_type: 'code',
146 }
146 }
147 };
147 };
148
148
149
149
150 /**
150 /**
151 * Create an HTML and CSS representation of the notebook.
151 * Create an HTML and CSS representation of the notebook.
152 *
152 *
153 * @method create_elements
153 * @method create_elements
154 */
154 */
155 Notebook.prototype.create_elements = function () {
155 Notebook.prototype.create_elements = function () {
156 var that = this;
156 var that = this;
157 this.element.attr('tabindex','-1');
157 this.element.attr('tabindex','-1');
158 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
158 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
159 // We add this end_space div to the end of the notebook div to:
159 // We add this end_space div to the end of the notebook div to:
160 // i) provide a margin between the last cell and the end of the notebook
160 // i) provide a margin between the last cell and the end of the notebook
161 // ii) to prevent the div from scrolling up when the last cell is being
161 // ii) to prevent the div from scrolling up when the last cell is being
162 // edited, but is too low on the page, which browsers will do automatically.
162 // edited, but is too low on the page, which browsers will do automatically.
163 var end_space = $('<div/>').addClass('end_space');
163 var end_space = $('<div/>').addClass('end_space');
164 end_space.dblclick(function (e) {
164 end_space.dblclick(function (e) {
165 var ncells = that.ncells();
165 var ncells = that.ncells();
166 that.insert_cell_below('code',ncells-1);
166 that.insert_cell_below('code',ncells-1);
167 });
167 });
168 this.element.append(this.container);
168 this.element.append(this.container);
169 this.container.append(end_space);
169 this.container.append(end_space);
170 };
170 };
171
171
172 /**
172 /**
173 * Bind JavaScript events: key presses and custom IPython events.
173 * Bind JavaScript events: key presses and custom IPython events.
174 *
174 *
175 * @method bind_events
175 * @method bind_events
176 */
176 */
177 Notebook.prototype.bind_events = function () {
177 Notebook.prototype.bind_events = function () {
178 var that = this;
178 var that = this;
179
179
180 this.events.on('set_next_input.Notebook', function (event, data) {
180 this.events.on('set_next_input.Notebook', function (event, data) {
181 var index = that.find_cell_index(data.cell);
181 var index = that.find_cell_index(data.cell);
182 var new_cell = that.insert_cell_below('code',index);
182 var new_cell = that.insert_cell_below('code',index);
183 new_cell.set_text(data.text);
183 new_cell.set_text(data.text);
184 that.dirty = true;
184 that.dirty = true;
185 });
185 });
186
186
187 this.events.on('set_dirty.Notebook', function (event, data) {
187 this.events.on('set_dirty.Notebook', function (event, data) {
188 that.dirty = data.value;
188 that.dirty = data.value;
189 });
189 });
190
190
191 this.events.on('trust_changed.Notebook', function (event, trusted) {
191 this.events.on('trust_changed.Notebook', function (event, trusted) {
192 that.trusted = trusted;
192 that.trusted = trusted;
193 });
193 });
194
194
195 this.events.on('select.Cell', function (event, data) {
195 this.events.on('select.Cell', function (event, data) {
196 var index = that.find_cell_index(data.cell);
196 var index = that.find_cell_index(data.cell);
197 that.select(index);
197 that.select(index);
198 });
198 });
199
199
200 this.events.on('edit_mode.Cell', function (event, data) {
200 this.events.on('edit_mode.Cell', function (event, data) {
201 that.handle_edit_mode(data.cell);
201 that.handle_edit_mode(data.cell);
202 });
202 });
203
203
204 this.events.on('command_mode.Cell', function (event, data) {
204 this.events.on('command_mode.Cell', function (event, data) {
205 that.handle_command_mode(data.cell);
205 that.handle_command_mode(data.cell);
206 });
206 });
207
207
208 this.events.on('spec_changed.Kernel', function(event, data) {
208 this.events.on('spec_changed.Kernel', function(event, data) {
209 that.metadata.kernelspec =
209 that.metadata.kernelspec =
210 {name: data.name, display_name: data.display_name};
210 {name: data.name, display_name: data.display_name};
211 });
211 });
212
212
213 this.events.on('kernel_ready.Kernel', function(event, data) {
213 this.events.on('kernel_ready.Kernel', function(event, data) {
214 var kinfo = data.kernel.info_reply
214 var kinfo = data.kernel.info_reply
215 var langinfo = kinfo.language_info || {};
215 var langinfo = kinfo.language_info || {};
216 if (!langinfo.name) langinfo.name = kinfo.language;
216 if (!langinfo.name) langinfo.name = kinfo.language;
217
217
218 that.metadata.language_info = langinfo;
218 that.metadata.language_info = langinfo;
219 // Mode 'null' should be plain, unhighlighted text.
219 // Mode 'null' should be plain, unhighlighted text.
220 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null'
220 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null'
221 that.set_codemirror_mode(cm_mode);
221 that.set_codemirror_mode(cm_mode);
222 });
222 });
223
223
224 var collapse_time = function (time) {
224 var collapse_time = function (time) {
225 var app_height = $('#ipython-main-app').height(); // content height
225 var app_height = $('#ipython-main-app').height(); // content height
226 var splitter_height = $('div#pager_splitter').outerHeight(true);
226 var splitter_height = $('div#pager_splitter').outerHeight(true);
227 var new_height = app_height - splitter_height;
227 var new_height = app_height - splitter_height;
228 that.element.animate({height : new_height + 'px'}, time);
228 that.element.animate({height : new_height + 'px'}, time);
229 };
229 };
230
230
231 this.element.bind('collapse_pager', function (event, extrap) {
231 this.element.bind('collapse_pager', function (event, extrap) {
232 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
232 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
233 collapse_time(time);
233 collapse_time(time);
234 });
234 });
235
235
236 var expand_time = function (time) {
236 var expand_time = function (time) {
237 var app_height = $('#ipython-main-app').height(); // content height
237 var app_height = $('#ipython-main-app').height(); // content height
238 var splitter_height = $('div#pager_splitter').outerHeight(true);
238 var splitter_height = $('div#pager_splitter').outerHeight(true);
239 var pager_height = $('div#pager').outerHeight(true);
239 var pager_height = $('div#pager').outerHeight(true);
240 var new_height = app_height - pager_height - splitter_height;
240 var new_height = app_height - pager_height - splitter_height;
241 that.element.animate({height : new_height + 'px'}, time);
241 that.element.animate({height : new_height + 'px'}, time);
242 };
242 };
243
243
244 this.element.bind('expand_pager', function (event, extrap) {
244 this.element.bind('expand_pager', function (event, extrap) {
245 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
245 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
246 expand_time(time);
246 expand_time(time);
247 });
247 });
248
248
249 // Firefox 22 broke $(window).on("beforeunload")
249 // Firefox 22 broke $(window).on("beforeunload")
250 // I'm not sure why or how.
250 // I'm not sure why or how.
251 window.onbeforeunload = function (e) {
251 window.onbeforeunload = function (e) {
252 // TODO: Make killing the kernel configurable.
252 // TODO: Make killing the kernel configurable.
253 var kill_kernel = false;
253 var kill_kernel = false;
254 if (kill_kernel) {
254 if (kill_kernel) {
255 that.session.delete();
255 that.session.delete();
256 }
256 }
257 // if we are autosaving, trigger an autosave on nav-away.
257 // if we are autosaving, trigger an autosave on nav-away.
258 // still warn, because if we don't the autosave may fail.
258 // still warn, because if we don't the autosave may fail.
259 if (that.dirty) {
259 if (that.dirty) {
260 if ( that.autosave_interval ) {
260 if ( that.autosave_interval ) {
261 // schedule autosave in a timeout
261 // schedule autosave in a timeout
262 // this gives you a chance to forcefully discard changes
262 // this gives you a chance to forcefully discard changes
263 // by reloading the page if you *really* want to.
263 // by reloading the page if you *really* want to.
264 // the timer doesn't start until you *dismiss* the dialog.
264 // the timer doesn't start until you *dismiss* the dialog.
265 setTimeout(function () {
265 setTimeout(function () {
266 if (that.dirty) {
266 if (that.dirty) {
267 that.save_notebook();
267 that.save_notebook();
268 }
268 }
269 }, 1000);
269 }, 1000);
270 return "Autosave in progress, latest changes may be lost.";
270 return "Autosave in progress, latest changes may be lost.";
271 } else {
271 } else {
272 return "Unsaved changes will be lost.";
272 return "Unsaved changes will be lost.";
273 }
273 }
274 }
274 }
275 // Null is the *only* return value that will make the browser not
275 // Null is the *only* return value that will make the browser not
276 // pop up the "don't leave" dialog.
276 // pop up the "don't leave" dialog.
277 return null;
277 return null;
278 };
278 };
279 };
279 };
280
280
281 /**
281 /**
282 * Set the dirty flag, and trigger the set_dirty.Notebook event
282 * Set the dirty flag, and trigger the set_dirty.Notebook event
283 *
283 *
284 * @method set_dirty
284 * @method set_dirty
285 */
285 */
286 Notebook.prototype.set_dirty = function (value) {
286 Notebook.prototype.set_dirty = function (value) {
287 if (value === undefined) {
287 if (value === undefined) {
288 value = true;
288 value = true;
289 }
289 }
290 if (this.dirty == value) {
290 if (this.dirty == value) {
291 return;
291 return;
292 }
292 }
293 this.events.trigger('set_dirty.Notebook', {value: value});
293 this.events.trigger('set_dirty.Notebook', {value: value});
294 };
294 };
295
295
296 /**
296 /**
297 * Scroll the top of the page to a given cell.
297 * Scroll the top of the page to a given cell.
298 *
298 *
299 * @method scroll_to_cell
299 * @method scroll_to_cell
300 * @param {Number} cell_number An index of the cell to view
300 * @param {Number} cell_number An index of the cell to view
301 * @param {Number} time Animation time in milliseconds
301 * @param {Number} time Animation time in milliseconds
302 * @return {Number} Pixel offset from the top of the container
302 * @return {Number} Pixel offset from the top of the container
303 */
303 */
304 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
304 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
305 var cells = this.get_cells();
305 var cells = this.get_cells();
306 time = time || 0;
306 time = time || 0;
307 cell_number = Math.min(cells.length-1,cell_number);
307 cell_number = Math.min(cells.length-1,cell_number);
308 cell_number = Math.max(0 ,cell_number);
308 cell_number = Math.max(0 ,cell_number);
309 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
309 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
310 this.element.animate({scrollTop:scroll_value}, time);
310 this.element.animate({scrollTop:scroll_value}, time);
311 return scroll_value;
311 return scroll_value;
312 };
312 };
313
313
314 /**
314 /**
315 * Scroll to the bottom of the page.
315 * Scroll to the bottom of the page.
316 *
316 *
317 * @method scroll_to_bottom
317 * @method scroll_to_bottom
318 */
318 */
319 Notebook.prototype.scroll_to_bottom = function () {
319 Notebook.prototype.scroll_to_bottom = function () {
320 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
320 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
321 };
321 };
322
322
323 /**
323 /**
324 * Scroll to the top of the page.
324 * Scroll to the top of the page.
325 *
325 *
326 * @method scroll_to_top
326 * @method scroll_to_top
327 */
327 */
328 Notebook.prototype.scroll_to_top = function () {
328 Notebook.prototype.scroll_to_top = function () {
329 this.element.animate({scrollTop:0}, 0);
329 this.element.animate({scrollTop:0}, 0);
330 };
330 };
331
331
332 // Edit Notebook metadata
332 // Edit Notebook metadata
333
333
334 Notebook.prototype.edit_metadata = function () {
334 Notebook.prototype.edit_metadata = function () {
335 var that = this;
335 var that = this;
336 dialog.edit_metadata({
336 dialog.edit_metadata({
337 md: this.metadata,
337 md: this.metadata,
338 callback: function (md) {
338 callback: function (md) {
339 that.metadata = md;
339 that.metadata = md;
340 },
340 },
341 name: 'Notebook',
341 name: 'Notebook',
342 notebook: this,
342 notebook: this,
343 keyboard_manager: this.keyboard_manager});
343 keyboard_manager: this.keyboard_manager});
344 };
344 };
345
345
346 // Cell indexing, retrieval, etc.
346 // Cell indexing, retrieval, etc.
347
347
348 /**
348 /**
349 * Get all cell elements in the notebook.
349 * Get all cell elements in the notebook.
350 *
350 *
351 * @method get_cell_elements
351 * @method get_cell_elements
352 * @return {jQuery} A selector of all cell elements
352 * @return {jQuery} A selector of all cell elements
353 */
353 */
354 Notebook.prototype.get_cell_elements = function () {
354 Notebook.prototype.get_cell_elements = function () {
355 return this.container.children("div.cell");
355 return this.container.children("div.cell");
356 };
356 };
357
357
358 /**
358 /**
359 * Get a particular cell element.
359 * Get a particular cell element.
360 *
360 *
361 * @method get_cell_element
361 * @method get_cell_element
362 * @param {Number} index An index of a cell to select
362 * @param {Number} index An index of a cell to select
363 * @return {jQuery} A selector of the given cell.
363 * @return {jQuery} A selector of the given cell.
364 */
364 */
365 Notebook.prototype.get_cell_element = function (index) {
365 Notebook.prototype.get_cell_element = function (index) {
366 var result = null;
366 var result = null;
367 var e = this.get_cell_elements().eq(index);
367 var e = this.get_cell_elements().eq(index);
368 if (e.length !== 0) {
368 if (e.length !== 0) {
369 result = e;
369 result = e;
370 }
370 }
371 return result;
371 return result;
372 };
372 };
373
373
374 /**
374 /**
375 * Try to get a particular cell by msg_id.
375 * Try to get a particular cell by msg_id.
376 *
376 *
377 * @method get_msg_cell
377 * @method get_msg_cell
378 * @param {String} msg_id A message UUID
378 * @param {String} msg_id A message UUID
379 * @return {Cell} Cell or null if no cell was found.
379 * @return {Cell} Cell or null if no cell was found.
380 */
380 */
381 Notebook.prototype.get_msg_cell = function (msg_id) {
381 Notebook.prototype.get_msg_cell = function (msg_id) {
382 return codecell.CodeCell.msg_cells[msg_id] || null;
382 return codecell.CodeCell.msg_cells[msg_id] || null;
383 };
383 };
384
384
385 /**
385 /**
386 * Count the cells in this notebook.
386 * Count the cells in this notebook.
387 *
387 *
388 * @method ncells
388 * @method ncells
389 * @return {Number} The number of cells in this notebook
389 * @return {Number} The number of cells in this notebook
390 */
390 */
391 Notebook.prototype.ncells = function () {
391 Notebook.prototype.ncells = function () {
392 return this.get_cell_elements().length;
392 return this.get_cell_elements().length;
393 };
393 };
394
394
395 /**
395 /**
396 * Get all Cell objects in this notebook.
396 * Get all Cell objects in this notebook.
397 *
397 *
398 * @method get_cells
398 * @method get_cells
399 * @return {Array} This notebook's Cell objects
399 * @return {Array} This notebook's Cell objects
400 */
400 */
401 // TODO: we are often calling cells as cells()[i], which we should optimize
401 // TODO: we are often calling cells as cells()[i], which we should optimize
402 // to cells(i) or a new method.
402 // to cells(i) or a new method.
403 Notebook.prototype.get_cells = function () {
403 Notebook.prototype.get_cells = function () {
404 return this.get_cell_elements().toArray().map(function (e) {
404 return this.get_cell_elements().toArray().map(function (e) {
405 return $(e).data("cell");
405 return $(e).data("cell");
406 });
406 });
407 };
407 };
408
408
409 /**
409 /**
410 * Get a Cell object from this notebook.
410 * Get a Cell object from this notebook.
411 *
411 *
412 * @method get_cell
412 * @method get_cell
413 * @param {Number} index An index of a cell to retrieve
413 * @param {Number} index An index of a cell to retrieve
414 * @return {Cell} Cell or null if no cell was found.
414 * @return {Cell} Cell or null if no cell was found.
415 */
415 */
416 Notebook.prototype.get_cell = function (index) {
416 Notebook.prototype.get_cell = function (index) {
417 var result = null;
417 var result = null;
418 var ce = this.get_cell_element(index);
418 var ce = this.get_cell_element(index);
419 if (ce !== null) {
419 if (ce !== null) {
420 result = ce.data('cell');
420 result = ce.data('cell');
421 }
421 }
422 return result;
422 return result;
423 };
423 };
424
424
425 /**
425 /**
426 * Get the cell below a given cell.
426 * Get the cell below a given cell.
427 *
427 *
428 * @method get_next_cell
428 * @method get_next_cell
429 * @param {Cell} cell The provided cell
429 * @param {Cell} cell The provided cell
430 * @return {Cell} the next cell or null if no cell was found.
430 * @return {Cell} the next cell or null if no cell was found.
431 */
431 */
432 Notebook.prototype.get_next_cell = function (cell) {
432 Notebook.prototype.get_next_cell = function (cell) {
433 var result = null;
433 var result = null;
434 var index = this.find_cell_index(cell);
434 var index = this.find_cell_index(cell);
435 if (this.is_valid_cell_index(index+1)) {
435 if (this.is_valid_cell_index(index+1)) {
436 result = this.get_cell(index+1);
436 result = this.get_cell(index+1);
437 }
437 }
438 return result;
438 return result;
439 };
439 };
440
440
441 /**
441 /**
442 * Get the cell above a given cell.
442 * Get the cell above a given cell.
443 *
443 *
444 * @method get_prev_cell
444 * @method get_prev_cell
445 * @param {Cell} cell The provided cell
445 * @param {Cell} cell The provided cell
446 * @return {Cell} The previous cell or null if no cell was found.
446 * @return {Cell} The previous cell or null if no cell was found.
447 */
447 */
448 Notebook.prototype.get_prev_cell = function (cell) {
448 Notebook.prototype.get_prev_cell = function (cell) {
449 var result = null;
449 var result = null;
450 var index = this.find_cell_index(cell);
450 var index = this.find_cell_index(cell);
451 if (index !== null && index > 0) {
451 if (index !== null && index > 0) {
452 result = this.get_cell(index-1);
452 result = this.get_cell(index-1);
453 }
453 }
454 return result;
454 return result;
455 };
455 };
456
456
457 /**
457 /**
458 * Get the numeric index of a given cell.
458 * Get the numeric index of a given cell.
459 *
459 *
460 * @method find_cell_index
460 * @method find_cell_index
461 * @param {Cell} cell The provided cell
461 * @param {Cell} cell The provided cell
462 * @return {Number} The cell's numeric index or null if no cell was found.
462 * @return {Number} The cell's numeric index or null if no cell was found.
463 */
463 */
464 Notebook.prototype.find_cell_index = function (cell) {
464 Notebook.prototype.find_cell_index = function (cell) {
465 var result = null;
465 var result = null;
466 this.get_cell_elements().filter(function (index) {
466 this.get_cell_elements().filter(function (index) {
467 if ($(this).data("cell") === cell) {
467 if ($(this).data("cell") === cell) {
468 result = index;
468 result = index;
469 }
469 }
470 });
470 });
471 return result;
471 return result;
472 };
472 };
473
473
474 /**
474 /**
475 * Get a given index , or the selected index if none is provided.
475 * Get a given index , or the selected index if none is provided.
476 *
476 *
477 * @method index_or_selected
477 * @method index_or_selected
478 * @param {Number} index A cell's index
478 * @param {Number} index A cell's index
479 * @return {Number} The given index, or selected index if none is provided.
479 * @return {Number} The given index, or selected index if none is provided.
480 */
480 */
481 Notebook.prototype.index_or_selected = function (index) {
481 Notebook.prototype.index_or_selected = function (index) {
482 var i;
482 var i;
483 if (index === undefined || index === null) {
483 if (index === undefined || index === null) {
484 i = this.get_selected_index();
484 i = this.get_selected_index();
485 if (i === null) {
485 if (i === null) {
486 i = 0;
486 i = 0;
487 }
487 }
488 } else {
488 } else {
489 i = index;
489 i = index;
490 }
490 }
491 return i;
491 return i;
492 };
492 };
493
493
494 /**
494 /**
495 * Get the currently selected cell.
495 * Get the currently selected cell.
496 * @method get_selected_cell
496 * @method get_selected_cell
497 * @return {Cell} The selected cell
497 * @return {Cell} The selected cell
498 */
498 */
499 Notebook.prototype.get_selected_cell = function () {
499 Notebook.prototype.get_selected_cell = function () {
500 var index = this.get_selected_index();
500 var index = this.get_selected_index();
501 return this.get_cell(index);
501 return this.get_cell(index);
502 };
502 };
503
503
504 /**
504 /**
505 * Check whether a cell index is valid.
505 * Check whether a cell index is valid.
506 *
506 *
507 * @method is_valid_cell_index
507 * @method is_valid_cell_index
508 * @param {Number} index A cell index
508 * @param {Number} index A cell index
509 * @return True if the index is valid, false otherwise
509 * @return True if the index is valid, false otherwise
510 */
510 */
511 Notebook.prototype.is_valid_cell_index = function (index) {
511 Notebook.prototype.is_valid_cell_index = function (index) {
512 if (index !== null && index >= 0 && index < this.ncells()) {
512 if (index !== null && index >= 0 && index < this.ncells()) {
513 return true;
513 return true;
514 } else {
514 } else {
515 return false;
515 return false;
516 }
516 }
517 };
517 };
518
518
519 /**
519 /**
520 * Get the index of the currently selected cell.
520 * Get the index of the currently selected cell.
521
521
522 * @method get_selected_index
522 * @method get_selected_index
523 * @return {Number} The selected cell's numeric index
523 * @return {Number} The selected cell's numeric index
524 */
524 */
525 Notebook.prototype.get_selected_index = function () {
525 Notebook.prototype.get_selected_index = function () {
526 var result = null;
526 var result = null;
527 this.get_cell_elements().filter(function (index) {
527 this.get_cell_elements().filter(function (index) {
528 if ($(this).data("cell").selected === true) {
528 if ($(this).data("cell").selected === true) {
529 result = index;
529 result = index;
530 }
530 }
531 });
531 });
532 return result;
532 return result;
533 };
533 };
534
534
535
535
536 // Cell selection.
536 // Cell selection.
537
537
538 /**
538 /**
539 * Programmatically select a cell.
539 * Programmatically select a cell.
540 *
540 *
541 * @method select
541 * @method select
542 * @param {Number} index A cell's index
542 * @param {Number} index A cell's index
543 * @return {Notebook} This notebook
543 * @return {Notebook} This notebook
544 */
544 */
545 Notebook.prototype.select = function (index) {
545 Notebook.prototype.select = function (index) {
546 if (this.is_valid_cell_index(index)) {
546 if (this.is_valid_cell_index(index)) {
547 var sindex = this.get_selected_index();
547 var sindex = this.get_selected_index();
548 if (sindex !== null && index !== sindex) {
548 if (sindex !== null && index !== sindex) {
549 // If we are about to select a different cell, make sure we are
549 // If we are about to select a different cell, make sure we are
550 // first in command mode.
550 // first in command mode.
551 if (this.mode !== 'command') {
551 if (this.mode !== 'command') {
552 this.command_mode();
552 this.command_mode();
553 }
553 }
554 this.get_cell(sindex).unselect();
554 this.get_cell(sindex).unselect();
555 }
555 }
556 var cell = this.get_cell(index);
556 var cell = this.get_cell(index);
557 cell.select();
557 cell.select();
558 if (cell.cell_type === 'heading') {
558 if (cell.cell_type === 'heading') {
559 this.events.trigger('selected_cell_type_changed.Notebook',
559 this.events.trigger('selected_cell_type_changed.Notebook',
560 {'cell_type':cell.cell_type,level:cell.level}
560 {'cell_type':cell.cell_type,level:cell.level}
561 );
561 );
562 } else {
562 } else {
563 this.events.trigger('selected_cell_type_changed.Notebook',
563 this.events.trigger('selected_cell_type_changed.Notebook',
564 {'cell_type':cell.cell_type}
564 {'cell_type':cell.cell_type}
565 );
565 );
566 }
566 }
567 }
567 }
568 return this;
568 return this;
569 };
569 };
570
570
571 /**
571 /**
572 * Programmatically select the next cell.
572 * Programmatically select the next cell.
573 *
573 *
574 * @method select_next
574 * @method select_next
575 * @return {Notebook} This notebook
575 * @return {Notebook} This notebook
576 */
576 */
577 Notebook.prototype.select_next = function () {
577 Notebook.prototype.select_next = function () {
578 var index = this.get_selected_index();
578 var index = this.get_selected_index();
579 this.select(index+1);
579 this.select(index+1);
580 return this;
580 return this;
581 };
581 };
582
582
583 /**
583 /**
584 * Programmatically select the previous cell.
584 * Programmatically select the previous cell.
585 *
585 *
586 * @method select_prev
586 * @method select_prev
587 * @return {Notebook} This notebook
587 * @return {Notebook} This notebook
588 */
588 */
589 Notebook.prototype.select_prev = function () {
589 Notebook.prototype.select_prev = function () {
590 var index = this.get_selected_index();
590 var index = this.get_selected_index();
591 this.select(index-1);
591 this.select(index-1);
592 return this;
592 return this;
593 };
593 };
594
594
595
595
596 // Edit/Command mode
596 // Edit/Command mode
597
597
598 /**
598 /**
599 * Gets the index of the cell that is in edit mode.
599 * Gets the index of the cell that is in edit mode.
600 *
600 *
601 * @method get_edit_index
601 * @method get_edit_index
602 *
602 *
603 * @return index {int}
603 * @return index {int}
604 **/
604 **/
605 Notebook.prototype.get_edit_index = function () {
605 Notebook.prototype.get_edit_index = function () {
606 var result = null;
606 var result = null;
607 this.get_cell_elements().filter(function (index) {
607 this.get_cell_elements().filter(function (index) {
608 if ($(this).data("cell").mode === 'edit') {
608 if ($(this).data("cell").mode === 'edit') {
609 result = index;
609 result = index;
610 }
610 }
611 });
611 });
612 return result;
612 return result;
613 };
613 };
614
614
615 /**
615 /**
616 * Handle when a a cell blurs and the notebook should enter command mode.
616 * Handle when a a cell blurs and the notebook should enter command mode.
617 *
617 *
618 * @method handle_command_mode
618 * @method handle_command_mode
619 * @param [cell] {Cell} Cell to enter command mode on.
619 * @param [cell] {Cell} Cell to enter command mode on.
620 **/
620 **/
621 Notebook.prototype.handle_command_mode = function (cell) {
621 Notebook.prototype.handle_command_mode = function (cell) {
622 if (this.mode !== 'command') {
622 if (this.mode !== 'command') {
623 cell.command_mode();
623 cell.command_mode();
624 this.mode = 'command';
624 this.mode = 'command';
625 this.events.trigger('command_mode.Notebook');
625 this.events.trigger('command_mode.Notebook');
626 this.keyboard_manager.command_mode();
626 this.keyboard_manager.command_mode();
627 }
627 }
628 };
628 };
629
629
630 /**
630 /**
631 * Make the notebook enter command mode.
631 * Make the notebook enter command mode.
632 *
632 *
633 * @method command_mode
633 * @method command_mode
634 **/
634 **/
635 Notebook.prototype.command_mode = function () {
635 Notebook.prototype.command_mode = function () {
636 var cell = this.get_cell(this.get_edit_index());
636 var cell = this.get_cell(this.get_edit_index());
637 if (cell && this.mode !== 'command') {
637 if (cell && this.mode !== 'command') {
638 // We don't call cell.command_mode, but rather call cell.focus_cell()
638 // We don't call cell.command_mode, but rather call cell.focus_cell()
639 // which will blur and CM editor and trigger the call to
639 // which will blur and CM editor and trigger the call to
640 // handle_command_mode.
640 // handle_command_mode.
641 cell.focus_cell();
641 cell.focus_cell();
642 }
642 }
643 };
643 };
644
644
645 /**
645 /**
646 * Handle when a cell fires it's edit_mode event.
646 * Handle when a cell fires it's edit_mode event.
647 *
647 *
648 * @method handle_edit_mode
648 * @method handle_edit_mode
649 * @param [cell] {Cell} Cell to enter edit mode on.
649 * @param [cell] {Cell} Cell to enter edit mode on.
650 **/
650 **/
651 Notebook.prototype.handle_edit_mode = function (cell) {
651 Notebook.prototype.handle_edit_mode = function (cell) {
652 if (cell && this.mode !== 'edit') {
652 if (cell && this.mode !== 'edit') {
653 cell.edit_mode();
653 cell.edit_mode();
654 this.mode = 'edit';
654 this.mode = 'edit';
655 this.events.trigger('edit_mode.Notebook');
655 this.events.trigger('edit_mode.Notebook');
656 this.keyboard_manager.edit_mode();
656 this.keyboard_manager.edit_mode();
657 }
657 }
658 };
658 };
659
659
660 /**
660 /**
661 * Make a cell enter edit mode.
661 * Make a cell enter edit mode.
662 *
662 *
663 * @method edit_mode
663 * @method edit_mode
664 **/
664 **/
665 Notebook.prototype.edit_mode = function () {
665 Notebook.prototype.edit_mode = function () {
666 var cell = this.get_selected_cell();
666 var cell = this.get_selected_cell();
667 if (cell && this.mode !== 'edit') {
667 if (cell && this.mode !== 'edit') {
668 cell.unrender();
668 cell.unrender();
669 cell.focus_editor();
669 cell.focus_editor();
670 }
670 }
671 };
671 };
672
672
673 /**
673 /**
674 * Focus the currently selected cell.
674 * Focus the currently selected cell.
675 *
675 *
676 * @method focus_cell
676 * @method focus_cell
677 **/
677 **/
678 Notebook.prototype.focus_cell = function () {
678 Notebook.prototype.focus_cell = function () {
679 var cell = this.get_selected_cell();
679 var cell = this.get_selected_cell();
680 if (cell === null) {return;} // No cell is selected
680 if (cell === null) {return;} // No cell is selected
681 cell.focus_cell();
681 cell.focus_cell();
682 };
682 };
683
683
684 // Cell movement
684 // Cell movement
685
685
686 /**
686 /**
687 * Move given (or selected) cell up and select it.
687 * Move given (or selected) cell up and select it.
688 *
688 *
689 * @method move_cell_up
689 * @method move_cell_up
690 * @param [index] {integer} cell index
690 * @param [index] {integer} cell index
691 * @return {Notebook} This notebook
691 * @return {Notebook} This notebook
692 **/
692 **/
693 Notebook.prototype.move_cell_up = function (index) {
693 Notebook.prototype.move_cell_up = function (index) {
694 var i = this.index_or_selected(index);
694 var i = this.index_or_selected(index);
695 if (this.is_valid_cell_index(i) && i > 0) {
695 if (this.is_valid_cell_index(i) && i > 0) {
696 var pivot = this.get_cell_element(i-1);
696 var pivot = this.get_cell_element(i-1);
697 var tomove = this.get_cell_element(i);
697 var tomove = this.get_cell_element(i);
698 if (pivot !== null && tomove !== null) {
698 if (pivot !== null && tomove !== null) {
699 tomove.detach();
699 tomove.detach();
700 pivot.before(tomove);
700 pivot.before(tomove);
701 this.select(i-1);
701 this.select(i-1);
702 var cell = this.get_selected_cell();
702 var cell = this.get_selected_cell();
703 cell.focus_cell();
703 cell.focus_cell();
704 }
704 }
705 this.set_dirty(true);
705 this.set_dirty(true);
706 }
706 }
707 return this;
707 return this;
708 };
708 };
709
709
710
710
711 /**
711 /**
712 * Move given (or selected) cell down and select it
712 * Move given (or selected) cell down and select it
713 *
713 *
714 * @method move_cell_down
714 * @method move_cell_down
715 * @param [index] {integer} cell index
715 * @param [index] {integer} cell index
716 * @return {Notebook} This notebook
716 * @return {Notebook} This notebook
717 **/
717 **/
718 Notebook.prototype.move_cell_down = function (index) {
718 Notebook.prototype.move_cell_down = function (index) {
719 var i = this.index_or_selected(index);
719 var i = this.index_or_selected(index);
720 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
720 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
721 var pivot = this.get_cell_element(i+1);
721 var pivot = this.get_cell_element(i+1);
722 var tomove = this.get_cell_element(i);
722 var tomove = this.get_cell_element(i);
723 if (pivot !== null && tomove !== null) {
723 if (pivot !== null && tomove !== null) {
724 tomove.detach();
724 tomove.detach();
725 pivot.after(tomove);
725 pivot.after(tomove);
726 this.select(i+1);
726 this.select(i+1);
727 var cell = this.get_selected_cell();
727 var cell = this.get_selected_cell();
728 cell.focus_cell();
728 cell.focus_cell();
729 }
729 }
730 }
730 }
731 this.set_dirty();
731 this.set_dirty();
732 return this;
732 return this;
733 };
733 };
734
734
735
735
736 // Insertion, deletion.
736 // Insertion, deletion.
737
737
738 /**
738 /**
739 * Delete a cell from the notebook.
739 * Delete a cell from the notebook.
740 *
740 *
741 * @method delete_cell
741 * @method delete_cell
742 * @param [index] A cell's numeric index
742 * @param [index] A cell's numeric index
743 * @return {Notebook} This notebook
743 * @return {Notebook} This notebook
744 */
744 */
745 Notebook.prototype.delete_cell = function (index) {
745 Notebook.prototype.delete_cell = function (index) {
746 var i = this.index_or_selected(index);
746 var i = this.index_or_selected(index);
747 var cell = this.get_cell(i);
747 var cell = this.get_cell(i);
748 if (!cell.is_deletable()) {
748 if (!cell.is_deletable()) {
749 return this;
749 return this;
750 }
750 }
751
751
752 this.undelete_backup = cell.toJSON();
752 this.undelete_backup = cell.toJSON();
753 $('#undelete_cell').removeClass('disabled');
753 $('#undelete_cell').removeClass('disabled');
754 if (this.is_valid_cell_index(i)) {
754 if (this.is_valid_cell_index(i)) {
755 var old_ncells = this.ncells();
755 var old_ncells = this.ncells();
756 var ce = this.get_cell_element(i);
756 var ce = this.get_cell_element(i);
757 ce.remove();
757 ce.remove();
758 if (i === 0) {
758 if (i === 0) {
759 // Always make sure we have at least one cell.
759 // Always make sure we have at least one cell.
760 if (old_ncells === 1) {
760 if (old_ncells === 1) {
761 this.insert_cell_below('code');
761 this.insert_cell_below('code');
762 }
762 }
763 this.select(0);
763 this.select(0);
764 this.undelete_index = 0;
764 this.undelete_index = 0;
765 this.undelete_below = false;
765 this.undelete_below = false;
766 } else if (i === old_ncells-1 && i !== 0) {
766 } else if (i === old_ncells-1 && i !== 0) {
767 this.select(i-1);
767 this.select(i-1);
768 this.undelete_index = i - 1;
768 this.undelete_index = i - 1;
769 this.undelete_below = true;
769 this.undelete_below = true;
770 } else {
770 } else {
771 this.select(i);
771 this.select(i);
772 this.undelete_index = i;
772 this.undelete_index = i;
773 this.undelete_below = false;
773 this.undelete_below = false;
774 }
774 }
775 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
775 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
776 this.set_dirty(true);
776 this.set_dirty(true);
777 }
777 }
778 return this;
778 return this;
779 };
779 };
780
780
781 /**
781 /**
782 * Restore the most recently deleted cell.
782 * Restore the most recently deleted cell.
783 *
783 *
784 * @method undelete
784 * @method undelete
785 */
785 */
786 Notebook.prototype.undelete_cell = function() {
786 Notebook.prototype.undelete_cell = function() {
787 if (this.undelete_backup !== null && this.undelete_index !== null) {
787 if (this.undelete_backup !== null && this.undelete_index !== null) {
788 var current_index = this.get_selected_index();
788 var current_index = this.get_selected_index();
789 if (this.undelete_index < current_index) {
789 if (this.undelete_index < current_index) {
790 current_index = current_index + 1;
790 current_index = current_index + 1;
791 }
791 }
792 if (this.undelete_index >= this.ncells()) {
792 if (this.undelete_index >= this.ncells()) {
793 this.select(this.ncells() - 1);
793 this.select(this.ncells() - 1);
794 }
794 }
795 else {
795 else {
796 this.select(this.undelete_index);
796 this.select(this.undelete_index);
797 }
797 }
798 var cell_data = this.undelete_backup;
798 var cell_data = this.undelete_backup;
799 var new_cell = null;
799 var new_cell = null;
800 if (this.undelete_below) {
800 if (this.undelete_below) {
801 new_cell = this.insert_cell_below(cell_data.cell_type);
801 new_cell = this.insert_cell_below(cell_data.cell_type);
802 } else {
802 } else {
803 new_cell = this.insert_cell_above(cell_data.cell_type);
803 new_cell = this.insert_cell_above(cell_data.cell_type);
804 }
804 }
805 new_cell.fromJSON(cell_data);
805 new_cell.fromJSON(cell_data);
806 if (this.undelete_below) {
806 if (this.undelete_below) {
807 this.select(current_index+1);
807 this.select(current_index+1);
808 } else {
808 } else {
809 this.select(current_index);
809 this.select(current_index);
810 }
810 }
811 this.undelete_backup = null;
811 this.undelete_backup = null;
812 this.undelete_index = null;
812 this.undelete_index = null;
813 }
813 }
814 $('#undelete_cell').addClass('disabled');
814 $('#undelete_cell').addClass('disabled');
815 };
815 };
816
816
817 /**
817 /**
818 * Insert a cell so that after insertion the cell is at given index.
818 * Insert a cell so that after insertion the cell is at given index.
819 *
819 *
820 * If cell type is not provided, it will default to the type of the
820 * If cell type is not provided, it will default to the type of the
821 * currently active cell.
821 * currently active cell.
822 *
822 *
823 * Similar to insert_above, but index parameter is mandatory
823 * Similar to insert_above, but index parameter is mandatory
824 *
824 *
825 * Index will be brought back into the accessible range [0,n]
825 * Index will be brought back into the accessible range [0,n]
826 *
826 *
827 * @method insert_cell_at_index
827 * @method insert_cell_at_index
828 * @param [type] {string} in ['code','markdown', 'raw'], defaults to 'code'
828 * @param [type] {string} in ['code','markdown', 'raw'], defaults to 'code'
829 * @param [index] {int} a valid index where to insert cell
829 * @param [index] {int} a valid index where to insert cell
830 *
830 *
831 * @return cell {cell|null} created cell or null
831 * @return cell {cell|null} created cell or null
832 **/
832 **/
833 Notebook.prototype.insert_cell_at_index = function(type, index){
833 Notebook.prototype.insert_cell_at_index = function(type, index){
834
834
835 var ncells = this.ncells();
835 var ncells = this.ncells();
836 index = Math.min(index, ncells);
836 index = Math.min(index, ncells);
837 index = Math.max(index, 0);
837 index = Math.max(index, 0);
838 var cell = null;
838 var cell = null;
839 type = type || this.default_cell_type;
839 type = type || this.default_cell_type;
840 if (type === 'above') {
840 if (type === 'above') {
841 if (index > 0) {
841 if (index > 0) {
842 type = this.get_cell(index-1).cell_type;
842 type = this.get_cell(index-1).cell_type;
843 } else {
843 } else {
844 type = 'code';
844 type = 'code';
845 }
845 }
846 } else if (type === 'below') {
846 } else if (type === 'below') {
847 if (index < ncells) {
847 if (index < ncells) {
848 type = this.get_cell(index).cell_type;
848 type = this.get_cell(index).cell_type;
849 } else {
849 } else {
850 type = 'code';
850 type = 'code';
851 }
851 }
852 } else if (type === 'selected') {
852 } else if (type === 'selected') {
853 type = this.get_selected_cell().cell_type;
853 type = this.get_selected_cell().cell_type;
854 }
854 }
855
855
856 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
856 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
857 var cell_options = {
857 var cell_options = {
858 events: this.events,
858 events: this.events,
859 config: this.config,
859 config: this.config,
860 keyboard_manager: this.keyboard_manager,
860 keyboard_manager: this.keyboard_manager,
861 notebook: this,
861 notebook: this,
862 tooltip: this.tooltip,
862 tooltip: this.tooltip,
863 };
863 };
864 switch(type) {
864 switch(type) {
865 case 'code':
865 case 'code':
866 cell = new codecell.CodeCell(this.kernel, cell_options);
866 cell = new codecell.CodeCell(this.kernel, cell_options);
867 cell.set_input_prompt();
867 cell.set_input_prompt();
868 break;
868 break;
869 case 'markdown':
869 case 'markdown':
870 cell = new textcell.MarkdownCell(cell_options);
870 cell = new textcell.MarkdownCell(cell_options);
871 break;
871 break;
872 case 'raw':
872 case 'raw':
873 cell = new textcell.RawCell(cell_options);
873 cell = new textcell.RawCell(cell_options);
874 break;
874 break;
875 default:
875 default:
876 console.log("invalid cell type: ", type);
876 console.log("invalid cell type: ", type);
877 }
877 }
878
878
879 if(this._insert_element_at_index(cell.element,index)) {
879 if(this._insert_element_at_index(cell.element,index)) {
880 cell.render();
880 cell.render();
881 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
881 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
882 cell.refresh();
882 cell.refresh();
883 // We used to select the cell after we refresh it, but there
883 // We used to select the cell after we refresh it, but there
884 // are now cases were this method is called where select is
884 // are now cases were this method is called where select is
885 // not appropriate. The selection logic should be handled by the
885 // not appropriate. The selection logic should be handled by the
886 // caller of the the top level insert_cell methods.
886 // caller of the the top level insert_cell methods.
887 this.set_dirty(true);
887 this.set_dirty(true);
888 }
888 }
889 }
889 }
890 return cell;
890 return cell;
891
891
892 };
892 };
893
893
894 /**
894 /**
895 * Insert an element at given cell index.
895 * Insert an element at given cell index.
896 *
896 *
897 * @method _insert_element_at_index
897 * @method _insert_element_at_index
898 * @param element {dom_element} a cell element
898 * @param element {dom_element} a cell element
899 * @param [index] {int} a valid index where to inser cell
899 * @param [index] {int} a valid index where to inser cell
900 * @private
900 * @private
901 *
901 *
902 * return true if everything whent fine.
902 * return true if everything whent fine.
903 **/
903 **/
904 Notebook.prototype._insert_element_at_index = function(element, index){
904 Notebook.prototype._insert_element_at_index = function(element, index){
905 if (element === undefined){
905 if (element === undefined){
906 return false;
906 return false;
907 }
907 }
908
908
909 var ncells = this.ncells();
909 var ncells = this.ncells();
910
910
911 if (ncells === 0) {
911 if (ncells === 0) {
912 // special case append if empty
912 // special case append if empty
913 this.element.find('div.end_space').before(element);
913 this.element.find('div.end_space').before(element);
914 } else if ( ncells === index ) {
914 } else if ( ncells === index ) {
915 // special case append it the end, but not empty
915 // special case append it the end, but not empty
916 this.get_cell_element(index-1).after(element);
916 this.get_cell_element(index-1).after(element);
917 } else if (this.is_valid_cell_index(index)) {
917 } else if (this.is_valid_cell_index(index)) {
918 // otherwise always somewhere to append to
918 // otherwise always somewhere to append to
919 this.get_cell_element(index).before(element);
919 this.get_cell_element(index).before(element);
920 } else {
920 } else {
921 return false;
921 return false;
922 }
922 }
923
923
924 if (this.undelete_index !== null && index <= this.undelete_index) {
924 if (this.undelete_index !== null && index <= this.undelete_index) {
925 this.undelete_index = this.undelete_index + 1;
925 this.undelete_index = this.undelete_index + 1;
926 this.set_dirty(true);
926 this.set_dirty(true);
927 }
927 }
928 return true;
928 return true;
929 };
929 };
930
930
931 /**
931 /**
932 * Insert a cell of given type above given index, or at top
932 * Insert a cell of given type above given index, or at top
933 * of notebook if index smaller than 0.
933 * of notebook if index smaller than 0.
934 *
934 *
935 * default index value is the one of currently selected cell
935 * default index value is the one of currently selected cell
936 *
936 *
937 * @method insert_cell_above
937 * @method insert_cell_above
938 * @param [type] {string} cell type
938 * @param [type] {string} cell type
939 * @param [index] {integer}
939 * @param [index] {integer}
940 *
940 *
941 * @return handle to created cell or null
941 * @return handle to created cell or null
942 **/
942 **/
943 Notebook.prototype.insert_cell_above = function (type, index) {
943 Notebook.prototype.insert_cell_above = function (type, index) {
944 index = this.index_or_selected(index);
944 index = this.index_or_selected(index);
945 return this.insert_cell_at_index(type, index);
945 return this.insert_cell_at_index(type, index);
946 };
946 };
947
947
948 /**
948 /**
949 * Insert a cell of given type below given index, or at bottom
949 * Insert a cell of given type below given index, or at bottom
950 * of notebook if index greater than number of cells
950 * of notebook if index greater than number of cells
951 *
951 *
952 * default index value is the one of currently selected cell
952 * default index value is the one of currently selected cell
953 *
953 *
954 * @method insert_cell_below
954 * @method insert_cell_below
955 * @param [type] {string} cell type
955 * @param [type] {string} cell type
956 * @param [index] {integer}
956 * @param [index] {integer}
957 *
957 *
958 * @return handle to created cell or null
958 * @return handle to created cell or null
959 *
959 *
960 **/
960 **/
961 Notebook.prototype.insert_cell_below = function (type, index) {
961 Notebook.prototype.insert_cell_below = function (type, index) {
962 index = this.index_or_selected(index);
962 index = this.index_or_selected(index);
963 return this.insert_cell_at_index(type, index+1);
963 return this.insert_cell_at_index(type, index+1);
964 };
964 };
965
965
966
966
967 /**
967 /**
968 * Insert cell at end of notebook
968 * Insert cell at end of notebook
969 *
969 *
970 * @method insert_cell_at_bottom
970 * @method insert_cell_at_bottom
971 * @param {String} type cell type
971 * @param {String} type cell type
972 *
972 *
973 * @return the added cell; or null
973 * @return the added cell; or null
974 **/
974 **/
975 Notebook.prototype.insert_cell_at_bottom = function (type){
975 Notebook.prototype.insert_cell_at_bottom = function (type){
976 var len = this.ncells();
976 var len = this.ncells();
977 return this.insert_cell_below(type,len-1);
977 return this.insert_cell_below(type,len-1);
978 };
978 };
979
979
980 /**
980 /**
981 * Turn a cell into a code cell.
981 * Turn a cell into a code cell.
982 *
982 *
983 * @method to_code
983 * @method to_code
984 * @param {Number} [index] A cell's index
984 * @param {Number} [index] A cell's index
985 */
985 */
986 Notebook.prototype.to_code = function (index) {
986 Notebook.prototype.to_code = function (index) {
987 var i = this.index_or_selected(index);
987 var i = this.index_or_selected(index);
988 if (this.is_valid_cell_index(i)) {
988 if (this.is_valid_cell_index(i)) {
989 var source_cell = this.get_cell(i);
989 var source_cell = this.get_cell(i);
990 if (!(source_cell instanceof codecell.CodeCell)) {
990 if (!(source_cell instanceof codecell.CodeCell)) {
991 var target_cell = this.insert_cell_below('code',i);
991 var target_cell = this.insert_cell_below('code',i);
992 var text = source_cell.get_text();
992 var text = source_cell.get_text();
993 if (text === source_cell.placeholder) {
993 if (text === source_cell.placeholder) {
994 text = '';
994 text = '';
995 }
995 }
996 //metadata
996 //metadata
997 target_cell.metadata = source_cell.metadata;
997 target_cell.metadata = source_cell.metadata;
998
998
999 target_cell.set_text(text);
999 target_cell.set_text(text);
1000 // make this value the starting point, so that we can only undo
1000 // make this value the starting point, so that we can only undo
1001 // to this state, instead of a blank cell
1001 // to this state, instead of a blank cell
1002 target_cell.code_mirror.clearHistory();
1002 target_cell.code_mirror.clearHistory();
1003 source_cell.element.remove();
1003 source_cell.element.remove();
1004 this.select(i);
1004 this.select(i);
1005 var cursor = source_cell.code_mirror.getCursor();
1005 var cursor = source_cell.code_mirror.getCursor();
1006 target_cell.code_mirror.setCursor(cursor);
1006 target_cell.code_mirror.setCursor(cursor);
1007 this.set_dirty(true);
1007 this.set_dirty(true);
1008 }
1008 }
1009 }
1009 }
1010 };
1010 };
1011
1011
1012 /**
1012 /**
1013 * Turn a cell into a Markdown cell.
1013 * Turn a cell into a Markdown cell.
1014 *
1014 *
1015 * @method to_markdown
1015 * @method to_markdown
1016 * @param {Number} [index] A cell's index
1016 * @param {Number} [index] A cell's index
1017 */
1017 */
1018 Notebook.prototype.to_markdown = function (index) {
1018 Notebook.prototype.to_markdown = function (index) {
1019 var i = this.index_or_selected(index);
1019 var i = this.index_or_selected(index);
1020 if (this.is_valid_cell_index(i)) {
1020 if (this.is_valid_cell_index(i)) {
1021 var source_cell = this.get_cell(i);
1021 var source_cell = this.get_cell(i);
1022
1022
1023 if (!(source_cell instanceof textcell.MarkdownCell)) {
1023 if (!(source_cell instanceof textcell.MarkdownCell)) {
1024 var target_cell = this.insert_cell_below('markdown',i);
1024 var target_cell = this.insert_cell_below('markdown',i);
1025 var text = source_cell.get_text();
1025 var text = source_cell.get_text();
1026
1026
1027 if (text === source_cell.placeholder) {
1027 if (text === source_cell.placeholder) {
1028 text = '';
1028 text = '';
1029 }
1029 }
1030 // metadata
1030 // metadata
1031 target_cell.metadata = source_cell.metadata
1031 target_cell.metadata = source_cell.metadata
1032 // We must show the editor before setting its contents
1032 // We must show the editor before setting its contents
1033 target_cell.unrender();
1033 target_cell.unrender();
1034 target_cell.set_text(text);
1034 target_cell.set_text(text);
1035 // make this value the starting point, so that we can only undo
1035 // make this value the starting point, so that we can only undo
1036 // to this state, instead of a blank cell
1036 // to this state, instead of a blank cell
1037 target_cell.code_mirror.clearHistory();
1037 target_cell.code_mirror.clearHistory();
1038 source_cell.element.remove();
1038 source_cell.element.remove();
1039 this.select(i);
1039 this.select(i);
1040 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1040 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1041 target_cell.render();
1041 target_cell.render();
1042 }
1042 }
1043 var cursor = source_cell.code_mirror.getCursor();
1043 var cursor = source_cell.code_mirror.getCursor();
1044 target_cell.code_mirror.setCursor(cursor);
1044 target_cell.code_mirror.setCursor(cursor);
1045 this.set_dirty(true);
1045 this.set_dirty(true);
1046 }
1046 }
1047 }
1047 }
1048 };
1048 };
1049
1049
1050 /**
1050 /**
1051 * Turn a cell into a raw text cell.
1051 * Turn a cell into a raw text cell.
1052 *
1052 *
1053 * @method to_raw
1053 * @method to_raw
1054 * @param {Number} [index] A cell's index
1054 * @param {Number} [index] A cell's index
1055 */
1055 */
1056 Notebook.prototype.to_raw = function (index) {
1056 Notebook.prototype.to_raw = function (index) {
1057 var i = this.index_or_selected(index);
1057 var i = this.index_or_selected(index);
1058 if (this.is_valid_cell_index(i)) {
1058 if (this.is_valid_cell_index(i)) {
1059 var target_cell = null;
1059 var target_cell = null;
1060 var source_cell = this.get_cell(i);
1060 var source_cell = this.get_cell(i);
1061
1061
1062 if (!(source_cell instanceof textcell.RawCell)) {
1062 if (!(source_cell instanceof textcell.RawCell)) {
1063 target_cell = this.insert_cell_below('raw',i);
1063 target_cell = this.insert_cell_below('raw',i);
1064 var text = source_cell.get_text();
1064 var text = source_cell.get_text();
1065 if (text === source_cell.placeholder) {
1065 if (text === source_cell.placeholder) {
1066 text = '';
1066 text = '';
1067 }
1067 }
1068 //metadata
1068 //metadata
1069 target_cell.metadata = source_cell.metadata;
1069 target_cell.metadata = source_cell.metadata;
1070 // We must show the editor before setting its contents
1070 // We must show the editor before setting its contents
1071 target_cell.unrender();
1071 target_cell.unrender();
1072 target_cell.set_text(text);
1072 target_cell.set_text(text);
1073 // make this value the starting point, so that we can only undo
1073 // make this value the starting point, so that we can only undo
1074 // to this state, instead of a blank cell
1074 // to this state, instead of a blank cell
1075 target_cell.code_mirror.clearHistory();
1075 target_cell.code_mirror.clearHistory();
1076 source_cell.element.remove();
1076 source_cell.element.remove();
1077 this.select(i);
1077 this.select(i);
1078 var cursor = source_cell.code_mirror.getCursor();
1078 var cursor = source_cell.code_mirror.getCursor();
1079 target_cell.code_mirror.setCursor(cursor);
1079 target_cell.code_mirror.setCursor(cursor);
1080 this.set_dirty(true);
1080 this.set_dirty(true);
1081 }
1081 }
1082 }
1082 }
1083 };
1083 };
1084
1084
1085 /**
1085 /**
1086 * Turn a cell into a heading cell.
1086 * Turn a cell into a heading cell.
1087 *
1087 *
1088 * @method to_heading
1088 * @method to_heading
1089 * @param {Number} [index] A cell's index
1089 * @param {Number} [index] A cell's index
1090 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1090 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1091 */
1091 */
1092 Notebook.prototype.to_heading = function (index, level) {
1092 Notebook.prototype.to_heading = function (index, level) {
1093 level = level || 1;
1093 level = level || 1;
1094 var i = this.index_or_selected(index);
1094 var i = this.index_or_selected(index);
1095 if (this.is_valid_cell_index(i)) {
1095 if (this.is_valid_cell_index(i)) {
1096 var source_cell = this.get_cell(i);
1096 var source_cell = this.get_cell(i);
1097 var target_cell = null;
1097 var target_cell = null;
1098 if (source_cell instanceof textcell.MarkdownCell) {
1098 if (source_cell instanceof textcell.MarkdownCell) {
1099 source_cell.set_heading_level(level);
1099 source_cell.set_heading_level(level);
1100 } else {
1100 } else {
1101 target_cell = this.insert_cell_below('markdown',i);
1101 target_cell = this.insert_cell_below('markdown',i);
1102 var text = source_cell.get_text();
1102 var text = source_cell.get_text();
1103 if (text === source_cell.placeholder) {
1103 if (text === source_cell.placeholder) {
1104 text = '';
1104 text = '';
1105 }
1105 }
1106 //metadata
1106 //metadata
1107 target_cell.metadata = source_cell.metadata;
1107 target_cell.metadata = source_cell.metadata;
1108 // We must show the editor before setting its contents
1108 // We must show the editor before setting its contents
1109 target_cell.unrender();
1109 target_cell.unrender();
1110 target_cell.set_text(text);
1110 target_cell.set_text(text);
1111 target_cell.set_heading_level(level);
1111 target_cell.set_heading_level(level);
1112 // make this value the starting point, so that we can only undo
1112 // make this value the starting point, so that we can only undo
1113 // to this state, instead of a blank cell
1113 // to this state, instead of a blank cell
1114 target_cell.code_mirror.clearHistory();
1114 target_cell.code_mirror.clearHistory();
1115 source_cell.element.remove();
1115 source_cell.element.remove();
1116 this.select(i);
1116 this.select(i);
1117 var cursor = source_cell.code_mirror.getCursor();
1117 var cursor = source_cell.code_mirror.getCursor();
1118 target_cell.code_mirror.setCursor(cursor);
1118 target_cell.code_mirror.setCursor(cursor);
1119 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1119 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1120 target_cell.render();
1120 target_cell.render();
1121 }
1121 }
1122 }
1122 }
1123 this.set_dirty(true);
1123 this.set_dirty(true);
1124 this.events.trigger('selected_cell_type_changed.Notebook',
1124 this.events.trigger('selected_cell_type_changed.Notebook',
1125 {'cell_type':'markdown',level:level}
1125 {'cell_type':'markdown',level:level}
1126 );
1126 );
1127 }
1127 }
1128 };
1128 };
1129
1129
1130
1130
1131 // Cut/Copy/Paste
1131 // Cut/Copy/Paste
1132
1132
1133 /**
1133 /**
1134 * Enable UI elements for pasting cells.
1134 * Enable UI elements for pasting cells.
1135 *
1135 *
1136 * @method enable_paste
1136 * @method enable_paste
1137 */
1137 */
1138 Notebook.prototype.enable_paste = function () {
1138 Notebook.prototype.enable_paste = function () {
1139 var that = this;
1139 var that = this;
1140 if (!this.paste_enabled) {
1140 if (!this.paste_enabled) {
1141 $('#paste_cell_replace').removeClass('disabled')
1141 $('#paste_cell_replace').removeClass('disabled')
1142 .on('click', function () {that.paste_cell_replace();});
1142 .on('click', function () {that.paste_cell_replace();});
1143 $('#paste_cell_above').removeClass('disabled')
1143 $('#paste_cell_above').removeClass('disabled')
1144 .on('click', function () {that.paste_cell_above();});
1144 .on('click', function () {that.paste_cell_above();});
1145 $('#paste_cell_below').removeClass('disabled')
1145 $('#paste_cell_below').removeClass('disabled')
1146 .on('click', function () {that.paste_cell_below();});
1146 .on('click', function () {that.paste_cell_below();});
1147 this.paste_enabled = true;
1147 this.paste_enabled = true;
1148 }
1148 }
1149 };
1149 };
1150
1150
1151 /**
1151 /**
1152 * Disable UI elements for pasting cells.
1152 * Disable UI elements for pasting cells.
1153 *
1153 *
1154 * @method disable_paste
1154 * @method disable_paste
1155 */
1155 */
1156 Notebook.prototype.disable_paste = function () {
1156 Notebook.prototype.disable_paste = function () {
1157 if (this.paste_enabled) {
1157 if (this.paste_enabled) {
1158 $('#paste_cell_replace').addClass('disabled').off('click');
1158 $('#paste_cell_replace').addClass('disabled').off('click');
1159 $('#paste_cell_above').addClass('disabled').off('click');
1159 $('#paste_cell_above').addClass('disabled').off('click');
1160 $('#paste_cell_below').addClass('disabled').off('click');
1160 $('#paste_cell_below').addClass('disabled').off('click');
1161 this.paste_enabled = false;
1161 this.paste_enabled = false;
1162 }
1162 }
1163 };
1163 };
1164
1164
1165 /**
1165 /**
1166 * Cut a cell.
1166 * Cut a cell.
1167 *
1167 *
1168 * @method cut_cell
1168 * @method cut_cell
1169 */
1169 */
1170 Notebook.prototype.cut_cell = function () {
1170 Notebook.prototype.cut_cell = function () {
1171 this.copy_cell();
1171 this.copy_cell();
1172 this.delete_cell();
1172 this.delete_cell();
1173 };
1173 };
1174
1174
1175 /**
1175 /**
1176 * Copy a cell.
1176 * Copy a cell.
1177 *
1177 *
1178 * @method copy_cell
1178 * @method copy_cell
1179 */
1179 */
1180 Notebook.prototype.copy_cell = function () {
1180 Notebook.prototype.copy_cell = function () {
1181 var cell = this.get_selected_cell();
1181 var cell = this.get_selected_cell();
1182 this.clipboard = cell.toJSON();
1182 this.clipboard = cell.toJSON();
1183 // remove undeletable status from the copied cell
1183 // remove undeletable status from the copied cell
1184 if (this.clipboard.metadata.deletable !== undefined) {
1184 if (this.clipboard.metadata.deletable !== undefined) {
1185 delete this.clipboard.metadata.deletable;
1185 delete this.clipboard.metadata.deletable;
1186 }
1186 }
1187 this.enable_paste();
1187 this.enable_paste();
1188 };
1188 };
1189
1189
1190 /**
1190 /**
1191 * Replace the selected cell with a cell in the clipboard.
1191 * Replace the selected cell with a cell in the clipboard.
1192 *
1192 *
1193 * @method paste_cell_replace
1193 * @method paste_cell_replace
1194 */
1194 */
1195 Notebook.prototype.paste_cell_replace = function () {
1195 Notebook.prototype.paste_cell_replace = function () {
1196 if (this.clipboard !== null && this.paste_enabled) {
1196 if (this.clipboard !== null && this.paste_enabled) {
1197 var cell_data = this.clipboard;
1197 var cell_data = this.clipboard;
1198 var new_cell = this.insert_cell_above(cell_data.cell_type);
1198 var new_cell = this.insert_cell_above(cell_data.cell_type);
1199 new_cell.fromJSON(cell_data);
1199 new_cell.fromJSON(cell_data);
1200 var old_cell = this.get_next_cell(new_cell);
1200 var old_cell = this.get_next_cell(new_cell);
1201 this.delete_cell(this.find_cell_index(old_cell));
1201 this.delete_cell(this.find_cell_index(old_cell));
1202 this.select(this.find_cell_index(new_cell));
1202 this.select(this.find_cell_index(new_cell));
1203 }
1203 }
1204 };
1204 };
1205
1205
1206 /**
1206 /**
1207 * Paste a cell from the clipboard above the selected cell.
1207 * Paste a cell from the clipboard above the selected cell.
1208 *
1208 *
1209 * @method paste_cell_above
1209 * @method paste_cell_above
1210 */
1210 */
1211 Notebook.prototype.paste_cell_above = function () {
1211 Notebook.prototype.paste_cell_above = function () {
1212 if (this.clipboard !== null && this.paste_enabled) {
1212 if (this.clipboard !== null && this.paste_enabled) {
1213 var cell_data = this.clipboard;
1213 var cell_data = this.clipboard;
1214 var new_cell = this.insert_cell_above(cell_data.cell_type);
1214 var new_cell = this.insert_cell_above(cell_data.cell_type);
1215 new_cell.fromJSON(cell_data);
1215 new_cell.fromJSON(cell_data);
1216 new_cell.focus_cell();
1216 new_cell.focus_cell();
1217 }
1217 }
1218 };
1218 };
1219
1219
1220 /**
1220 /**
1221 * Paste a cell from the clipboard below the selected cell.
1221 * Paste a cell from the clipboard below the selected cell.
1222 *
1222 *
1223 * @method paste_cell_below
1223 * @method paste_cell_below
1224 */
1224 */
1225 Notebook.prototype.paste_cell_below = function () {
1225 Notebook.prototype.paste_cell_below = function () {
1226 if (this.clipboard !== null && this.paste_enabled) {
1226 if (this.clipboard !== null && this.paste_enabled) {
1227 var cell_data = this.clipboard;
1227 var cell_data = this.clipboard;
1228 var new_cell = this.insert_cell_below(cell_data.cell_type);
1228 var new_cell = this.insert_cell_below(cell_data.cell_type);
1229 new_cell.fromJSON(cell_data);
1229 new_cell.fromJSON(cell_data);
1230 new_cell.focus_cell();
1230 new_cell.focus_cell();
1231 }
1231 }
1232 };
1232 };
1233
1233
1234 // Split/merge
1234 // Split/merge
1235
1235
1236 /**
1236 /**
1237 * Split the selected cell into two, at the cursor.
1237 * Split the selected cell into two, at the cursor.
1238 *
1238 *
1239 * @method split_cell
1239 * @method split_cell
1240 */
1240 */
1241 Notebook.prototype.split_cell = function () {
1241 Notebook.prototype.split_cell = function () {
1242 var mdc = textcell.MarkdownCell;
1242 var mdc = textcell.MarkdownCell;
1243 var rc = textcell.RawCell;
1243 var rc = textcell.RawCell;
1244 var cell = this.get_selected_cell();
1244 var cell = this.get_selected_cell();
1245 if (cell.is_splittable()) {
1245 if (cell.is_splittable()) {
1246 var texta = cell.get_pre_cursor();
1246 var texta = cell.get_pre_cursor();
1247 var textb = cell.get_post_cursor();
1247 var textb = cell.get_post_cursor();
1248 cell.set_text(textb);
1248 cell.set_text(textb);
1249 var new_cell = this.insert_cell_above(cell.cell_type);
1249 var new_cell = this.insert_cell_above(cell.cell_type);
1250 // Unrender the new cell so we can call set_text.
1250 // Unrender the new cell so we can call set_text.
1251 new_cell.unrender();
1251 new_cell.unrender();
1252 new_cell.set_text(texta);
1252 new_cell.set_text(texta);
1253 }
1253 }
1254 };
1254 };
1255
1255
1256 /**
1256 /**
1257 * Combine the selected cell into the cell above it.
1257 * Combine the selected cell into the cell above it.
1258 *
1258 *
1259 * @method merge_cell_above
1259 * @method merge_cell_above
1260 */
1260 */
1261 Notebook.prototype.merge_cell_above = function () {
1261 Notebook.prototype.merge_cell_above = function () {
1262 var mdc = textcell.MarkdownCell;
1262 var mdc = textcell.MarkdownCell;
1263 var rc = textcell.RawCell;
1263 var rc = textcell.RawCell;
1264 var index = this.get_selected_index();
1264 var index = this.get_selected_index();
1265 var cell = this.get_cell(index);
1265 var cell = this.get_cell(index);
1266 var render = cell.rendered;
1266 var render = cell.rendered;
1267 if (!cell.is_mergeable()) {
1267 if (!cell.is_mergeable()) {
1268 return;
1268 return;
1269 }
1269 }
1270 if (index > 0) {
1270 if (index > 0) {
1271 var upper_cell = this.get_cell(index-1);
1271 var upper_cell = this.get_cell(index-1);
1272 if (!upper_cell.is_mergeable()) {
1272 if (!upper_cell.is_mergeable()) {
1273 return;
1273 return;
1274 }
1274 }
1275 var upper_text = upper_cell.get_text();
1275 var upper_text = upper_cell.get_text();
1276 var text = cell.get_text();
1276 var text = cell.get_text();
1277 if (cell instanceof codecell.CodeCell) {
1277 if (cell instanceof codecell.CodeCell) {
1278 cell.set_text(upper_text+'\n'+text);
1278 cell.set_text(upper_text+'\n'+text);
1279 } else {
1279 } else {
1280 cell.unrender(); // Must unrender before we set_text.
1280 cell.unrender(); // Must unrender before we set_text.
1281 cell.set_text(upper_text+'\n\n'+text);
1281 cell.set_text(upper_text+'\n\n'+text);
1282 if (render) {
1282 if (render) {
1283 // The rendered state of the final cell should match
1283 // The rendered state of the final cell should match
1284 // that of the original selected cell;
1284 // that of the original selected cell;
1285 cell.render();
1285 cell.render();
1286 }
1286 }
1287 }
1287 }
1288 this.delete_cell(index-1);
1288 this.delete_cell(index-1);
1289 this.select(this.find_cell_index(cell));
1289 this.select(this.find_cell_index(cell));
1290 }
1290 }
1291 };
1291 };
1292
1292
1293 /**
1293 /**
1294 * Combine the selected cell into the cell below it.
1294 * Combine the selected cell into the cell below it.
1295 *
1295 *
1296 * @method merge_cell_below
1296 * @method merge_cell_below
1297 */
1297 */
1298 Notebook.prototype.merge_cell_below = function () {
1298 Notebook.prototype.merge_cell_below = function () {
1299 var mdc = textcell.MarkdownCell;
1299 var mdc = textcell.MarkdownCell;
1300 var rc = textcell.RawCell;
1300 var rc = textcell.RawCell;
1301 var index = this.get_selected_index();
1301 var index = this.get_selected_index();
1302 var cell = this.get_cell(index);
1302 var cell = this.get_cell(index);
1303 var render = cell.rendered;
1303 var render = cell.rendered;
1304 if (!cell.is_mergeable()) {
1304 if (!cell.is_mergeable()) {
1305 return;
1305 return;
1306 }
1306 }
1307 if (index < this.ncells()-1) {
1307 if (index < this.ncells()-1) {
1308 var lower_cell = this.get_cell(index+1);
1308 var lower_cell = this.get_cell(index+1);
1309 if (!lower_cell.is_mergeable()) {
1309 if (!lower_cell.is_mergeable()) {
1310 return;
1310 return;
1311 }
1311 }
1312 var lower_text = lower_cell.get_text();
1312 var lower_text = lower_cell.get_text();
1313 var text = cell.get_text();
1313 var text = cell.get_text();
1314 if (cell instanceof codecell.CodeCell) {
1314 if (cell instanceof codecell.CodeCell) {
1315 cell.set_text(text+'\n'+lower_text);
1315 cell.set_text(text+'\n'+lower_text);
1316 } else {
1316 } else {
1317 cell.unrender(); // Must unrender before we set_text.
1317 cell.unrender(); // Must unrender before we set_text.
1318 cell.set_text(text+'\n\n'+lower_text);
1318 cell.set_text(text+'\n\n'+lower_text);
1319 if (render) {
1319 if (render) {
1320 // The rendered state of the final cell should match
1320 // The rendered state of the final cell should match
1321 // that of the original selected cell;
1321 // that of the original selected cell;
1322 cell.render();
1322 cell.render();
1323 }
1323 }
1324 }
1324 }
1325 this.delete_cell(index+1);
1325 this.delete_cell(index+1);
1326 this.select(this.find_cell_index(cell));
1326 this.select(this.find_cell_index(cell));
1327 }
1327 }
1328 };
1328 };
1329
1329
1330
1330
1331 // Cell collapsing and output clearing
1331 // Cell collapsing and output clearing
1332
1332
1333 /**
1333 /**
1334 * Hide a cell's output.
1334 * Hide a cell's output.
1335 *
1335 *
1336 * @method collapse_output
1336 * @method collapse_output
1337 * @param {Number} index A cell's numeric index
1337 * @param {Number} index A cell's numeric index
1338 */
1338 */
1339 Notebook.prototype.collapse_output = function (index) {
1339 Notebook.prototype.collapse_output = function (index) {
1340 var i = this.index_or_selected(index);
1340 var i = this.index_or_selected(index);
1341 var cell = this.get_cell(i);
1341 var cell = this.get_cell(i);
1342 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1342 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1343 cell.collapse_output();
1343 cell.collapse_output();
1344 this.set_dirty(true);
1344 this.set_dirty(true);
1345 }
1345 }
1346 };
1346 };
1347
1347
1348 /**
1348 /**
1349 * Hide each code cell's output area.
1349 * Hide each code cell's output area.
1350 *
1350 *
1351 * @method collapse_all_output
1351 * @method collapse_all_output
1352 */
1352 */
1353 Notebook.prototype.collapse_all_output = function () {
1353 Notebook.prototype.collapse_all_output = function () {
1354 $.map(this.get_cells(), function (cell, i) {
1354 $.map(this.get_cells(), function (cell, i) {
1355 if (cell instanceof codecell.CodeCell) {
1355 if (cell instanceof codecell.CodeCell) {
1356 cell.collapse_output();
1356 cell.collapse_output();
1357 }
1357 }
1358 });
1358 });
1359 // this should not be set if the `collapse` key is removed from nbformat
1359 // this should not be set if the `collapse` key is removed from nbformat
1360 this.set_dirty(true);
1360 this.set_dirty(true);
1361 };
1361 };
1362
1362
1363 /**
1363 /**
1364 * Show a cell's output.
1364 * Show a cell's output.
1365 *
1365 *
1366 * @method expand_output
1366 * @method expand_output
1367 * @param {Number} index A cell's numeric index
1367 * @param {Number} index A cell's numeric index
1368 */
1368 */
1369 Notebook.prototype.expand_output = function (index) {
1369 Notebook.prototype.expand_output = function (index) {
1370 var i = this.index_or_selected(index);
1370 var i = this.index_or_selected(index);
1371 var cell = this.get_cell(i);
1371 var cell = this.get_cell(i);
1372 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1372 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1373 cell.expand_output();
1373 cell.expand_output();
1374 this.set_dirty(true);
1374 this.set_dirty(true);
1375 }
1375 }
1376 };
1376 };
1377
1377
1378 /**
1378 /**
1379 * Expand each code cell's output area, and remove scrollbars.
1379 * Expand each code cell's output area, and remove scrollbars.
1380 *
1380 *
1381 * @method expand_all_output
1381 * @method expand_all_output
1382 */
1382 */
1383 Notebook.prototype.expand_all_output = function () {
1383 Notebook.prototype.expand_all_output = function () {
1384 $.map(this.get_cells(), function (cell, i) {
1384 $.map(this.get_cells(), function (cell, i) {
1385 if (cell instanceof codecell.CodeCell) {
1385 if (cell instanceof codecell.CodeCell) {
1386 cell.expand_output();
1386 cell.expand_output();
1387 }
1387 }
1388 });
1388 });
1389 // this should not be set if the `collapse` key is removed from nbformat
1389 // this should not be set if the `collapse` key is removed from nbformat
1390 this.set_dirty(true);
1390 this.set_dirty(true);
1391 };
1391 };
1392
1392
1393 /**
1393 /**
1394 * Clear the selected CodeCell's output area.
1394 * Clear the selected CodeCell's output area.
1395 *
1395 *
1396 * @method clear_output
1396 * @method clear_output
1397 * @param {Number} index A cell's numeric index
1397 * @param {Number} index A cell's numeric index
1398 */
1398 */
1399 Notebook.prototype.clear_output = function (index) {
1399 Notebook.prototype.clear_output = function (index) {
1400 var i = this.index_or_selected(index);
1400 var i = this.index_or_selected(index);
1401 var cell = this.get_cell(i);
1401 var cell = this.get_cell(i);
1402 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1402 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1403 cell.clear_output();
1403 cell.clear_output();
1404 this.set_dirty(true);
1404 this.set_dirty(true);
1405 }
1405 }
1406 };
1406 };
1407
1407
1408 /**
1408 /**
1409 * Clear each code cell's output area.
1409 * Clear each code cell's output area.
1410 *
1410 *
1411 * @method clear_all_output
1411 * @method clear_all_output
1412 */
1412 */
1413 Notebook.prototype.clear_all_output = function () {
1413 Notebook.prototype.clear_all_output = function () {
1414 $.map(this.get_cells(), function (cell, i) {
1414 $.map(this.get_cells(), function (cell, i) {
1415 if (cell instanceof codecell.CodeCell) {
1415 if (cell instanceof codecell.CodeCell) {
1416 cell.clear_output();
1416 cell.clear_output();
1417 }
1417 }
1418 });
1418 });
1419 this.set_dirty(true);
1419 this.set_dirty(true);
1420 };
1420 };
1421
1421
1422 /**
1422 /**
1423 * Scroll the selected CodeCell's output area.
1423 * Scroll the selected CodeCell's output area.
1424 *
1424 *
1425 * @method scroll_output
1425 * @method scroll_output
1426 * @param {Number} index A cell's numeric index
1426 * @param {Number} index A cell's numeric index
1427 */
1427 */
1428 Notebook.prototype.scroll_output = function (index) {
1428 Notebook.prototype.scroll_output = function (index) {
1429 var i = this.index_or_selected(index);
1429 var i = this.index_or_selected(index);
1430 var cell = this.get_cell(i);
1430 var cell = this.get_cell(i);
1431 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1431 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1432 cell.scroll_output();
1432 cell.scroll_output();
1433 this.set_dirty(true);
1433 this.set_dirty(true);
1434 }
1434 }
1435 };
1435 };
1436
1436
1437 /**
1437 /**
1438 * Expand each code cell's output area, and add a scrollbar for long output.
1438 * Expand each code cell's output area, and add a scrollbar for long output.
1439 *
1439 *
1440 * @method scroll_all_output
1440 * @method scroll_all_output
1441 */
1441 */
1442 Notebook.prototype.scroll_all_output = function () {
1442 Notebook.prototype.scroll_all_output = function () {
1443 $.map(this.get_cells(), function (cell, i) {
1443 $.map(this.get_cells(), function (cell, i) {
1444 if (cell instanceof codecell.CodeCell) {
1444 if (cell instanceof codecell.CodeCell) {
1445 cell.scroll_output();
1445 cell.scroll_output();
1446 }
1446 }
1447 });
1447 });
1448 // this should not be set if the `collapse` key is removed from nbformat
1448 // this should not be set if the `collapse` key is removed from nbformat
1449 this.set_dirty(true);
1449 this.set_dirty(true);
1450 };
1450 };
1451
1451
1452 /** Toggle whether a cell's output is collapsed or expanded.
1452 /** Toggle whether a cell's output is collapsed or expanded.
1453 *
1453 *
1454 * @method toggle_output
1454 * @method toggle_output
1455 * @param {Number} index A cell's numeric index
1455 * @param {Number} index A cell's numeric index
1456 */
1456 */
1457 Notebook.prototype.toggle_output = function (index) {
1457 Notebook.prototype.toggle_output = function (index) {
1458 var i = this.index_or_selected(index);
1458 var i = this.index_or_selected(index);
1459 var cell = this.get_cell(i);
1459 var cell = this.get_cell(i);
1460 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1460 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1461 cell.toggle_output();
1461 cell.toggle_output();
1462 this.set_dirty(true);
1462 this.set_dirty(true);
1463 }
1463 }
1464 };
1464 };
1465
1465
1466 /**
1466 /**
1467 * Hide/show the output of all cells.
1467 * Hide/show the output of all cells.
1468 *
1468 *
1469 * @method toggle_all_output
1469 * @method toggle_all_output
1470 */
1470 */
1471 Notebook.prototype.toggle_all_output = function () {
1471 Notebook.prototype.toggle_all_output = function () {
1472 $.map(this.get_cells(), function (cell, i) {
1472 $.map(this.get_cells(), function (cell, i) {
1473 if (cell instanceof codecell.CodeCell) {
1473 if (cell instanceof codecell.CodeCell) {
1474 cell.toggle_output();
1474 cell.toggle_output();
1475 }
1475 }
1476 });
1476 });
1477 // this should not be set if the `collapse` key is removed from nbformat
1477 // this should not be set if the `collapse` key is removed from nbformat
1478 this.set_dirty(true);
1478 this.set_dirty(true);
1479 };
1479 };
1480
1480
1481 /**
1481 /**
1482 * Toggle a scrollbar for long cell outputs.
1482 * Toggle a scrollbar for long cell outputs.
1483 *
1483 *
1484 * @method toggle_output_scroll
1484 * @method toggle_output_scroll
1485 * @param {Number} index A cell's numeric index
1485 * @param {Number} index A cell's numeric index
1486 */
1486 */
1487 Notebook.prototype.toggle_output_scroll = function (index) {
1487 Notebook.prototype.toggle_output_scroll = function (index) {
1488 var i = this.index_or_selected(index);
1488 var i = this.index_or_selected(index);
1489 var cell = this.get_cell(i);
1489 var cell = this.get_cell(i);
1490 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1490 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1491 cell.toggle_output_scroll();
1491 cell.toggle_output_scroll();
1492 this.set_dirty(true);
1492 this.set_dirty(true);
1493 }
1493 }
1494 };
1494 };
1495
1495
1496 /**
1496 /**
1497 * Toggle the scrolling of long output on all cells.
1497 * Toggle the scrolling of long output on all cells.
1498 *
1498 *
1499 * @method toggle_all_output_scrolling
1499 * @method toggle_all_output_scrolling
1500 */
1500 */
1501 Notebook.prototype.toggle_all_output_scroll = function () {
1501 Notebook.prototype.toggle_all_output_scroll = function () {
1502 $.map(this.get_cells(), function (cell, i) {
1502 $.map(this.get_cells(), function (cell, i) {
1503 if (cell instanceof codecell.CodeCell) {
1503 if (cell instanceof codecell.CodeCell) {
1504 cell.toggle_output_scroll();
1504 cell.toggle_output_scroll();
1505 }
1505 }
1506 });
1506 });
1507 // this should not be set if the `collapse` key is removed from nbformat
1507 // this should not be set if the `collapse` key is removed from nbformat
1508 this.set_dirty(true);
1508 this.set_dirty(true);
1509 };
1509 };
1510
1510
1511 // Other cell functions: line numbers, ...
1511 // Other cell functions: line numbers, ...
1512
1512
1513 /**
1513 /**
1514 * Toggle line numbers in the selected cell's input area.
1514 * Toggle line numbers in the selected cell's input area.
1515 *
1515 *
1516 * @method cell_toggle_line_numbers
1516 * @method cell_toggle_line_numbers
1517 */
1517 */
1518 Notebook.prototype.cell_toggle_line_numbers = function() {
1518 Notebook.prototype.cell_toggle_line_numbers = function() {
1519 this.get_selected_cell().toggle_line_numbers();
1519 this.get_selected_cell().toggle_line_numbers();
1520 };
1520 };
1521
1521
1522 /**
1522 /**
1523 * Set the codemirror mode for all code cells, including the default for
1523 * Set the codemirror mode for all code cells, including the default for
1524 * new code cells.
1524 * new code cells.
1525 *
1525 *
1526 * @method set_codemirror_mode
1526 * @method set_codemirror_mode
1527 */
1527 */
1528 Notebook.prototype.set_codemirror_mode = function(newmode){
1528 Notebook.prototype.set_codemirror_mode = function(newmode){
1529 if (newmode === this.codemirror_mode) {
1529 if (newmode === this.codemirror_mode) {
1530 return;
1530 return;
1531 }
1531 }
1532 this.codemirror_mode = newmode;
1532 this.codemirror_mode = newmode;
1533 codecell.CodeCell.options_default.cm_config.mode = newmode;
1533 codecell.CodeCell.options_default.cm_config.mode = newmode;
1534 modename = newmode.mode || newmode.name || newmode;
1534 modename = newmode.mode || newmode.name || newmode;
1535
1535
1536 that = this;
1536 that = this;
1537 utils.requireCodeMirrorMode(modename, function () {
1537 utils.requireCodeMirrorMode(modename, function () {
1538 $.map(that.get_cells(), function(cell, i) {
1538 $.map(that.get_cells(), function(cell, i) {
1539 if (cell.cell_type === 'code'){
1539 if (cell.cell_type === 'code'){
1540 cell.code_mirror.setOption('mode', newmode);
1540 cell.code_mirror.setOption('mode', newmode);
1541 // This is currently redundant, because cm_config ends up as
1541 // This is currently redundant, because cm_config ends up as
1542 // codemirror's own .options object, but I don't want to
1542 // codemirror's own .options object, but I don't want to
1543 // rely on that.
1543 // rely on that.
1544 cell.cm_config.mode = newmode;
1544 cell.cm_config.mode = newmode;
1545 }
1545 }
1546 });
1546 });
1547 });
1547 });
1548 };
1548 };
1549
1549
1550 // Session related things
1550 // Session related things
1551
1551
1552 /**
1552 /**
1553 * Start a new session and set it on each code cell.
1553 * Start a new session and set it on each code cell.
1554 *
1554 *
1555 * @method start_session
1555 * @method start_session
1556 */
1556 */
1557 Notebook.prototype.start_session = function (kernel_name) {
1557 Notebook.prototype.start_session = function (kernel_name) {
1558 var that = this;
1558 var that = this;
1559 if (this._session_starting) {
1559 if (this._session_starting) {
1560 throw new session.SessionAlreadyStarting();
1560 throw new session.SessionAlreadyStarting();
1561 }
1561 }
1562 this._session_starting = true;
1562 this._session_starting = true;
1563
1563
1564 var options = {
1564 var options = {
1565 base_url: this.base_url,
1565 base_url: this.base_url,
1566 ws_url: this.ws_url,
1566 ws_url: this.ws_url,
1567 notebook_path: this.notebook_path,
1567 notebook_path: this.notebook_path,
1568 notebook_name: this.notebook_name,
1568 notebook_name: this.notebook_name,
1569 kernel_name: kernel_name,
1569 kernel_name: kernel_name,
1570 notebook: this
1570 notebook: this
1571 };
1571 };
1572
1572
1573 var success = $.proxy(this._session_started, this);
1573 var success = $.proxy(this._session_started, this);
1574 var failure = $.proxy(this._session_start_failed, this);
1574 var failure = $.proxy(this._session_start_failed, this);
1575
1575
1576 if (this.session !== null) {
1576 if (this.session !== null) {
1577 this.session.restart(options, success, failure);
1577 this.session.restart(options, success, failure);
1578 } else {
1578 } else {
1579 this.session = new session.Session(options);
1579 this.session = new session.Session(options);
1580 this.session.start(success, failure);
1580 this.session.start(success, failure);
1581 }
1581 }
1582 };
1582 };
1583
1583
1584
1584
1585 /**
1585 /**
1586 * Once a session is started, link the code cells to the kernel and pass the
1586 * Once a session is started, link the code cells to the kernel and pass the
1587 * comm manager to the widget manager
1587 * comm manager to the widget manager
1588 *
1588 *
1589 */
1589 */
1590 Notebook.prototype._session_started = function (){
1590 Notebook.prototype._session_started = function (){
1591 this._session_starting = false;
1591 this._session_starting = false;
1592 this.kernel = this.session.kernel;
1592 this.kernel = this.session.kernel;
1593 var ncells = this.ncells();
1593 var ncells = this.ncells();
1594 for (var i=0; i<ncells; i++) {
1594 for (var i=0; i<ncells; i++) {
1595 var cell = this.get_cell(i);
1595 var cell = this.get_cell(i);
1596 if (cell instanceof codecell.CodeCell) {
1596 if (cell instanceof codecell.CodeCell) {
1597 cell.set_kernel(this.session.kernel);
1597 cell.set_kernel(this.session.kernel);
1598 }
1598 }
1599 }
1599 }
1600 };
1600 };
1601 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1601 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1602 this._session_starting = false;
1602 this._session_starting = false;
1603 utils.log_ajax_error(jqxhr, status, error);
1603 utils.log_ajax_error(jqxhr, status, error);
1604 };
1604 };
1605
1605
1606 /**
1606 /**
1607 * Prompt the user to restart the IPython kernel.
1607 * Prompt the user to restart the IPython kernel.
1608 *
1608 *
1609 * @method restart_kernel
1609 * @method restart_kernel
1610 */
1610 */
1611 Notebook.prototype.restart_kernel = function () {
1611 Notebook.prototype.restart_kernel = function () {
1612 var that = this;
1612 var that = this;
1613 dialog.modal({
1613 dialog.modal({
1614 notebook: this,
1614 notebook: this,
1615 keyboard_manager: this.keyboard_manager,
1615 keyboard_manager: this.keyboard_manager,
1616 title : "Restart kernel or continue running?",
1616 title : "Restart kernel or continue running?",
1617 body : $("<p/>").text(
1617 body : $("<p/>").text(
1618 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1618 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1619 ),
1619 ),
1620 buttons : {
1620 buttons : {
1621 "Continue running" : {},
1621 "Continue running" : {},
1622 "Restart" : {
1622 "Restart" : {
1623 "class" : "btn-danger",
1623 "class" : "btn-danger",
1624 "click" : function() {
1624 "click" : function() {
1625 that.kernel.restart();
1625 that.kernel.restart();
1626 }
1626 }
1627 }
1627 }
1628 }
1628 }
1629 });
1629 });
1630 };
1630 };
1631
1631
1632 /**
1632 /**
1633 * Execute or render cell outputs and go into command mode.
1633 * Execute or render cell outputs and go into command mode.
1634 *
1634 *
1635 * @method execute_cell
1635 * @method execute_cell
1636 */
1636 */
1637 Notebook.prototype.execute_cell = function () {
1637 Notebook.prototype.execute_cell = function () {
1638 // mode = shift, ctrl, alt
1638 // mode = shift, ctrl, alt
1639 var cell = this.get_selected_cell();
1639 var cell = this.get_selected_cell();
1640 var cell_index = this.find_cell_index(cell);
1640 var cell_index = this.find_cell_index(cell);
1641
1641
1642 cell.execute();
1642 cell.execute();
1643 this.command_mode();
1643 this.command_mode();
1644 this.set_dirty(true);
1644 this.set_dirty(true);
1645 };
1645 };
1646
1646
1647 /**
1647 /**
1648 * Execute or render cell outputs and insert a new cell below.
1648 * Execute or render cell outputs and insert a new cell below.
1649 *
1649 *
1650 * @method execute_cell_and_insert_below
1650 * @method execute_cell_and_insert_below
1651 */
1651 */
1652 Notebook.prototype.execute_cell_and_insert_below = function () {
1652 Notebook.prototype.execute_cell_and_insert_below = function () {
1653 var cell = this.get_selected_cell();
1653 var cell = this.get_selected_cell();
1654 var cell_index = this.find_cell_index(cell);
1654 var cell_index = this.find_cell_index(cell);
1655
1655
1656 cell.execute();
1656 cell.execute();
1657
1657
1658 // If we are at the end always insert a new cell and return
1658 // If we are at the end always insert a new cell and return
1659 if (cell_index === (this.ncells()-1)) {
1659 if (cell_index === (this.ncells()-1)) {
1660 this.command_mode();
1660 this.command_mode();
1661 this.insert_cell_below();
1661 this.insert_cell_below();
1662 this.select(cell_index+1);
1662 this.select(cell_index+1);
1663 this.edit_mode();
1663 this.edit_mode();
1664 this.scroll_to_bottom();
1664 this.scroll_to_bottom();
1665 this.set_dirty(true);
1665 this.set_dirty(true);
1666 return;
1666 return;
1667 }
1667 }
1668
1668
1669 this.command_mode();
1669 this.command_mode();
1670 this.insert_cell_below();
1670 this.insert_cell_below();
1671 this.select(cell_index+1);
1671 this.select(cell_index+1);
1672 this.edit_mode();
1672 this.edit_mode();
1673 this.set_dirty(true);
1673 this.set_dirty(true);
1674 };
1674 };
1675
1675
1676 /**
1676 /**
1677 * Execute or render cell outputs and select the next cell.
1677 * Execute or render cell outputs and select the next cell.
1678 *
1678 *
1679 * @method execute_cell_and_select_below
1679 * @method execute_cell_and_select_below
1680 */
1680 */
1681 Notebook.prototype.execute_cell_and_select_below = function () {
1681 Notebook.prototype.execute_cell_and_select_below = function () {
1682
1682
1683 var cell = this.get_selected_cell();
1683 var cell = this.get_selected_cell();
1684 var cell_index = this.find_cell_index(cell);
1684 var cell_index = this.find_cell_index(cell);
1685
1685
1686 cell.execute();
1686 cell.execute();
1687
1687
1688 // If we are at the end always insert a new cell and return
1688 // If we are at the end always insert a new cell and return
1689 if (cell_index === (this.ncells()-1)) {
1689 if (cell_index === (this.ncells()-1)) {
1690 this.command_mode();
1690 this.command_mode();
1691 this.insert_cell_below();
1691 this.insert_cell_below();
1692 this.select(cell_index+1);
1692 this.select(cell_index+1);
1693 this.edit_mode();
1693 this.edit_mode();
1694 this.scroll_to_bottom();
1694 this.scroll_to_bottom();
1695 this.set_dirty(true);
1695 this.set_dirty(true);
1696 return;
1696 return;
1697 }
1697 }
1698
1698
1699 this.command_mode();
1699 this.command_mode();
1700 this.select(cell_index+1);
1700 this.select(cell_index+1);
1701 this.focus_cell();
1701 this.focus_cell();
1702 this.set_dirty(true);
1702 this.set_dirty(true);
1703 };
1703 };
1704
1704
1705 /**
1705 /**
1706 * Execute all cells below the selected cell.
1706 * Execute all cells below the selected cell.
1707 *
1707 *
1708 * @method execute_cells_below
1708 * @method execute_cells_below
1709 */
1709 */
1710 Notebook.prototype.execute_cells_below = function () {
1710 Notebook.prototype.execute_cells_below = function () {
1711 this.execute_cell_range(this.get_selected_index(), this.ncells());
1711 this.execute_cell_range(this.get_selected_index(), this.ncells());
1712 this.scroll_to_bottom();
1712 this.scroll_to_bottom();
1713 };
1713 };
1714
1714
1715 /**
1715 /**
1716 * Execute all cells above the selected cell.
1716 * Execute all cells above the selected cell.
1717 *
1717 *
1718 * @method execute_cells_above
1718 * @method execute_cells_above
1719 */
1719 */
1720 Notebook.prototype.execute_cells_above = function () {
1720 Notebook.prototype.execute_cells_above = function () {
1721 this.execute_cell_range(0, this.get_selected_index());
1721 this.execute_cell_range(0, this.get_selected_index());
1722 };
1722 };
1723
1723
1724 /**
1724 /**
1725 * Execute all cells.
1725 * Execute all cells.
1726 *
1726 *
1727 * @method execute_all_cells
1727 * @method execute_all_cells
1728 */
1728 */
1729 Notebook.prototype.execute_all_cells = function () {
1729 Notebook.prototype.execute_all_cells = function () {
1730 this.execute_cell_range(0, this.ncells());
1730 this.execute_cell_range(0, this.ncells());
1731 this.scroll_to_bottom();
1731 this.scroll_to_bottom();
1732 };
1732 };
1733
1733
1734 /**
1734 /**
1735 * Execute a contiguous range of cells.
1735 * Execute a contiguous range of cells.
1736 *
1736 *
1737 * @method execute_cell_range
1737 * @method execute_cell_range
1738 * @param {Number} start Index of the first cell to execute (inclusive)
1738 * @param {Number} start Index of the first cell to execute (inclusive)
1739 * @param {Number} end Index of the last cell to execute (exclusive)
1739 * @param {Number} end Index of the last cell to execute (exclusive)
1740 */
1740 */
1741 Notebook.prototype.execute_cell_range = function (start, end) {
1741 Notebook.prototype.execute_cell_range = function (start, end) {
1742 this.command_mode();
1742 this.command_mode();
1743 for (var i=start; i<end; i++) {
1743 for (var i=start; i<end; i++) {
1744 this.select(i);
1744 this.select(i);
1745 this.execute_cell();
1745 this.execute_cell();
1746 }
1746 }
1747 };
1747 };
1748
1748
1749 // Persistance and loading
1749 // Persistance and loading
1750
1750
1751 /**
1751 /**
1752 * Getter method for this notebook's name.
1752 * Getter method for this notebook's name.
1753 *
1753 *
1754 * @method get_notebook_name
1754 * @method get_notebook_name
1755 * @return {String} This notebook's name (excluding file extension)
1755 * @return {String} This notebook's name (excluding file extension)
1756 */
1756 */
1757 Notebook.prototype.get_notebook_name = function () {
1757 Notebook.prototype.get_notebook_name = function () {
1758 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1758 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1759 return nbname;
1759 return nbname;
1760 };
1760 };
1761
1761
1762 /**
1762 /**
1763 * Setter method for this notebook's name.
1763 * Setter method for this notebook's name.
1764 *
1764 *
1765 * @method set_notebook_name
1765 * @method set_notebook_name
1766 * @param {String} name A new name for this notebook
1766 * @param {String} name A new name for this notebook
1767 */
1767 */
1768 Notebook.prototype.set_notebook_name = function (name) {
1768 Notebook.prototype.set_notebook_name = function (name) {
1769 this.notebook_name = name;
1769 this.notebook_name = name;
1770 };
1770 };
1771
1771
1772 /**
1772 /**
1773 * Check that a notebook's name is valid.
1773 * Check that a notebook's name is valid.
1774 *
1774 *
1775 * @method test_notebook_name
1775 * @method test_notebook_name
1776 * @param {String} nbname A name for this notebook
1776 * @param {String} nbname A name for this notebook
1777 * @return {Boolean} True if the name is valid, false if invalid
1777 * @return {Boolean} True if the name is valid, false if invalid
1778 */
1778 */
1779 Notebook.prototype.test_notebook_name = function (nbname) {
1779 Notebook.prototype.test_notebook_name = function (nbname) {
1780 nbname = nbname || '';
1780 nbname = nbname || '';
1781 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1781 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1782 return true;
1782 return true;
1783 } else {
1783 } else {
1784 return false;
1784 return false;
1785 }
1785 }
1786 };
1786 };
1787
1787
1788 /**
1788 /**
1789 * Load a notebook from JSON (.ipynb).
1789 * Load a notebook from JSON (.ipynb).
1790 *
1790 *
1791 * @method fromJSON
1791 * @method fromJSON
1792 * @param {Object} data JSON representation of a notebook
1792 * @param {Object} data JSON representation of a notebook
1793 */
1793 */
1794 Notebook.prototype.fromJSON = function (data) {
1794 Notebook.prototype.fromJSON = function (data) {
1795
1795
1796 var content = data.content;
1796 var content = data.content;
1797 var ncells = this.ncells();
1797 var ncells = this.ncells();
1798 var i;
1798 var i;
1799 for (i=0; i<ncells; i++) {
1799 for (i=0; i<ncells; i++) {
1800 // Always delete cell 0 as they get renumbered as they are deleted.
1800 // Always delete cell 0 as they get renumbered as they are deleted.
1801 this.delete_cell(0);
1801 this.delete_cell(0);
1802 }
1802 }
1803 // Save the metadata and name.
1803 // Save the metadata and name.
1804 this.metadata = content.metadata;
1804 this.metadata = content.metadata;
1805 this.notebook_name = data.name;
1805 this.notebook_name = data.name;
1806 var trusted = true;
1806 var trusted = true;
1807
1807
1808 // Trigger an event changing the kernel spec - this will set the default
1808 // Trigger an event changing the kernel spec - this will set the default
1809 // codemirror mode
1809 // codemirror mode
1810 if (this.metadata.kernelspec !== undefined) {
1810 if (this.metadata.kernelspec !== undefined) {
1811 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1811 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1812 }
1812 }
1813
1813
1814 // Set the codemirror mode from language_info metadata
1814 // Set the codemirror mode from language_info metadata
1815 if (this.metadata.language_info !== undefined) {
1815 if (this.metadata.language_info !== undefined) {
1816 var langinfo = this.metadata.language_info;
1816 var langinfo = this.metadata.language_info;
1817 // Mode 'null' should be plain, unhighlighted text.
1817 // Mode 'null' should be plain, unhighlighted text.
1818 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null'
1818 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null'
1819 this.set_codemirror_mode(cm_mode);
1819 this.set_codemirror_mode(cm_mode);
1820 }
1820 }
1821
1821
1822 var new_cells = content.cells;
1822 var new_cells = content.cells;
1823 ncells = new_cells.length;
1823 ncells = new_cells.length;
1824 var cell_data = null;
1824 var cell_data = null;
1825 var new_cell = null;
1825 var new_cell = null;
1826 for (i=0; i<ncells; i++) {
1826 for (i=0; i<ncells; i++) {
1827 cell_data = new_cells[i];
1827 cell_data = new_cells[i];
1828 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1828 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1829 new_cell.fromJSON(cell_data);
1829 new_cell.fromJSON(cell_data);
1830 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1830 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1831 trusted = false;
1831 trusted = false;
1832 }
1832 }
1833 }
1833 }
1834 if (trusted !== this.trusted) {
1834 if (trusted !== this.trusted) {
1835 this.trusted = trusted;
1835 this.trusted = trusted;
1836 this.events.trigger("trust_changed.Notebook", trusted);
1836 this.events.trigger("trust_changed.Notebook", trusted);
1837 }
1837 }
1838 };
1838 };
1839
1839
1840 /**
1840 /**
1841 * Dump this notebook into a JSON-friendly object.
1841 * Dump this notebook into a JSON-friendly object.
1842 *
1842 *
1843 * @method toJSON
1843 * @method toJSON
1844 * @return {Object} A JSON-friendly representation of this notebook.
1844 * @return {Object} A JSON-friendly representation of this notebook.
1845 */
1845 */
1846 Notebook.prototype.toJSON = function () {
1846 Notebook.prototype.toJSON = function () {
1847 // remove the conversion indicator, which only belongs in-memory
1847 // remove the conversion indicator, which only belongs in-memory
1848 delete this.metadata.orig_nbformat;
1848 delete this.metadata.orig_nbformat;
1849 delete this.metadata.orig_nbformat_minor;
1849 delete this.metadata.orig_nbformat_minor;
1850
1850
1851 var cells = this.get_cells();
1851 var cells = this.get_cells();
1852 var ncells = cells.length;
1852 var ncells = cells.length;
1853 var cell_array = new Array(ncells);
1853 var cell_array = new Array(ncells);
1854 var trusted = true;
1854 var trusted = true;
1855 for (var i=0; i<ncells; i++) {
1855 for (var i=0; i<ncells; i++) {
1856 var cell = cells[i];
1856 var cell = cells[i];
1857 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1857 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1858 trusted = false;
1858 trusted = false;
1859 }
1859 }
1860 cell_array[i] = cell.toJSON();
1860 cell_array[i] = cell.toJSON();
1861 }
1861 }
1862 var data = {
1862 var data = {
1863 cells: cell_array,
1863 cells: cell_array,
1864 metadata : this.metadata
1864 metadata : this.metadata
1865 };
1865 };
1866 if (trusted != this.trusted) {
1866 if (trusted != this.trusted) {
1867 this.trusted = trusted;
1867 this.trusted = trusted;
1868 this.events.trigger("trust_changed.Notebook", trusted);
1868 this.events.trigger("trust_changed.Notebook", trusted);
1869 }
1869 }
1870 return data;
1870 return data;
1871 };
1871 };
1872
1872
1873 /**
1873 /**
1874 * Start an autosave timer, for periodically saving the notebook.
1874 * Start an autosave timer, for periodically saving the notebook.
1875 *
1875 *
1876 * @method set_autosave_interval
1876 * @method set_autosave_interval
1877 * @param {Integer} interval the autosave interval in milliseconds
1877 * @param {Integer} interval the autosave interval in milliseconds
1878 */
1878 */
1879 Notebook.prototype.set_autosave_interval = function (interval) {
1879 Notebook.prototype.set_autosave_interval = function (interval) {
1880 var that = this;
1880 var that = this;
1881 // clear previous interval, so we don't get simultaneous timers
1881 // clear previous interval, so we don't get simultaneous timers
1882 if (this.autosave_timer) {
1882 if (this.autosave_timer) {
1883 clearInterval(this.autosave_timer);
1883 clearInterval(this.autosave_timer);
1884 }
1884 }
1885
1885
1886 this.autosave_interval = this.minimum_autosave_interval = interval;
1886 this.autosave_interval = this.minimum_autosave_interval = interval;
1887 if (interval) {
1887 if (interval) {
1888 this.autosave_timer = setInterval(function() {
1888 this.autosave_timer = setInterval(function() {
1889 if (that.dirty) {
1889 if (that.dirty) {
1890 that.save_notebook();
1890 that.save_notebook();
1891 }
1891 }
1892 }, interval);
1892 }, interval);
1893 this.events.trigger("autosave_enabled.Notebook", interval);
1893 this.events.trigger("autosave_enabled.Notebook", interval);
1894 } else {
1894 } else {
1895 this.autosave_timer = null;
1895 this.autosave_timer = null;
1896 this.events.trigger("autosave_disabled.Notebook");
1896 this.events.trigger("autosave_disabled.Notebook");
1897 }
1897 }
1898 };
1898 };
1899
1899
1900 /**
1900 /**
1901 * Save this notebook on the server. This becomes a notebook instance's
1901 * Save this notebook on the server. This becomes a notebook instance's
1902 * .save_notebook method *after* the entire notebook has been loaded.
1902 * .save_notebook method *after* the entire notebook has been loaded.
1903 *
1903 *
1904 * @method save_notebook
1904 * @method save_notebook
1905 */
1905 */
1906 Notebook.prototype.save_notebook = function (extra_settings) {
1906 Notebook.prototype.save_notebook = function (extra_settings) {
1907 // Create a JSON model to be sent to the server.
1907 this.content_manager.save_notebook(this, extra_settings);
1908 var model = {};
1909 model.name = this.notebook_name;
1910 model.path = this.notebook_path;
1911 model.type = 'notebook';
1912 model.format = 'json';
1913 model.content = this.toJSON();
1914 model.content.nbformat = this.nbformat;
1915 model.content.nbformat_minor = this.nbformat_minor;
1916 // time the ajax call for autosave tuning purposes.
1917 var start = new Date().getTime();
1918 // We do the call with settings so we can set cache to false.
1919 var settings = {
1920 processData : false,
1921 cache : false,
1922 type : "PUT",
1923 data : JSON.stringify(model),
1924 contentType: 'application/json',
1925 dataType : "json",
1926 success : $.proxy(this.save_notebook_success, this, start),
1927 error : $.proxy(this.save_notebook_error, this)
1928 };
1929 if (extra_settings) {
1930 for (var key in extra_settings) {
1931 settings[key] = extra_settings[key];
1932 }
1933 }
1934 this.events.trigger('notebook_saving.Notebook');
1935 var url = utils.url_join_encode(
1936 this.base_url,
1937 'api/contents',
1938 this.notebook_path,
1939 this.notebook_name
1940 );
1941 $.ajax(url, settings);
1942 };
1908 };
1943
1909
1944 /**
1910 /**
1945 * Success callback for saving a notebook.
1911 * Success callback for saving a notebook.
1946 *
1912 *
1947 * @method save_notebook_success
1913 * @method save_notebook_success
1948 * @param {Integer} start the time when the save request started
1914 * @param {Integer} start the time when the save request started
1949 * @param {Object} data JSON representation of a notebook
1915 * @param {Object} data JSON representation of a notebook
1950 * @param {String} status Description of response status
1916 * @param {String} status Description of response status
1951 * @param {jqXHR} xhr jQuery Ajax object
1917 * @param {jqXHR} xhr jQuery Ajax object
1952 */
1918 */
1953 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1919 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1954 this.set_dirty(false);
1920 this.set_dirty(false);
1955 if (data.message) {
1921 if (data.message) {
1956 // save succeeded, but validation failed.
1922 // save succeeded, but validation failed.
1957 var body = $("<div>");
1923 var body = $("<div>");
1958 var title = "Notebook validation failed";
1924 var title = "Notebook validation failed";
1959
1925
1960 body.append($("<p>").text(
1926 body.append($("<p>").text(
1961 "The save operation succeeded," +
1927 "The save operation succeeded," +
1962 " but the notebook does not appear to be valid." +
1928 " but the notebook does not appear to be valid." +
1963 " The validation error was:"
1929 " The validation error was:"
1964 )).append($("<div>").addClass("validation-error").append(
1930 )).append($("<div>").addClass("validation-error").append(
1965 $("<pre>").text(data.message)
1931 $("<pre>").text(data.message)
1966 ));
1932 ));
1967 dialog.modal({
1933 dialog.modal({
1968 notebook: this,
1934 notebook: this,
1969 keyboard_manager: this.keyboard_manager,
1935 keyboard_manager: this.keyboard_manager,
1970 title: title,
1936 title: title,
1971 body: body,
1937 body: body,
1972 buttons : {
1938 buttons : {
1973 OK : {
1939 OK : {
1974 "class" : "btn-primary"
1940 "class" : "btn-primary"
1975 }
1941 }
1976 }
1942 }
1977 });
1943 });
1978 }
1944 }
1979 this.events.trigger('notebook_saved.Notebook');
1945 this.events.trigger('notebook_saved.Notebook');
1980 this._update_autosave_interval(start);
1946 this._update_autosave_interval(start);
1981 if (this._checkpoint_after_save) {
1947 if (this._checkpoint_after_save) {
1982 this.create_checkpoint();
1948 this.create_checkpoint();
1983 this._checkpoint_after_save = false;
1949 this._checkpoint_after_save = false;
1984 }
1950 }
1985 };
1951 };
1986
1952
1987 /**
1953 /**
1988 * update the autosave interval based on how long the last save took
1954 * update the autosave interval based on how long the last save took
1989 *
1955 *
1990 * @method _update_autosave_interval
1956 * @method _update_autosave_interval
1991 * @param {Integer} timestamp when the save request started
1957 * @param {Integer} timestamp when the save request started
1992 */
1958 */
1993 Notebook.prototype._update_autosave_interval = function (start) {
1959 Notebook.prototype._update_autosave_interval = function (start) {
1994 var duration = (new Date().getTime() - start);
1960 var duration = (new Date().getTime() - start);
1995 if (this.autosave_interval) {
1961 if (this.autosave_interval) {
1996 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1962 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1997 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1963 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1998 // round to 10 seconds, otherwise we will be setting a new interval too often
1964 // round to 10 seconds, otherwise we will be setting a new interval too often
1999 interval = 10000 * Math.round(interval / 10000);
1965 interval = 10000 * Math.round(interval / 10000);
2000 // set new interval, if it's changed
1966 // set new interval, if it's changed
2001 if (interval != this.autosave_interval) {
1967 if (interval != this.autosave_interval) {
2002 this.set_autosave_interval(interval);
1968 this.set_autosave_interval(interval);
2003 }
1969 }
2004 }
1970 }
2005 };
1971 };
2006
1972
2007 /**
1973 /**
2008 * Failure callback for saving a notebook.
1974 * Failure callback for saving a notebook.
2009 *
1975 *
2010 * @method save_notebook_error
1976 * @method save_notebook_error
2011 * @param {jqXHR} xhr jQuery Ajax object
1977 * @param {jqXHR} xhr jQuery Ajax object
2012 * @param {String} status Description of response status
1978 * @param {String} status Description of response status
2013 * @param {String} error HTTP error message
1979 * @param {String} error HTTP error message
2014 */
1980 */
2015 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1981 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
2016 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1982 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
2017 };
1983 };
2018
1984
2019 /**
1985 /**
2020 * Explicitly trust the output of this notebook.
1986 * Explicitly trust the output of this notebook.
2021 *
1987 *
2022 * @method trust_notebook
1988 * @method trust_notebook
2023 */
1989 */
2024 Notebook.prototype.trust_notebook = function (extra_settings) {
1990 Notebook.prototype.trust_notebook = function (extra_settings) {
2025 var body = $("<div>").append($("<p>")
1991 var body = $("<div>").append($("<p>")
2026 .text("A trusted IPython notebook may execute hidden malicious code ")
1992 .text("A trusted IPython notebook may execute hidden malicious code ")
2027 .append($("<strong>")
1993 .append($("<strong>")
2028 .append(
1994 .append(
2029 $("<em>").text("when you open it")
1995 $("<em>").text("when you open it")
2030 )
1996 )
2031 ).append(".").append(
1997 ).append(".").append(
2032 " Selecting trust will immediately reload this notebook in a trusted state."
1998 " Selecting trust will immediately reload this notebook in a trusted state."
2033 ).append(
1999 ).append(
2034 " For more information, see the "
2000 " For more information, see the "
2035 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
2001 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
2036 .text("IPython security documentation")
2002 .text("IPython security documentation")
2037 ).append(".")
2003 ).append(".")
2038 );
2004 );
2039
2005
2040 var nb = this;
2006 var nb = this;
2041 dialog.modal({
2007 dialog.modal({
2042 notebook: this,
2008 notebook: this,
2043 keyboard_manager: this.keyboard_manager,
2009 keyboard_manager: this.keyboard_manager,
2044 title: "Trust this notebook?",
2010 title: "Trust this notebook?",
2045 body: body,
2011 body: body,
2046
2012
2047 buttons: {
2013 buttons: {
2048 Cancel : {},
2014 Cancel : {},
2049 Trust : {
2015 Trust : {
2050 class : "btn-danger",
2016 class : "btn-danger",
2051 click : function () {
2017 click : function () {
2052 var cells = nb.get_cells();
2018 var cells = nb.get_cells();
2053 for (var i = 0; i < cells.length; i++) {
2019 for (var i = 0; i < cells.length; i++) {
2054 var cell = cells[i];
2020 var cell = cells[i];
2055 if (cell.cell_type == 'code') {
2021 if (cell.cell_type == 'code') {
2056 cell.output_area.trusted = true;
2022 cell.output_area.trusted = true;
2057 }
2023 }
2058 }
2024 }
2059 nb.events.on('notebook_saved.Notebook', function () {
2025 nb.events.on('notebook_saved.Notebook', function () {
2060 window.location.reload();
2026 window.location.reload();
2061 });
2027 });
2062 nb.save_notebook();
2028 nb.save_notebook();
2063 }
2029 }
2064 }
2030 }
2065 }
2031 }
2066 });
2032 });
2067 };
2033 };
2068
2034
2069 Notebook.prototype.copy_notebook = function(){
2035 Notebook.prototype.copy_notebook = function(){
2070 var path = this.notebook_path;
2036 var path = this.notebook_path;
2071 var base_url = this.base_url;
2037 var base_url = this.base_url;
2072 var settings = {
2038 var settings = {
2073 processData : false,
2039 processData : false,
2074 cache : false,
2040 cache : false,
2075 type : "POST",
2041 type : "POST",
2076 dataType : "json",
2042 dataType : "json",
2077 data : JSON.stringify({copy_from : this.notebook_name}),
2043 data : JSON.stringify({copy_from : this.notebook_name}),
2078 async : false,
2044 async : false,
2079 success : function (data, status, xhr) {
2045 success : function (data, status, xhr) {
2080 window.open(utils.url_join_encode(
2046 window.open(utils.url_join_encode(
2081 base_url,
2047 base_url,
2082 'notebooks',
2048 'notebooks',
2083 data.path,
2049 data.path,
2084 data.name
2050 data.name
2085 ), '_blank');
2051 ), '_blank');
2086 },
2052 },
2087 error : utils.log_ajax_error,
2053 error : utils.log_ajax_error,
2088 };
2054 };
2089 var url = utils.url_join_encode(
2055 var url = utils.url_join_encode(
2090 base_url,
2056 base_url,
2091 'api/contents',
2057 'api/contents',
2092 path
2058 path
2093 );
2059 );
2094 $.ajax(url,settings);
2060 $.ajax(url,settings);
2095 };
2061 };
2096
2062
2097 Notebook.prototype.rename = function (nbname) {
2063 Notebook.prototype.rename = function (nbname) {
2098 this.content_manager.rename_notebook(this, nbname);
2064 this.content_manager.rename_notebook(this, nbname);
2099 };
2065 };
2100
2066
2101 Notebook.prototype.delete = function () {
2067 Notebook.prototype.delete = function () {
2102 this.content_manager.delete_notebook(this.notebook_name, this.notebook_path, this.base_url);
2068 this.content_manager.delete_notebook(this.notebook_name, this.notebook_path, this.base_url);
2103 };
2069 };
2104
2070
2105 Notebook.prototype.rename_success = function (json, status, xhr) {
2071 Notebook.prototype.rename_success = function (json, status, xhr) {
2106 var name = this.notebook_name = json.name;
2072 var name = this.notebook_name = json.name;
2107 var path = json.path;
2073 var path = json.path;
2108 this.session.rename_notebook(name, path);
2074 this.session.rename_notebook(name, path);
2109 this.events.trigger('notebook_renamed.Notebook', json);
2075 this.events.trigger('notebook_renamed.Notebook', json);
2110 };
2076 };
2111
2077
2112 Notebook.prototype.rename_error = function (xhr, status, error) {
2078 Notebook.prototype.rename_error = function (xhr, status, error) {
2113 var that = this;
2079 var that = this;
2114 var dialog_body = $('<div/>').append(
2080 var dialog_body = $('<div/>').append(
2115 $("<p/>").text('This notebook name already exists.')
2081 $("<p/>").text('This notebook name already exists.')
2116 );
2082 );
2117 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2083 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2118 dialog.modal({
2084 dialog.modal({
2119 notebook: this,
2085 notebook: this,
2120 keyboard_manager: this.keyboard_manager,
2086 keyboard_manager: this.keyboard_manager,
2121 title: "Notebook Rename Error!",
2087 title: "Notebook Rename Error!",
2122 body: dialog_body,
2088 body: dialog_body,
2123 buttons : {
2089 buttons : {
2124 "Cancel": {},
2090 "Cancel": {},
2125 "OK": {
2091 "OK": {
2126 class: "btn-primary",
2092 class: "btn-primary",
2127 click: function () {
2093 click: function () {
2128 this.save_widget.rename_notebook({notebook:that});
2094 this.save_widget.rename_notebook({notebook:that});
2129 }}
2095 }}
2130 },
2096 },
2131 open : function (event, ui) {
2097 open : function (event, ui) {
2132 var that = $(this);
2098 var that = $(this);
2133 // Upon ENTER, click the OK button.
2099 // Upon ENTER, click the OK button.
2134 that.find('input[type="text"]').keydown(function (event, ui) {
2100 that.find('input[type="text"]').keydown(function (event, ui) {
2135 if (event.which === this.keyboard.keycodes.enter) {
2101 if (event.which === this.keyboard.keycodes.enter) {
2136 that.find('.btn-primary').first().click();
2102 that.find('.btn-primary').first().click();
2137 }
2103 }
2138 });
2104 });
2139 that.find('input[type="text"]').focus();
2105 that.find('input[type="text"]').focus();
2140 }
2106 }
2141 });
2107 });
2142 };
2108 };
2143
2109
2144 /**
2110 /**
2145 * Request a notebook's data from the server.
2111 * Request a notebook's data from the server.
2146 *
2112 *
2147 * @method load_notebook
2113 * @method load_notebook
2148 * @param {String} notebook_name and path A notebook to load
2114 * @param {String} notebook_name and path A notebook to load
2149 */
2115 */
2150 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2116 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2151 var that = this;
2117 var that = this;
2152 this.notebook_name = notebook_name;
2118 this.notebook_name = notebook_name;
2153 this.notebook_path = notebook_path;
2119 this.notebook_path = notebook_path;
2154 // We do the call with settings so we can set cache to false.
2120 // We do the call with settings so we can set cache to false.
2155 var settings = {
2121 var settings = {
2156 processData : false,
2122 processData : false,
2157 cache : false,
2123 cache : false,
2158 type : "GET",
2124 type : "GET",
2159 dataType : "json",
2125 dataType : "json",
2160 success : $.proxy(this.load_notebook_success,this),
2126 success : $.proxy(this.load_notebook_success,this),
2161 error : $.proxy(this.load_notebook_error,this),
2127 error : $.proxy(this.load_notebook_error,this),
2162 };
2128 };
2163 this.events.trigger('notebook_loading.Notebook');
2129 this.events.trigger('notebook_loading.Notebook');
2164 var url = utils.url_join_encode(
2130 var url = utils.url_join_encode(
2165 this.base_url,
2131 this.base_url,
2166 'api/contents',
2132 'api/contents',
2167 this.notebook_path,
2133 this.notebook_path,
2168 this.notebook_name
2134 this.notebook_name
2169 );
2135 );
2170 $.ajax(url, settings);
2136 $.ajax(url, settings);
2171 };
2137 };
2172
2138
2173 /**
2139 /**
2174 * Success callback for loading a notebook from the server.
2140 * Success callback for loading a notebook from the server.
2175 *
2141 *
2176 * Load notebook data from the JSON response.
2142 * Load notebook data from the JSON response.
2177 *
2143 *
2178 * @method load_notebook_success
2144 * @method load_notebook_success
2179 * @param {Object} data JSON representation of a notebook
2145 * @param {Object} data JSON representation of a notebook
2180 * @param {String} status Description of response status
2146 * @param {String} status Description of response status
2181 * @param {jqXHR} xhr jQuery Ajax object
2147 * @param {jqXHR} xhr jQuery Ajax object
2182 */
2148 */
2183 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2149 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2184 var failed;
2150 var failed;
2185 try {
2151 try {
2186 this.fromJSON(data);
2152 this.fromJSON(data);
2187 } catch (e) {
2153 } catch (e) {
2188 failed = e;
2154 failed = e;
2189 console.log("Notebook failed to load from JSON:", e);
2155 console.log("Notebook failed to load from JSON:", e);
2190 }
2156 }
2191 if (failed || data.message) {
2157 if (failed || data.message) {
2192 // *either* fromJSON failed or validation failed
2158 // *either* fromJSON failed or validation failed
2193 var body = $("<div>");
2159 var body = $("<div>");
2194 var title;
2160 var title;
2195 if (failed) {
2161 if (failed) {
2196 title = "Notebook failed to load";
2162 title = "Notebook failed to load";
2197 body.append($("<p>").text(
2163 body.append($("<p>").text(
2198 "The error was: "
2164 "The error was: "
2199 )).append($("<div>").addClass("js-error").text(
2165 )).append($("<div>").addClass("js-error").text(
2200 failed.toString()
2166 failed.toString()
2201 )).append($("<p>").text(
2167 )).append($("<p>").text(
2202 "See the error console for details."
2168 "See the error console for details."
2203 ));
2169 ));
2204 } else {
2170 } else {
2205 title = "Notebook validation failed";
2171 title = "Notebook validation failed";
2206 }
2172 }
2207
2173
2208 if (data.message) {
2174 if (data.message) {
2209 var msg;
2175 var msg;
2210 if (failed) {
2176 if (failed) {
2211 msg = "The notebook also failed validation:"
2177 msg = "The notebook also failed validation:"
2212 } else {
2178 } else {
2213 msg = "An invalid notebook may not function properly." +
2179 msg = "An invalid notebook may not function properly." +
2214 " The validation error was:"
2180 " The validation error was:"
2215 }
2181 }
2216 body.append($("<p>").text(
2182 body.append($("<p>").text(
2217 msg
2183 msg
2218 )).append($("<div>").addClass("validation-error").append(
2184 )).append($("<div>").addClass("validation-error").append(
2219 $("<pre>").text(data.message)
2185 $("<pre>").text(data.message)
2220 ));
2186 ));
2221 }
2187 }
2222
2188
2223 dialog.modal({
2189 dialog.modal({
2224 notebook: this,
2190 notebook: this,
2225 keyboard_manager: this.keyboard_manager,
2191 keyboard_manager: this.keyboard_manager,
2226 title: title,
2192 title: title,
2227 body: body,
2193 body: body,
2228 buttons : {
2194 buttons : {
2229 OK : {
2195 OK : {
2230 "class" : "btn-primary"
2196 "class" : "btn-primary"
2231 }
2197 }
2232 }
2198 }
2233 });
2199 });
2234 }
2200 }
2235 if (this.ncells() === 0) {
2201 if (this.ncells() === 0) {
2236 this.insert_cell_below('code');
2202 this.insert_cell_below('code');
2237 this.edit_mode(0);
2203 this.edit_mode(0);
2238 } else {
2204 } else {
2239 this.select(0);
2205 this.select(0);
2240 this.handle_command_mode(this.get_cell(0));
2206 this.handle_command_mode(this.get_cell(0));
2241 }
2207 }
2242 this.set_dirty(false);
2208 this.set_dirty(false);
2243 this.scroll_to_top();
2209 this.scroll_to_top();
2244 var nbmodel = data.content;
2210 var nbmodel = data.content;
2245 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2211 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2246 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2212 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2247 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2213 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2248 var msg = "This notebook has been converted from an older " +
2214 var msg = "This notebook has been converted from an older " +
2249 "notebook format (v"+orig_nbformat+") to the current notebook " +
2215 "notebook format (v"+orig_nbformat+") to the current notebook " +
2250 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2216 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2251 "newer notebook format will be used and older versions of IPython " +
2217 "newer notebook format will be used and older versions of IPython " +
2252 "may not be able to read it. To keep the older version, close the " +
2218 "may not be able to read it. To keep the older version, close the " +
2253 "notebook without saving it.";
2219 "notebook without saving it.";
2254 dialog.modal({
2220 dialog.modal({
2255 notebook: this,
2221 notebook: this,
2256 keyboard_manager: this.keyboard_manager,
2222 keyboard_manager: this.keyboard_manager,
2257 title : "Notebook converted",
2223 title : "Notebook converted",
2258 body : msg,
2224 body : msg,
2259 buttons : {
2225 buttons : {
2260 OK : {
2226 OK : {
2261 class : "btn-primary"
2227 class : "btn-primary"
2262 }
2228 }
2263 }
2229 }
2264 });
2230 });
2265 } else if (orig_nbformat_minor !== undefined && nbmodel.nbformat_minor !== orig_nbformat_minor) {
2231 } else if (orig_nbformat_minor !== undefined && nbmodel.nbformat_minor !== orig_nbformat_minor) {
2266 var that = this;
2232 var that = this;
2267 var orig_vs = 'v' + nbmodel.nbformat + '.' + orig_nbformat_minor;
2233 var orig_vs = 'v' + nbmodel.nbformat + '.' + orig_nbformat_minor;
2268 var this_vs = 'v' + nbmodel.nbformat + '.' + this.nbformat_minor;
2234 var this_vs = 'v' + nbmodel.nbformat + '.' + this.nbformat_minor;
2269 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2235 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2270 this_vs + ". You can still work with this notebook, but some features " +
2236 this_vs + ". You can still work with this notebook, but some features " +
2271 "introduced in later notebook versions may not be available.";
2237 "introduced in later notebook versions may not be available.";
2272
2238
2273 dialog.modal({
2239 dialog.modal({
2274 notebook: this,
2240 notebook: this,
2275 keyboard_manager: this.keyboard_manager,
2241 keyboard_manager: this.keyboard_manager,
2276 title : "Newer Notebook",
2242 title : "Newer Notebook",
2277 body : msg,
2243 body : msg,
2278 buttons : {
2244 buttons : {
2279 OK : {
2245 OK : {
2280 class : "btn-danger"
2246 class : "btn-danger"
2281 }
2247 }
2282 }
2248 }
2283 });
2249 });
2284
2250
2285 }
2251 }
2286
2252
2287 // Create the session after the notebook is completely loaded to prevent
2253 // Create the session after the notebook is completely loaded to prevent
2288 // code execution upon loading, which is a security risk.
2254 // code execution upon loading, which is a security risk.
2289 if (this.session === null) {
2255 if (this.session === null) {
2290 var kernelspec = this.metadata.kernelspec || {};
2256 var kernelspec = this.metadata.kernelspec || {};
2291 var kernel_name = kernelspec.name;
2257 var kernel_name = kernelspec.name;
2292
2258
2293 this.start_session(kernel_name);
2259 this.start_session(kernel_name);
2294 }
2260 }
2295 // load our checkpoint list
2261 // load our checkpoint list
2296 this.list_checkpoints();
2262 this.list_checkpoints();
2297
2263
2298 // load toolbar state
2264 // load toolbar state
2299 if (this.metadata.celltoolbar) {
2265 if (this.metadata.celltoolbar) {
2300 celltoolbar.CellToolbar.global_show();
2266 celltoolbar.CellToolbar.global_show();
2301 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2267 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2302 } else {
2268 } else {
2303 celltoolbar.CellToolbar.global_hide();
2269 celltoolbar.CellToolbar.global_hide();
2304 }
2270 }
2305
2271
2306 // now that we're fully loaded, it is safe to restore save functionality
2272 // now that we're fully loaded, it is safe to restore save functionality
2307 delete(this.save_notebook);
2273 delete(this.save_notebook);
2308 this.events.trigger('notebook_loaded.Notebook');
2274 this.events.trigger('notebook_loaded.Notebook');
2309 };
2275 };
2310
2276
2311 /**
2277 /**
2312 * Failure callback for loading a notebook from the server.
2278 * Failure callback for loading a notebook from the server.
2313 *
2279 *
2314 * @method load_notebook_error
2280 * @method load_notebook_error
2315 * @param {jqXHR} xhr jQuery Ajax object
2281 * @param {jqXHR} xhr jQuery Ajax object
2316 * @param {String} status Description of response status
2282 * @param {String} status Description of response status
2317 * @param {String} error HTTP error message
2283 * @param {String} error HTTP error message
2318 */
2284 */
2319 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2285 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2320 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2286 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2321 utils.log_ajax_error(xhr, status, error);
2287 utils.log_ajax_error(xhr, status, error);
2322 var msg = $("<div>");
2288 var msg = $("<div>");
2323 if (xhr.status === 400) {
2289 if (xhr.status === 400) {
2324 msg.text(utils.ajax_error_msg(xhr));
2290 msg.text(utils.ajax_error_msg(xhr));
2325 } else if (xhr.status === 500) {
2291 } else if (xhr.status === 500) {
2326 msg.text("An unknown error occurred while loading this notebook. " +
2292 msg.text("An unknown error occurred while loading this notebook. " +
2327 "This version can load notebook formats " +
2293 "This version can load notebook formats " +
2328 "v" + this.nbformat + " or earlier. See the server log for details.");
2294 "v" + this.nbformat + " or earlier. See the server log for details.");
2329 }
2295 }
2330 dialog.modal({
2296 dialog.modal({
2331 notebook: this,
2297 notebook: this,
2332 keyboard_manager: this.keyboard_manager,
2298 keyboard_manager: this.keyboard_manager,
2333 title: "Error loading notebook",
2299 title: "Error loading notebook",
2334 body : msg,
2300 body : msg,
2335 buttons : {
2301 buttons : {
2336 "OK": {}
2302 "OK": {}
2337 }
2303 }
2338 });
2304 });
2339 };
2305 };
2340
2306
2341 /********************* checkpoint-related *********************/
2307 /********************* checkpoint-related *********************/
2342
2308
2343 /**
2309 /**
2344 * Save the notebook then immediately create a checkpoint.
2310 * Save the notebook then immediately create a checkpoint.
2345 *
2311 *
2346 * @method save_checkpoint
2312 * @method save_checkpoint
2347 */
2313 */
2348 Notebook.prototype.save_checkpoint = function () {
2314 Notebook.prototype.save_checkpoint = function () {
2349 this._checkpoint_after_save = true;
2315 this._checkpoint_after_save = true;
2350 this.save_notebook();
2316 this.save_notebook();
2351 };
2317 };
2352
2318
2353 /**
2319 /**
2354 * Add a checkpoint for this notebook.
2320 * Add a checkpoint for this notebook.
2355 * for use as a callback from checkpoint creation.
2321 * for use as a callback from checkpoint creation.
2356 *
2322 *
2357 * @method add_checkpoint
2323 * @method add_checkpoint
2358 */
2324 */
2359 Notebook.prototype.add_checkpoint = function (checkpoint) {
2325 Notebook.prototype.add_checkpoint = function (checkpoint) {
2360 var found = false;
2326 var found = false;
2361 for (var i = 0; i < this.checkpoints.length; i++) {
2327 for (var i = 0; i < this.checkpoints.length; i++) {
2362 var existing = this.checkpoints[i];
2328 var existing = this.checkpoints[i];
2363 if (existing.id == checkpoint.id) {
2329 if (existing.id == checkpoint.id) {
2364 found = true;
2330 found = true;
2365 this.checkpoints[i] = checkpoint;
2331 this.checkpoints[i] = checkpoint;
2366 break;
2332 break;
2367 }
2333 }
2368 }
2334 }
2369 if (!found) {
2335 if (!found) {
2370 this.checkpoints.push(checkpoint);
2336 this.checkpoints.push(checkpoint);
2371 }
2337 }
2372 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2338 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2373 };
2339 };
2374
2340
2375 /**
2341 /**
2376 * List checkpoints for this notebook.
2342 * List checkpoints for this notebook.
2377 *
2343 *
2378 * @method list_checkpoints
2344 * @method list_checkpoints
2379 */
2345 */
2380 Notebook.prototype.list_checkpoints = function () {
2346 Notebook.prototype.list_checkpoints = function () {
2381 var url = utils.url_join_encode(
2347 var url = utils.url_join_encode(
2382 this.base_url,
2348 this.base_url,
2383 'api/contents',
2349 'api/contents',
2384 this.notebook_path,
2350 this.notebook_path,
2385 this.notebook_name,
2351 this.notebook_name,
2386 'checkpoints'
2352 'checkpoints'
2387 );
2353 );
2388 $.get(url).done(
2354 $.get(url).done(
2389 $.proxy(this.list_checkpoints_success, this)
2355 $.proxy(this.list_checkpoints_success, this)
2390 ).fail(
2356 ).fail(
2391 $.proxy(this.list_checkpoints_error, this)
2357 $.proxy(this.list_checkpoints_error, this)
2392 );
2358 );
2393 };
2359 };
2394
2360
2395 /**
2361 /**
2396 * Success callback for listing checkpoints.
2362 * Success callback for listing checkpoints.
2397 *
2363 *
2398 * @method list_checkpoint_success
2364 * @method list_checkpoint_success
2399 * @param {Object} data JSON representation of a checkpoint
2365 * @param {Object} data JSON representation of a checkpoint
2400 * @param {String} status Description of response status
2366 * @param {String} status Description of response status
2401 * @param {jqXHR} xhr jQuery Ajax object
2367 * @param {jqXHR} xhr jQuery Ajax object
2402 */
2368 */
2403 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2369 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2404 data = $.parseJSON(data);
2370 data = $.parseJSON(data);
2405 this.checkpoints = data;
2371 this.checkpoints = data;
2406 if (data.length) {
2372 if (data.length) {
2407 this.last_checkpoint = data[data.length - 1];
2373 this.last_checkpoint = data[data.length - 1];
2408 } else {
2374 } else {
2409 this.last_checkpoint = null;
2375 this.last_checkpoint = null;
2410 }
2376 }
2411 this.events.trigger('checkpoints_listed.Notebook', [data]);
2377 this.events.trigger('checkpoints_listed.Notebook', [data]);
2412 };
2378 };
2413
2379
2414 /**
2380 /**
2415 * Failure callback for listing a checkpoint.
2381 * Failure callback for listing a checkpoint.
2416 *
2382 *
2417 * @method list_checkpoint_error
2383 * @method list_checkpoint_error
2418 * @param {jqXHR} xhr jQuery Ajax object
2384 * @param {jqXHR} xhr jQuery Ajax object
2419 * @param {String} status Description of response status
2385 * @param {String} status Description of response status
2420 * @param {String} error_msg HTTP error message
2386 * @param {String} error_msg HTTP error message
2421 */
2387 */
2422 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2388 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2423 this.events.trigger('list_checkpoints_failed.Notebook');
2389 this.events.trigger('list_checkpoints_failed.Notebook');
2424 };
2390 };
2425
2391
2426 /**
2392 /**
2427 * Create a checkpoint of this notebook on the server from the most recent save.
2393 * Create a checkpoint of this notebook on the server from the most recent save.
2428 *
2394 *
2429 * @method create_checkpoint
2395 * @method create_checkpoint
2430 */
2396 */
2431 Notebook.prototype.create_checkpoint = function () {
2397 Notebook.prototype.create_checkpoint = function () {
2432 var url = utils.url_join_encode(
2398 var url = utils.url_join_encode(
2433 this.base_url,
2399 this.base_url,
2434 'api/contents',
2400 'api/contents',
2435 this.notebook_path,
2401 this.notebook_path,
2436 this.notebook_name,
2402 this.notebook_name,
2437 'checkpoints'
2403 'checkpoints'
2438 );
2404 );
2439 $.post(url).done(
2405 $.post(url).done(
2440 $.proxy(this.create_checkpoint_success, this)
2406 $.proxy(this.create_checkpoint_success, this)
2441 ).fail(
2407 ).fail(
2442 $.proxy(this.create_checkpoint_error, this)
2408 $.proxy(this.create_checkpoint_error, this)
2443 );
2409 );
2444 };
2410 };
2445
2411
2446 /**
2412 /**
2447 * Success callback for creating a checkpoint.
2413 * Success callback for creating a checkpoint.
2448 *
2414 *
2449 * @method create_checkpoint_success
2415 * @method create_checkpoint_success
2450 * @param {Object} data JSON representation of a checkpoint
2416 * @param {Object} data JSON representation of a checkpoint
2451 * @param {String} status Description of response status
2417 * @param {String} status Description of response status
2452 * @param {jqXHR} xhr jQuery Ajax object
2418 * @param {jqXHR} xhr jQuery Ajax object
2453 */
2419 */
2454 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2420 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2455 data = $.parseJSON(data);
2421 data = $.parseJSON(data);
2456 this.add_checkpoint(data);
2422 this.add_checkpoint(data);
2457 this.events.trigger('checkpoint_created.Notebook', data);
2423 this.events.trigger('checkpoint_created.Notebook', data);
2458 };
2424 };
2459
2425
2460 /**
2426 /**
2461 * Failure callback for creating a checkpoint.
2427 * Failure callback for creating a checkpoint.
2462 *
2428 *
2463 * @method create_checkpoint_error
2429 * @method create_checkpoint_error
2464 * @param {jqXHR} xhr jQuery Ajax object
2430 * @param {jqXHR} xhr jQuery Ajax object
2465 * @param {String} status Description of response status
2431 * @param {String} status Description of response status
2466 * @param {String} error_msg HTTP error message
2432 * @param {String} error_msg HTTP error message
2467 */
2433 */
2468 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2434 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2469 this.events.trigger('checkpoint_failed.Notebook');
2435 this.events.trigger('checkpoint_failed.Notebook');
2470 };
2436 };
2471
2437
2472 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2438 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2473 var that = this;
2439 var that = this;
2474 checkpoint = checkpoint || this.last_checkpoint;
2440 checkpoint = checkpoint || this.last_checkpoint;
2475 if ( ! checkpoint ) {
2441 if ( ! checkpoint ) {
2476 console.log("restore dialog, but no checkpoint to restore to!");
2442 console.log("restore dialog, but no checkpoint to restore to!");
2477 return;
2443 return;
2478 }
2444 }
2479 var body = $('<div/>').append(
2445 var body = $('<div/>').append(
2480 $('<p/>').addClass("p-space").text(
2446 $('<p/>').addClass("p-space").text(
2481 "Are you sure you want to revert the notebook to " +
2447 "Are you sure you want to revert the notebook to " +
2482 "the latest checkpoint?"
2448 "the latest checkpoint?"
2483 ).append(
2449 ).append(
2484 $("<strong/>").text(
2450 $("<strong/>").text(
2485 " This cannot be undone."
2451 " This cannot be undone."
2486 )
2452 )
2487 )
2453 )
2488 ).append(
2454 ).append(
2489 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2455 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2490 ).append(
2456 ).append(
2491 $('<p/>').addClass("p-space").text(
2457 $('<p/>').addClass("p-space").text(
2492 Date(checkpoint.last_modified)
2458 Date(checkpoint.last_modified)
2493 ).css("text-align", "center")
2459 ).css("text-align", "center")
2494 );
2460 );
2495
2461
2496 dialog.modal({
2462 dialog.modal({
2497 notebook: this,
2463 notebook: this,
2498 keyboard_manager: this.keyboard_manager,
2464 keyboard_manager: this.keyboard_manager,
2499 title : "Revert notebook to checkpoint",
2465 title : "Revert notebook to checkpoint",
2500 body : body,
2466 body : body,
2501 buttons : {
2467 buttons : {
2502 Revert : {
2468 Revert : {
2503 class : "btn-danger",
2469 class : "btn-danger",
2504 click : function () {
2470 click : function () {
2505 that.restore_checkpoint(checkpoint.id);
2471 that.restore_checkpoint(checkpoint.id);
2506 }
2472 }
2507 },
2473 },
2508 Cancel : {}
2474 Cancel : {}
2509 }
2475 }
2510 });
2476 });
2511 };
2477 };
2512
2478
2513 /**
2479 /**
2514 * Restore the notebook to a checkpoint state.
2480 * Restore the notebook to a checkpoint state.
2515 *
2481 *
2516 * @method restore_checkpoint
2482 * @method restore_checkpoint
2517 * @param {String} checkpoint ID
2483 * @param {String} checkpoint ID
2518 */
2484 */
2519 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2485 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2520 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2486 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2521 var url = utils.url_join_encode(
2487 var url = utils.url_join_encode(
2522 this.base_url,
2488 this.base_url,
2523 'api/contents',
2489 'api/contents',
2524 this.notebook_path,
2490 this.notebook_path,
2525 this.notebook_name,
2491 this.notebook_name,
2526 'checkpoints',
2492 'checkpoints',
2527 checkpoint
2493 checkpoint
2528 );
2494 );
2529 $.post(url).done(
2495 $.post(url).done(
2530 $.proxy(this.restore_checkpoint_success, this)
2496 $.proxy(this.restore_checkpoint_success, this)
2531 ).fail(
2497 ).fail(
2532 $.proxy(this.restore_checkpoint_error, this)
2498 $.proxy(this.restore_checkpoint_error, this)
2533 );
2499 );
2534 };
2500 };
2535
2501
2536 /**
2502 /**
2537 * Success callback for restoring a notebook to a checkpoint.
2503 * Success callback for restoring a notebook to a checkpoint.
2538 *
2504 *
2539 * @method restore_checkpoint_success
2505 * @method restore_checkpoint_success
2540 * @param {Object} data (ignored, should be empty)
2506 * @param {Object} data (ignored, should be empty)
2541 * @param {String} status Description of response status
2507 * @param {String} status Description of response status
2542 * @param {jqXHR} xhr jQuery Ajax object
2508 * @param {jqXHR} xhr jQuery Ajax object
2543 */
2509 */
2544 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2510 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2545 this.events.trigger('checkpoint_restored.Notebook');
2511 this.events.trigger('checkpoint_restored.Notebook');
2546 this.load_notebook(this.notebook_name, this.notebook_path);
2512 this.load_notebook(this.notebook_name, this.notebook_path);
2547 };
2513 };
2548
2514
2549 /**
2515 /**
2550 * Failure callback for restoring a notebook to a checkpoint.
2516 * Failure callback for restoring a notebook to a checkpoint.
2551 *
2517 *
2552 * @method restore_checkpoint_error
2518 * @method restore_checkpoint_error
2553 * @param {jqXHR} xhr jQuery Ajax object
2519 * @param {jqXHR} xhr jQuery Ajax object
2554 * @param {String} status Description of response status
2520 * @param {String} status Description of response status
2555 * @param {String} error_msg HTTP error message
2521 * @param {String} error_msg HTTP error message
2556 */
2522 */
2557 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2523 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2558 this.events.trigger('checkpoint_restore_failed.Notebook');
2524 this.events.trigger('checkpoint_restore_failed.Notebook');
2559 };
2525 };
2560
2526
2561 /**
2527 /**
2562 * Delete a notebook checkpoint.
2528 * Delete a notebook checkpoint.
2563 *
2529 *
2564 * @method delete_checkpoint
2530 * @method delete_checkpoint
2565 * @param {String} checkpoint ID
2531 * @param {String} checkpoint ID
2566 */
2532 */
2567 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2533 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2568 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2534 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2569 var url = utils.url_join_encode(
2535 var url = utils.url_join_encode(
2570 this.base_url,
2536 this.base_url,
2571 'api/contents',
2537 'api/contents',
2572 this.notebook_path,
2538 this.notebook_path,
2573 this.notebook_name,
2539 this.notebook_name,
2574 'checkpoints',
2540 'checkpoints',
2575 checkpoint
2541 checkpoint
2576 );
2542 );
2577 $.ajax(url, {
2543 $.ajax(url, {
2578 type: 'DELETE',
2544 type: 'DELETE',
2579 success: $.proxy(this.delete_checkpoint_success, this),
2545 success: $.proxy(this.delete_checkpoint_success, this),
2580 error: $.proxy(this.delete_checkpoint_error, this)
2546 error: $.proxy(this.delete_checkpoint_error, this)
2581 });
2547 });
2582 };
2548 };
2583
2549
2584 /**
2550 /**
2585 * Success callback for deleting a notebook checkpoint
2551 * Success callback for deleting a notebook checkpoint
2586 *
2552 *
2587 * @method delete_checkpoint_success
2553 * @method delete_checkpoint_success
2588 * @param {Object} data (ignored, should be empty)
2554 * @param {Object} data (ignored, should be empty)
2589 * @param {String} status Description of response status
2555 * @param {String} status Description of response status
2590 * @param {jqXHR} xhr jQuery Ajax object
2556 * @param {jqXHR} xhr jQuery Ajax object
2591 */
2557 */
2592 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2558 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2593 this.events.trigger('checkpoint_deleted.Notebook', data);
2559 this.events.trigger('checkpoint_deleted.Notebook', data);
2594 this.load_notebook(this.notebook_name, this.notebook_path);
2560 this.load_notebook(this.notebook_name, this.notebook_path);
2595 };
2561 };
2596
2562
2597 /**
2563 /**
2598 * Failure callback for deleting a notebook checkpoint.
2564 * Failure callback for deleting a notebook checkpoint.
2599 *
2565 *
2600 * @method delete_checkpoint_error
2566 * @method delete_checkpoint_error
2601 * @param {jqXHR} xhr jQuery Ajax object
2567 * @param {jqXHR} xhr jQuery Ajax object
2602 * @param {String} status Description of response status
2568 * @param {String} status Description of response status
2603 * @param {String} error HTTP error message
2569 * @param {String} error HTTP error message
2604 */
2570 */
2605 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error) {
2571 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error) {
2606 this.events.trigger('checkpoint_delete_failed.Notebook', [xhr, status, error]);
2572 this.events.trigger('checkpoint_delete_failed.Notebook', [xhr, status, error]);
2607 };
2573 };
2608
2574
2609
2575
2610 // For backwards compatability.
2576 // For backwards compatability.
2611 IPython.Notebook = Notebook;
2577 IPython.Notebook = Notebook;
2612
2578
2613 return {'Notebook': Notebook};
2579 return {'Notebook': Notebook};
2614 });
2580 });
General Comments 0
You need to be logged in to leave comments. Login now