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