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