##// END OF EJS Templates
Cleaning up output management in code and menus.
Brian E. Granger -
Show More
@@ -1,563 +1,568 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // CodeCell
9 // CodeCell
10 //============================================================================
10 //============================================================================
11 /**
11 /**
12 * An extendable module that provide base functionnality to create cell for notebook.
12 * An extendable module that provide base functionnality to create cell for notebook.
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule CodeCell
15 * @submodule CodeCell
16 */
16 */
17
17
18
18
19 /* local util for codemirror */
19 /* local util for codemirror */
20 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
20 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
21
21
22 /**
22 /**
23 *
23 *
24 * function to delete until previous non blanking space character
24 * function to delete until previous non blanking space character
25 * or first multiple of 4 tabstop.
25 * or first multiple of 4 tabstop.
26 * @private
26 * @private
27 */
27 */
28 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
28 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
29 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
29 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
30 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
30 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
31 var cur = cm.getCursor(), line = cm.getLine(cur.line);
31 var cur = cm.getCursor(), line = cm.getLine(cur.line);
32 var tabsize = cm.getOption('tabSize');
32 var tabsize = cm.getOption('tabSize');
33 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
33 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
34 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
34 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
35 var select = cm.getRange(from,cur);
35 var select = cm.getRange(from,cur);
36 if( select.match(/^\ +$/) !== null){
36 if( select.match(/^\ +$/) !== null){
37 cm.replaceRange("",from,cur);
37 cm.replaceRange("",from,cur);
38 } else {
38 } else {
39 cm.deleteH(-1,"char");
39 cm.deleteH(-1,"char");
40 }
40 }
41 };
41 };
42
42
43
43
44 var IPython = (function (IPython) {
44 var IPython = (function (IPython) {
45 "use strict";
45 "use strict";
46
46
47 var utils = IPython.utils;
47 var utils = IPython.utils;
48 var key = IPython.utils.keycodes;
48 var key = IPython.utils.keycodes;
49
49
50 /**
50 /**
51 * A Cell conceived to write code.
51 * A Cell conceived to write code.
52 *
52 *
53 * The kernel doesn't have to be set at creation time, in that case
53 * The kernel doesn't have to be set at creation time, in that case
54 * it will be null and set_kernel has to be called later.
54 * it will be null and set_kernel has to be called later.
55 * @class CodeCell
55 * @class CodeCell
56 * @extends IPython.Cell
56 * @extends IPython.Cell
57 *
57 *
58 * @constructor
58 * @constructor
59 * @param {Object|null} kernel
59 * @param {Object|null} kernel
60 * @param {object|undefined} [options]
60 * @param {object|undefined} [options]
61 * @param [options.cm_config] {object} config to pass to CodeMirror
61 * @param [options.cm_config] {object} config to pass to CodeMirror
62 */
62 */
63 var CodeCell = function (kernel, options) {
63 var CodeCell = function (kernel, options) {
64 this.kernel = kernel || null;
64 this.kernel = kernel || null;
65 this.collapsed = false;
65 this.collapsed = false;
66
66
67 // create all attributed in constructor function
67 // create all attributed in constructor function
68 // even if null for V8 VM optimisation
68 // even if null for V8 VM optimisation
69 this.input_prompt_number = null;
69 this.input_prompt_number = null;
70 this.celltoolbar = null;
70 this.celltoolbar = null;
71 this.output_area = null;
71 this.output_area = null;
72 this.last_msg_id = null;
72 this.last_msg_id = null;
73 this.completer = null;
73 this.completer = null;
74
74
75
75
76 var cm_overwrite_options = {
76 var cm_overwrite_options = {
77 onKeyEvent: $.proxy(this.handle_keyevent,this)
77 onKeyEvent: $.proxy(this.handle_keyevent,this)
78 };
78 };
79
79
80 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
80 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
81
81
82 IPython.Cell.apply(this,[options]);
82 IPython.Cell.apply(this,[options]);
83
83
84 // Attributes we want to override in this subclass.
84 // Attributes we want to override in this subclass.
85 this.cell_type = "code";
85 this.cell_type = "code";
86
86
87 var that = this;
87 var that = this;
88 this.element.focusout(
88 this.element.focusout(
89 function() { that.auto_highlight(); }
89 function() { that.auto_highlight(); }
90 );
90 );
91 };
91 };
92
92
93 CodeCell.options_default = {
93 CodeCell.options_default = {
94 cm_config : {
94 cm_config : {
95 extraKeys: {
95 extraKeys: {
96 "Tab" : "indentMore",
96 "Tab" : "indentMore",
97 "Shift-Tab" : "indentLess",
97 "Shift-Tab" : "indentLess",
98 "Backspace" : "delSpaceToPrevTabStop",
98 "Backspace" : "delSpaceToPrevTabStop",
99 "Cmd-/" : "toggleComment",
99 "Cmd-/" : "toggleComment",
100 "Ctrl-/" : "toggleComment"
100 "Ctrl-/" : "toggleComment"
101 },
101 },
102 mode: 'ipython',
102 mode: 'ipython',
103 theme: 'ipython',
103 theme: 'ipython',
104 matchBrackets: true
104 matchBrackets: true
105 }
105 }
106 };
106 };
107
107
108 CodeCell.msg_cells = {};
108 CodeCell.msg_cells = {};
109
109
110 CodeCell.prototype = new IPython.Cell();
110 CodeCell.prototype = new IPython.Cell();
111
111
112 /**
112 /**
113 * @method auto_highlight
113 * @method auto_highlight
114 */
114 */
115 CodeCell.prototype.auto_highlight = function () {
115 CodeCell.prototype.auto_highlight = function () {
116 this._auto_highlight(IPython.config.cell_magic_highlight);
116 this._auto_highlight(IPython.config.cell_magic_highlight);
117 };
117 };
118
118
119 /** @method create_element */
119 /** @method create_element */
120 CodeCell.prototype.create_element = function () {
120 CodeCell.prototype.create_element = function () {
121 IPython.Cell.prototype.create_element.apply(this, arguments);
121 IPython.Cell.prototype.create_element.apply(this, arguments);
122
122
123 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
123 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
124 cell.attr('tabindex','2');
124 cell.attr('tabindex','2');
125
125
126 var input = $('<div></div>').addClass('input');
126 var input = $('<div></div>').addClass('input');
127 var prompt = $('<div/>').addClass('prompt input_prompt');
127 var prompt = $('<div/>').addClass('prompt input_prompt');
128 var inner_cell = $('<div/>').addClass('inner_cell');
128 var inner_cell = $('<div/>').addClass('inner_cell');
129 this.celltoolbar = new IPython.CellToolbar(this);
129 this.celltoolbar = new IPython.CellToolbar(this);
130 inner_cell.append(this.celltoolbar.element);
130 inner_cell.append(this.celltoolbar.element);
131 var input_area = $('<div/>').addClass('input_area');
131 var input_area = $('<div/>').addClass('input_area');
132 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
132 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
133 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
133 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
134 inner_cell.append(input_area);
134 inner_cell.append(input_area);
135 input.append(prompt).append(inner_cell);
135 input.append(prompt).append(inner_cell);
136
136
137 var widget_area = $('<div/>')
137 var widget_area = $('<div/>')
138 .addClass('widget-area')
138 .addClass('widget-area')
139 .hide();
139 .hide();
140 this.widget_area = widget_area;
140 this.widget_area = widget_area;
141 var widget_prompt = $('<div/>')
141 var widget_prompt = $('<div/>')
142 .addClass('prompt')
142 .addClass('prompt')
143 .appendTo(widget_area);
143 .appendTo(widget_area);
144 var widget_subarea = $('<div/>')
144 var widget_subarea = $('<div/>')
145 .addClass('widget-subarea')
145 .addClass('widget-subarea')
146 .appendTo(widget_area);
146 .appendTo(widget_area);
147 this.widget_subarea = widget_subarea;
147 this.widget_subarea = widget_subarea;
148 var widget_clear_buton = $('<button />')
148 var widget_clear_buton = $('<button />')
149 .addClass('close')
149 .addClass('close')
150 .html('&times;')
150 .html('&times;')
151 .click(function() {
151 .click(function() {
152 widget_area.slideUp('', function(){ widget_subarea.html(''); });
152 widget_area.slideUp('', function(){ widget_subarea.html(''); });
153 })
153 })
154 .appendTo(widget_prompt);
154 .appendTo(widget_prompt);
155
155
156 var output = $('<div></div>');
156 var output = $('<div></div>');
157 cell.append(input).append(widget_area).append(output);
157 cell.append(input).append(widget_area).append(output);
158 this.element = cell;
158 this.element = cell;
159 this.output_area = new IPython.OutputArea(output, true);
159 this.output_area = new IPython.OutputArea(output, true);
160 this.completer = new IPython.Completer(this);
160 this.completer = new IPython.Completer(this);
161 };
161 };
162
162
163 /** @method bind_events */
163 /** @method bind_events */
164 CodeCell.prototype.bind_events = function () {
164 CodeCell.prototype.bind_events = function () {
165 IPython.Cell.prototype.bind_events.apply(this);
165 IPython.Cell.prototype.bind_events.apply(this);
166 var that = this;
166 var that = this;
167
167
168 this.element.focusout(
168 this.element.focusout(
169 function() { that.auto_highlight(); }
169 function() { that.auto_highlight(); }
170 );
170 );
171 };
171 };
172
172
173 CodeCell.prototype.handle_keyevent = function (editor, event) {
173 CodeCell.prototype.handle_keyevent = function (editor, event) {
174
174
175 // console.log('CM', this.mode, event.which, event.type)
175 // console.log('CM', this.mode, event.which, event.type)
176
176
177 if (this.mode === 'command') {
177 if (this.mode === 'command') {
178 return true;
178 return true;
179 } else if (this.mode === 'edit') {
179 } else if (this.mode === 'edit') {
180 return this.handle_codemirror_keyevent(editor, event);
180 return this.handle_codemirror_keyevent(editor, event);
181 }
181 }
182 };
182 };
183
183
184 /**
184 /**
185 * This method gets called in CodeMirror's onKeyDown/onKeyPress
185 * This method gets called in CodeMirror's onKeyDown/onKeyPress
186 * handlers and is used to provide custom key handling. Its return
186 * handlers and is used to provide custom key handling. Its return
187 * value is used to determine if CodeMirror should ignore the event:
187 * value is used to determine if CodeMirror should ignore the event:
188 * true = ignore, false = don't ignore.
188 * true = ignore, false = don't ignore.
189 * @method handle_codemirror_keyevent
189 * @method handle_codemirror_keyevent
190 */
190 */
191 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
191 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
192
192
193 var that = this;
193 var that = this;
194 // whatever key is pressed, first, cancel the tooltip request before
194 // whatever key is pressed, first, cancel the tooltip request before
195 // they are sent, and remove tooltip if any, except for tab again
195 // they are sent, and remove tooltip if any, except for tab again
196 var tooltip_closed = null;
196 var tooltip_closed = null;
197 if (event.type === 'keydown' && event.which != key.TAB ) {
197 if (event.type === 'keydown' && event.which != key.TAB ) {
198 tooltip_closed = IPython.tooltip.remove_and_cancel_tooltip();
198 tooltip_closed = IPython.tooltip.remove_and_cancel_tooltip();
199 }
199 }
200
200
201 var cur = editor.getCursor();
201 var cur = editor.getCursor();
202 if (event.keyCode === key.ENTER){
202 if (event.keyCode === key.ENTER){
203 this.auto_highlight();
203 this.auto_highlight();
204 }
204 }
205
205
206 if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey || event.altKey)) {
206 if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey || event.altKey)) {
207 // Always ignore shift-enter in CodeMirror as we handle it.
207 // Always ignore shift-enter in CodeMirror as we handle it.
208 return true;
208 return true;
209 } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
209 } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
210 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
210 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
211 // browser and keyboard layout !
211 // browser and keyboard layout !
212 // Pressing '(' , request tooltip, don't forget to reappend it
212 // Pressing '(' , request tooltip, don't forget to reappend it
213 // The second argument says to hide the tooltip if the docstring
213 // The second argument says to hide the tooltip if the docstring
214 // is actually empty
214 // is actually empty
215 IPython.tooltip.pending(that, true);
215 IPython.tooltip.pending(that, true);
216 } else if (event.which === key.UPARROW && event.type === 'keydown') {
216 } else if (event.which === key.UPARROW && event.type === 'keydown') {
217 // If we are not at the top, let CM handle the up arrow and
217 // If we are not at the top, let CM handle the up arrow and
218 // prevent the global keydown handler from handling it.
218 // prevent the global keydown handler from handling it.
219 if (!that.at_top()) {
219 if (!that.at_top()) {
220 event.stop();
220 event.stop();
221 return false;
221 return false;
222 } else {
222 } else {
223 return true;
223 return true;
224 }
224 }
225 } else if (event.which === key.ESC && event.type === 'keydown') {
225 } else if (event.which === key.ESC && event.type === 'keydown') {
226 // First see if the tooltip is active and if so cancel it.
226 // First see if the tooltip is active and if so cancel it.
227 if (tooltip_closed) {
227 if (tooltip_closed) {
228 // The call to remove_and_cancel_tooltip above in L177 doesn't pass
228 // The call to remove_and_cancel_tooltip above in L177 doesn't pass
229 // force=true. Because of this it won't actually close the tooltip
229 // force=true. Because of this it won't actually close the tooltip
230 // if it is in sticky mode. Thus, we have to check again if it is open
230 // if it is in sticky mode. Thus, we have to check again if it is open
231 // and close it with force=true.
231 // and close it with force=true.
232 if (!IPython.tooltip._hidden) {
232 if (!IPython.tooltip._hidden) {
233 IPython.tooltip.remove_and_cancel_tooltip(true);
233 IPython.tooltip.remove_and_cancel_tooltip(true);
234 }
234 }
235 // If we closed the tooltip, don't let CM or the global handlers
235 // If we closed the tooltip, don't let CM or the global handlers
236 // handle this event.
236 // handle this event.
237 event.stop();
237 event.stop();
238 return true;
238 return true;
239 }
239 }
240 if (that.code_mirror.options.keyMap === "vim-insert") {
240 if (that.code_mirror.options.keyMap === "vim-insert") {
241 // vim keyMap is active and in insert mode. In this case we leave vim
241 // vim keyMap is active and in insert mode. In this case we leave vim
242 // insert mode, but remain in notebook edit mode.
242 // insert mode, but remain in notebook edit mode.
243 // Let' CM handle this event and prevent global handling.
243 // Let' CM handle this event and prevent global handling.
244 event.stop();
244 event.stop();
245 return false;
245 return false;
246 } else {
246 } else {
247 // vim keyMap is not active. Leave notebook edit mode.
247 // vim keyMap is not active. Leave notebook edit mode.
248 // Don't let CM handle the event, defer to global handling.
248 // Don't let CM handle the event, defer to global handling.
249 return true;
249 return true;
250 }
250 }
251 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
251 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
252 // If we are not at the bottom, let CM handle the down arrow and
252 // If we are not at the bottom, let CM handle the down arrow and
253 // prevent the global keydown handler from handling it.
253 // prevent the global keydown handler from handling it.
254 if (!that.at_bottom()) {
254 if (!that.at_bottom()) {
255 event.stop();
255 event.stop();
256 return false;
256 return false;
257 } else {
257 } else {
258 return true;
258 return true;
259 }
259 }
260 } else if (event.keyCode === key.TAB && event.type === 'keydown' && event.shiftKey) {
260 } else if (event.keyCode === key.TAB && event.type === 'keydown' && event.shiftKey) {
261 if (editor.somethingSelected()){
261 if (editor.somethingSelected()){
262 var anchor = editor.getCursor("anchor");
262 var anchor = editor.getCursor("anchor");
263 var head = editor.getCursor("head");
263 var head = editor.getCursor("head");
264 if( anchor.line != head.line){
264 if( anchor.line != head.line){
265 return false;
265 return false;
266 }
266 }
267 }
267 }
268 IPython.tooltip.request(that);
268 IPython.tooltip.request(that);
269 event.stop();
269 event.stop();
270 return true;
270 return true;
271 } else if (event.keyCode === key.TAB && event.type == 'keydown') {
271 } else if (event.keyCode === key.TAB && event.type == 'keydown') {
272 // Tab completion.
272 // Tab completion.
273 IPython.tooltip.remove_and_cancel_tooltip();
273 IPython.tooltip.remove_and_cancel_tooltip();
274 if (editor.somethingSelected()) {
274 if (editor.somethingSelected()) {
275 return false;
275 return false;
276 }
276 }
277 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
277 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
278 if (pre_cursor.trim() === "") {
278 if (pre_cursor.trim() === "") {
279 // Don't autocomplete if the part of the line before the cursor
279 // Don't autocomplete if the part of the line before the cursor
280 // is empty. In this case, let CodeMirror handle indentation.
280 // is empty. In this case, let CodeMirror handle indentation.
281 return false;
281 return false;
282 } else {
282 } else {
283 event.stop();
283 event.stop();
284 this.completer.startCompletion();
284 this.completer.startCompletion();
285 return true;
285 return true;
286 }
286 }
287 } else {
287 } else {
288 // keypress/keyup also trigger on TAB press, and we don't want to
288 // keypress/keyup also trigger on TAB press, and we don't want to
289 // use those to disable tab completion.
289 // use those to disable tab completion.
290 return false;
290 return false;
291 }
291 }
292 return false;
292 return false;
293 };
293 };
294
294
295 // Kernel related calls.
295 // Kernel related calls.
296
296
297 CodeCell.prototype.set_kernel = function (kernel) {
297 CodeCell.prototype.set_kernel = function (kernel) {
298 this.kernel = kernel;
298 this.kernel = kernel;
299 };
299 };
300
300
301 /**
301 /**
302 * Execute current code cell to the kernel
302 * Execute current code cell to the kernel
303 * @method execute
303 * @method execute
304 */
304 */
305 CodeCell.prototype.execute = function () {
305 CodeCell.prototype.execute = function () {
306 this.output_area.clear_output();
306 this.output_area.clear_output();
307
307
308 // Clear widget area
308 // Clear widget area
309 this.widget_subarea.html('');
309 this.widget_subarea.html('');
310 this.widget_subarea.height('');
310 this.widget_subarea.height('');
311 this.widget_area.height('');
311 this.widget_area.height('');
312 this.widget_area.hide();
312 this.widget_area.hide();
313
313
314 this.set_input_prompt('*');
314 this.set_input_prompt('*');
315 this.element.addClass("running");
315 this.element.addClass("running");
316 if (this.last_msg_id) {
316 if (this.last_msg_id) {
317 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
317 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
318 }
318 }
319 var callbacks = this.get_callbacks();
319 var callbacks = this.get_callbacks();
320
320
321 var old_msg_id = this.last_msg_id;
321 var old_msg_id = this.last_msg_id;
322 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
322 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
323 if (old_msg_id) {
323 if (old_msg_id) {
324 delete CodeCell.msg_cells[old_msg_id];
324 delete CodeCell.msg_cells[old_msg_id];
325 }
325 }
326 CodeCell.msg_cells[this.last_msg_id] = this;
326 CodeCell.msg_cells[this.last_msg_id] = this;
327 };
327 };
328
328
329 /**
329 /**
330 * Construct the default callbacks for
330 * Construct the default callbacks for
331 * @method get_callbacks
331 * @method get_callbacks
332 */
332 */
333 CodeCell.prototype.get_callbacks = function () {
333 CodeCell.prototype.get_callbacks = function () {
334 return {
334 return {
335 shell : {
335 shell : {
336 reply : $.proxy(this._handle_execute_reply, this),
336 reply : $.proxy(this._handle_execute_reply, this),
337 payload : {
337 payload : {
338 set_next_input : $.proxy(this._handle_set_next_input, this),
338 set_next_input : $.proxy(this._handle_set_next_input, this),
339 page : $.proxy(this._open_with_pager, this)
339 page : $.proxy(this._open_with_pager, this)
340 }
340 }
341 },
341 },
342 iopub : {
342 iopub : {
343 output : $.proxy(this.output_area.handle_output, this.output_area),
343 output : $.proxy(this.output_area.handle_output, this.output_area),
344 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
344 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
345 },
345 },
346 input : $.proxy(this._handle_input_request, this)
346 input : $.proxy(this._handle_input_request, this)
347 };
347 };
348 };
348 };
349
349
350 CodeCell.prototype._open_with_pager = function (payload) {
350 CodeCell.prototype._open_with_pager = function (payload) {
351 $([IPython.events]).trigger('open_with_text.Pager', payload);
351 $([IPython.events]).trigger('open_with_text.Pager', payload);
352 };
352 };
353
353
354 /**
354 /**
355 * @method _handle_execute_reply
355 * @method _handle_execute_reply
356 * @private
356 * @private
357 */
357 */
358 CodeCell.prototype._handle_execute_reply = function (msg) {
358 CodeCell.prototype._handle_execute_reply = function (msg) {
359 this.set_input_prompt(msg.content.execution_count);
359 this.set_input_prompt(msg.content.execution_count);
360 this.element.removeClass("running");
360 this.element.removeClass("running");
361 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
361 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
362 };
362 };
363
363
364 /**
364 /**
365 * @method _handle_set_next_input
365 * @method _handle_set_next_input
366 * @private
366 * @private
367 */
367 */
368 CodeCell.prototype._handle_set_next_input = function (payload) {
368 CodeCell.prototype._handle_set_next_input = function (payload) {
369 var data = {'cell': this, 'text': payload.text};
369 var data = {'cell': this, 'text': payload.text};
370 $([IPython.events]).trigger('set_next_input.Notebook', data);
370 $([IPython.events]).trigger('set_next_input.Notebook', data);
371 };
371 };
372
372
373 /**
373 /**
374 * @method _handle_input_request
374 * @method _handle_input_request
375 * @private
375 * @private
376 */
376 */
377 CodeCell.prototype._handle_input_request = function (msg) {
377 CodeCell.prototype._handle_input_request = function (msg) {
378 this.output_area.append_raw_input(msg);
378 this.output_area.append_raw_input(msg);
379 };
379 };
380
380
381
381
382 // Basic cell manipulation.
382 // Basic cell manipulation.
383
383
384 CodeCell.prototype.select = function () {
384 CodeCell.prototype.select = function () {
385 var cont = IPython.Cell.prototype.select.apply(this);
385 var cont = IPython.Cell.prototype.select.apply(this);
386 if (cont) {
386 if (cont) {
387 this.code_mirror.refresh();
387 this.code_mirror.refresh();
388 this.auto_highlight();
388 this.auto_highlight();
389 }
389 }
390 return cont;
390 return cont;
391 };
391 };
392
392
393 CodeCell.prototype.render = function () {
393 CodeCell.prototype.render = function () {
394 var cont = IPython.Cell.prototype.render.apply(this);
394 var cont = IPython.Cell.prototype.render.apply(this);
395 // Always execute, even if we are already in the rendered state
395 // Always execute, even if we are already in the rendered state
396 return cont;
396 return cont;
397 };
397 };
398
398
399 CodeCell.prototype.unrender = function () {
399 CodeCell.prototype.unrender = function () {
400 // CodeCell is always rendered
400 // CodeCell is always rendered
401 return false;
401 return false;
402 };
402 };
403
403
404 CodeCell.prototype.edit_mode = function () {
404 CodeCell.prototype.edit_mode = function () {
405 var cont = IPython.Cell.prototype.edit_mode.apply(this);
405 var cont = IPython.Cell.prototype.edit_mode.apply(this);
406 if (cont) {
406 if (cont) {
407 this.focus_editor();
407 this.focus_editor();
408 }
408 }
409 return cont;
409 return cont;
410 }
410 }
411
411
412 CodeCell.prototype.select_all = function () {
412 CodeCell.prototype.select_all = function () {
413 var start = {line: 0, ch: 0};
413 var start = {line: 0, ch: 0};
414 var nlines = this.code_mirror.lineCount();
414 var nlines = this.code_mirror.lineCount();
415 var last_line = this.code_mirror.getLine(nlines-1);
415 var last_line = this.code_mirror.getLine(nlines-1);
416 var end = {line: nlines-1, ch: last_line.length};
416 var end = {line: nlines-1, ch: last_line.length};
417 this.code_mirror.setSelection(start, end);
417 this.code_mirror.setSelection(start, end);
418 };
418 };
419
419
420
420
421 CodeCell.prototype.collapse = function () {
421 CodeCell.prototype.collapse_output = function () {
422 this.collapsed = true;
422 this.collapsed = true;
423 this.output_area.collapse();
423 this.output_area.collapse();
424 };
424 };
425
425
426
426
427 CodeCell.prototype.expand = function () {
427 CodeCell.prototype.expand_output = function () {
428 this.collapsed = false;
428 this.collapsed = false;
429 this.output_area.expand();
429 this.output_area.expand();
430 this.output_area.unscroll_area();
430 };
431 };
431
432
433 CodeCell.prototype.scroll_output = function () {
434 this.output_area.expand();
435 this.output_area.scroll_if_long();
436 };
432
437
433 CodeCell.prototype.toggle_output = function () {
438 CodeCell.prototype.toggle_output = function () {
434 this.collapsed = Boolean(1 - this.collapsed);
439 this.collapsed = Boolean(1 - this.collapsed);
435 this.output_area.toggle_output();
440 this.output_area.toggle_output();
436 };
441 };
437
442
438
439 CodeCell.prototype.toggle_output_scroll = function () {
443 CodeCell.prototype.toggle_output_scroll = function () {
440 this.output_area.toggle_scroll();
444 this.output_area.toggle_scroll();
441 };
445 };
442
446
443
447
444 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
448 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
445 var ns;
449 var ns;
446 if (prompt_value == undefined) {
450 if (prompt_value == undefined) {
447 ns = "&nbsp;";
451 ns = "&nbsp;";
448 } else {
452 } else {
449 ns = encodeURIComponent(prompt_value);
453 ns = encodeURIComponent(prompt_value);
450 }
454 }
451 return 'In&nbsp;[' + ns + ']:';
455 return 'In&nbsp;[' + ns + ']:';
452 };
456 };
453
457
454 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
458 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
455 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
459 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
456 for(var i=1; i < lines_number; i++) {
460 for(var i=1; i < lines_number; i++) {
457 html.push(['...:']);
461 html.push(['...:']);
458 }
462 }
459 return html.join('<br/>');
463 return html.join('<br/>');
460 };
464 };
461
465
462 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
466 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
463
467
464
468
465 CodeCell.prototype.set_input_prompt = function (number) {
469 CodeCell.prototype.set_input_prompt = function (number) {
466 var nline = 1;
470 var nline = 1;
467 if (this.code_mirror !== undefined) {
471 if (this.code_mirror !== undefined) {
468 nline = this.code_mirror.lineCount();
472 nline = this.code_mirror.lineCount();
469 }
473 }
470 this.input_prompt_number = number;
474 this.input_prompt_number = number;
471 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
475 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
472 this.element.find('div.input_prompt').html(prompt_html);
476 this.element.find('div.input_prompt').html(prompt_html);
473 };
477 };
474
478
475
479
476 CodeCell.prototype.clear_input = function () {
480 CodeCell.prototype.clear_input = function () {
477 this.code_mirror.setValue('');
481 this.code_mirror.setValue('');
478 };
482 };
479
483
480
484
481 CodeCell.prototype.get_text = function () {
485 CodeCell.prototype.get_text = function () {
482 return this.code_mirror.getValue();
486 return this.code_mirror.getValue();
483 };
487 };
484
488
485
489
486 CodeCell.prototype.set_text = function (code) {
490 CodeCell.prototype.set_text = function (code) {
487 return this.code_mirror.setValue(code);
491 return this.code_mirror.setValue(code);
488 };
492 };
489
493
490
494
491 CodeCell.prototype.at_top = function () {
495 CodeCell.prototype.at_top = function () {
492 var cursor = this.code_mirror.getCursor();
496 var cursor = this.code_mirror.getCursor();
493 if (cursor.line === 0 && cursor.ch === 0) {
497 if (cursor.line === 0 && cursor.ch === 0) {
494 return true;
498 return true;
495 } else {
499 } else {
496 return false;
500 return false;
497 }
501 }
498 };
502 };
499
503
500
504
501 CodeCell.prototype.at_bottom = function () {
505 CodeCell.prototype.at_bottom = function () {
502 var cursor = this.code_mirror.getCursor();
506 var cursor = this.code_mirror.getCursor();
503 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
507 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
504 return true;
508 return true;
505 } else {
509 } else {
506 return false;
510 return false;
507 }
511 }
508 };
512 };
509
513
510
514
511 CodeCell.prototype.clear_output = function (wait) {
515 CodeCell.prototype.clear_output = function (wait) {
512 this.output_area.clear_output(wait);
516 this.output_area.clear_output(wait);
517 this.set_input_prompt();
513 };
518 };
514
519
515
520
516 // JSON serialization
521 // JSON serialization
517
522
518 CodeCell.prototype.fromJSON = function (data) {
523 CodeCell.prototype.fromJSON = function (data) {
519 IPython.Cell.prototype.fromJSON.apply(this, arguments);
524 IPython.Cell.prototype.fromJSON.apply(this, arguments);
520 if (data.cell_type === 'code') {
525 if (data.cell_type === 'code') {
521 if (data.input !== undefined) {
526 if (data.input !== undefined) {
522 this.set_text(data.input);
527 this.set_text(data.input);
523 // make this value the starting point, so that we can only undo
528 // make this value the starting point, so that we can only undo
524 // to this state, instead of a blank cell
529 // to this state, instead of a blank cell
525 this.code_mirror.clearHistory();
530 this.code_mirror.clearHistory();
526 this.auto_highlight();
531 this.auto_highlight();
527 }
532 }
528 if (data.prompt_number !== undefined) {
533 if (data.prompt_number !== undefined) {
529 this.set_input_prompt(data.prompt_number);
534 this.set_input_prompt(data.prompt_number);
530 } else {
535 } else {
531 this.set_input_prompt();
536 this.set_input_prompt();
532 }
537 }
533 this.output_area.fromJSON(data.outputs);
538 this.output_area.fromJSON(data.outputs);
534 if (data.collapsed !== undefined) {
539 if (data.collapsed !== undefined) {
535 if (data.collapsed) {
540 if (data.collapsed) {
536 this.collapse();
541 this.collapse_output();
537 } else {
542 } else {
538 this.expand();
543 this.expand_output();
539 }
544 }
540 }
545 }
541 }
546 }
542 };
547 };
543
548
544
549
545 CodeCell.prototype.toJSON = function () {
550 CodeCell.prototype.toJSON = function () {
546 var data = IPython.Cell.prototype.toJSON.apply(this);
551 var data = IPython.Cell.prototype.toJSON.apply(this);
547 data.input = this.get_text();
552 data.input = this.get_text();
548 // is finite protect against undefined and '*' value
553 // is finite protect against undefined and '*' value
549 if (isFinite(this.input_prompt_number)) {
554 if (isFinite(this.input_prompt_number)) {
550 data.prompt_number = this.input_prompt_number;
555 data.prompt_number = this.input_prompt_number;
551 }
556 }
552 var outputs = this.output_area.toJSON();
557 var outputs = this.output_area.toJSON();
553 data.outputs = outputs;
558 data.outputs = outputs;
554 data.language = 'python';
559 data.language = 'python';
555 data.collapsed = this.collapsed;
560 data.collapsed = this.collapsed;
556 return data;
561 return data;
557 };
562 };
558
563
559
564
560 IPython.CodeCell = CodeCell;
565 IPython.CodeCell = CodeCell;
561
566
562 return IPython;
567 return IPython;
563 }(IPython));
568 }(IPython));
@@ -1,320 +1,302 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // MenuBar
9 // MenuBar
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule MenuBar
15 * @submodule MenuBar
16 */
16 */
17
17
18
18
19 var IPython = (function (IPython) {
19 var IPython = (function (IPython) {
20 "use strict";
20 "use strict";
21
21
22 var utils = IPython.utils;
22 var utils = IPython.utils;
23
23
24 /**
24 /**
25 * A MenuBar Class to generate the menubar of IPython notebook
25 * A MenuBar Class to generate the menubar of IPython notebook
26 * @Class MenuBar
26 * @Class MenuBar
27 *
27 *
28 * @constructor
28 * @constructor
29 *
29 *
30 *
30 *
31 * @param selector {string} selector for the menubar element in DOM
31 * @param selector {string} selector for the menubar element in DOM
32 * @param {object} [options]
32 * @param {object} [options]
33 * @param [options.baseProjectUrl] {String} String to use for the
33 * @param [options.baseProjectUrl] {String} String to use for the
34 * Base Project url, default would be to inspect
34 * Base Project url, default would be to inspect
35 * $('body').data('baseProjectUrl');
35 * $('body').data('baseProjectUrl');
36 * does not support change for now is set through this option
36 * does not support change for now is set through this option
37 */
37 */
38 var MenuBar = function (selector, options) {
38 var MenuBar = function (selector, options) {
39 options = options || {};
39 options = options || {};
40 if (options.baseProjectUrl !== undefined) {
40 if (options.baseProjectUrl !== undefined) {
41 this._baseProjectUrl = options.baseProjectUrl;
41 this._baseProjectUrl = options.baseProjectUrl;
42 }
42 }
43 this.selector = selector;
43 this.selector = selector;
44 if (this.selector !== undefined) {
44 if (this.selector !== undefined) {
45 this.element = $(selector);
45 this.element = $(selector);
46 this.style();
46 this.style();
47 this.bind_events();
47 this.bind_events();
48 }
48 }
49 };
49 };
50
50
51 MenuBar.prototype.baseProjectUrl = function(){
51 MenuBar.prototype.baseProjectUrl = function(){
52 return this._baseProjectUrl || $('body').data('baseProjectUrl');
52 return this._baseProjectUrl || $('body').data('baseProjectUrl');
53 };
53 };
54
54
55 MenuBar.prototype.notebookPath = function() {
55 MenuBar.prototype.notebookPath = function() {
56 var path = $('body').data('notebookPath');
56 var path = $('body').data('notebookPath');
57 path = decodeURIComponent(path);
57 path = decodeURIComponent(path);
58 return path;
58 return path;
59 };
59 };
60
60
61 MenuBar.prototype.style = function () {
61 MenuBar.prototype.style = function () {
62 this.element.addClass('border-box-sizing');
62 this.element.addClass('border-box-sizing');
63 this.element.find("li").click(function (event, ui) {
63 this.element.find("li").click(function (event, ui) {
64 // The selected cell loses focus when the menu is entered, so we
64 // The selected cell loses focus when the menu is entered, so we
65 // re-select it upon selection.
65 // re-select it upon selection.
66 var i = IPython.notebook.get_selected_index();
66 var i = IPython.notebook.get_selected_index();
67 IPython.notebook.select(i);
67 IPython.notebook.select(i);
68 }
68 }
69 );
69 );
70 };
70 };
71
71
72 MenuBar.prototype._nbconvert = function (format, download) {
72 MenuBar.prototype._nbconvert = function (format, download) {
73 download = download || false;
73 download = download || false;
74 var notebook_name = IPython.notebook.get_notebook_name();
74 var notebook_name = IPython.notebook.get_notebook_name();
75 if (IPython.notebook.dirty) {
75 if (IPython.notebook.dirty) {
76 IPython.notebook.save_notebook({async : false});
76 IPython.notebook.save_notebook({async : false});
77 }
77 }
78 var url = utils.url_path_join(
78 var url = utils.url_path_join(
79 this.baseProjectUrl(),
79 this.baseProjectUrl(),
80 'nbconvert',
80 'nbconvert',
81 format,
81 format,
82 this.notebookPath(),
82 this.notebookPath(),
83 notebook_name + '.ipynb'
83 notebook_name + '.ipynb'
84 ) + "?download=" + download.toString();
84 ) + "?download=" + download.toString();
85
85
86 window.open(url);
86 window.open(url);
87 }
87 }
88
88
89 MenuBar.prototype.bind_events = function () {
89 MenuBar.prototype.bind_events = function () {
90 // File
90 // File
91 var that = this;
91 var that = this;
92 this.element.find('#new_notebook').click(function () {
92 this.element.find('#new_notebook').click(function () {
93 IPython.notebook.new_notebook();
93 IPython.notebook.new_notebook();
94 });
94 });
95 this.element.find('#open_notebook').click(function () {
95 this.element.find('#open_notebook').click(function () {
96 window.open(utils.url_join_encode(
96 window.open(utils.url_join_encode(
97 that.baseProjectUrl(),
97 that.baseProjectUrl(),
98 'tree',
98 'tree',
99 that.notebookPath()
99 that.notebookPath()
100 ));
100 ));
101 });
101 });
102 this.element.find('#copy_notebook').click(function () {
102 this.element.find('#copy_notebook').click(function () {
103 IPython.notebook.copy_notebook();
103 IPython.notebook.copy_notebook();
104 return false;
104 return false;
105 });
105 });
106 this.element.find('#download_ipynb').click(function () {
106 this.element.find('#download_ipynb').click(function () {
107 var notebook_name = IPython.notebook.get_notebook_name();
107 var notebook_name = IPython.notebook.get_notebook_name();
108 if (IPython.notebook.dirty) {
108 if (IPython.notebook.dirty) {
109 IPython.notebook.save_notebook({async : false});
109 IPython.notebook.save_notebook({async : false});
110 }
110 }
111
111
112 var url = utils.url_join_encode(
112 var url = utils.url_join_encode(
113 that.baseProjectUrl(),
113 that.baseProjectUrl(),
114 'files',
114 'files',
115 that.notebookPath(),
115 that.notebookPath(),
116 notebook_name + '.ipynb'
116 notebook_name + '.ipynb'
117 );
117 );
118 window.location.assign(url);
118 window.location.assign(url);
119 });
119 });
120
120
121 this.element.find('#print_preview').click(function () {
121 this.element.find('#print_preview').click(function () {
122 that._nbconvert('html', false);
122 that._nbconvert('html', false);
123 });
123 });
124
124
125 this.element.find('#download_py').click(function () {
125 this.element.find('#download_py').click(function () {
126 that._nbconvert('python', true);
126 that._nbconvert('python', true);
127 });
127 });
128
128
129 this.element.find('#download_html').click(function () {
129 this.element.find('#download_html').click(function () {
130 that._nbconvert('html', true);
130 that._nbconvert('html', true);
131 });
131 });
132
132
133 this.element.find('#download_rst').click(function () {
133 this.element.find('#download_rst').click(function () {
134 that._nbconvert('rst', true);
134 that._nbconvert('rst', true);
135 });
135 });
136
136
137 this.element.find('#rename_notebook').click(function () {
137 this.element.find('#rename_notebook').click(function () {
138 IPython.save_widget.rename_notebook();
138 IPython.save_widget.rename_notebook();
139 });
139 });
140 this.element.find('#save_checkpoint').click(function () {
140 this.element.find('#save_checkpoint').click(function () {
141 IPython.notebook.save_checkpoint();
141 IPython.notebook.save_checkpoint();
142 });
142 });
143 this.element.find('#restore_checkpoint').click(function () {
143 this.element.find('#restore_checkpoint').click(function () {
144 });
144 });
145 this.element.find('#kill_and_exit').click(function () {
145 this.element.find('#kill_and_exit').click(function () {
146 IPython.notebook.session.delete();
146 IPython.notebook.session.delete();
147 setTimeout(function(){
147 setTimeout(function(){
148 // allow closing of new tabs in Chromium, impossible in FF
148 // allow closing of new tabs in Chromium, impossible in FF
149 window.open('', '_self', '');
149 window.open('', '_self', '');
150 window.close();
150 window.close();
151 }, 500);
151 }, 500);
152 });
152 });
153 // Edit
153 // Edit
154 this.element.find('#cut_cell').click(function () {
154 this.element.find('#cut_cell').click(function () {
155 IPython.notebook.cut_cell();
155 IPython.notebook.cut_cell();
156 });
156 });
157 this.element.find('#copy_cell').click(function () {
157 this.element.find('#copy_cell').click(function () {
158 IPython.notebook.copy_cell();
158 IPython.notebook.copy_cell();
159 });
159 });
160 this.element.find('#delete_cell').click(function () {
160 this.element.find('#delete_cell').click(function () {
161 IPython.notebook.delete_cell();
161 IPython.notebook.delete_cell();
162 });
162 });
163 this.element.find('#undelete_cell').click(function () {
163 this.element.find('#undelete_cell').click(function () {
164 IPython.notebook.undelete_cell();
164 IPython.notebook.undelete_cell();
165 });
165 });
166 this.element.find('#split_cell').click(function () {
166 this.element.find('#split_cell').click(function () {
167 IPython.notebook.split_cell();
167 IPython.notebook.split_cell();
168 });
168 });
169 this.element.find('#merge_cell_above').click(function () {
169 this.element.find('#merge_cell_above').click(function () {
170 IPython.notebook.merge_cell_above();
170 IPython.notebook.merge_cell_above();
171 });
171 });
172 this.element.find('#merge_cell_below').click(function () {
172 this.element.find('#merge_cell_below').click(function () {
173 IPython.notebook.merge_cell_below();
173 IPython.notebook.merge_cell_below();
174 });
174 });
175 this.element.find('#move_cell_up').click(function () {
175 this.element.find('#move_cell_up').click(function () {
176 IPython.notebook.move_cell_up();
176 IPython.notebook.move_cell_up();
177 });
177 });
178 this.element.find('#move_cell_down').click(function () {
178 this.element.find('#move_cell_down').click(function () {
179 IPython.notebook.move_cell_down();
179 IPython.notebook.move_cell_down();
180 });
180 });
181 this.element.find('#select_previous').click(function () {
181 this.element.find('#select_previous').click(function () {
182 IPython.notebook.select_prev();
182 IPython.notebook.select_prev();
183 });
183 });
184 this.element.find('#select_next').click(function () {
184 this.element.find('#select_next').click(function () {
185 IPython.notebook.select_next();
185 IPython.notebook.select_next();
186 });
186 });
187 this.element.find('#edit_nb_metadata').click(function () {
187 this.element.find('#edit_nb_metadata').click(function () {
188 IPython.notebook.edit_metadata();
188 IPython.notebook.edit_metadata();
189 });
189 });
190
190
191 // View
191 // View
192 this.element.find('#toggle_header').click(function () {
192 this.element.find('#toggle_header').click(function () {
193 $('div#header').toggle();
193 $('div#header').toggle();
194 IPython.layout_manager.do_resize();
194 IPython.layout_manager.do_resize();
195 });
195 });
196 this.element.find('#toggle_toolbar').click(function () {
196 this.element.find('#toggle_toolbar').click(function () {
197 $('div#maintoolbar').toggle();
197 $('div#maintoolbar').toggle();
198 IPython.layout_manager.do_resize();
198 IPython.layout_manager.do_resize();
199 });
199 });
200 // Insert
200 // Insert
201 this.element.find('#insert_cell_above').click(function () {
201 this.element.find('#insert_cell_above').click(function () {
202 IPython.notebook.insert_cell_above('code');
202 IPython.notebook.insert_cell_above('code');
203 IPython.notebook.select_prev();
203 IPython.notebook.select_prev();
204 });
204 });
205 this.element.find('#insert_cell_below').click(function () {
205 this.element.find('#insert_cell_below').click(function () {
206 IPython.notebook.insert_cell_below('code');
206 IPython.notebook.insert_cell_below('code');
207 IPython.notebook.select_next();
207 IPython.notebook.select_next();
208 });
208 });
209 // Cell
209 // Cell
210 this.element.find('#run_cell').click(function () {
210 this.element.find('#run_cell').click(function () {
211 IPython.notebook.execute_cell();
211 IPython.notebook.execute_cell();
212 });
212 });
213 this.element.find('#run_cell_select_below').click(function () {
213 this.element.find('#run_cell_select_below').click(function () {
214 IPython.notebook.execute_cell_and_select_below();
214 IPython.notebook.execute_cell_and_select_below();
215 });
215 });
216 this.element.find('#run_cell_insert_below').click(function () {
216 this.element.find('#run_cell_insert_below').click(function () {
217 IPython.notebook.execute_cell_and_insert_below();
217 IPython.notebook.execute_cell_and_insert_below();
218 });
218 });
219 this.element.find('#run_all_cells').click(function () {
219 this.element.find('#run_all_cells').click(function () {
220 IPython.notebook.execute_all_cells();
220 IPython.notebook.execute_all_cells();
221 });
221 });
222 this.element.find('#run_all_cells_above').click(function () {
222 this.element.find('#run_all_cells_above').click(function () {
223 IPython.notebook.execute_cells_above();
223 IPython.notebook.execute_cells_above();
224 });
224 });
225 this.element.find('#run_all_cells_below').click(function () {
225 this.element.find('#run_all_cells_below').click(function () {
226 IPython.notebook.execute_cells_below();
226 IPython.notebook.execute_cells_below();
227 });
227 });
228 this.element.find('#to_code').click(function () {
228 this.element.find('#collapse_current_output').click(function () {
229 IPython.notebook.to_code();
229 IPython.notebook.collapse_output();
230 });
230 });
231 this.element.find('#to_markdown').click(function () {
231 this.element.find('#scroll_current_output').click(function () {
232 IPython.notebook.to_markdown();
232 IPython.notebook.scroll_output();
233 });
233 });
234 this.element.find('#to_raw').click(function () {
234 this.element.find('#expand_current_output').click(function () {
235 IPython.notebook.to_raw();
235 IPython.notebook.expand_output();
236 });
236 });
237 this.element.find('#to_heading1').click(function () {
237 this.element.find('#clear_current_output').click(function () {
238 IPython.notebook.to_heading(undefined, 1);
238 IPython.notebook.clear_output();
239 });
240 this.element.find('#to_heading2').click(function () {
241 IPython.notebook.to_heading(undefined, 2);
242 });
243 this.element.find('#to_heading3').click(function () {
244 IPython.notebook.to_heading(undefined, 3);
245 });
246 this.element.find('#to_heading4').click(function () {
247 IPython.notebook.to_heading(undefined, 4);
248 });
249 this.element.find('#to_heading5').click(function () {
250 IPython.notebook.to_heading(undefined, 5);
251 });
252 this.element.find('#to_heading6').click(function () {
253 IPython.notebook.to_heading(undefined, 6);
254 });
255 this.element.find('#toggle_output').click(function () {
256 IPython.notebook.toggle_output();
257 });
239 });
258 this.element.find('#collapse_all_output').click(function () {
240 this.element.find('#collapse_all_output').click(function () {
259 IPython.notebook.collapse_all_output();
241 IPython.notebook.collapse_all_output();
260 });
242 });
261 this.element.find('#scroll_all_output').click(function () {
243 this.element.find('#scroll_all_output').click(function () {
262 IPython.notebook.scroll_all_output();
244 IPython.notebook.scroll_all_output();
263 });
245 });
264 this.element.find('#expand_all_output').click(function () {
246 this.element.find('#expand_all_output').click(function () {
265 IPython.notebook.expand_all_output();
247 IPython.notebook.expand_all_output();
266 });
248 });
267 this.element.find('#clear_all_output').click(function () {
249 this.element.find('#clear_all_output').click(function () {
268 IPython.notebook.clear_all_output();
250 IPython.notebook.clear_all_output();
269 });
251 });
270 // Help
252 // Help
271 this.element.find('#keyboard_shortcuts').click(function () {
253 this.element.find('#keyboard_shortcuts').click(function () {
272 IPython.quick_help.show_keyboard_shortcuts();
254 IPython.quick_help.show_keyboard_shortcuts();
273 });
255 });
274
256
275 this.update_restore_checkpoint(null);
257 this.update_restore_checkpoint(null);
276
258
277 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
259 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
278 that.update_restore_checkpoint(IPython.notebook.checkpoints);
260 that.update_restore_checkpoint(IPython.notebook.checkpoints);
279 });
261 });
280
262
281 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
263 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
282 that.update_restore_checkpoint(IPython.notebook.checkpoints);
264 that.update_restore_checkpoint(IPython.notebook.checkpoints);
283 });
265 });
284 };
266 };
285
267
286 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
268 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
287 var ul = this.element.find("#restore_checkpoint").find("ul");
269 var ul = this.element.find("#restore_checkpoint").find("ul");
288 ul.empty();
270 ul.empty();
289 if (!checkpoints || checkpoints.length === 0) {
271 if (!checkpoints || checkpoints.length === 0) {
290 ul.append(
272 ul.append(
291 $("<li/>")
273 $("<li/>")
292 .addClass("disabled")
274 .addClass("disabled")
293 .append(
275 .append(
294 $("<a/>")
276 $("<a/>")
295 .text("No checkpoints")
277 .text("No checkpoints")
296 )
278 )
297 );
279 );
298 return;
280 return;
299 }
281 }
300
282
301 checkpoints.map(function (checkpoint) {
283 checkpoints.map(function (checkpoint) {
302 var d = new Date(checkpoint.last_modified);
284 var d = new Date(checkpoint.last_modified);
303 ul.append(
285 ul.append(
304 $("<li/>").append(
286 $("<li/>").append(
305 $("<a/>")
287 $("<a/>")
306 .attr("href", "#")
288 .attr("href", "#")
307 .text(d.format("mmm dd HH:MM:ss"))
289 .text(d.format("mmm dd HH:MM:ss"))
308 .click(function () {
290 .click(function () {
309 IPython.notebook.restore_checkpoint_dialog(checkpoint);
291 IPython.notebook.restore_checkpoint_dialog(checkpoint);
310 })
292 })
311 )
293 )
312 );
294 );
313 });
295 });
314 };
296 };
315
297
316 IPython.MenuBar = MenuBar;
298 IPython.MenuBar = MenuBar;
317
299
318 return IPython;
300 return IPython;
319
301
320 }(IPython));
302 }(IPython));
@@ -1,2225 +1,2263 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Notebook
9 // Notebook
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16
16
17 /**
17 /**
18 * A notebook contains and manages cells.
18 * A notebook contains and manages cells.
19 *
19 *
20 * @class Notebook
20 * @class Notebook
21 * @constructor
21 * @constructor
22 * @param {String} selector A jQuery selector for the notebook's DOM element
22 * @param {String} selector A jQuery selector for the notebook's DOM element
23 * @param {Object} [options] A config object
23 * @param {Object} [options] A config object
24 */
24 */
25 var Notebook = function (selector, options) {
25 var Notebook = function (selector, options) {
26 var options = options || {};
26 var options = options || {};
27 this._baseProjectUrl = options.baseProjectUrl;
27 this._baseProjectUrl = options.baseProjectUrl;
28 this.notebook_path = options.notebookPath;
28 this.notebook_path = options.notebookPath;
29 this.notebook_name = options.notebookName;
29 this.notebook_name = options.notebookName;
30 this.element = $(selector);
30 this.element = $(selector);
31 this.element.scroll();
31 this.element.scroll();
32 this.element.data("notebook", this);
32 this.element.data("notebook", this);
33 this.next_prompt_number = 1;
33 this.next_prompt_number = 1;
34 this.session = null;
34 this.session = null;
35 this.kernel = null;
35 this.kernel = null;
36 this.clipboard = null;
36 this.clipboard = null;
37 this.undelete_backup = null;
37 this.undelete_backup = null;
38 this.undelete_index = null;
38 this.undelete_index = null;
39 this.undelete_below = false;
39 this.undelete_below = false;
40 this.paste_enabled = false;
40 this.paste_enabled = false;
41 // It is important to start out in command mode to match the intial mode
41 // It is important to start out in command mode to match the intial mode
42 // of the KeyboardManager.
42 // of the KeyboardManager.
43 this.mode = 'command';
43 this.mode = 'command';
44 this.set_dirty(false);
44 this.set_dirty(false);
45 this.metadata = {};
45 this.metadata = {};
46 this._checkpoint_after_save = false;
46 this._checkpoint_after_save = false;
47 this.last_checkpoint = null;
47 this.last_checkpoint = null;
48 this.checkpoints = [];
48 this.checkpoints = [];
49 this.autosave_interval = 0;
49 this.autosave_interval = 0;
50 this.autosave_timer = null;
50 this.autosave_timer = null;
51 // autosave *at most* every two minutes
51 // autosave *at most* every two minutes
52 this.minimum_autosave_interval = 120000;
52 this.minimum_autosave_interval = 120000;
53 // single worksheet for now
53 // single worksheet for now
54 this.worksheet_metadata = {};
54 this.worksheet_metadata = {};
55 this.notebook_name_blacklist_re = /[\/\\:]/;
55 this.notebook_name_blacklist_re = /[\/\\:]/;
56 this.nbformat = 3 // Increment this when changing the nbformat
56 this.nbformat = 3 // Increment this when changing the nbformat
57 this.nbformat_minor = 0 // Increment this when changing the nbformat
57 this.nbformat_minor = 0 // Increment this when changing the nbformat
58 this.style();
58 this.style();
59 this.create_elements();
59 this.create_elements();
60 this.bind_events();
60 this.bind_events();
61 };
61 };
62
62
63 /**
63 /**
64 * Tweak the notebook's CSS style.
64 * Tweak the notebook's CSS style.
65 *
65 *
66 * @method style
66 * @method style
67 */
67 */
68 Notebook.prototype.style = function () {
68 Notebook.prototype.style = function () {
69 $('div#notebook').addClass('border-box-sizing');
69 $('div#notebook').addClass('border-box-sizing');
70 };
70 };
71
71
72 /**
72 /**
73 * Get the root URL of the notebook server.
73 * Get the root URL of the notebook server.
74 *
74 *
75 * @method baseProjectUrl
75 * @method baseProjectUrl
76 * @return {String} The base project URL
76 * @return {String} The base project URL
77 */
77 */
78 Notebook.prototype.baseProjectUrl = function() {
78 Notebook.prototype.baseProjectUrl = function() {
79 return this._baseProjectUrl || $('body').data('baseProjectUrl');
79 return this._baseProjectUrl || $('body').data('baseProjectUrl');
80 };
80 };
81
81
82 Notebook.prototype.notebookName = function() {
82 Notebook.prototype.notebookName = function() {
83 return $('body').data('notebookName');
83 return $('body').data('notebookName');
84 };
84 };
85
85
86 Notebook.prototype.notebookPath = function() {
86 Notebook.prototype.notebookPath = function() {
87 return $('body').data('notebookPath');
87 return $('body').data('notebookPath');
88 };
88 };
89
89
90 /**
90 /**
91 * Create an HTML and CSS representation of the notebook.
91 * Create an HTML and CSS representation of the notebook.
92 *
92 *
93 * @method create_elements
93 * @method create_elements
94 */
94 */
95 Notebook.prototype.create_elements = function () {
95 Notebook.prototype.create_elements = function () {
96 var that = this;
96 var that = this;
97 this.element.attr('tabindex','-1');
97 this.element.attr('tabindex','-1');
98 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
98 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
99 // We add this end_space div to the end of the notebook div to:
99 // We add this end_space div to the end of the notebook div to:
100 // i) provide a margin between the last cell and the end of the notebook
100 // i) provide a margin between the last cell and the end of the notebook
101 // ii) to prevent the div from scrolling up when the last cell is being
101 // ii) to prevent the div from scrolling up when the last cell is being
102 // edited, but is too low on the page, which browsers will do automatically.
102 // edited, but is too low on the page, which browsers will do automatically.
103 var end_space = $('<div/>').addClass('end_space');
103 var end_space = $('<div/>').addClass('end_space');
104 end_space.dblclick(function (e) {
104 end_space.dblclick(function (e) {
105 var ncells = that.ncells();
105 var ncells = that.ncells();
106 that.insert_cell_below('code',ncells-1);
106 that.insert_cell_below('code',ncells-1);
107 });
107 });
108 this.element.append(this.container);
108 this.element.append(this.container);
109 this.container.append(end_space);
109 this.container.append(end_space);
110 };
110 };
111
111
112 /**
112 /**
113 * Bind JavaScript events: key presses and custom IPython events.
113 * Bind JavaScript events: key presses and custom IPython events.
114 *
114 *
115 * @method bind_events
115 * @method bind_events
116 */
116 */
117 Notebook.prototype.bind_events = function () {
117 Notebook.prototype.bind_events = function () {
118 var that = this;
118 var that = this;
119
119
120 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
120 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
121 var index = that.find_cell_index(data.cell);
121 var index = that.find_cell_index(data.cell);
122 var new_cell = that.insert_cell_below('code',index);
122 var new_cell = that.insert_cell_below('code',index);
123 new_cell.set_text(data.text);
123 new_cell.set_text(data.text);
124 that.dirty = true;
124 that.dirty = true;
125 });
125 });
126
126
127 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
127 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
128 that.dirty = data.value;
128 that.dirty = data.value;
129 });
129 });
130
130
131 $([IPython.events]).on('select.Cell', function (event, data) {
131 $([IPython.events]).on('select.Cell', function (event, data) {
132 var index = that.find_cell_index(data.cell);
132 var index = that.find_cell_index(data.cell);
133 that.select(index);
133 that.select(index);
134 });
134 });
135
135
136 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
136 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
137 var index = that.find_cell_index(data.cell);
137 var index = that.find_cell_index(data.cell);
138 that.select(index);
138 that.select(index);
139 that.edit_mode();
139 that.edit_mode();
140 });
140 });
141
141
142 $([IPython.events]).on('command_mode.Cell', function (event, data) {
142 $([IPython.events]).on('command_mode.Cell', function (event, data) {
143 that.command_mode();
143 that.command_mode();
144 });
144 });
145
145
146 $([IPython.events]).on('status_autorestarting.Kernel', function () {
146 $([IPython.events]).on('status_autorestarting.Kernel', function () {
147 IPython.dialog.modal({
147 IPython.dialog.modal({
148 title: "Kernel Restarting",
148 title: "Kernel Restarting",
149 body: "The kernel appears to have died. It will restart automatically.",
149 body: "The kernel appears to have died. It will restart automatically.",
150 buttons: {
150 buttons: {
151 OK : {
151 OK : {
152 class : "btn-primary"
152 class : "btn-primary"
153 }
153 }
154 }
154 }
155 });
155 });
156 });
156 });
157
157
158 var collapse_time = function (time) {
158 var collapse_time = function (time) {
159 var app_height = $('#ipython-main-app').height(); // content height
159 var app_height = $('#ipython-main-app').height(); // content height
160 var splitter_height = $('div#pager_splitter').outerHeight(true);
160 var splitter_height = $('div#pager_splitter').outerHeight(true);
161 var new_height = app_height - splitter_height;
161 var new_height = app_height - splitter_height;
162 that.element.animate({height : new_height + 'px'}, time);
162 that.element.animate({height : new_height + 'px'}, time);
163 };
163 };
164
164
165 this.element.bind('collapse_pager', function (event, extrap) {
165 this.element.bind('collapse_pager', function (event, extrap) {
166 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
166 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
167 collapse_time(time);
167 collapse_time(time);
168 });
168 });
169
169
170 var expand_time = function (time) {
170 var expand_time = function (time) {
171 var app_height = $('#ipython-main-app').height(); // content height
171 var app_height = $('#ipython-main-app').height(); // content height
172 var splitter_height = $('div#pager_splitter').outerHeight(true);
172 var splitter_height = $('div#pager_splitter').outerHeight(true);
173 var pager_height = $('div#pager').outerHeight(true);
173 var pager_height = $('div#pager').outerHeight(true);
174 var new_height = app_height - pager_height - splitter_height;
174 var new_height = app_height - pager_height - splitter_height;
175 that.element.animate({height : new_height + 'px'}, time);
175 that.element.animate({height : new_height + 'px'}, time);
176 };
176 };
177
177
178 this.element.bind('expand_pager', function (event, extrap) {
178 this.element.bind('expand_pager', function (event, extrap) {
179 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
179 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
180 expand_time(time);
180 expand_time(time);
181 });
181 });
182
182
183 // Firefox 22 broke $(window).on("beforeunload")
183 // Firefox 22 broke $(window).on("beforeunload")
184 // I'm not sure why or how.
184 // I'm not sure why or how.
185 window.onbeforeunload = function (e) {
185 window.onbeforeunload = function (e) {
186 // TODO: Make killing the kernel configurable.
186 // TODO: Make killing the kernel configurable.
187 var kill_kernel = false;
187 var kill_kernel = false;
188 if (kill_kernel) {
188 if (kill_kernel) {
189 that.session.kill_kernel();
189 that.session.kill_kernel();
190 }
190 }
191 // if we are autosaving, trigger an autosave on nav-away.
191 // if we are autosaving, trigger an autosave on nav-away.
192 // still warn, because if we don't the autosave may fail.
192 // still warn, because if we don't the autosave may fail.
193 if (that.dirty) {
193 if (that.dirty) {
194 if ( that.autosave_interval ) {
194 if ( that.autosave_interval ) {
195 // schedule autosave in a timeout
195 // schedule autosave in a timeout
196 // this gives you a chance to forcefully discard changes
196 // this gives you a chance to forcefully discard changes
197 // by reloading the page if you *really* want to.
197 // by reloading the page if you *really* want to.
198 // the timer doesn't start until you *dismiss* the dialog.
198 // the timer doesn't start until you *dismiss* the dialog.
199 setTimeout(function () {
199 setTimeout(function () {
200 if (that.dirty) {
200 if (that.dirty) {
201 that.save_notebook();
201 that.save_notebook();
202 }
202 }
203 }, 1000);
203 }, 1000);
204 return "Autosave in progress, latest changes may be lost.";
204 return "Autosave in progress, latest changes may be lost.";
205 } else {
205 } else {
206 return "Unsaved changes will be lost.";
206 return "Unsaved changes will be lost.";
207 }
207 }
208 };
208 };
209 // Null is the *only* return value that will make the browser not
209 // Null is the *only* return value that will make the browser not
210 // pop up the "don't leave" dialog.
210 // pop up the "don't leave" dialog.
211 return null;
211 return null;
212 };
212 };
213 };
213 };
214
214
215 /**
215 /**
216 * Set the dirty flag, and trigger the set_dirty.Notebook event
216 * Set the dirty flag, and trigger the set_dirty.Notebook event
217 *
217 *
218 * @method set_dirty
218 * @method set_dirty
219 */
219 */
220 Notebook.prototype.set_dirty = function (value) {
220 Notebook.prototype.set_dirty = function (value) {
221 if (value === undefined) {
221 if (value === undefined) {
222 value = true;
222 value = true;
223 }
223 }
224 if (this.dirty == value) {
224 if (this.dirty == value) {
225 return;
225 return;
226 }
226 }
227 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
227 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
228 };
228 };
229
229
230 /**
230 /**
231 * Scroll the top of the page to a given cell.
231 * Scroll the top of the page to a given cell.
232 *
232 *
233 * @method scroll_to_cell
233 * @method scroll_to_cell
234 * @param {Number} cell_number An index of the cell to view
234 * @param {Number} cell_number An index of the cell to view
235 * @param {Number} time Animation time in milliseconds
235 * @param {Number} time Animation time in milliseconds
236 * @return {Number} Pixel offset from the top of the container
236 * @return {Number} Pixel offset from the top of the container
237 */
237 */
238 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
238 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
239 var cells = this.get_cells();
239 var cells = this.get_cells();
240 var time = time || 0;
240 var time = time || 0;
241 cell_number = Math.min(cells.length-1,cell_number);
241 cell_number = Math.min(cells.length-1,cell_number);
242 cell_number = Math.max(0 ,cell_number);
242 cell_number = Math.max(0 ,cell_number);
243 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
243 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
244 this.element.animate({scrollTop:scroll_value}, time);
244 this.element.animate({scrollTop:scroll_value}, time);
245 return scroll_value;
245 return scroll_value;
246 };
246 };
247
247
248 /**
248 /**
249 * Scroll to the bottom of the page.
249 * Scroll to the bottom of the page.
250 *
250 *
251 * @method scroll_to_bottom
251 * @method scroll_to_bottom
252 */
252 */
253 Notebook.prototype.scroll_to_bottom = function () {
253 Notebook.prototype.scroll_to_bottom = function () {
254 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
254 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
255 };
255 };
256
256
257 /**
257 /**
258 * Scroll to the top of the page.
258 * Scroll to the top of the page.
259 *
259 *
260 * @method scroll_to_top
260 * @method scroll_to_top
261 */
261 */
262 Notebook.prototype.scroll_to_top = function () {
262 Notebook.prototype.scroll_to_top = function () {
263 this.element.animate({scrollTop:0}, 0);
263 this.element.animate({scrollTop:0}, 0);
264 };
264 };
265
265
266 // Edit Notebook metadata
266 // Edit Notebook metadata
267
267
268 Notebook.prototype.edit_metadata = function () {
268 Notebook.prototype.edit_metadata = function () {
269 var that = this;
269 var that = this;
270 IPython.dialog.edit_metadata(this.metadata, function (md) {
270 IPython.dialog.edit_metadata(this.metadata, function (md) {
271 that.metadata = md;
271 that.metadata = md;
272 }, 'Notebook');
272 }, 'Notebook');
273 };
273 };
274
274
275 // Cell indexing, retrieval, etc.
275 // Cell indexing, retrieval, etc.
276
276
277 /**
277 /**
278 * Get all cell elements in the notebook.
278 * Get all cell elements in the notebook.
279 *
279 *
280 * @method get_cell_elements
280 * @method get_cell_elements
281 * @return {jQuery} A selector of all cell elements
281 * @return {jQuery} A selector of all cell elements
282 */
282 */
283 Notebook.prototype.get_cell_elements = function () {
283 Notebook.prototype.get_cell_elements = function () {
284 return this.container.children("div.cell");
284 return this.container.children("div.cell");
285 };
285 };
286
286
287 /**
287 /**
288 * Get a particular cell element.
288 * Get a particular cell element.
289 *
289 *
290 * @method get_cell_element
290 * @method get_cell_element
291 * @param {Number} index An index of a cell to select
291 * @param {Number} index An index of a cell to select
292 * @return {jQuery} A selector of the given cell.
292 * @return {jQuery} A selector of the given cell.
293 */
293 */
294 Notebook.prototype.get_cell_element = function (index) {
294 Notebook.prototype.get_cell_element = function (index) {
295 var result = null;
295 var result = null;
296 var e = this.get_cell_elements().eq(index);
296 var e = this.get_cell_elements().eq(index);
297 if (e.length !== 0) {
297 if (e.length !== 0) {
298 result = e;
298 result = e;
299 }
299 }
300 return result;
300 return result;
301 };
301 };
302
302
303 /**
303 /**
304 * Try to get a particular cell by msg_id.
304 * Try to get a particular cell by msg_id.
305 *
305 *
306 * @method get_msg_cell
306 * @method get_msg_cell
307 * @param {String} msg_id A message UUID
307 * @param {String} msg_id A message UUID
308 * @return {Cell} Cell or null if no cell was found.
308 * @return {Cell} Cell or null if no cell was found.
309 */
309 */
310 Notebook.prototype.get_msg_cell = function (msg_id) {
310 Notebook.prototype.get_msg_cell = function (msg_id) {
311 return IPython.CodeCell.msg_cells[msg_id] || null;
311 return IPython.CodeCell.msg_cells[msg_id] || null;
312 };
312 };
313
313
314 /**
314 /**
315 * Count the cells in this notebook.
315 * Count the cells in this notebook.
316 *
316 *
317 * @method ncells
317 * @method ncells
318 * @return {Number} The number of cells in this notebook
318 * @return {Number} The number of cells in this notebook
319 */
319 */
320 Notebook.prototype.ncells = function () {
320 Notebook.prototype.ncells = function () {
321 return this.get_cell_elements().length;
321 return this.get_cell_elements().length;
322 };
322 };
323
323
324 /**
324 /**
325 * Get all Cell objects in this notebook.
325 * Get all Cell objects in this notebook.
326 *
326 *
327 * @method get_cells
327 * @method get_cells
328 * @return {Array} This notebook's Cell objects
328 * @return {Array} This notebook's Cell objects
329 */
329 */
330 // TODO: we are often calling cells as cells()[i], which we should optimize
330 // TODO: we are often calling cells as cells()[i], which we should optimize
331 // to cells(i) or a new method.
331 // to cells(i) or a new method.
332 Notebook.prototype.get_cells = function () {
332 Notebook.prototype.get_cells = function () {
333 return this.get_cell_elements().toArray().map(function (e) {
333 return this.get_cell_elements().toArray().map(function (e) {
334 return $(e).data("cell");
334 return $(e).data("cell");
335 });
335 });
336 };
336 };
337
337
338 /**
338 /**
339 * Get a Cell object from this notebook.
339 * Get a Cell object from this notebook.
340 *
340 *
341 * @method get_cell
341 * @method get_cell
342 * @param {Number} index An index of a cell to retrieve
342 * @param {Number} index An index of a cell to retrieve
343 * @return {Cell} A particular cell
343 * @return {Cell} A particular cell
344 */
344 */
345 Notebook.prototype.get_cell = function (index) {
345 Notebook.prototype.get_cell = function (index) {
346 var result = null;
346 var result = null;
347 var ce = this.get_cell_element(index);
347 var ce = this.get_cell_element(index);
348 if (ce !== null) {
348 if (ce !== null) {
349 result = ce.data('cell');
349 result = ce.data('cell');
350 }
350 }
351 return result;
351 return result;
352 }
352 }
353
353
354 /**
354 /**
355 * Get the cell below a given cell.
355 * Get the cell below a given cell.
356 *
356 *
357 * @method get_next_cell
357 * @method get_next_cell
358 * @param {Cell} cell The provided cell
358 * @param {Cell} cell The provided cell
359 * @return {Cell} The next cell
359 * @return {Cell} The next cell
360 */
360 */
361 Notebook.prototype.get_next_cell = function (cell) {
361 Notebook.prototype.get_next_cell = function (cell) {
362 var result = null;
362 var result = null;
363 var index = this.find_cell_index(cell);
363 var index = this.find_cell_index(cell);
364 if (this.is_valid_cell_index(index+1)) {
364 if (this.is_valid_cell_index(index+1)) {
365 result = this.get_cell(index+1);
365 result = this.get_cell(index+1);
366 }
366 }
367 return result;
367 return result;
368 }
368 }
369
369
370 /**
370 /**
371 * Get the cell above a given cell.
371 * Get the cell above a given cell.
372 *
372 *
373 * @method get_prev_cell
373 * @method get_prev_cell
374 * @param {Cell} cell The provided cell
374 * @param {Cell} cell The provided cell
375 * @return {Cell} The previous cell
375 * @return {Cell} The previous cell
376 */
376 */
377 Notebook.prototype.get_prev_cell = function (cell) {
377 Notebook.prototype.get_prev_cell = function (cell) {
378 // TODO: off-by-one
378 // TODO: off-by-one
379 // nb.get_prev_cell(nb.get_cell(1)) is null
379 // nb.get_prev_cell(nb.get_cell(1)) is null
380 var result = null;
380 var result = null;
381 var index = this.find_cell_index(cell);
381 var index = this.find_cell_index(cell);
382 if (index !== null && index > 1) {
382 if (index !== null && index > 1) {
383 result = this.get_cell(index-1);
383 result = this.get_cell(index-1);
384 }
384 }
385 return result;
385 return result;
386 }
386 }
387
387
388 /**
388 /**
389 * Get the numeric index of a given cell.
389 * Get the numeric index of a given cell.
390 *
390 *
391 * @method find_cell_index
391 * @method find_cell_index
392 * @param {Cell} cell The provided cell
392 * @param {Cell} cell The provided cell
393 * @return {Number} The cell's numeric index
393 * @return {Number} The cell's numeric index
394 */
394 */
395 Notebook.prototype.find_cell_index = function (cell) {
395 Notebook.prototype.find_cell_index = function (cell) {
396 var result = null;
396 var result = null;
397 this.get_cell_elements().filter(function (index) {
397 this.get_cell_elements().filter(function (index) {
398 if ($(this).data("cell") === cell) {
398 if ($(this).data("cell") === cell) {
399 result = index;
399 result = index;
400 };
400 };
401 });
401 });
402 return result;
402 return result;
403 };
403 };
404
404
405 /**
405 /**
406 * Get a given index , or the selected index if none is provided.
406 * Get a given index , or the selected index if none is provided.
407 *
407 *
408 * @method index_or_selected
408 * @method index_or_selected
409 * @param {Number} index A cell's index
409 * @param {Number} index A cell's index
410 * @return {Number} The given index, or selected index if none is provided.
410 * @return {Number} The given index, or selected index if none is provided.
411 */
411 */
412 Notebook.prototype.index_or_selected = function (index) {
412 Notebook.prototype.index_or_selected = function (index) {
413 var i;
413 var i;
414 if (index === undefined || index === null) {
414 if (index === undefined || index === null) {
415 i = this.get_selected_index();
415 i = this.get_selected_index();
416 if (i === null) {
416 if (i === null) {
417 i = 0;
417 i = 0;
418 }
418 }
419 } else {
419 } else {
420 i = index;
420 i = index;
421 }
421 }
422 return i;
422 return i;
423 };
423 };
424
424
425 /**
425 /**
426 * Get the currently selected cell.
426 * Get the currently selected cell.
427 * @method get_selected_cell
427 * @method get_selected_cell
428 * @return {Cell} The selected cell
428 * @return {Cell} The selected cell
429 */
429 */
430 Notebook.prototype.get_selected_cell = function () {
430 Notebook.prototype.get_selected_cell = function () {
431 var index = this.get_selected_index();
431 var index = this.get_selected_index();
432 return this.get_cell(index);
432 return this.get_cell(index);
433 };
433 };
434
434
435 /**
435 /**
436 * Check whether a cell index is valid.
436 * Check whether a cell index is valid.
437 *
437 *
438 * @method is_valid_cell_index
438 * @method is_valid_cell_index
439 * @param {Number} index A cell index
439 * @param {Number} index A cell index
440 * @return True if the index is valid, false otherwise
440 * @return True if the index is valid, false otherwise
441 */
441 */
442 Notebook.prototype.is_valid_cell_index = function (index) {
442 Notebook.prototype.is_valid_cell_index = function (index) {
443 if (index !== null && index >= 0 && index < this.ncells()) {
443 if (index !== null && index >= 0 && index < this.ncells()) {
444 return true;
444 return true;
445 } else {
445 } else {
446 return false;
446 return false;
447 };
447 };
448 }
448 }
449
449
450 /**
450 /**
451 * Get the index of the currently selected cell.
451 * Get the index of the currently selected cell.
452
452
453 * @method get_selected_index
453 * @method get_selected_index
454 * @return {Number} The selected cell's numeric index
454 * @return {Number} The selected cell's numeric index
455 */
455 */
456 Notebook.prototype.get_selected_index = function () {
456 Notebook.prototype.get_selected_index = function () {
457 var result = null;
457 var result = null;
458 this.get_cell_elements().filter(function (index) {
458 this.get_cell_elements().filter(function (index) {
459 if ($(this).data("cell").selected === true) {
459 if ($(this).data("cell").selected === true) {
460 result = index;
460 result = index;
461 };
461 };
462 });
462 });
463 return result;
463 return result;
464 };
464 };
465
465
466
466
467 // Cell selection.
467 // Cell selection.
468
468
469 /**
469 /**
470 * Programmatically select a cell.
470 * Programmatically select a cell.
471 *
471 *
472 * @method select
472 * @method select
473 * @param {Number} index A cell's index
473 * @param {Number} index A cell's index
474 * @return {Notebook} This notebook
474 * @return {Notebook} This notebook
475 */
475 */
476 Notebook.prototype.select = function (index) {
476 Notebook.prototype.select = function (index) {
477 if (this.is_valid_cell_index(index)) {
477 if (this.is_valid_cell_index(index)) {
478 var sindex = this.get_selected_index()
478 var sindex = this.get_selected_index()
479 if (sindex !== null && index !== sindex) {
479 if (sindex !== null && index !== sindex) {
480 this.command_mode();
480 this.command_mode();
481 this.get_cell(sindex).unselect();
481 this.get_cell(sindex).unselect();
482 };
482 };
483 var cell = this.get_cell(index);
483 var cell = this.get_cell(index);
484 cell.select();
484 cell.select();
485 if (cell.cell_type === 'heading') {
485 if (cell.cell_type === 'heading') {
486 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
486 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
487 {'cell_type':cell.cell_type,level:cell.level}
487 {'cell_type':cell.cell_type,level:cell.level}
488 );
488 );
489 } else {
489 } else {
490 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
490 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
491 {'cell_type':cell.cell_type}
491 {'cell_type':cell.cell_type}
492 );
492 );
493 };
493 };
494 };
494 };
495 return this;
495 return this;
496 };
496 };
497
497
498 /**
498 /**
499 * Programmatically select the next cell.
499 * Programmatically select the next cell.
500 *
500 *
501 * @method select_next
501 * @method select_next
502 * @return {Notebook} This notebook
502 * @return {Notebook} This notebook
503 */
503 */
504 Notebook.prototype.select_next = function () {
504 Notebook.prototype.select_next = function () {
505 var index = this.get_selected_index();
505 var index = this.get_selected_index();
506 this.select(index+1);
506 this.select(index+1);
507 return this;
507 return this;
508 };
508 };
509
509
510 /**
510 /**
511 * Programmatically select the previous cell.
511 * Programmatically select the previous cell.
512 *
512 *
513 * @method select_prev
513 * @method select_prev
514 * @return {Notebook} This notebook
514 * @return {Notebook} This notebook
515 */
515 */
516 Notebook.prototype.select_prev = function () {
516 Notebook.prototype.select_prev = function () {
517 var index = this.get_selected_index();
517 var index = this.get_selected_index();
518 this.select(index-1);
518 this.select(index-1);
519 return this;
519 return this;
520 };
520 };
521
521
522
522
523 // Edit/Command mode
523 // Edit/Command mode
524
524
525 Notebook.prototype.get_edit_index = function () {
525 Notebook.prototype.get_edit_index = function () {
526 var result = null;
526 var result = null;
527 this.get_cell_elements().filter(function (index) {
527 this.get_cell_elements().filter(function (index) {
528 if ($(this).data("cell").mode === 'edit') {
528 if ($(this).data("cell").mode === 'edit') {
529 result = index;
529 result = index;
530 };
530 };
531 });
531 });
532 return result;
532 return result;
533 };
533 };
534
534
535 Notebook.prototype.command_mode = function () {
535 Notebook.prototype.command_mode = function () {
536 if (this.mode !== 'command') {
536 if (this.mode !== 'command') {
537 var index = this.get_edit_index();
537 var index = this.get_edit_index();
538 var cell = this.get_cell(index);
538 var cell = this.get_cell(index);
539 if (cell) {
539 if (cell) {
540 cell.command_mode();
540 cell.command_mode();
541 };
541 };
542 this.mode = 'command';
542 this.mode = 'command';
543 IPython.keyboard_manager.command_mode();
543 IPython.keyboard_manager.command_mode();
544 };
544 };
545 };
545 };
546
546
547 Notebook.prototype.edit_mode = function () {
547 Notebook.prototype.edit_mode = function () {
548 if (this.mode !== 'edit') {
548 if (this.mode !== 'edit') {
549 var cell = this.get_selected_cell();
549 var cell = this.get_selected_cell();
550 if (cell === null) {return;} // No cell is selected
550 if (cell === null) {return;} // No cell is selected
551 // We need to set the mode to edit to prevent reentering this method
551 // We need to set the mode to edit to prevent reentering this method
552 // when cell.edit_mode() is called below.
552 // when cell.edit_mode() is called below.
553 this.mode = 'edit';
553 this.mode = 'edit';
554 IPython.keyboard_manager.edit_mode();
554 IPython.keyboard_manager.edit_mode();
555 cell.edit_mode();
555 cell.edit_mode();
556 };
556 };
557 };
557 };
558
558
559 Notebook.prototype.focus_cell = function () {
559 Notebook.prototype.focus_cell = function () {
560 var cell = this.get_selected_cell();
560 var cell = this.get_selected_cell();
561 if (cell === null) {return;} // No cell is selected
561 if (cell === null) {return;} // No cell is selected
562 cell.focus_cell();
562 cell.focus_cell();
563 };
563 };
564
564
565 // Cell movement
565 // Cell movement
566
566
567 /**
567 /**
568 * Move given (or selected) cell up and select it.
568 * Move given (or selected) cell up and select it.
569 *
569 *
570 * @method move_cell_up
570 * @method move_cell_up
571 * @param [index] {integer} cell index
571 * @param [index] {integer} cell index
572 * @return {Notebook} This notebook
572 * @return {Notebook} This notebook
573 **/
573 **/
574 Notebook.prototype.move_cell_up = function (index) {
574 Notebook.prototype.move_cell_up = function (index) {
575 var i = this.index_or_selected(index);
575 var i = this.index_or_selected(index);
576 if (this.is_valid_cell_index(i) && i > 0) {
576 if (this.is_valid_cell_index(i) && i > 0) {
577 var pivot = this.get_cell_element(i-1);
577 var pivot = this.get_cell_element(i-1);
578 var tomove = this.get_cell_element(i);
578 var tomove = this.get_cell_element(i);
579 if (pivot !== null && tomove !== null) {
579 if (pivot !== null && tomove !== null) {
580 tomove.detach();
580 tomove.detach();
581 pivot.before(tomove);
581 pivot.before(tomove);
582 this.select(i-1);
582 this.select(i-1);
583 var cell = this.get_selected_cell();
583 var cell = this.get_selected_cell();
584 cell.focus_cell();
584 cell.focus_cell();
585 };
585 };
586 this.set_dirty(true);
586 this.set_dirty(true);
587 };
587 };
588 return this;
588 return this;
589 };
589 };
590
590
591
591
592 /**
592 /**
593 * Move given (or selected) cell down and select it
593 * Move given (or selected) cell down and select it
594 *
594 *
595 * @method move_cell_down
595 * @method move_cell_down
596 * @param [index] {integer} cell index
596 * @param [index] {integer} cell index
597 * @return {Notebook} This notebook
597 * @return {Notebook} This notebook
598 **/
598 **/
599 Notebook.prototype.move_cell_down = function (index) {
599 Notebook.prototype.move_cell_down = function (index) {
600 var i = this.index_or_selected(index);
600 var i = this.index_or_selected(index);
601 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
601 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
602 var pivot = this.get_cell_element(i+1);
602 var pivot = this.get_cell_element(i+1);
603 var tomove = this.get_cell_element(i);
603 var tomove = this.get_cell_element(i);
604 if (pivot !== null && tomove !== null) {
604 if (pivot !== null && tomove !== null) {
605 tomove.detach();
605 tomove.detach();
606 pivot.after(tomove);
606 pivot.after(tomove);
607 this.select(i+1);
607 this.select(i+1);
608 var cell = this.get_selected_cell();
608 var cell = this.get_selected_cell();
609 cell.focus_cell();
609 cell.focus_cell();
610 };
610 };
611 };
611 };
612 this.set_dirty();
612 this.set_dirty();
613 return this;
613 return this;
614 };
614 };
615
615
616
616
617 // Insertion, deletion.
617 // Insertion, deletion.
618
618
619 /**
619 /**
620 * Delete a cell from the notebook.
620 * Delete a cell from the notebook.
621 *
621 *
622 * @method delete_cell
622 * @method delete_cell
623 * @param [index] A cell's numeric index
623 * @param [index] A cell's numeric index
624 * @return {Notebook} This notebook
624 * @return {Notebook} This notebook
625 */
625 */
626 Notebook.prototype.delete_cell = function (index) {
626 Notebook.prototype.delete_cell = function (index) {
627 var i = this.index_or_selected(index);
627 var i = this.index_or_selected(index);
628 var cell = this.get_selected_cell();
628 var cell = this.get_selected_cell();
629 this.undelete_backup = cell.toJSON();
629 this.undelete_backup = cell.toJSON();
630 $('#undelete_cell').removeClass('disabled');
630 $('#undelete_cell').removeClass('disabled');
631 if (this.is_valid_cell_index(i)) {
631 if (this.is_valid_cell_index(i)) {
632 var old_ncells = this.ncells();
632 var old_ncells = this.ncells();
633 var ce = this.get_cell_element(i);
633 var ce = this.get_cell_element(i);
634 ce.remove();
634 ce.remove();
635 if (i === 0) {
635 if (i === 0) {
636 // Always make sure we have at least one cell.
636 // Always make sure we have at least one cell.
637 if (old_ncells === 1) {
637 if (old_ncells === 1) {
638 this.insert_cell_below('code');
638 this.insert_cell_below('code');
639 }
639 }
640 this.select(0);
640 this.select(0);
641 this.undelete_index = 0;
641 this.undelete_index = 0;
642 this.undelete_below = false;
642 this.undelete_below = false;
643 } else if (i === old_ncells-1 && i !== 0) {
643 } else if (i === old_ncells-1 && i !== 0) {
644 this.select(i-1);
644 this.select(i-1);
645 this.undelete_index = i - 1;
645 this.undelete_index = i - 1;
646 this.undelete_below = true;
646 this.undelete_below = true;
647 } else {
647 } else {
648 this.select(i);
648 this.select(i);
649 this.undelete_index = i;
649 this.undelete_index = i;
650 this.undelete_below = false;
650 this.undelete_below = false;
651 };
651 };
652 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
652 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
653 this.set_dirty(true);
653 this.set_dirty(true);
654 };
654 };
655 return this;
655 return this;
656 };
656 };
657
657
658 /**
658 /**
659 * Restore the most recently deleted cell.
659 * Restore the most recently deleted cell.
660 *
660 *
661 * @method undelete
661 * @method undelete
662 */
662 */
663 Notebook.prototype.undelete_cell = function() {
663 Notebook.prototype.undelete_cell = function() {
664 if (this.undelete_backup !== null && this.undelete_index !== null) {
664 if (this.undelete_backup !== null && this.undelete_index !== null) {
665 var current_index = this.get_selected_index();
665 var current_index = this.get_selected_index();
666 if (this.undelete_index < current_index) {
666 if (this.undelete_index < current_index) {
667 current_index = current_index + 1;
667 current_index = current_index + 1;
668 }
668 }
669 if (this.undelete_index >= this.ncells()) {
669 if (this.undelete_index >= this.ncells()) {
670 this.select(this.ncells() - 1);
670 this.select(this.ncells() - 1);
671 }
671 }
672 else {
672 else {
673 this.select(this.undelete_index);
673 this.select(this.undelete_index);
674 }
674 }
675 var cell_data = this.undelete_backup;
675 var cell_data = this.undelete_backup;
676 var new_cell = null;
676 var new_cell = null;
677 if (this.undelete_below) {
677 if (this.undelete_below) {
678 new_cell = this.insert_cell_below(cell_data.cell_type);
678 new_cell = this.insert_cell_below(cell_data.cell_type);
679 } else {
679 } else {
680 new_cell = this.insert_cell_above(cell_data.cell_type);
680 new_cell = this.insert_cell_above(cell_data.cell_type);
681 }
681 }
682 new_cell.fromJSON(cell_data);
682 new_cell.fromJSON(cell_data);
683 if (this.undelete_below) {
683 if (this.undelete_below) {
684 this.select(current_index+1);
684 this.select(current_index+1);
685 } else {
685 } else {
686 this.select(current_index);
686 this.select(current_index);
687 }
687 }
688 this.undelete_backup = null;
688 this.undelete_backup = null;
689 this.undelete_index = null;
689 this.undelete_index = null;
690 }
690 }
691 $('#undelete_cell').addClass('disabled');
691 $('#undelete_cell').addClass('disabled');
692 }
692 }
693
693
694 /**
694 /**
695 * Insert a cell so that after insertion the cell is at given index.
695 * Insert a cell so that after insertion the cell is at given index.
696 *
696 *
697 * Similar to insert_above, but index parameter is mandatory
697 * Similar to insert_above, but index parameter is mandatory
698 *
698 *
699 * Index will be brought back into the accissible range [0,n]
699 * Index will be brought back into the accissible range [0,n]
700 *
700 *
701 * @method insert_cell_at_index
701 * @method insert_cell_at_index
702 * @param type {string} in ['code','markdown','heading']
702 * @param type {string} in ['code','markdown','heading']
703 * @param [index] {int} a valid index where to inser cell
703 * @param [index] {int} a valid index where to inser cell
704 *
704 *
705 * @return cell {cell|null} created cell or null
705 * @return cell {cell|null} created cell or null
706 **/
706 **/
707 Notebook.prototype.insert_cell_at_index = function(type, index){
707 Notebook.prototype.insert_cell_at_index = function(type, index){
708
708
709 var ncells = this.ncells();
709 var ncells = this.ncells();
710 var index = Math.min(index,ncells);
710 var index = Math.min(index,ncells);
711 index = Math.max(index,0);
711 index = Math.max(index,0);
712 var cell = null;
712 var cell = null;
713
713
714 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
714 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
715 if (type === 'code') {
715 if (type === 'code') {
716 cell = new IPython.CodeCell(this.kernel);
716 cell = new IPython.CodeCell(this.kernel);
717 cell.set_input_prompt();
717 cell.set_input_prompt();
718 } else if (type === 'markdown') {
718 } else if (type === 'markdown') {
719 cell = new IPython.MarkdownCell();
719 cell = new IPython.MarkdownCell();
720 } else if (type === 'raw') {
720 } else if (type === 'raw') {
721 cell = new IPython.RawCell();
721 cell = new IPython.RawCell();
722 } else if (type === 'heading') {
722 } else if (type === 'heading') {
723 cell = new IPython.HeadingCell();
723 cell = new IPython.HeadingCell();
724 }
724 }
725
725
726 if(this._insert_element_at_index(cell.element,index)) {
726 if(this._insert_element_at_index(cell.element,index)) {
727 cell.render();
727 cell.render();
728 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
728 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
729 cell.refresh();
729 cell.refresh();
730 // We used to select the cell after we refresh it, but there
730 // We used to select the cell after we refresh it, but there
731 // are now cases were this method is called where select is
731 // are now cases were this method is called where select is
732 // not appropriate. The selection logic should be handled by the
732 // not appropriate. The selection logic should be handled by the
733 // caller of the the top level insert_cell methods.
733 // caller of the the top level insert_cell methods.
734 this.set_dirty(true);
734 this.set_dirty(true);
735 }
735 }
736 }
736 }
737 return cell;
737 return cell;
738
738
739 };
739 };
740
740
741 /**
741 /**
742 * Insert an element at given cell index.
742 * Insert an element at given cell index.
743 *
743 *
744 * @method _insert_element_at_index
744 * @method _insert_element_at_index
745 * @param element {dom element} a cell element
745 * @param element {dom element} a cell element
746 * @param [index] {int} a valid index where to inser cell
746 * @param [index] {int} a valid index where to inser cell
747 * @private
747 * @private
748 *
748 *
749 * return true if everything whent fine.
749 * return true if everything whent fine.
750 **/
750 **/
751 Notebook.prototype._insert_element_at_index = function(element, index){
751 Notebook.prototype._insert_element_at_index = function(element, index){
752 if (element === undefined){
752 if (element === undefined){
753 return false;
753 return false;
754 }
754 }
755
755
756 var ncells = this.ncells();
756 var ncells = this.ncells();
757
757
758 if (ncells === 0) {
758 if (ncells === 0) {
759 // special case append if empty
759 // special case append if empty
760 this.element.find('div.end_space').before(element);
760 this.element.find('div.end_space').before(element);
761 } else if ( ncells === index ) {
761 } else if ( ncells === index ) {
762 // special case append it the end, but not empty
762 // special case append it the end, but not empty
763 this.get_cell_element(index-1).after(element);
763 this.get_cell_element(index-1).after(element);
764 } else if (this.is_valid_cell_index(index)) {
764 } else if (this.is_valid_cell_index(index)) {
765 // otherwise always somewhere to append to
765 // otherwise always somewhere to append to
766 this.get_cell_element(index).before(element);
766 this.get_cell_element(index).before(element);
767 } else {
767 } else {
768 return false;
768 return false;
769 }
769 }
770
770
771 if (this.undelete_index !== null && index <= this.undelete_index) {
771 if (this.undelete_index !== null && index <= this.undelete_index) {
772 this.undelete_index = this.undelete_index + 1;
772 this.undelete_index = this.undelete_index + 1;
773 this.set_dirty(true);
773 this.set_dirty(true);
774 }
774 }
775 return true;
775 return true;
776 };
776 };
777
777
778 /**
778 /**
779 * Insert a cell of given type above given index, or at top
779 * Insert a cell of given type above given index, or at top
780 * of notebook if index smaller than 0.
780 * of notebook if index smaller than 0.
781 *
781 *
782 * default index value is the one of currently selected cell
782 * default index value is the one of currently selected cell
783 *
783 *
784 * @method insert_cell_above
784 * @method insert_cell_above
785 * @param type {string} cell type
785 * @param type {string} cell type
786 * @param [index] {integer}
786 * @param [index] {integer}
787 *
787 *
788 * @return handle to created cell or null
788 * @return handle to created cell or null
789 **/
789 **/
790 Notebook.prototype.insert_cell_above = function (type, index) {
790 Notebook.prototype.insert_cell_above = function (type, index) {
791 index = this.index_or_selected(index);
791 index = this.index_or_selected(index);
792 return this.insert_cell_at_index(type, index);
792 return this.insert_cell_at_index(type, index);
793 };
793 };
794
794
795 /**
795 /**
796 * Insert a cell of given type below given index, or at bottom
796 * Insert a cell of given type below given index, or at bottom
797 * of notebook if index greater thatn number of cell
797 * of notebook if index greater thatn number of cell
798 *
798 *
799 * default index value is the one of currently selected cell
799 * default index value is the one of currently selected cell
800 *
800 *
801 * @method insert_cell_below
801 * @method insert_cell_below
802 * @param type {string} cell type
802 * @param type {string} cell type
803 * @param [index] {integer}
803 * @param [index] {integer}
804 *
804 *
805 * @return handle to created cell or null
805 * @return handle to created cell or null
806 *
806 *
807 **/
807 **/
808 Notebook.prototype.insert_cell_below = function (type, index) {
808 Notebook.prototype.insert_cell_below = function (type, index) {
809 index = this.index_or_selected(index);
809 index = this.index_or_selected(index);
810 return this.insert_cell_at_index(type, index+1);
810 return this.insert_cell_at_index(type, index+1);
811 };
811 };
812
812
813
813
814 /**
814 /**
815 * Insert cell at end of notebook
815 * Insert cell at end of notebook
816 *
816 *
817 * @method insert_cell_at_bottom
817 * @method insert_cell_at_bottom
818 * @param {String} type cell type
818 * @param {String} type cell type
819 *
819 *
820 * @return the added cell; or null
820 * @return the added cell; or null
821 **/
821 **/
822 Notebook.prototype.insert_cell_at_bottom = function (type){
822 Notebook.prototype.insert_cell_at_bottom = function (type){
823 var len = this.ncells();
823 var len = this.ncells();
824 return this.insert_cell_below(type,len-1);
824 return this.insert_cell_below(type,len-1);
825 };
825 };
826
826
827 /**
827 /**
828 * Turn a cell into a code cell.
828 * Turn a cell into a code cell.
829 *
829 *
830 * @method to_code
830 * @method to_code
831 * @param {Number} [index] A cell's index
831 * @param {Number} [index] A cell's index
832 */
832 */
833 Notebook.prototype.to_code = function (index) {
833 Notebook.prototype.to_code = function (index) {
834 var i = this.index_or_selected(index);
834 var i = this.index_or_selected(index);
835 if (this.is_valid_cell_index(i)) {
835 if (this.is_valid_cell_index(i)) {
836 var source_element = this.get_cell_element(i);
836 var source_element = this.get_cell_element(i);
837 var source_cell = source_element.data("cell");
837 var source_cell = source_element.data("cell");
838 if (!(source_cell instanceof IPython.CodeCell)) {
838 if (!(source_cell instanceof IPython.CodeCell)) {
839 var target_cell = this.insert_cell_below('code',i);
839 var target_cell = this.insert_cell_below('code',i);
840 var text = source_cell.get_text();
840 var text = source_cell.get_text();
841 if (text === source_cell.placeholder) {
841 if (text === source_cell.placeholder) {
842 text = '';
842 text = '';
843 }
843 }
844 target_cell.set_text(text);
844 target_cell.set_text(text);
845 // make this value the starting point, so that we can only undo
845 // make this value the starting point, so that we can only undo
846 // to this state, instead of a blank cell
846 // to this state, instead of a blank cell
847 target_cell.code_mirror.clearHistory();
847 target_cell.code_mirror.clearHistory();
848 source_element.remove();
848 source_element.remove();
849 this.select(i);
849 this.select(i);
850 this.edit_mode();
850 this.edit_mode();
851 this.set_dirty(true);
851 this.set_dirty(true);
852 };
852 };
853 };
853 };
854 };
854 };
855
855
856 /**
856 /**
857 * Turn a cell into a Markdown cell.
857 * Turn a cell into a Markdown cell.
858 *
858 *
859 * @method to_markdown
859 * @method to_markdown
860 * @param {Number} [index] A cell's index
860 * @param {Number} [index] A cell's index
861 */
861 */
862 Notebook.prototype.to_markdown = function (index) {
862 Notebook.prototype.to_markdown = function (index) {
863 var i = this.index_or_selected(index);
863 var i = this.index_or_selected(index);
864 if (this.is_valid_cell_index(i)) {
864 if (this.is_valid_cell_index(i)) {
865 var source_element = this.get_cell_element(i);
865 var source_element = this.get_cell_element(i);
866 var source_cell = source_element.data("cell");
866 var source_cell = source_element.data("cell");
867 if (!(source_cell instanceof IPython.MarkdownCell)) {
867 if (!(source_cell instanceof IPython.MarkdownCell)) {
868 var target_cell = this.insert_cell_below('markdown',i);
868 var target_cell = this.insert_cell_below('markdown',i);
869 var text = source_cell.get_text();
869 var text = source_cell.get_text();
870 if (text === source_cell.placeholder) {
870 if (text === source_cell.placeholder) {
871 text = '';
871 text = '';
872 };
872 };
873 // We must show the editor before setting its contents
873 // We must show the editor before setting its contents
874 target_cell.unrender();
874 target_cell.unrender();
875 target_cell.set_text(text);
875 target_cell.set_text(text);
876 // make this value the starting point, so that we can only undo
876 // make this value the starting point, so that we can only undo
877 // to this state, instead of a blank cell
877 // to this state, instead of a blank cell
878 target_cell.code_mirror.clearHistory();
878 target_cell.code_mirror.clearHistory();
879 source_element.remove();
879 source_element.remove();
880 this.select(i);
880 this.select(i);
881 this.edit_mode();
881 this.edit_mode();
882 this.set_dirty(true);
882 this.set_dirty(true);
883 };
883 };
884 };
884 };
885 };
885 };
886
886
887 /**
887 /**
888 * Turn a cell into a raw text cell.
888 * Turn a cell into a raw text cell.
889 *
889 *
890 * @method to_raw
890 * @method to_raw
891 * @param {Number} [index] A cell's index
891 * @param {Number} [index] A cell's index
892 */
892 */
893 Notebook.prototype.to_raw = function (index) {
893 Notebook.prototype.to_raw = function (index) {
894 var i = this.index_or_selected(index);
894 var i = this.index_or_selected(index);
895 if (this.is_valid_cell_index(i)) {
895 if (this.is_valid_cell_index(i)) {
896 var source_element = this.get_cell_element(i);
896 var source_element = this.get_cell_element(i);
897 var source_cell = source_element.data("cell");
897 var source_cell = source_element.data("cell");
898 var target_cell = null;
898 var target_cell = null;
899 if (!(source_cell instanceof IPython.RawCell)) {
899 if (!(source_cell instanceof IPython.RawCell)) {
900 target_cell = this.insert_cell_below('raw',i);
900 target_cell = this.insert_cell_below('raw',i);
901 var text = source_cell.get_text();
901 var text = source_cell.get_text();
902 if (text === source_cell.placeholder) {
902 if (text === source_cell.placeholder) {
903 text = '';
903 text = '';
904 };
904 };
905 // We must show the editor before setting its contents
905 // We must show the editor before setting its contents
906 target_cell.unrender();
906 target_cell.unrender();
907 target_cell.set_text(text);
907 target_cell.set_text(text);
908 // make this value the starting point, so that we can only undo
908 // make this value the starting point, so that we can only undo
909 // to this state, instead of a blank cell
909 // to this state, instead of a blank cell
910 target_cell.code_mirror.clearHistory();
910 target_cell.code_mirror.clearHistory();
911 source_element.remove();
911 source_element.remove();
912 this.select(i);
912 this.select(i);
913 this.edit_mode();
913 this.edit_mode();
914 this.set_dirty(true);
914 this.set_dirty(true);
915 };
915 };
916 };
916 };
917 };
917 };
918
918
919 /**
919 /**
920 * Turn a cell into a heading cell.
920 * Turn a cell into a heading cell.
921 *
921 *
922 * @method to_heading
922 * @method to_heading
923 * @param {Number} [index] A cell's index
923 * @param {Number} [index] A cell's index
924 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
924 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
925 */
925 */
926 Notebook.prototype.to_heading = function (index, level) {
926 Notebook.prototype.to_heading = function (index, level) {
927 level = level || 1;
927 level = level || 1;
928 var i = this.index_or_selected(index);
928 var i = this.index_or_selected(index);
929 if (this.is_valid_cell_index(i)) {
929 if (this.is_valid_cell_index(i)) {
930 var source_element = this.get_cell_element(i);
930 var source_element = this.get_cell_element(i);
931 var source_cell = source_element.data("cell");
931 var source_cell = source_element.data("cell");
932 var target_cell = null;
932 var target_cell = null;
933 if (source_cell instanceof IPython.HeadingCell) {
933 if (source_cell instanceof IPython.HeadingCell) {
934 source_cell.set_level(level);
934 source_cell.set_level(level);
935 } else {
935 } else {
936 target_cell = this.insert_cell_below('heading',i);
936 target_cell = this.insert_cell_below('heading',i);
937 var text = source_cell.get_text();
937 var text = source_cell.get_text();
938 if (text === source_cell.placeholder) {
938 if (text === source_cell.placeholder) {
939 text = '';
939 text = '';
940 };
940 };
941 // We must show the editor before setting its contents
941 // We must show the editor before setting its contents
942 target_cell.set_level(level);
942 target_cell.set_level(level);
943 target_cell.unrender();
943 target_cell.unrender();
944 target_cell.set_text(text);
944 target_cell.set_text(text);
945 // make this value the starting point, so that we can only undo
945 // make this value the starting point, so that we can only undo
946 // to this state, instead of a blank cell
946 // to this state, instead of a blank cell
947 target_cell.code_mirror.clearHistory();
947 target_cell.code_mirror.clearHistory();
948 source_element.remove();
948 source_element.remove();
949 this.select(i);
949 this.select(i);
950 };
950 };
951 this.edit_mode();
951 this.edit_mode();
952 this.set_dirty(true);
952 this.set_dirty(true);
953 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
953 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
954 {'cell_type':'heading',level:level}
954 {'cell_type':'heading',level:level}
955 );
955 );
956 };
956 };
957 };
957 };
958
958
959
959
960 // Cut/Copy/Paste
960 // Cut/Copy/Paste
961
961
962 /**
962 /**
963 * Enable UI elements for pasting cells.
963 * Enable UI elements for pasting cells.
964 *
964 *
965 * @method enable_paste
965 * @method enable_paste
966 */
966 */
967 Notebook.prototype.enable_paste = function () {
967 Notebook.prototype.enable_paste = function () {
968 var that = this;
968 var that = this;
969 if (!this.paste_enabled) {
969 if (!this.paste_enabled) {
970 $('#paste_cell_replace').removeClass('disabled')
970 $('#paste_cell_replace').removeClass('disabled')
971 .on('click', function () {that.paste_cell_replace();});
971 .on('click', function () {that.paste_cell_replace();});
972 $('#paste_cell_above').removeClass('disabled')
972 $('#paste_cell_above').removeClass('disabled')
973 .on('click', function () {that.paste_cell_above();});
973 .on('click', function () {that.paste_cell_above();});
974 $('#paste_cell_below').removeClass('disabled')
974 $('#paste_cell_below').removeClass('disabled')
975 .on('click', function () {that.paste_cell_below();});
975 .on('click', function () {that.paste_cell_below();});
976 this.paste_enabled = true;
976 this.paste_enabled = true;
977 };
977 };
978 };
978 };
979
979
980 /**
980 /**
981 * Disable UI elements for pasting cells.
981 * Disable UI elements for pasting cells.
982 *
982 *
983 * @method disable_paste
983 * @method disable_paste
984 */
984 */
985 Notebook.prototype.disable_paste = function () {
985 Notebook.prototype.disable_paste = function () {
986 if (this.paste_enabled) {
986 if (this.paste_enabled) {
987 $('#paste_cell_replace').addClass('disabled').off('click');
987 $('#paste_cell_replace').addClass('disabled').off('click');
988 $('#paste_cell_above').addClass('disabled').off('click');
988 $('#paste_cell_above').addClass('disabled').off('click');
989 $('#paste_cell_below').addClass('disabled').off('click');
989 $('#paste_cell_below').addClass('disabled').off('click');
990 this.paste_enabled = false;
990 this.paste_enabled = false;
991 };
991 };
992 };
992 };
993
993
994 /**
994 /**
995 * Cut a cell.
995 * Cut a cell.
996 *
996 *
997 * @method cut_cell
997 * @method cut_cell
998 */
998 */
999 Notebook.prototype.cut_cell = function () {
999 Notebook.prototype.cut_cell = function () {
1000 this.copy_cell();
1000 this.copy_cell();
1001 this.delete_cell();
1001 this.delete_cell();
1002 }
1002 }
1003
1003
1004 /**
1004 /**
1005 * Copy a cell.
1005 * Copy a cell.
1006 *
1006 *
1007 * @method copy_cell
1007 * @method copy_cell
1008 */
1008 */
1009 Notebook.prototype.copy_cell = function () {
1009 Notebook.prototype.copy_cell = function () {
1010 var cell = this.get_selected_cell();
1010 var cell = this.get_selected_cell();
1011 this.clipboard = cell.toJSON();
1011 this.clipboard = cell.toJSON();
1012 this.enable_paste();
1012 this.enable_paste();
1013 };
1013 };
1014
1014
1015 /**
1015 /**
1016 * Replace the selected cell with a cell in the clipboard.
1016 * Replace the selected cell with a cell in the clipboard.
1017 *
1017 *
1018 * @method paste_cell_replace
1018 * @method paste_cell_replace
1019 */
1019 */
1020 Notebook.prototype.paste_cell_replace = function () {
1020 Notebook.prototype.paste_cell_replace = function () {
1021 if (this.clipboard !== null && this.paste_enabled) {
1021 if (this.clipboard !== null && this.paste_enabled) {
1022 var cell_data = this.clipboard;
1022 var cell_data = this.clipboard;
1023 var new_cell = this.insert_cell_above(cell_data.cell_type);
1023 var new_cell = this.insert_cell_above(cell_data.cell_type);
1024 new_cell.fromJSON(cell_data);
1024 new_cell.fromJSON(cell_data);
1025 var old_cell = this.get_next_cell(new_cell);
1025 var old_cell = this.get_next_cell(new_cell);
1026 this.delete_cell(this.find_cell_index(old_cell));
1026 this.delete_cell(this.find_cell_index(old_cell));
1027 this.select(this.find_cell_index(new_cell));
1027 this.select(this.find_cell_index(new_cell));
1028 };
1028 };
1029 };
1029 };
1030
1030
1031 /**
1031 /**
1032 * Paste a cell from the clipboard above the selected cell.
1032 * Paste a cell from the clipboard above the selected cell.
1033 *
1033 *
1034 * @method paste_cell_above
1034 * @method paste_cell_above
1035 */
1035 */
1036 Notebook.prototype.paste_cell_above = function () {
1036 Notebook.prototype.paste_cell_above = function () {
1037 if (this.clipboard !== null && this.paste_enabled) {
1037 if (this.clipboard !== null && this.paste_enabled) {
1038 var cell_data = this.clipboard;
1038 var cell_data = this.clipboard;
1039 var new_cell = this.insert_cell_above(cell_data.cell_type);
1039 var new_cell = this.insert_cell_above(cell_data.cell_type);
1040 new_cell.fromJSON(cell_data);
1040 new_cell.fromJSON(cell_data);
1041 };
1041 };
1042 };
1042 };
1043
1043
1044 /**
1044 /**
1045 * Paste a cell from the clipboard below the selected cell.
1045 * Paste a cell from the clipboard below the selected cell.
1046 *
1046 *
1047 * @method paste_cell_below
1047 * @method paste_cell_below
1048 */
1048 */
1049 Notebook.prototype.paste_cell_below = function () {
1049 Notebook.prototype.paste_cell_below = function () {
1050 if (this.clipboard !== null && this.paste_enabled) {
1050 if (this.clipboard !== null && this.paste_enabled) {
1051 var cell_data = this.clipboard;
1051 var cell_data = this.clipboard;
1052 var new_cell = this.insert_cell_below(cell_data.cell_type);
1052 var new_cell = this.insert_cell_below(cell_data.cell_type);
1053 new_cell.fromJSON(cell_data);
1053 new_cell.fromJSON(cell_data);
1054 };
1054 };
1055 };
1055 };
1056
1056
1057 // Split/merge
1057 // Split/merge
1058
1058
1059 /**
1059 /**
1060 * Split the selected cell into two, at the cursor.
1060 * Split the selected cell into two, at the cursor.
1061 *
1061 *
1062 * @method split_cell
1062 * @method split_cell
1063 */
1063 */
1064 Notebook.prototype.split_cell = function () {
1064 Notebook.prototype.split_cell = function () {
1065 var mdc = IPython.MarkdownCell;
1065 var mdc = IPython.MarkdownCell;
1066 var rc = IPython.RawCell;
1066 var rc = IPython.RawCell;
1067 var cell = this.get_selected_cell();
1067 var cell = this.get_selected_cell();
1068 if (cell.is_splittable()) {
1068 if (cell.is_splittable()) {
1069 var texta = cell.get_pre_cursor();
1069 var texta = cell.get_pre_cursor();
1070 var textb = cell.get_post_cursor();
1070 var textb = cell.get_post_cursor();
1071 if (cell instanceof IPython.CodeCell) {
1071 if (cell instanceof IPython.CodeCell) {
1072 // In this case the operations keep the notebook in its existing mode
1072 // In this case the operations keep the notebook in its existing mode
1073 // so we don't need to do any post-op mode changes.
1073 // so we don't need to do any post-op mode changes.
1074 cell.set_text(textb);
1074 cell.set_text(textb);
1075 var new_cell = this.insert_cell_above('code');
1075 var new_cell = this.insert_cell_above('code');
1076 new_cell.set_text(texta);
1076 new_cell.set_text(texta);
1077 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1077 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1078 // We know cell is !rendered so we can use set_text.
1078 // We know cell is !rendered so we can use set_text.
1079 cell.set_text(textb);
1079 cell.set_text(textb);
1080 var new_cell = this.insert_cell_above(cell.cell_type);
1080 var new_cell = this.insert_cell_above(cell.cell_type);
1081 // Unrender the new cell so we can call set_text.
1081 // Unrender the new cell so we can call set_text.
1082 new_cell.unrender();
1082 new_cell.unrender();
1083 new_cell.set_text(texta);
1083 new_cell.set_text(texta);
1084 }
1084 }
1085 };
1085 };
1086 };
1086 };
1087
1087
1088 /**
1088 /**
1089 * Combine the selected cell into the cell above it.
1089 * Combine the selected cell into the cell above it.
1090 *
1090 *
1091 * @method merge_cell_above
1091 * @method merge_cell_above
1092 */
1092 */
1093 Notebook.prototype.merge_cell_above = function () {
1093 Notebook.prototype.merge_cell_above = function () {
1094 var mdc = IPython.MarkdownCell;
1094 var mdc = IPython.MarkdownCell;
1095 var rc = IPython.RawCell;
1095 var rc = IPython.RawCell;
1096 var index = this.get_selected_index();
1096 var index = this.get_selected_index();
1097 var cell = this.get_cell(index);
1097 var cell = this.get_cell(index);
1098 var render = cell.rendered;
1098 var render = cell.rendered;
1099 if (!cell.is_mergeable()) {
1099 if (!cell.is_mergeable()) {
1100 return;
1100 return;
1101 }
1101 }
1102 if (index > 0) {
1102 if (index > 0) {
1103 var upper_cell = this.get_cell(index-1);
1103 var upper_cell = this.get_cell(index-1);
1104 if (!upper_cell.is_mergeable()) {
1104 if (!upper_cell.is_mergeable()) {
1105 return;
1105 return;
1106 }
1106 }
1107 var upper_text = upper_cell.get_text();
1107 var upper_text = upper_cell.get_text();
1108 var text = cell.get_text();
1108 var text = cell.get_text();
1109 if (cell instanceof IPython.CodeCell) {
1109 if (cell instanceof IPython.CodeCell) {
1110 cell.set_text(upper_text+'\n'+text);
1110 cell.set_text(upper_text+'\n'+text);
1111 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1111 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1112 cell.unrender(); // Must unrender before we set_text.
1112 cell.unrender(); // Must unrender before we set_text.
1113 cell.set_text(upper_text+'\n\n'+text);
1113 cell.set_text(upper_text+'\n\n'+text);
1114 if (render) {
1114 if (render) {
1115 // The rendered state of the final cell should match
1115 // The rendered state of the final cell should match
1116 // that of the original selected cell;
1116 // that of the original selected cell;
1117 cell.render();
1117 cell.render();
1118 }
1118 }
1119 };
1119 };
1120 this.delete_cell(index-1);
1120 this.delete_cell(index-1);
1121 this.select(this.find_cell_index(cell));
1121 this.select(this.find_cell_index(cell));
1122 };
1122 };
1123 };
1123 };
1124
1124
1125 /**
1125 /**
1126 * Combine the selected cell into the cell below it.
1126 * Combine the selected cell into the cell below it.
1127 *
1127 *
1128 * @method merge_cell_below
1128 * @method merge_cell_below
1129 */
1129 */
1130 Notebook.prototype.merge_cell_below = function () {
1130 Notebook.prototype.merge_cell_below = function () {
1131 var mdc = IPython.MarkdownCell;
1131 var mdc = IPython.MarkdownCell;
1132 var rc = IPython.RawCell;
1132 var rc = IPython.RawCell;
1133 var index = this.get_selected_index();
1133 var index = this.get_selected_index();
1134 var cell = this.get_cell(index);
1134 var cell = this.get_cell(index);
1135 var render = cell.rendered;
1135 var render = cell.rendered;
1136 if (!cell.is_mergeable()) {
1136 if (!cell.is_mergeable()) {
1137 return;
1137 return;
1138 }
1138 }
1139 if (index < this.ncells()-1) {
1139 if (index < this.ncells()-1) {
1140 var lower_cell = this.get_cell(index+1);
1140 var lower_cell = this.get_cell(index+1);
1141 if (!lower_cell.is_mergeable()) {
1141 if (!lower_cell.is_mergeable()) {
1142 return;
1142 return;
1143 }
1143 }
1144 var lower_text = lower_cell.get_text();
1144 var lower_text = lower_cell.get_text();
1145 var text = cell.get_text();
1145 var text = cell.get_text();
1146 if (cell instanceof IPython.CodeCell) {
1146 if (cell instanceof IPython.CodeCell) {
1147 cell.set_text(text+'\n'+lower_text);
1147 cell.set_text(text+'\n'+lower_text);
1148 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1148 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1149 cell.unrender(); // Must unrender before we set_text.
1149 cell.unrender(); // Must unrender before we set_text.
1150 cell.set_text(text+'\n\n'+lower_text);
1150 cell.set_text(text+'\n\n'+lower_text);
1151 if (render) {
1151 if (render) {
1152 // The rendered state of the final cell should match
1152 // The rendered state of the final cell should match
1153 // that of the original selected cell;
1153 // that of the original selected cell;
1154 cell.render();
1154 cell.render();
1155 }
1155 }
1156 };
1156 };
1157 this.delete_cell(index+1);
1157 this.delete_cell(index+1);
1158 this.select(this.find_cell_index(cell));
1158 this.select(this.find_cell_index(cell));
1159 };
1159 };
1160 };
1160 };
1161
1161
1162
1162
1163 // Cell collapsing and output clearing
1163 // Cell collapsing and output clearing
1164
1164
1165 /**
1165 /**
1166 * Hide a cell's output.
1166 * Hide a cell's output.
1167 *
1167 *
1168 * @method collapse
1168 * @method collapse_output
1169 * @param {Number} index A cell's numeric index
1169 * @param {Number} index A cell's numeric index
1170 */
1170 */
1171 Notebook.prototype.collapse = function (index) {
1171 Notebook.prototype.collapse_output = function (index) {
1172 var i = this.index_or_selected(index);
1172 var i = this.index_or_selected(index);
1173 this.get_cell(i).collapse();
1173 var cell = this.get_cell(i);
1174 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1175 cell.collapse_output();
1176 this.set_dirty(true);
1177 }
1178 };
1179
1180 /**
1181 * Hide each code cell's output area.
1182 *
1183 * @method collapse_all_output
1184 */
1185 Notebook.prototype.collapse_all_output = function () {
1186 var ncells = this.ncells();
1187 var cells = this.get_cells();
1188 for (var i=0; i<ncells; i++) {
1189 if (cells[i] instanceof IPython.CodeCell) {
1190 cells[i].collapse_output();
1191 }
1192 };
1193 // this should not be set if the `collapse` key is removed from nbformat
1174 this.set_dirty(true);
1194 this.set_dirty(true);
1175 };
1195 };
1176
1196
1177 /**
1197 /**
1178 * Show a cell's output.
1198 * Show a cell's output.
1179 *
1199 *
1180 * @method expand
1200 * @method expand_output
1181 * @param {Number} index A cell's numeric index
1201 * @param {Number} index A cell's numeric index
1182 */
1202 */
1183 Notebook.prototype.expand = function (index) {
1203 Notebook.prototype.expand_output = function (index) {
1184 var i = this.index_or_selected(index);
1204 var i = this.index_or_selected(index);
1185 this.get_cell(i).expand();
1205 var cell = this.get_cell(i);
1206 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1207 cell.expand_output();
1186 this.set_dirty(true);
1208 this.set_dirty(true);
1209 }
1187 };
1210 };
1188
1211
1189 /** Toggle whether a cell's output is collapsed or expanded.
1212 /**
1213 * Expand each code cell's output area, and remove scrollbars.
1190 *
1214 *
1191 * @method toggle_output
1215 * @method expand_all_output
1192 * @param {Number} index A cell's numeric index
1193 */
1216 */
1194 Notebook.prototype.toggle_output = function (index) {
1217 Notebook.prototype.expand_all_output = function () {
1195 var i = this.index_or_selected(index);
1218 var ncells = this.ncells();
1196 this.get_cell(i).toggle_output();
1219 var cells = this.get_cells();
1220 for (var i=0; i<ncells; i++) {
1221 if (cells[i] instanceof IPython.CodeCell) {
1222 cells[i].expand_output();
1223 }
1224 };
1225 // this should not be set if the `collapse` key is removed from nbformat
1197 this.set_dirty(true);
1226 this.set_dirty(true);
1198 };
1227 };
1199
1228
1200 /**
1229 /**
1201 * Toggle a scrollbar for long cell outputs.
1230 * Clear the selected CodeCell's output area.
1202 *
1231 *
1203 * @method toggle_output_scroll
1232 * @method clear_output
1204 * @param {Number} index A cell's numeric index
1233 * @param {Number} index A cell's numeric index
1205 */
1234 */
1206 Notebook.prototype.toggle_output_scroll = function (index) {
1235 Notebook.prototype.clear_output = function (index) {
1207 var i = this.index_or_selected(index);
1236 var i = this.index_or_selected(index);
1208 this.get_cell(i).toggle_output_scroll();
1237 var cell = this.get_cell(i);
1238 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1239 cell.clear_output();
1240 this.set_dirty(true);
1241 }
1209 };
1242 };
1210
1243
1211 /**
1244 /**
1212 * Hide each code cell's output area.
1245 * Clear each code cell's output area.
1213 *
1246 *
1214 * @method collapse_all_output
1247 * @method clear_all_output
1215 */
1248 */
1216 Notebook.prototype.collapse_all_output = function () {
1249 Notebook.prototype.clear_all_output = function () {
1217 var ncells = this.ncells();
1250 var ncells = this.ncells();
1218 var cells = this.get_cells();
1251 var cells = this.get_cells();
1219 for (var i=0; i<ncells; i++) {
1252 for (var i=0; i<ncells; i++) {
1220 if (cells[i] instanceof IPython.CodeCell) {
1253 if (cells[i] instanceof IPython.CodeCell) {
1221 cells[i].output_area.collapse();
1254 cells[i].clear_output();
1222 }
1255 }
1223 };
1256 };
1224 // this should not be set if the `collapse` key is removed from nbformat
1225 this.set_dirty(true);
1257 this.set_dirty(true);
1226 };
1258 };
1227
1259
1228 /**
1260 /**
1261 * Scroll the selected CodeCell's output area.
1262 *
1263 * @method scroll_output
1264 * @param {Number} index A cell's numeric index
1265 */
1266 Notebook.prototype.scroll_output = function (index) {
1267 var i = this.index_or_selected(index);
1268 var cell = this.get_cell(i);
1269 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1270 cell.scroll_output();
1271 this.set_dirty(true);
1272 }
1273 };
1274
1275 /**
1229 * Expand each code cell's output area, and add a scrollbar for long output.
1276 * Expand each code cell's output area, and add a scrollbar for long output.
1230 *
1277 *
1231 * @method scroll_all_output
1278 * @method scroll_all_output
1232 */
1279 */
1233 Notebook.prototype.scroll_all_output = function () {
1280 Notebook.prototype.scroll_all_output = function () {
1234 var ncells = this.ncells();
1281 var ncells = this.ncells();
1235 var cells = this.get_cells();
1282 var cells = this.get_cells();
1236 for (var i=0; i<ncells; i++) {
1283 for (var i=0; i<ncells; i++) {
1237 if (cells[i] instanceof IPython.CodeCell) {
1284 if (cells[i] instanceof IPython.CodeCell) {
1238 cells[i].output_area.expand();
1285 cells[i].scroll_output();
1239 cells[i].output_area.scroll_if_long();
1240 }
1286 }
1241 };
1287 };
1242 // this should not be set if the `collapse` key is removed from nbformat
1288 // this should not be set if the `collapse` key is removed from nbformat
1243 this.set_dirty(true);
1289 this.set_dirty(true);
1244 };
1290 };
1245
1291
1246 /**
1292 /** Toggle whether a cell's output is collapsed or expanded.
1247 * Expand each code cell's output area, and remove scrollbars.
1248 *
1293 *
1249 * @method expand_all_output
1294 * @method toggle_output
1295 * @param {Number} index A cell's numeric index
1250 */
1296 */
1251 Notebook.prototype.expand_all_output = function () {
1297 Notebook.prototype.toggle_output = function (index) {
1252 var ncells = this.ncells();
1298 var i = this.index_or_selected(index);
1253 var cells = this.get_cells();
1299 var cell = this.get_cell(i);
1254 for (var i=0; i<ncells; i++) {
1300 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1255 if (cells[i] instanceof IPython.CodeCell) {
1301 cell.toggle_output();
1256 cells[i].output_area.expand();
1257 cells[i].output_area.unscroll_area();
1258 }
1259 };
1260 // this should not be set if the `collapse` key is removed from nbformat
1261 this.set_dirty(true);
1302 this.set_dirty(true);
1303 }
1262 };
1304 };
1263
1305
1264 /**
1306 /**
1265 * Clear each code cell's output area.
1307 * Toggle a scrollbar for long cell outputs.
1266 *
1308 *
1267 * @method clear_all_output
1309 * @method toggle_output_scroll
1310 * @param {Number} index A cell's numeric index
1268 */
1311 */
1269 Notebook.prototype.clear_all_output = function () {
1312 Notebook.prototype.toggle_output_scroll = function (index) {
1270 var ncells = this.ncells();
1313 var i = this.index_or_selected(index);
1271 var cells = this.get_cells();
1314 var cell = this.get_cell(i);
1272 for (var i=0; i<ncells; i++) {
1315 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1273 if (cells[i] instanceof IPython.CodeCell) {
1316 cell.toggle_output_scroll();
1274 cells[i].clear_output();
1275 // Make all In[] prompts blank, as well
1276 // TODO: make this configurable (via checkbox?)
1277 cells[i].set_input_prompt();
1278 }
1279 };
1280 this.set_dirty(true);
1317 this.set_dirty(true);
1318 }
1281 };
1319 };
1282
1320
1283
1321
1284 // Other cell functions: line numbers, ...
1322 // Other cell functions: line numbers, ...
1285
1323
1286 /**
1324 /**
1287 * Toggle line numbers in the selected cell's input area.
1325 * Toggle line numbers in the selected cell's input area.
1288 *
1326 *
1289 * @method cell_toggle_line_numbers
1327 * @method cell_toggle_line_numbers
1290 */
1328 */
1291 Notebook.prototype.cell_toggle_line_numbers = function() {
1329 Notebook.prototype.cell_toggle_line_numbers = function() {
1292 this.get_selected_cell().toggle_line_numbers();
1330 this.get_selected_cell().toggle_line_numbers();
1293 };
1331 };
1294
1332
1295 // Session related things
1333 // Session related things
1296
1334
1297 /**
1335 /**
1298 * Start a new session and set it on each code cell.
1336 * Start a new session and set it on each code cell.
1299 *
1337 *
1300 * @method start_session
1338 * @method start_session
1301 */
1339 */
1302 Notebook.prototype.start_session = function () {
1340 Notebook.prototype.start_session = function () {
1303 this.session = new IPython.Session(this.notebook_name, this.notebook_path, this);
1341 this.session = new IPython.Session(this.notebook_name, this.notebook_path, this);
1304 this.session.start($.proxy(this._session_started, this));
1342 this.session.start($.proxy(this._session_started, this));
1305 };
1343 };
1306
1344
1307
1345
1308 /**
1346 /**
1309 * Once a session is started, link the code cells to the kernel and pass the
1347 * Once a session is started, link the code cells to the kernel and pass the
1310 * comm manager to the widget manager
1348 * comm manager to the widget manager
1311 *
1349 *
1312 */
1350 */
1313 Notebook.prototype._session_started = function(){
1351 Notebook.prototype._session_started = function(){
1314 this.kernel = this.session.kernel;
1352 this.kernel = this.session.kernel;
1315 var ncells = this.ncells();
1353 var ncells = this.ncells();
1316 for (var i=0; i<ncells; i++) {
1354 for (var i=0; i<ncells; i++) {
1317 var cell = this.get_cell(i);
1355 var cell = this.get_cell(i);
1318 if (cell instanceof IPython.CodeCell) {
1356 if (cell instanceof IPython.CodeCell) {
1319 cell.set_kernel(this.session.kernel);
1357 cell.set_kernel(this.session.kernel);
1320 };
1358 };
1321 };
1359 };
1322 };
1360 };
1323
1361
1324 /**
1362 /**
1325 * Prompt the user to restart the IPython kernel.
1363 * Prompt the user to restart the IPython kernel.
1326 *
1364 *
1327 * @method restart_kernel
1365 * @method restart_kernel
1328 */
1366 */
1329 Notebook.prototype.restart_kernel = function () {
1367 Notebook.prototype.restart_kernel = function () {
1330 var that = this;
1368 var that = this;
1331 IPython.dialog.modal({
1369 IPython.dialog.modal({
1332 title : "Restart kernel or continue running?",
1370 title : "Restart kernel or continue running?",
1333 body : $("<p/>").text(
1371 body : $("<p/>").text(
1334 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1372 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1335 ),
1373 ),
1336 buttons : {
1374 buttons : {
1337 "Continue running" : {},
1375 "Continue running" : {},
1338 "Restart" : {
1376 "Restart" : {
1339 "class" : "btn-danger",
1377 "class" : "btn-danger",
1340 "click" : function() {
1378 "click" : function() {
1341 that.session.restart_kernel();
1379 that.session.restart_kernel();
1342 }
1380 }
1343 }
1381 }
1344 }
1382 }
1345 });
1383 });
1346 };
1384 };
1347
1385
1348 /**
1386 /**
1349 * Execute or render cell outputs and go into command mode.
1387 * Execute or render cell outputs and go into command mode.
1350 *
1388 *
1351 * @method execute_cell
1389 * @method execute_cell
1352 */
1390 */
1353 Notebook.prototype.execute_cell = function () {
1391 Notebook.prototype.execute_cell = function () {
1354 // mode = shift, ctrl, alt
1392 // mode = shift, ctrl, alt
1355 var cell = this.get_selected_cell();
1393 var cell = this.get_selected_cell();
1356 var cell_index = this.find_cell_index(cell);
1394 var cell_index = this.find_cell_index(cell);
1357
1395
1358 cell.execute();
1396 cell.execute();
1359 this.command_mode();
1397 this.command_mode();
1360 cell.focus_cell();
1398 cell.focus_cell();
1361 this.set_dirty(true);
1399 this.set_dirty(true);
1362 }
1400 }
1363
1401
1364 /**
1402 /**
1365 * Execute or render cell outputs and insert a new cell below.
1403 * Execute or render cell outputs and insert a new cell below.
1366 *
1404 *
1367 * @method execute_cell_and_insert_below
1405 * @method execute_cell_and_insert_below
1368 */
1406 */
1369 Notebook.prototype.execute_cell_and_insert_below = function () {
1407 Notebook.prototype.execute_cell_and_insert_below = function () {
1370 var cell = this.get_selected_cell();
1408 var cell = this.get_selected_cell();
1371 var cell_index = this.find_cell_index(cell);
1409 var cell_index = this.find_cell_index(cell);
1372
1410
1373 cell.execute();
1411 cell.execute();
1374
1412
1375 // If we are at the end always insert a new cell and return
1413 // If we are at the end always insert a new cell and return
1376 if (cell_index === (this.ncells()-1)) {
1414 if (cell_index === (this.ncells()-1)) {
1377 this.insert_cell_below('code');
1415 this.insert_cell_below('code');
1378 this.select(cell_index+1);
1416 this.select(cell_index+1);
1379 this.edit_mode();
1417 this.edit_mode();
1380 this.scroll_to_bottom();
1418 this.scroll_to_bottom();
1381 this.set_dirty(true);
1419 this.set_dirty(true);
1382 return;
1420 return;
1383 }
1421 }
1384
1422
1385 // Only insert a new cell, if we ended up in an already populated cell
1423 // Only insert a new cell, if we ended up in an already populated cell
1386 var next_text = this.get_cell(cell_index+1).get_text();
1424 var next_text = this.get_cell(cell_index+1).get_text();
1387 if (/\S/.test(next_text) === true) {
1425 if (/\S/.test(next_text) === true) {
1388 this.insert_cell_below('code');
1426 this.insert_cell_below('code');
1389 }
1427 }
1390 this.select(cell_index+1);
1428 this.select(cell_index+1);
1391 this.edit_mode();
1429 this.edit_mode();
1392 this.set_dirty(true);
1430 this.set_dirty(true);
1393 };
1431 };
1394
1432
1395 /**
1433 /**
1396 * Execute or render cell outputs and select the next cell.
1434 * Execute or render cell outputs and select the next cell.
1397 *
1435 *
1398 * @method execute_cell_and_select_below
1436 * @method execute_cell_and_select_below
1399 */
1437 */
1400 Notebook.prototype.execute_cell_and_select_below = function () {
1438 Notebook.prototype.execute_cell_and_select_below = function () {
1401
1439
1402 var cell = this.get_selected_cell();
1440 var cell = this.get_selected_cell();
1403 var cell_index = this.find_cell_index(cell);
1441 var cell_index = this.find_cell_index(cell);
1404
1442
1405 cell.execute();
1443 cell.execute();
1406
1444
1407 // If we are at the end always insert a new cell and return
1445 // If we are at the end always insert a new cell and return
1408 if (cell_index === (this.ncells()-1)) {
1446 if (cell_index === (this.ncells()-1)) {
1409 this.insert_cell_below('code');
1447 this.insert_cell_below('code');
1410 this.select(cell_index+1);
1448 this.select(cell_index+1);
1411 this.edit_mode();
1449 this.edit_mode();
1412 this.scroll_to_bottom();
1450 this.scroll_to_bottom();
1413 this.set_dirty(true);
1451 this.set_dirty(true);
1414 return;
1452 return;
1415 }
1453 }
1416
1454
1417 this.select(cell_index+1);
1455 this.select(cell_index+1);
1418 this.get_cell(cell_index+1).focus_cell();
1456 this.get_cell(cell_index+1).focus_cell();
1419 this.set_dirty(true);
1457 this.set_dirty(true);
1420 };
1458 };
1421
1459
1422 /**
1460 /**
1423 * Execute all cells below the selected cell.
1461 * Execute all cells below the selected cell.
1424 *
1462 *
1425 * @method execute_cells_below
1463 * @method execute_cells_below
1426 */
1464 */
1427 Notebook.prototype.execute_cells_below = function () {
1465 Notebook.prototype.execute_cells_below = function () {
1428 this.execute_cell_range(this.get_selected_index(), this.ncells());
1466 this.execute_cell_range(this.get_selected_index(), this.ncells());
1429 this.scroll_to_bottom();
1467 this.scroll_to_bottom();
1430 };
1468 };
1431
1469
1432 /**
1470 /**
1433 * Execute all cells above the selected cell.
1471 * Execute all cells above the selected cell.
1434 *
1472 *
1435 * @method execute_cells_above
1473 * @method execute_cells_above
1436 */
1474 */
1437 Notebook.prototype.execute_cells_above = function () {
1475 Notebook.prototype.execute_cells_above = function () {
1438 this.execute_cell_range(0, this.get_selected_index());
1476 this.execute_cell_range(0, this.get_selected_index());
1439 };
1477 };
1440
1478
1441 /**
1479 /**
1442 * Execute all cells.
1480 * Execute all cells.
1443 *
1481 *
1444 * @method execute_all_cells
1482 * @method execute_all_cells
1445 */
1483 */
1446 Notebook.prototype.execute_all_cells = function () {
1484 Notebook.prototype.execute_all_cells = function () {
1447 this.execute_cell_range(0, this.ncells());
1485 this.execute_cell_range(0, this.ncells());
1448 this.scroll_to_bottom();
1486 this.scroll_to_bottom();
1449 };
1487 };
1450
1488
1451 /**
1489 /**
1452 * Execute a contiguous range of cells.
1490 * Execute a contiguous range of cells.
1453 *
1491 *
1454 * @method execute_cell_range
1492 * @method execute_cell_range
1455 * @param {Number} start Index of the first cell to execute (inclusive)
1493 * @param {Number} start Index of the first cell to execute (inclusive)
1456 * @param {Number} end Index of the last cell to execute (exclusive)
1494 * @param {Number} end Index of the last cell to execute (exclusive)
1457 */
1495 */
1458 Notebook.prototype.execute_cell_range = function (start, end) {
1496 Notebook.prototype.execute_cell_range = function (start, end) {
1459 for (var i=start; i<end; i++) {
1497 for (var i=start; i<end; i++) {
1460 this.select(i);
1498 this.select(i);
1461 this.execute_cell();
1499 this.execute_cell();
1462 };
1500 };
1463 };
1501 };
1464
1502
1465 // Persistance and loading
1503 // Persistance and loading
1466
1504
1467 /**
1505 /**
1468 * Getter method for this notebook's name.
1506 * Getter method for this notebook's name.
1469 *
1507 *
1470 * @method get_notebook_name
1508 * @method get_notebook_name
1471 * @return {String} This notebook's name
1509 * @return {String} This notebook's name
1472 */
1510 */
1473 Notebook.prototype.get_notebook_name = function () {
1511 Notebook.prototype.get_notebook_name = function () {
1474 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1512 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1475 return nbname;
1513 return nbname;
1476 };
1514 };
1477
1515
1478 /**
1516 /**
1479 * Setter method for this notebook's name.
1517 * Setter method for this notebook's name.
1480 *
1518 *
1481 * @method set_notebook_name
1519 * @method set_notebook_name
1482 * @param {String} name A new name for this notebook
1520 * @param {String} name A new name for this notebook
1483 */
1521 */
1484 Notebook.prototype.set_notebook_name = function (name) {
1522 Notebook.prototype.set_notebook_name = function (name) {
1485 this.notebook_name = name;
1523 this.notebook_name = name;
1486 };
1524 };
1487
1525
1488 /**
1526 /**
1489 * Check that a notebook's name is valid.
1527 * Check that a notebook's name is valid.
1490 *
1528 *
1491 * @method test_notebook_name
1529 * @method test_notebook_name
1492 * @param {String} nbname A name for this notebook
1530 * @param {String} nbname A name for this notebook
1493 * @return {Boolean} True if the name is valid, false if invalid
1531 * @return {Boolean} True if the name is valid, false if invalid
1494 */
1532 */
1495 Notebook.prototype.test_notebook_name = function (nbname) {
1533 Notebook.prototype.test_notebook_name = function (nbname) {
1496 nbname = nbname || '';
1534 nbname = nbname || '';
1497 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1535 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1498 return true;
1536 return true;
1499 } else {
1537 } else {
1500 return false;
1538 return false;
1501 };
1539 };
1502 };
1540 };
1503
1541
1504 /**
1542 /**
1505 * Load a notebook from JSON (.ipynb).
1543 * Load a notebook from JSON (.ipynb).
1506 *
1544 *
1507 * This currently handles one worksheet: others are deleted.
1545 * This currently handles one worksheet: others are deleted.
1508 *
1546 *
1509 * @method fromJSON
1547 * @method fromJSON
1510 * @param {Object} data JSON representation of a notebook
1548 * @param {Object} data JSON representation of a notebook
1511 */
1549 */
1512 Notebook.prototype.fromJSON = function (data) {
1550 Notebook.prototype.fromJSON = function (data) {
1513 var content = data.content;
1551 var content = data.content;
1514 var ncells = this.ncells();
1552 var ncells = this.ncells();
1515 var i;
1553 var i;
1516 for (i=0; i<ncells; i++) {
1554 for (i=0; i<ncells; i++) {
1517 // Always delete cell 0 as they get renumbered as they are deleted.
1555 // Always delete cell 0 as they get renumbered as they are deleted.
1518 this.delete_cell(0);
1556 this.delete_cell(0);
1519 };
1557 };
1520 // Save the metadata and name.
1558 // Save the metadata and name.
1521 this.metadata = content.metadata;
1559 this.metadata = content.metadata;
1522 this.notebook_name = data.name;
1560 this.notebook_name = data.name;
1523 // Only handle 1 worksheet for now.
1561 // Only handle 1 worksheet for now.
1524 var worksheet = content.worksheets[0];
1562 var worksheet = content.worksheets[0];
1525 if (worksheet !== undefined) {
1563 if (worksheet !== undefined) {
1526 if (worksheet.metadata) {
1564 if (worksheet.metadata) {
1527 this.worksheet_metadata = worksheet.metadata;
1565 this.worksheet_metadata = worksheet.metadata;
1528 }
1566 }
1529 var new_cells = worksheet.cells;
1567 var new_cells = worksheet.cells;
1530 ncells = new_cells.length;
1568 ncells = new_cells.length;
1531 var cell_data = null;
1569 var cell_data = null;
1532 var new_cell = null;
1570 var new_cell = null;
1533 for (i=0; i<ncells; i++) {
1571 for (i=0; i<ncells; i++) {
1534 cell_data = new_cells[i];
1572 cell_data = new_cells[i];
1535 // VERSIONHACK: plaintext -> raw
1573 // VERSIONHACK: plaintext -> raw
1536 // handle never-released plaintext name for raw cells
1574 // handle never-released plaintext name for raw cells
1537 if (cell_data.cell_type === 'plaintext'){
1575 if (cell_data.cell_type === 'plaintext'){
1538 cell_data.cell_type = 'raw';
1576 cell_data.cell_type = 'raw';
1539 }
1577 }
1540
1578
1541 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1579 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1542 new_cell.fromJSON(cell_data);
1580 new_cell.fromJSON(cell_data);
1543 };
1581 };
1544 };
1582 };
1545 if (content.worksheets.length > 1) {
1583 if (content.worksheets.length > 1) {
1546 IPython.dialog.modal({
1584 IPython.dialog.modal({
1547 title : "Multiple worksheets",
1585 title : "Multiple worksheets",
1548 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1586 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1549 "but this version of IPython can only handle the first. " +
1587 "but this version of IPython can only handle the first. " +
1550 "If you save this notebook, worksheets after the first will be lost.",
1588 "If you save this notebook, worksheets after the first will be lost.",
1551 buttons : {
1589 buttons : {
1552 OK : {
1590 OK : {
1553 class : "btn-danger"
1591 class : "btn-danger"
1554 }
1592 }
1555 }
1593 }
1556 });
1594 });
1557 }
1595 }
1558 };
1596 };
1559
1597
1560 /**
1598 /**
1561 * Dump this notebook into a JSON-friendly object.
1599 * Dump this notebook into a JSON-friendly object.
1562 *
1600 *
1563 * @method toJSON
1601 * @method toJSON
1564 * @return {Object} A JSON-friendly representation of this notebook.
1602 * @return {Object} A JSON-friendly representation of this notebook.
1565 */
1603 */
1566 Notebook.prototype.toJSON = function () {
1604 Notebook.prototype.toJSON = function () {
1567 var cells = this.get_cells();
1605 var cells = this.get_cells();
1568 var ncells = cells.length;
1606 var ncells = cells.length;
1569 var cell_array = new Array(ncells);
1607 var cell_array = new Array(ncells);
1570 for (var i=0; i<ncells; i++) {
1608 for (var i=0; i<ncells; i++) {
1571 cell_array[i] = cells[i].toJSON();
1609 cell_array[i] = cells[i].toJSON();
1572 };
1610 };
1573 var data = {
1611 var data = {
1574 // Only handle 1 worksheet for now.
1612 // Only handle 1 worksheet for now.
1575 worksheets : [{
1613 worksheets : [{
1576 cells: cell_array,
1614 cells: cell_array,
1577 metadata: this.worksheet_metadata
1615 metadata: this.worksheet_metadata
1578 }],
1616 }],
1579 metadata : this.metadata
1617 metadata : this.metadata
1580 };
1618 };
1581 return data;
1619 return data;
1582 };
1620 };
1583
1621
1584 /**
1622 /**
1585 * Start an autosave timer, for periodically saving the notebook.
1623 * Start an autosave timer, for periodically saving the notebook.
1586 *
1624 *
1587 * @method set_autosave_interval
1625 * @method set_autosave_interval
1588 * @param {Integer} interval the autosave interval in milliseconds
1626 * @param {Integer} interval the autosave interval in milliseconds
1589 */
1627 */
1590 Notebook.prototype.set_autosave_interval = function (interval) {
1628 Notebook.prototype.set_autosave_interval = function (interval) {
1591 var that = this;
1629 var that = this;
1592 // clear previous interval, so we don't get simultaneous timers
1630 // clear previous interval, so we don't get simultaneous timers
1593 if (this.autosave_timer) {
1631 if (this.autosave_timer) {
1594 clearInterval(this.autosave_timer);
1632 clearInterval(this.autosave_timer);
1595 }
1633 }
1596
1634
1597 this.autosave_interval = this.minimum_autosave_interval = interval;
1635 this.autosave_interval = this.minimum_autosave_interval = interval;
1598 if (interval) {
1636 if (interval) {
1599 this.autosave_timer = setInterval(function() {
1637 this.autosave_timer = setInterval(function() {
1600 if (that.dirty) {
1638 if (that.dirty) {
1601 that.save_notebook();
1639 that.save_notebook();
1602 }
1640 }
1603 }, interval);
1641 }, interval);
1604 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1642 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1605 } else {
1643 } else {
1606 this.autosave_timer = null;
1644 this.autosave_timer = null;
1607 $([IPython.events]).trigger("autosave_disabled.Notebook");
1645 $([IPython.events]).trigger("autosave_disabled.Notebook");
1608 };
1646 };
1609 };
1647 };
1610
1648
1611 /**
1649 /**
1612 * Save this notebook on the server.
1650 * Save this notebook on the server.
1613 *
1651 *
1614 * @method save_notebook
1652 * @method save_notebook
1615 */
1653 */
1616 Notebook.prototype.save_notebook = function (extra_settings) {
1654 Notebook.prototype.save_notebook = function (extra_settings) {
1617 // Create a JSON model to be sent to the server.
1655 // Create a JSON model to be sent to the server.
1618 var model = {};
1656 var model = {};
1619 model.name = this.notebook_name;
1657 model.name = this.notebook_name;
1620 model.path = this.notebook_path;
1658 model.path = this.notebook_path;
1621 model.content = this.toJSON();
1659 model.content = this.toJSON();
1622 model.content.nbformat = this.nbformat;
1660 model.content.nbformat = this.nbformat;
1623 model.content.nbformat_minor = this.nbformat_minor;
1661 model.content.nbformat_minor = this.nbformat_minor;
1624 // time the ajax call for autosave tuning purposes.
1662 // time the ajax call for autosave tuning purposes.
1625 var start = new Date().getTime();
1663 var start = new Date().getTime();
1626 // We do the call with settings so we can set cache to false.
1664 // We do the call with settings so we can set cache to false.
1627 var settings = {
1665 var settings = {
1628 processData : false,
1666 processData : false,
1629 cache : false,
1667 cache : false,
1630 type : "PUT",
1668 type : "PUT",
1631 data : JSON.stringify(model),
1669 data : JSON.stringify(model),
1632 headers : {'Content-Type': 'application/json'},
1670 headers : {'Content-Type': 'application/json'},
1633 success : $.proxy(this.save_notebook_success, this, start),
1671 success : $.proxy(this.save_notebook_success, this, start),
1634 error : $.proxy(this.save_notebook_error, this)
1672 error : $.proxy(this.save_notebook_error, this)
1635 };
1673 };
1636 if (extra_settings) {
1674 if (extra_settings) {
1637 for (var key in extra_settings) {
1675 for (var key in extra_settings) {
1638 settings[key] = extra_settings[key];
1676 settings[key] = extra_settings[key];
1639 }
1677 }
1640 }
1678 }
1641 $([IPython.events]).trigger('notebook_saving.Notebook');
1679 $([IPython.events]).trigger('notebook_saving.Notebook');
1642 var url = utils.url_join_encode(
1680 var url = utils.url_join_encode(
1643 this._baseProjectUrl,
1681 this._baseProjectUrl,
1644 'api/notebooks',
1682 'api/notebooks',
1645 this.notebook_path,
1683 this.notebook_path,
1646 this.notebook_name
1684 this.notebook_name
1647 );
1685 );
1648 $.ajax(url, settings);
1686 $.ajax(url, settings);
1649 };
1687 };
1650
1688
1651 /**
1689 /**
1652 * Success callback for saving a notebook.
1690 * Success callback for saving a notebook.
1653 *
1691 *
1654 * @method save_notebook_success
1692 * @method save_notebook_success
1655 * @param {Integer} start the time when the save request started
1693 * @param {Integer} start the time when the save request started
1656 * @param {Object} data JSON representation of a notebook
1694 * @param {Object} data JSON representation of a notebook
1657 * @param {String} status Description of response status
1695 * @param {String} status Description of response status
1658 * @param {jqXHR} xhr jQuery Ajax object
1696 * @param {jqXHR} xhr jQuery Ajax object
1659 */
1697 */
1660 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1698 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1661 this.set_dirty(false);
1699 this.set_dirty(false);
1662 $([IPython.events]).trigger('notebook_saved.Notebook');
1700 $([IPython.events]).trigger('notebook_saved.Notebook');
1663 this._update_autosave_interval(start);
1701 this._update_autosave_interval(start);
1664 if (this._checkpoint_after_save) {
1702 if (this._checkpoint_after_save) {
1665 this.create_checkpoint();
1703 this.create_checkpoint();
1666 this._checkpoint_after_save = false;
1704 this._checkpoint_after_save = false;
1667 };
1705 };
1668 };
1706 };
1669
1707
1670 /**
1708 /**
1671 * update the autosave interval based on how long the last save took
1709 * update the autosave interval based on how long the last save took
1672 *
1710 *
1673 * @method _update_autosave_interval
1711 * @method _update_autosave_interval
1674 * @param {Integer} timestamp when the save request started
1712 * @param {Integer} timestamp when the save request started
1675 */
1713 */
1676 Notebook.prototype._update_autosave_interval = function (start) {
1714 Notebook.prototype._update_autosave_interval = function (start) {
1677 var duration = (new Date().getTime() - start);
1715 var duration = (new Date().getTime() - start);
1678 if (this.autosave_interval) {
1716 if (this.autosave_interval) {
1679 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1717 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1680 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1718 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1681 // round to 10 seconds, otherwise we will be setting a new interval too often
1719 // round to 10 seconds, otherwise we will be setting a new interval too often
1682 interval = 10000 * Math.round(interval / 10000);
1720 interval = 10000 * Math.round(interval / 10000);
1683 // set new interval, if it's changed
1721 // set new interval, if it's changed
1684 if (interval != this.autosave_interval) {
1722 if (interval != this.autosave_interval) {
1685 this.set_autosave_interval(interval);
1723 this.set_autosave_interval(interval);
1686 }
1724 }
1687 }
1725 }
1688 };
1726 };
1689
1727
1690 /**
1728 /**
1691 * Failure callback for saving a notebook.
1729 * Failure callback for saving a notebook.
1692 *
1730 *
1693 * @method save_notebook_error
1731 * @method save_notebook_error
1694 * @param {jqXHR} xhr jQuery Ajax object
1732 * @param {jqXHR} xhr jQuery Ajax object
1695 * @param {String} status Description of response status
1733 * @param {String} status Description of response status
1696 * @param {String} error HTTP error message
1734 * @param {String} error HTTP error message
1697 */
1735 */
1698 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1736 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1699 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1737 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1700 };
1738 };
1701
1739
1702 Notebook.prototype.new_notebook = function(){
1740 Notebook.prototype.new_notebook = function(){
1703 var path = this.notebook_path;
1741 var path = this.notebook_path;
1704 var base_project_url = this._baseProjectUrl;
1742 var base_project_url = this._baseProjectUrl;
1705 var settings = {
1743 var settings = {
1706 processData : false,
1744 processData : false,
1707 cache : false,
1745 cache : false,
1708 type : "POST",
1746 type : "POST",
1709 dataType : "json",
1747 dataType : "json",
1710 async : false,
1748 async : false,
1711 success : function (data, status, xhr){
1749 success : function (data, status, xhr){
1712 var notebook_name = data.name;
1750 var notebook_name = data.name;
1713 window.open(
1751 window.open(
1714 utils.url_join_encode(
1752 utils.url_join_encode(
1715 base_project_url,
1753 base_project_url,
1716 'notebooks',
1754 'notebooks',
1717 path,
1755 path,
1718 notebook_name
1756 notebook_name
1719 ),
1757 ),
1720 '_blank'
1758 '_blank'
1721 );
1759 );
1722 }
1760 }
1723 };
1761 };
1724 var url = utils.url_join_encode(
1762 var url = utils.url_join_encode(
1725 base_project_url,
1763 base_project_url,
1726 'api/notebooks',
1764 'api/notebooks',
1727 path
1765 path
1728 );
1766 );
1729 $.ajax(url,settings);
1767 $.ajax(url,settings);
1730 };
1768 };
1731
1769
1732
1770
1733 Notebook.prototype.copy_notebook = function(){
1771 Notebook.prototype.copy_notebook = function(){
1734 var path = this.notebook_path;
1772 var path = this.notebook_path;
1735 var base_project_url = this._baseProjectUrl;
1773 var base_project_url = this._baseProjectUrl;
1736 var settings = {
1774 var settings = {
1737 processData : false,
1775 processData : false,
1738 cache : false,
1776 cache : false,
1739 type : "POST",
1777 type : "POST",
1740 dataType : "json",
1778 dataType : "json",
1741 data : JSON.stringify({copy_from : this.notebook_name}),
1779 data : JSON.stringify({copy_from : this.notebook_name}),
1742 async : false,
1780 async : false,
1743 success : function (data, status, xhr) {
1781 success : function (data, status, xhr) {
1744 window.open(utils.url_join_encode(
1782 window.open(utils.url_join_encode(
1745 base_project_url,
1783 base_project_url,
1746 'notebooks',
1784 'notebooks',
1747 data.path,
1785 data.path,
1748 data.name
1786 data.name
1749 ), '_blank');
1787 ), '_blank');
1750 }
1788 }
1751 };
1789 };
1752 var url = utils.url_join_encode(
1790 var url = utils.url_join_encode(
1753 base_project_url,
1791 base_project_url,
1754 'api/notebooks',
1792 'api/notebooks',
1755 path
1793 path
1756 );
1794 );
1757 $.ajax(url,settings);
1795 $.ajax(url,settings);
1758 };
1796 };
1759
1797
1760 Notebook.prototype.rename = function (nbname) {
1798 Notebook.prototype.rename = function (nbname) {
1761 var that = this;
1799 var that = this;
1762 var data = {name: nbname + '.ipynb'};
1800 var data = {name: nbname + '.ipynb'};
1763 var settings = {
1801 var settings = {
1764 processData : false,
1802 processData : false,
1765 cache : false,
1803 cache : false,
1766 type : "PATCH",
1804 type : "PATCH",
1767 data : JSON.stringify(data),
1805 data : JSON.stringify(data),
1768 dataType: "json",
1806 dataType: "json",
1769 headers : {'Content-Type': 'application/json'},
1807 headers : {'Content-Type': 'application/json'},
1770 success : $.proxy(that.rename_success, this),
1808 success : $.proxy(that.rename_success, this),
1771 error : $.proxy(that.rename_error, this)
1809 error : $.proxy(that.rename_error, this)
1772 };
1810 };
1773 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1811 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1774 var url = utils.url_join_encode(
1812 var url = utils.url_join_encode(
1775 this._baseProjectUrl,
1813 this._baseProjectUrl,
1776 'api/notebooks',
1814 'api/notebooks',
1777 this.notebook_path,
1815 this.notebook_path,
1778 this.notebook_name
1816 this.notebook_name
1779 );
1817 );
1780 $.ajax(url, settings);
1818 $.ajax(url, settings);
1781 };
1819 };
1782
1820
1783
1821
1784 Notebook.prototype.rename_success = function (json, status, xhr) {
1822 Notebook.prototype.rename_success = function (json, status, xhr) {
1785 this.notebook_name = json.name;
1823 this.notebook_name = json.name;
1786 var name = this.notebook_name;
1824 var name = this.notebook_name;
1787 var path = json.path;
1825 var path = json.path;
1788 this.session.rename_notebook(name, path);
1826 this.session.rename_notebook(name, path);
1789 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1827 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1790 }
1828 }
1791
1829
1792 Notebook.prototype.rename_error = function (xhr, status, error) {
1830 Notebook.prototype.rename_error = function (xhr, status, error) {
1793 var that = this;
1831 var that = this;
1794 var dialog = $('<div/>').append(
1832 var dialog = $('<div/>').append(
1795 $("<p/>").addClass("rename-message")
1833 $("<p/>").addClass("rename-message")
1796 .text('This notebook name already exists.')
1834 .text('This notebook name already exists.')
1797 )
1835 )
1798 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1836 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1799 IPython.dialog.modal({
1837 IPython.dialog.modal({
1800 title: "Notebook Rename Error!",
1838 title: "Notebook Rename Error!",
1801 body: dialog,
1839 body: dialog,
1802 buttons : {
1840 buttons : {
1803 "Cancel": {},
1841 "Cancel": {},
1804 "OK": {
1842 "OK": {
1805 class: "btn-primary",
1843 class: "btn-primary",
1806 click: function () {
1844 click: function () {
1807 IPython.save_widget.rename_notebook();
1845 IPython.save_widget.rename_notebook();
1808 }}
1846 }}
1809 },
1847 },
1810 open : function (event, ui) {
1848 open : function (event, ui) {
1811 var that = $(this);
1849 var that = $(this);
1812 // Upon ENTER, click the OK button.
1850 // Upon ENTER, click the OK button.
1813 that.find('input[type="text"]').keydown(function (event, ui) {
1851 that.find('input[type="text"]').keydown(function (event, ui) {
1814 if (event.which === utils.keycodes.ENTER) {
1852 if (event.which === utils.keycodes.ENTER) {
1815 that.find('.btn-primary').first().click();
1853 that.find('.btn-primary').first().click();
1816 }
1854 }
1817 });
1855 });
1818 that.find('input[type="text"]').focus();
1856 that.find('input[type="text"]').focus();
1819 }
1857 }
1820 });
1858 });
1821 }
1859 }
1822
1860
1823 /**
1861 /**
1824 * Request a notebook's data from the server.
1862 * Request a notebook's data from the server.
1825 *
1863 *
1826 * @method load_notebook
1864 * @method load_notebook
1827 * @param {String} notebook_name and path A notebook to load
1865 * @param {String} notebook_name and path A notebook to load
1828 */
1866 */
1829 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1867 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1830 var that = this;
1868 var that = this;
1831 this.notebook_name = notebook_name;
1869 this.notebook_name = notebook_name;
1832 this.notebook_path = notebook_path;
1870 this.notebook_path = notebook_path;
1833 // We do the call with settings so we can set cache to false.
1871 // We do the call with settings so we can set cache to false.
1834 var settings = {
1872 var settings = {
1835 processData : false,
1873 processData : false,
1836 cache : false,
1874 cache : false,
1837 type : "GET",
1875 type : "GET",
1838 dataType : "json",
1876 dataType : "json",
1839 success : $.proxy(this.load_notebook_success,this),
1877 success : $.proxy(this.load_notebook_success,this),
1840 error : $.proxy(this.load_notebook_error,this),
1878 error : $.proxy(this.load_notebook_error,this),
1841 };
1879 };
1842 $([IPython.events]).trigger('notebook_loading.Notebook');
1880 $([IPython.events]).trigger('notebook_loading.Notebook');
1843 var url = utils.url_join_encode(
1881 var url = utils.url_join_encode(
1844 this._baseProjectUrl,
1882 this._baseProjectUrl,
1845 'api/notebooks',
1883 'api/notebooks',
1846 this.notebook_path,
1884 this.notebook_path,
1847 this.notebook_name
1885 this.notebook_name
1848 );
1886 );
1849 $.ajax(url, settings);
1887 $.ajax(url, settings);
1850 };
1888 };
1851
1889
1852 /**
1890 /**
1853 * Success callback for loading a notebook from the server.
1891 * Success callback for loading a notebook from the server.
1854 *
1892 *
1855 * Load notebook data from the JSON response.
1893 * Load notebook data from the JSON response.
1856 *
1894 *
1857 * @method load_notebook_success
1895 * @method load_notebook_success
1858 * @param {Object} data JSON representation of a notebook
1896 * @param {Object} data JSON representation of a notebook
1859 * @param {String} status Description of response status
1897 * @param {String} status Description of response status
1860 * @param {jqXHR} xhr jQuery Ajax object
1898 * @param {jqXHR} xhr jQuery Ajax object
1861 */
1899 */
1862 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1900 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1863 this.fromJSON(data);
1901 this.fromJSON(data);
1864 if (this.ncells() === 0) {
1902 if (this.ncells() === 0) {
1865 this.insert_cell_below('code');
1903 this.insert_cell_below('code');
1866 this.select(0);
1904 this.select(0);
1867 this.edit_mode();
1905 this.edit_mode();
1868 } else {
1906 } else {
1869 this.select(0);
1907 this.select(0);
1870 this.command_mode();
1908 this.command_mode();
1871 };
1909 };
1872 this.set_dirty(false);
1910 this.set_dirty(false);
1873 this.scroll_to_top();
1911 this.scroll_to_top();
1874 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1912 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1875 var msg = "This notebook has been converted from an older " +
1913 var msg = "This notebook has been converted from an older " +
1876 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1914 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1877 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1915 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1878 "newer notebook format will be used and older versions of IPython " +
1916 "newer notebook format will be used and older versions of IPython " +
1879 "may not be able to read it. To keep the older version, close the " +
1917 "may not be able to read it. To keep the older version, close the " +
1880 "notebook without saving it.";
1918 "notebook without saving it.";
1881 IPython.dialog.modal({
1919 IPython.dialog.modal({
1882 title : "Notebook converted",
1920 title : "Notebook converted",
1883 body : msg,
1921 body : msg,
1884 buttons : {
1922 buttons : {
1885 OK : {
1923 OK : {
1886 class : "btn-primary"
1924 class : "btn-primary"
1887 }
1925 }
1888 }
1926 }
1889 });
1927 });
1890 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1928 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1891 var that = this;
1929 var that = this;
1892 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1930 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1893 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1931 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1894 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1932 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1895 this_vs + ". You can still work with this notebook, but some features " +
1933 this_vs + ". You can still work with this notebook, but some features " +
1896 "introduced in later notebook versions may not be available."
1934 "introduced in later notebook versions may not be available."
1897
1935
1898 IPython.dialog.modal({
1936 IPython.dialog.modal({
1899 title : "Newer Notebook",
1937 title : "Newer Notebook",
1900 body : msg,
1938 body : msg,
1901 buttons : {
1939 buttons : {
1902 OK : {
1940 OK : {
1903 class : "btn-danger"
1941 class : "btn-danger"
1904 }
1942 }
1905 }
1943 }
1906 });
1944 });
1907
1945
1908 }
1946 }
1909
1947
1910 // Create the session after the notebook is completely loaded to prevent
1948 // Create the session after the notebook is completely loaded to prevent
1911 // code execution upon loading, which is a security risk.
1949 // code execution upon loading, which is a security risk.
1912 if (this.session == null) {
1950 if (this.session == null) {
1913 this.start_session();
1951 this.start_session();
1914 }
1952 }
1915 // load our checkpoint list
1953 // load our checkpoint list
1916 this.list_checkpoints();
1954 this.list_checkpoints();
1917
1955
1918 // load toolbar state
1956 // load toolbar state
1919 if (this.metadata.celltoolbar) {
1957 if (this.metadata.celltoolbar) {
1920 IPython.CellToolbar.global_show();
1958 IPython.CellToolbar.global_show();
1921 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
1959 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
1922 }
1960 }
1923
1961
1924 $([IPython.events]).trigger('notebook_loaded.Notebook');
1962 $([IPython.events]).trigger('notebook_loaded.Notebook');
1925 };
1963 };
1926
1964
1927 /**
1965 /**
1928 * Failure callback for loading a notebook from the server.
1966 * Failure callback for loading a notebook from the server.
1929 *
1967 *
1930 * @method load_notebook_error
1968 * @method load_notebook_error
1931 * @param {jqXHR} xhr jQuery Ajax object
1969 * @param {jqXHR} xhr jQuery Ajax object
1932 * @param {String} status Description of response status
1970 * @param {String} status Description of response status
1933 * @param {String} error HTTP error message
1971 * @param {String} error HTTP error message
1934 */
1972 */
1935 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
1973 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
1936 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
1974 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
1937 if (xhr.status === 400) {
1975 if (xhr.status === 400) {
1938 var msg = error;
1976 var msg = error;
1939 } else if (xhr.status === 500) {
1977 } else if (xhr.status === 500) {
1940 var msg = "An unknown error occurred while loading this notebook. " +
1978 var msg = "An unknown error occurred while loading this notebook. " +
1941 "This version can load notebook formats " +
1979 "This version can load notebook formats " +
1942 "v" + this.nbformat + " or earlier.";
1980 "v" + this.nbformat + " or earlier.";
1943 }
1981 }
1944 IPython.dialog.modal({
1982 IPython.dialog.modal({
1945 title: "Error loading notebook",
1983 title: "Error loading notebook",
1946 body : msg,
1984 body : msg,
1947 buttons : {
1985 buttons : {
1948 "OK": {}
1986 "OK": {}
1949 }
1987 }
1950 });
1988 });
1951 }
1989 }
1952
1990
1953 /********************* checkpoint-related *********************/
1991 /********************* checkpoint-related *********************/
1954
1992
1955 /**
1993 /**
1956 * Save the notebook then immediately create a checkpoint.
1994 * Save the notebook then immediately create a checkpoint.
1957 *
1995 *
1958 * @method save_checkpoint
1996 * @method save_checkpoint
1959 */
1997 */
1960 Notebook.prototype.save_checkpoint = function () {
1998 Notebook.prototype.save_checkpoint = function () {
1961 this._checkpoint_after_save = true;
1999 this._checkpoint_after_save = true;
1962 this.save_notebook();
2000 this.save_notebook();
1963 };
2001 };
1964
2002
1965 /**
2003 /**
1966 * Add a checkpoint for this notebook.
2004 * Add a checkpoint for this notebook.
1967 * for use as a callback from checkpoint creation.
2005 * for use as a callback from checkpoint creation.
1968 *
2006 *
1969 * @method add_checkpoint
2007 * @method add_checkpoint
1970 */
2008 */
1971 Notebook.prototype.add_checkpoint = function (checkpoint) {
2009 Notebook.prototype.add_checkpoint = function (checkpoint) {
1972 var found = false;
2010 var found = false;
1973 for (var i = 0; i < this.checkpoints.length; i++) {
2011 for (var i = 0; i < this.checkpoints.length; i++) {
1974 var existing = this.checkpoints[i];
2012 var existing = this.checkpoints[i];
1975 if (existing.id == checkpoint.id) {
2013 if (existing.id == checkpoint.id) {
1976 found = true;
2014 found = true;
1977 this.checkpoints[i] = checkpoint;
2015 this.checkpoints[i] = checkpoint;
1978 break;
2016 break;
1979 }
2017 }
1980 }
2018 }
1981 if (!found) {
2019 if (!found) {
1982 this.checkpoints.push(checkpoint);
2020 this.checkpoints.push(checkpoint);
1983 }
2021 }
1984 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2022 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
1985 };
2023 };
1986
2024
1987 /**
2025 /**
1988 * List checkpoints for this notebook.
2026 * List checkpoints for this notebook.
1989 *
2027 *
1990 * @method list_checkpoints
2028 * @method list_checkpoints
1991 */
2029 */
1992 Notebook.prototype.list_checkpoints = function () {
2030 Notebook.prototype.list_checkpoints = function () {
1993 var url = utils.url_join_encode(
2031 var url = utils.url_join_encode(
1994 this._baseProjectUrl,
2032 this._baseProjectUrl,
1995 'api/notebooks',
2033 'api/notebooks',
1996 this.notebook_path,
2034 this.notebook_path,
1997 this.notebook_name,
2035 this.notebook_name,
1998 'checkpoints'
2036 'checkpoints'
1999 );
2037 );
2000 $.get(url).done(
2038 $.get(url).done(
2001 $.proxy(this.list_checkpoints_success, this)
2039 $.proxy(this.list_checkpoints_success, this)
2002 ).fail(
2040 ).fail(
2003 $.proxy(this.list_checkpoints_error, this)
2041 $.proxy(this.list_checkpoints_error, this)
2004 );
2042 );
2005 };
2043 };
2006
2044
2007 /**
2045 /**
2008 * Success callback for listing checkpoints.
2046 * Success callback for listing checkpoints.
2009 *
2047 *
2010 * @method list_checkpoint_success
2048 * @method list_checkpoint_success
2011 * @param {Object} data JSON representation of a checkpoint
2049 * @param {Object} data JSON representation of a checkpoint
2012 * @param {String} status Description of response status
2050 * @param {String} status Description of response status
2013 * @param {jqXHR} xhr jQuery Ajax object
2051 * @param {jqXHR} xhr jQuery Ajax object
2014 */
2052 */
2015 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2053 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2016 var data = $.parseJSON(data);
2054 var data = $.parseJSON(data);
2017 this.checkpoints = data;
2055 this.checkpoints = data;
2018 if (data.length) {
2056 if (data.length) {
2019 this.last_checkpoint = data[data.length - 1];
2057 this.last_checkpoint = data[data.length - 1];
2020 } else {
2058 } else {
2021 this.last_checkpoint = null;
2059 this.last_checkpoint = null;
2022 }
2060 }
2023 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2061 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2024 };
2062 };
2025
2063
2026 /**
2064 /**
2027 * Failure callback for listing a checkpoint.
2065 * Failure callback for listing a checkpoint.
2028 *
2066 *
2029 * @method list_checkpoint_error
2067 * @method list_checkpoint_error
2030 * @param {jqXHR} xhr jQuery Ajax object
2068 * @param {jqXHR} xhr jQuery Ajax object
2031 * @param {String} status Description of response status
2069 * @param {String} status Description of response status
2032 * @param {String} error_msg HTTP error message
2070 * @param {String} error_msg HTTP error message
2033 */
2071 */
2034 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2072 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2035 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2073 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2036 };
2074 };
2037
2075
2038 /**
2076 /**
2039 * Create a checkpoint of this notebook on the server from the most recent save.
2077 * Create a checkpoint of this notebook on the server from the most recent save.
2040 *
2078 *
2041 * @method create_checkpoint
2079 * @method create_checkpoint
2042 */
2080 */
2043 Notebook.prototype.create_checkpoint = function () {
2081 Notebook.prototype.create_checkpoint = function () {
2044 var url = utils.url_join_encode(
2082 var url = utils.url_join_encode(
2045 this._baseProjectUrl,
2083 this._baseProjectUrl,
2046 'api/notebooks',
2084 'api/notebooks',
2047 this.notebookPath(),
2085 this.notebookPath(),
2048 this.notebook_name,
2086 this.notebook_name,
2049 'checkpoints'
2087 'checkpoints'
2050 );
2088 );
2051 $.post(url).done(
2089 $.post(url).done(
2052 $.proxy(this.create_checkpoint_success, this)
2090 $.proxy(this.create_checkpoint_success, this)
2053 ).fail(
2091 ).fail(
2054 $.proxy(this.create_checkpoint_error, this)
2092 $.proxy(this.create_checkpoint_error, this)
2055 );
2093 );
2056 };
2094 };
2057
2095
2058 /**
2096 /**
2059 * Success callback for creating a checkpoint.
2097 * Success callback for creating a checkpoint.
2060 *
2098 *
2061 * @method create_checkpoint_success
2099 * @method create_checkpoint_success
2062 * @param {Object} data JSON representation of a checkpoint
2100 * @param {Object} data JSON representation of a checkpoint
2063 * @param {String} status Description of response status
2101 * @param {String} status Description of response status
2064 * @param {jqXHR} xhr jQuery Ajax object
2102 * @param {jqXHR} xhr jQuery Ajax object
2065 */
2103 */
2066 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2104 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2067 var data = $.parseJSON(data);
2105 var data = $.parseJSON(data);
2068 this.add_checkpoint(data);
2106 this.add_checkpoint(data);
2069 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2107 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2070 };
2108 };
2071
2109
2072 /**
2110 /**
2073 * Failure callback for creating a checkpoint.
2111 * Failure callback for creating a checkpoint.
2074 *
2112 *
2075 * @method create_checkpoint_error
2113 * @method create_checkpoint_error
2076 * @param {jqXHR} xhr jQuery Ajax object
2114 * @param {jqXHR} xhr jQuery Ajax object
2077 * @param {String} status Description of response status
2115 * @param {String} status Description of response status
2078 * @param {String} error_msg HTTP error message
2116 * @param {String} error_msg HTTP error message
2079 */
2117 */
2080 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2118 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2081 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2119 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2082 };
2120 };
2083
2121
2084 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2122 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2085 var that = this;
2123 var that = this;
2086 var checkpoint = checkpoint || this.last_checkpoint;
2124 var checkpoint = checkpoint || this.last_checkpoint;
2087 if ( ! checkpoint ) {
2125 if ( ! checkpoint ) {
2088 console.log("restore dialog, but no checkpoint to restore to!");
2126 console.log("restore dialog, but no checkpoint to restore to!");
2089 return;
2127 return;
2090 }
2128 }
2091 var body = $('<div/>').append(
2129 var body = $('<div/>').append(
2092 $('<p/>').addClass("p-space").text(
2130 $('<p/>').addClass("p-space").text(
2093 "Are you sure you want to revert the notebook to " +
2131 "Are you sure you want to revert the notebook to " +
2094 "the latest checkpoint?"
2132 "the latest checkpoint?"
2095 ).append(
2133 ).append(
2096 $("<strong/>").text(
2134 $("<strong/>").text(
2097 " This cannot be undone."
2135 " This cannot be undone."
2098 )
2136 )
2099 )
2137 )
2100 ).append(
2138 ).append(
2101 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2139 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2102 ).append(
2140 ).append(
2103 $('<p/>').addClass("p-space").text(
2141 $('<p/>').addClass("p-space").text(
2104 Date(checkpoint.last_modified)
2142 Date(checkpoint.last_modified)
2105 ).css("text-align", "center")
2143 ).css("text-align", "center")
2106 );
2144 );
2107
2145
2108 IPython.dialog.modal({
2146 IPython.dialog.modal({
2109 title : "Revert notebook to checkpoint",
2147 title : "Revert notebook to checkpoint",
2110 body : body,
2148 body : body,
2111 buttons : {
2149 buttons : {
2112 Revert : {
2150 Revert : {
2113 class : "btn-danger",
2151 class : "btn-danger",
2114 click : function () {
2152 click : function () {
2115 that.restore_checkpoint(checkpoint.id);
2153 that.restore_checkpoint(checkpoint.id);
2116 }
2154 }
2117 },
2155 },
2118 Cancel : {}
2156 Cancel : {}
2119 }
2157 }
2120 });
2158 });
2121 }
2159 }
2122
2160
2123 /**
2161 /**
2124 * Restore the notebook to a checkpoint state.
2162 * Restore the notebook to a checkpoint state.
2125 *
2163 *
2126 * @method restore_checkpoint
2164 * @method restore_checkpoint
2127 * @param {String} checkpoint ID
2165 * @param {String} checkpoint ID
2128 */
2166 */
2129 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2167 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2130 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2168 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2131 var url = utils.url_join_encode(
2169 var url = utils.url_join_encode(
2132 this._baseProjectUrl,
2170 this._baseProjectUrl,
2133 'api/notebooks',
2171 'api/notebooks',
2134 this.notebookPath(),
2172 this.notebookPath(),
2135 this.notebook_name,
2173 this.notebook_name,
2136 'checkpoints',
2174 'checkpoints',
2137 checkpoint
2175 checkpoint
2138 );
2176 );
2139 $.post(url).done(
2177 $.post(url).done(
2140 $.proxy(this.restore_checkpoint_success, this)
2178 $.proxy(this.restore_checkpoint_success, this)
2141 ).fail(
2179 ).fail(
2142 $.proxy(this.restore_checkpoint_error, this)
2180 $.proxy(this.restore_checkpoint_error, this)
2143 );
2181 );
2144 };
2182 };
2145
2183
2146 /**
2184 /**
2147 * Success callback for restoring a notebook to a checkpoint.
2185 * Success callback for restoring a notebook to a checkpoint.
2148 *
2186 *
2149 * @method restore_checkpoint_success
2187 * @method restore_checkpoint_success
2150 * @param {Object} data (ignored, should be empty)
2188 * @param {Object} data (ignored, should be empty)
2151 * @param {String} status Description of response status
2189 * @param {String} status Description of response status
2152 * @param {jqXHR} xhr jQuery Ajax object
2190 * @param {jqXHR} xhr jQuery Ajax object
2153 */
2191 */
2154 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2192 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2155 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2193 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2156 this.load_notebook(this.notebook_name, this.notebook_path);
2194 this.load_notebook(this.notebook_name, this.notebook_path);
2157 };
2195 };
2158
2196
2159 /**
2197 /**
2160 * Failure callback for restoring a notebook to a checkpoint.
2198 * Failure callback for restoring a notebook to a checkpoint.
2161 *
2199 *
2162 * @method restore_checkpoint_error
2200 * @method restore_checkpoint_error
2163 * @param {jqXHR} xhr jQuery Ajax object
2201 * @param {jqXHR} xhr jQuery Ajax object
2164 * @param {String} status Description of response status
2202 * @param {String} status Description of response status
2165 * @param {String} error_msg HTTP error message
2203 * @param {String} error_msg HTTP error message
2166 */
2204 */
2167 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2205 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2168 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2206 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2169 };
2207 };
2170
2208
2171 /**
2209 /**
2172 * Delete a notebook checkpoint.
2210 * Delete a notebook checkpoint.
2173 *
2211 *
2174 * @method delete_checkpoint
2212 * @method delete_checkpoint
2175 * @param {String} checkpoint ID
2213 * @param {String} checkpoint ID
2176 */
2214 */
2177 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2215 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2178 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2216 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2179 var url = utils.url_join_encode(
2217 var url = utils.url_join_encode(
2180 this._baseProjectUrl,
2218 this._baseProjectUrl,
2181 'api/notebooks',
2219 'api/notebooks',
2182 this.notebookPath(),
2220 this.notebookPath(),
2183 this.notebook_name,
2221 this.notebook_name,
2184 'checkpoints',
2222 'checkpoints',
2185 checkpoint
2223 checkpoint
2186 );
2224 );
2187 $.ajax(url, {
2225 $.ajax(url, {
2188 type: 'DELETE',
2226 type: 'DELETE',
2189 success: $.proxy(this.delete_checkpoint_success, this),
2227 success: $.proxy(this.delete_checkpoint_success, this),
2190 error: $.proxy(this.delete_notebook_error,this)
2228 error: $.proxy(this.delete_notebook_error,this)
2191 });
2229 });
2192 };
2230 };
2193
2231
2194 /**
2232 /**
2195 * Success callback for deleting a notebook checkpoint
2233 * Success callback for deleting a notebook checkpoint
2196 *
2234 *
2197 * @method delete_checkpoint_success
2235 * @method delete_checkpoint_success
2198 * @param {Object} data (ignored, should be empty)
2236 * @param {Object} data (ignored, should be empty)
2199 * @param {String} status Description of response status
2237 * @param {String} status Description of response status
2200 * @param {jqXHR} xhr jQuery Ajax object
2238 * @param {jqXHR} xhr jQuery Ajax object
2201 */
2239 */
2202 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2240 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2203 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2241 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2204 this.load_notebook(this.notebook_name, this.notebook_path);
2242 this.load_notebook(this.notebook_name, this.notebook_path);
2205 };
2243 };
2206
2244
2207 /**
2245 /**
2208 * Failure callback for deleting a notebook checkpoint.
2246 * Failure callback for deleting a notebook checkpoint.
2209 *
2247 *
2210 * @method delete_checkpoint_error
2248 * @method delete_checkpoint_error
2211 * @param {jqXHR} xhr jQuery Ajax object
2249 * @param {jqXHR} xhr jQuery Ajax object
2212 * @param {String} status Description of response status
2250 * @param {String} status Description of response status
2213 * @param {String} error_msg HTTP error message
2251 * @param {String} error_msg HTTP error message
2214 */
2252 */
2215 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2253 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2216 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2254 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2217 };
2255 };
2218
2256
2219
2257
2220 IPython.Notebook = Notebook;
2258 IPython.Notebook = Notebook;
2221
2259
2222
2260
2223 return IPython;
2261 return IPython;
2224
2262
2225 }(IPython));
2263 }(IPython));
@@ -1,299 +1,286 b''
1 {% extends "page.html" %}
1 {% extends "page.html" %}
2
2
3 {% block stylesheet %}
3 {% block stylesheet %}
4
4
5 {% if mathjax_url %}
5 {% if mathjax_url %}
6 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script>
6 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script>
7 {% endif %}
7 {% endif %}
8 <script type="text/javascript">
8 <script type="text/javascript">
9 // MathJax disabled, set as null to distingish from *missing* MathJax,
9 // MathJax disabled, set as null to distingish from *missing* MathJax,
10 // where it will be undefined, and should prompt a dialog later.
10 // where it will be undefined, and should prompt a dialog later.
11 window.mathjax_url = "{{mathjax_url}}";
11 window.mathjax_url = "{{mathjax_url}}";
12 </script>
12 </script>
13
13
14 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
14 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
15
15
16 {{super()}}
16 {{super()}}
17
17
18 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
18 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
19
19
20 {% endblock %}
20 {% endblock %}
21
21
22 {% block params %}
22 {% block params %}
23
23
24 data-project="{{project}}"
24 data-project="{{project}}"
25 data-base-project-url="{{base_project_url}}"
25 data-base-project-url="{{base_project_url}}"
26 data-base-kernel-url="{{base_kernel_url}}"
26 data-base-kernel-url="{{base_kernel_url}}"
27 data-notebook-name="{{notebook_name}}"
27 data-notebook-name="{{notebook_name}}"
28 data-notebook-path="{{notebook_path}}"
28 data-notebook-path="{{notebook_path}}"
29 class="notebook_app"
29 class="notebook_app"
30
30
31 {% endblock %}
31 {% endblock %}
32
32
33
33
34 {% block header %}
34 {% block header %}
35
35
36 <span id="save_widget" class="nav pull-left">
36 <span id="save_widget" class="nav pull-left">
37 <span id="notebook_name"></span>
37 <span id="notebook_name"></span>
38 <span id="checkpoint_status"></span>
38 <span id="checkpoint_status"></span>
39 <span id="autosave_status"></span>
39 <span id="autosave_status"></span>
40 </span>
40 </span>
41
41
42 {% endblock %}
42 {% endblock %}
43
43
44
44
45 {% block site %}
45 {% block site %}
46
46
47 <div id="menubar-container" class="container">
47 <div id="menubar-container" class="container">
48 <div id="menubar">
48 <div id="menubar">
49 <div class="navbar">
49 <div class="navbar">
50 <div class="navbar-inner">
50 <div class="navbar-inner">
51 <div class="container">
51 <div class="container">
52 <ul id="menus" class="nav">
52 <ul id="menus" class="nav">
53 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
53 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
54 <ul class="dropdown-menu">
54 <ul class="dropdown-menu">
55 <li id="new_notebook"
55 <li id="new_notebook"
56 title="Make a new notebook (Opens a new window)">
56 title="Make a new notebook (Opens a new window)">
57 <a href="#">New</a></li>
57 <a href="#">New</a></li>
58 <li id="open_notebook"
58 <li id="open_notebook"
59 title="Opens a new window with the Dashboard view">
59 title="Opens a new window with the Dashboard view">
60 <a href="#">Open...</a></li>
60 <a href="#">Open...</a></li>
61 <!-- <hr/> -->
61 <!-- <hr/> -->
62 <li class="divider"></li>
62 <li class="divider"></li>
63 <li id="copy_notebook"
63 <li id="copy_notebook"
64 title="Open a copy of this notebook's contents and start a new kernel">
64 title="Open a copy of this notebook's contents and start a new kernel">
65 <a href="#">Make a Copy...</a></li>
65 <a href="#">Make a Copy...</a></li>
66 <li id="rename_notebook"><a href="#">Rename...</a></li>
66 <li id="rename_notebook"><a href="#">Rename...</a></li>
67 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
67 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
68 <!-- <hr/> -->
68 <!-- <hr/> -->
69 <li class="divider"></li>
69 <li class="divider"></li>
70 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
70 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
71 <ul class="dropdown-menu">
71 <ul class="dropdown-menu">
72 <li><a href="#"></a></li>
72 <li><a href="#"></a></li>
73 <li><a href="#"></a></li>
73 <li><a href="#"></a></li>
74 <li><a href="#"></a></li>
74 <li><a href="#"></a></li>
75 <li><a href="#"></a></li>
75 <li><a href="#"></a></li>
76 <li><a href="#"></a></li>
76 <li><a href="#"></a></li>
77 </ul>
77 </ul>
78 </li>
78 </li>
79 <li class="divider"></li>
79 <li class="divider"></li>
80 <li id="print_preview"><a href="#">Print Preview</a></li>
80 <li id="print_preview"><a href="#">Print Preview</a></li>
81 <li class="dropdown-submenu"><a href="#">Download as</a>
81 <li class="dropdown-submenu"><a href="#">Download as</a>
82 <ul class="dropdown-menu">
82 <ul class="dropdown-menu">
83 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
83 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
84 <li id="download_py"><a href="#">Python (.py)</a></li>
84 <li id="download_py"><a href="#">Python (.py)</a></li>
85 <li id="download_html"><a href="#">HTML (.html)</a></li>
85 <li id="download_html"><a href="#">HTML (.html)</a></li>
86 <li id="download_rst"><a href="#">reST (.rst)</a></li>
86 <li id="download_rst"><a href="#">reST (.rst)</a></li>
87 </ul>
87 </ul>
88 </li>
88 </li>
89 <li class="divider"></li>
89 <li class="divider"></li>
90
90
91 <li id="kill_and_exit"
91 <li id="kill_and_exit"
92 title="Shutdown this notebook's kernel, and close this window">
92 title="Shutdown this notebook's kernel, and close this window">
93 <a href="#" >Close and halt</a></li>
93 <a href="#" >Close and halt</a></li>
94 </ul>
94 </ul>
95 </li>
95 </li>
96 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
96 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
97 <ul class="dropdown-menu">
97 <ul class="dropdown-menu">
98 <li id="cut_cell"><a href="#">Cut Cell</a></li>
98 <li id="cut_cell"><a href="#">Cut Cell</a></li>
99 <li id="copy_cell"><a href="#">Copy Cell</a></li>
99 <li id="copy_cell"><a href="#">Copy Cell</a></li>
100 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
100 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
101 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
101 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
102 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
102 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
103 <li id="delete_cell"><a href="#">Delete Cell</a></li>
103 <li id="delete_cell"><a href="#">Delete Cell</a></li>
104 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
104 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
105 <li class="divider"></li>
105 <li class="divider"></li>
106 <li id="split_cell"><a href="#">Split Cell</a></li>
106 <li id="split_cell"><a href="#">Split Cell</a></li>
107 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
107 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
108 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
108 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
109 <li class="divider"></li>
109 <li class="divider"></li>
110 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
110 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
111 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
111 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
112 <li class="divider"></li>
112 <li class="divider"></li>
113 <li id="select_previous"><a href="#">Select Previous Cell</a></li>
113 <li id="select_previous"><a href="#">Select Previous Cell</a></li>
114 <li id="select_next"><a href="#">Select Next Cell</a></li>
114 <li id="select_next"><a href="#">Select Next Cell</a></li>
115 <li class="divider"></li>
115 <li class="divider"></li>
116 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
116 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
117 </ul>
117 </ul>
118 </li>
118 </li>
119 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
119 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
120 <ul class="dropdown-menu">
120 <ul class="dropdown-menu">
121 <li id="toggle_header"
121 <li id="toggle_header"
122 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
122 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
123 <a href="#">Toggle Header</a></li>
123 <a href="#">Toggle Header</a></li>
124 <li id="toggle_toolbar"
124 <li id="toggle_toolbar"
125 title="Show/Hide the action icons (below menu bar)">
125 title="Show/Hide the action icons (below menu bar)">
126 <a href="#">Toggle Toolbar</a></li>
126 <a href="#">Toggle Toolbar</a></li>
127 </ul>
127 </ul>
128 </li>
128 </li>
129 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
129 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
130 <ul class="dropdown-menu">
130 <ul class="dropdown-menu">
131 <li id="insert_cell_above"
131 <li id="insert_cell_above"
132 title="Insert an empty Code cell above the currently active cell">
132 title="Insert an empty Code cell above the currently active cell">
133 <a href="#">Insert Cell Above</a></li>
133 <a href="#">Insert Cell Above</a></li>
134 <li id="insert_cell_below"
134 <li id="insert_cell_below"
135 title="Insert an empty Code cell below the currently active cell">
135 title="Insert an empty Code cell below the currently active cell">
136 <a href="#">Insert Cell Below</a></li>
136 <a href="#">Insert Cell Below</a></li>
137 </ul>
137 </ul>
138 </li>
138 </li>
139 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
139 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
140 <ul class="dropdown-menu">
140 <ul class="dropdown-menu">
141 <li id="run_cell" title="Run this cell, and move cursor to the next one">
141 <li id="run_cell" title="Run this cell, and move cursor to the next one">
142 <a href="#">Run</a></li>
142 <a href="#">Run</a></li>
143 <li id="run_cell_select_below" title="Run this cell, select below">
143 <li id="run_cell_select_below" title="Run this cell, select below">
144 <a href="#">Run and Select Below</a></li>
144 <a href="#">Run and Select Below</a></li>
145 <li id="run_cell_insert_below" title="Run this cell, insert below">
145 <li id="run_cell_insert_below" title="Run this cell, insert below">
146 <a href="#">Run and Insert Below</a></li>
146 <a href="#">Run and Insert Below</a></li>
147 <li id="run_all_cells" title="Run all cells in the notebook">
147 <li id="run_all_cells" title="Run all cells in the notebook">
148 <a href="#">Run All</a></li>
148 <a href="#">Run All</a></li>
149 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
149 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
150 <a href="#">Run All Above</a></li>
150 <a href="#">Run All Above</a></li>
151 <li id="run_all_cells_below" title="Run this cell and all cells below it">
151 <li id="run_all_cells_below" title="Run this cell and all cells below it">
152 <a href="#">Run All Below</a></li>
152 <a href="#">Run All Below</a></li>
153 <li class="divider"></li>
153 <li class="divider"></li>
154 <li id="change_cell_type" class="dropdown-submenu"
154 <li id="current_outputs" class="dropdown-submenu"><a href="#">Current Output</a>
155 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
156 <a href="#">Cell Type</a>
157 <ul class="dropdown-menu">
155 <ul class="dropdown-menu">
158 <li id="to_code"
156 <li id="collapse_current_output"><a href="#">Collapse</a></li>
159 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
157 <li id="expand_current_output"><a href="#">Expand</a></li>
160 <a href="#">Code</a></li>
158 <li id="clear_current_output"
161 <li id="to_markdown"
159 title="Clear the output portion of the current cell">
162 title="Contents will be rendered as HTML and serve as explanatory text">
160 <a href="#">Clear</a>
163 <a href="#">Markdown</a></li>
161 </li>
164 <li id="to_raw"
162 <li id="scroll_current_output"><a href="#">Scroll Long</a></li>
165 title="Contents will pass through nbconvert unmodified">
166 <a href="#">Raw NBConvert</a></li>
167 <li id="to_heading1"><a href="#">Heading 1</a></li>
168 <li id="to_heading2"><a href="#">Heading 2</a></li>
169 <li id="to_heading3"><a href="#">Heading 3</a></li>
170 <li id="to_heading4"><a href="#">Heading 4</a></li>
171 <li id="to_heading5"><a href="#">Heading 5</a></li>
172 <li id="to_heading6"><a href="#">Heading 6</a></li>
173 </ul>
163 </ul>
174 </li>
164 </li>
175 <li class="divider"></li>
176 <li id="toggle_output"
177 title="Show/Hide the output portion of a Code cell">
178 <a href="#">Toggle Current Output</a></li>
179 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
165 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
180 <ul class="dropdown-menu">
166 <ul class="dropdown-menu">
181 <li id="expand_all_output"><a href="#">Expand</a></li>
182 <li id="scroll_all_output"><a href="#">Scroll Long</a></li>
183 <li id="collapse_all_output"><a href="#">Collapse</a></li>
167 <li id="collapse_all_output"><a href="#">Collapse</a></li>
168 <li id="expand_all_output"><a href="#">Expand</a></li>
184 <li id="clear_all_output"
169 <li id="clear_all_output"
185 title="Remove the output portion of all Code cells">
170 title="Clear the output portion of all Code cells">
186 <a href="#">Clear</a></li>
171 <a href="#">Clear</a>
172 </li>
173 <li id="scroll_all_output"><a href="#">Scroll Long</a></li>
187 </ul>
174 </ul>
188 </li>
175 </li>
189 </ul>
176 </ul>
190 </li>
177 </li>
191 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
178 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
192 <ul class="dropdown-menu" title="Opens in a new window">
179 <ul class="dropdown-menu" title="Opens in a new window">
193 <li><a href="http://ipython.org/documentation.html" target="_blank">IPython Help</a></li>
180 <li><a href="http://ipython.org/documentation.html" target="_blank">IPython Help</a></li>
194 <li><a href="http://ipython.org/ipython-doc/stable/interactive/notebook.html" target="_blank">Notebook Help</a></li>
181 <li><a href="http://ipython.org/ipython-doc/stable/interactive/notebook.html" target="_blank">Notebook Help</a></li>
195 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
182 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
196 <li><a href="http://ipython.org/ipython-doc/dev/interactive/cm_keyboard.html" target="_blank">Editor Shortcuts</a></li>
183 <li><a href="http://ipython.org/ipython-doc/dev/interactive/cm_keyboard.html" target="_blank">Editor Shortcuts</a></li>
197 <li class="divider"></li>
184 <li class="divider"></li>
198 <li><a href="http://docs.python.org" target="_blank">Python</a></li>
185 <li><a href="http://docs.python.org" target="_blank">Python</a></li>
199 <li><a href="http://docs.scipy.org/doc/numpy/reference/" target="_blank">NumPy</a></li>
186 <li><a href="http://docs.scipy.org/doc/numpy/reference/" target="_blank">NumPy</a></li>
200 <li><a href="http://docs.scipy.org/doc/scipy/reference/" target="_blank">SciPy</a></li>
187 <li><a href="http://docs.scipy.org/doc/scipy/reference/" target="_blank">SciPy</a></li>
201 <li><a href="http://matplotlib.org/" target="_blank">Matplotlib</a></li>
188 <li><a href="http://matplotlib.org/" target="_blank">Matplotlib</a></li>
202 <li><a href="http://docs.sympy.org/dev/index.html" target="_blank">SymPy</a></li>
189 <li><a href="http://docs.sympy.org/dev/index.html" target="_blank">SymPy</a></li>
203 <li><a href="http://pandas.pydata.org/pandas-docs/stable/" target="_blank">pandas</a></li>
190 <li><a href="http://pandas.pydata.org/pandas-docs/stable/" target="_blank">pandas</a></li>
204 </ul>
191 </ul>
205 </li>
192 </li>
206 </ul>
193 </ul>
207 <div id="notification_area"></div>
194 <div id="notification_area"></div>
208 </div>
195 </div>
209 </div>
196 </div>
210 </div>
197 </div>
211 </div>
198 </div>
212 <div id="maintoolbar" class="navbar">
199 <div id="maintoolbar" class="navbar">
213 <div class="toolbar-inner navbar-inner navbar-nobg">
200 <div class="toolbar-inner navbar-inner navbar-nobg">
214 <div id="maintoolbar-container" class="container"></div>
201 <div id="maintoolbar-container" class="container"></div>
215 </div>
202 </div>
216 </div>
203 </div>
217 </div>
204 </div>
218
205
219 <div id="ipython-main-app">
206 <div id="ipython-main-app">
220
207
221 <div id="notebook_panel">
208 <div id="notebook_panel">
222 <div id="notebook"></div>
209 <div id="notebook"></div>
223 <div id="pager_splitter"></div>
210 <div id="pager_splitter"></div>
224 <div id="pager">
211 <div id="pager">
225 <div id='pager_button_area'>
212 <div id='pager_button_area'>
226 </div>
213 </div>
227 <div id="pager-container" class="container"></div>
214 <div id="pager-container" class="container"></div>
228 </div>
215 </div>
229 </div>
216 </div>
230
217
231 </div>
218 </div>
232 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
219 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
233
220
234
221
235 {% endblock %}
222 {% endblock %}
236
223
237
224
238 {% block script %}
225 {% block script %}
239
226
240 {{super()}}
227 {{super()}}
241
228
242 <script src="{{ static_url("components/codemirror/lib/codemirror.js") }}" charset="utf-8"></script>
229 <script src="{{ static_url("components/codemirror/lib/codemirror.js") }}" charset="utf-8"></script>
243 <script type="text/javascript">
230 <script type="text/javascript">
244 CodeMirror.modeURL = "{{ static_url("components/codemirror/mode/%N/%N.js") }}";
231 CodeMirror.modeURL = "{{ static_url("components/codemirror/mode/%N/%N.js") }}";
245 </script>
232 </script>
246 <script src="{{ static_url("components/codemirror/addon/mode/loadmode.js") }}" charset="utf-8"></script>
233 <script src="{{ static_url("components/codemirror/addon/mode/loadmode.js") }}" charset="utf-8"></script>
247 <script src="{{ static_url("components/codemirror/addon/mode/multiplex.js") }}" charset="utf-8"></script>
234 <script src="{{ static_url("components/codemirror/addon/mode/multiplex.js") }}" charset="utf-8"></script>
248 <script src="{{ static_url("components/codemirror/addon/mode/overlay.js") }}" charset="utf-8"></script>
235 <script src="{{ static_url("components/codemirror/addon/mode/overlay.js") }}" charset="utf-8"></script>
249 <script src="{{ static_url("components/codemirror/addon/edit/matchbrackets.js") }}" charset="utf-8"></script>
236 <script src="{{ static_url("components/codemirror/addon/edit/matchbrackets.js") }}" charset="utf-8"></script>
250 <script src="{{ static_url("components/codemirror/addon/comment/comment.js") }}" charset="utf-8"></script>
237 <script src="{{ static_url("components/codemirror/addon/comment/comment.js") }}" charset="utf-8"></script>
251 <script src="{{ static_url("components/codemirror/mode/htmlmixed/htmlmixed.js") }}" charset="utf-8"></script>
238 <script src="{{ static_url("components/codemirror/mode/htmlmixed/htmlmixed.js") }}" charset="utf-8"></script>
252 <script src="{{ static_url("components/codemirror/mode/xml/xml.js") }}" charset="utf-8"></script>
239 <script src="{{ static_url("components/codemirror/mode/xml/xml.js") }}" charset="utf-8"></script>
253 <script src="{{ static_url("components/codemirror/mode/javascript/javascript.js") }}" charset="utf-8"></script>
240 <script src="{{ static_url("components/codemirror/mode/javascript/javascript.js") }}" charset="utf-8"></script>
254 <script src="{{ static_url("components/codemirror/mode/css/css.js") }}" charset="utf-8"></script>
241 <script src="{{ static_url("components/codemirror/mode/css/css.js") }}" charset="utf-8"></script>
255 <script src="{{ static_url("components/codemirror/mode/rst/rst.js") }}" charset="utf-8"></script>
242 <script src="{{ static_url("components/codemirror/mode/rst/rst.js") }}" charset="utf-8"></script>
256 <script src="{{ static_url("components/codemirror/mode/markdown/markdown.js") }}" charset="utf-8"></script>
243 <script src="{{ static_url("components/codemirror/mode/markdown/markdown.js") }}" charset="utf-8"></script>
257 <script src="{{ static_url("components/codemirror/mode/gfm/gfm.js") }}" charset="utf-8"></script>
244 <script src="{{ static_url("components/codemirror/mode/gfm/gfm.js") }}" charset="utf-8"></script>
258 <script src="{{ static_url("components/codemirror/mode/python/python.js") }}" charset="utf-8"></script>
245 <script src="{{ static_url("components/codemirror/mode/python/python.js") }}" charset="utf-8"></script>
259 <script src="{{ static_url("notebook/js/codemirror-ipython.js") }}" charset="utf-8"></script>
246 <script src="{{ static_url("notebook/js/codemirror-ipython.js") }}" charset="utf-8"></script>
260
247
261 <script src="{{ static_url("components/highlight.js/build/highlight.pack.js") }}" charset="utf-8"></script>
248 <script src="{{ static_url("components/highlight.js/build/highlight.pack.js") }}" charset="utf-8"></script>
262
249
263 <script src="{{ static_url("dateformat/date.format.js") }}" charset="utf-8"></script>
250 <script src="{{ static_url("dateformat/date.format.js") }}" charset="utf-8"></script>
264
251
265 <script src="{{ static_url("base/js/events.js") }}" type="text/javascript" charset="utf-8"></script>
252 <script src="{{ static_url("base/js/events.js") }}" type="text/javascript" charset="utf-8"></script>
266 <script src="{{ static_url("base/js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
253 <script src="{{ static_url("base/js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
267 <script src="{{ static_url("base/js/dialog.js") }}" type="text/javascript" charset="utf-8"></script>
254 <script src="{{ static_url("base/js/dialog.js") }}" type="text/javascript" charset="utf-8"></script>
268 <script src="{{ static_url("services/kernels/js/kernel.js") }}" type="text/javascript" charset="utf-8"></script>
255 <script src="{{ static_url("services/kernels/js/kernel.js") }}" type="text/javascript" charset="utf-8"></script>
269 <script src="{{ static_url("services/kernels/js/comm.js") }}" type="text/javascript" charset="utf-8"></script>
256 <script src="{{ static_url("services/kernels/js/comm.js") }}" type="text/javascript" charset="utf-8"></script>
270 <script src="{{ static_url("services/sessions/js/session.js") }}" type="text/javascript" charset="utf-8"></script>
257 <script src="{{ static_url("services/sessions/js/session.js") }}" type="text/javascript" charset="utf-8"></script>
271 <script src="{{ static_url("notebook/js/layoutmanager.js") }}" type="text/javascript" charset="utf-8"></script>
258 <script src="{{ static_url("notebook/js/layoutmanager.js") }}" type="text/javascript" charset="utf-8"></script>
272 <script src="{{ static_url("notebook/js/mathjaxutils.js") }}" type="text/javascript" charset="utf-8"></script>
259 <script src="{{ static_url("notebook/js/mathjaxutils.js") }}" type="text/javascript" charset="utf-8"></script>
273 <script src="{{ static_url("notebook/js/outputarea.js") }}" type="text/javascript" charset="utf-8"></script>
260 <script src="{{ static_url("notebook/js/outputarea.js") }}" type="text/javascript" charset="utf-8"></script>
274 <script src="{{ static_url("notebook/js/cell.js") }}" type="text/javascript" charset="utf-8"></script>
261 <script src="{{ static_url("notebook/js/cell.js") }}" type="text/javascript" charset="utf-8"></script>
275 <script src="{{ static_url("notebook/js/celltoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
262 <script src="{{ static_url("notebook/js/celltoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
276 <script src="{{ static_url("notebook/js/codecell.js") }}" type="text/javascript" charset="utf-8"></script>
263 <script src="{{ static_url("notebook/js/codecell.js") }}" type="text/javascript" charset="utf-8"></script>
277 <script src="{{ static_url("notebook/js/completer.js") }}" type="text/javascript" charset="utf-8"></script>
264 <script src="{{ static_url("notebook/js/completer.js") }}" type="text/javascript" charset="utf-8"></script>
278 <script src="{{ static_url("notebook/js/textcell.js") }}" type="text/javascript" charset="utf-8"></script>
265 <script src="{{ static_url("notebook/js/textcell.js") }}" type="text/javascript" charset="utf-8"></script>
279 <script src="{{ static_url("notebook/js/savewidget.js") }}" type="text/javascript" charset="utf-8"></script>
266 <script src="{{ static_url("notebook/js/savewidget.js") }}" type="text/javascript" charset="utf-8"></script>
280 <script src="{{ static_url("notebook/js/quickhelp.js") }}" type="text/javascript" charset="utf-8"></script>
267 <script src="{{ static_url("notebook/js/quickhelp.js") }}" type="text/javascript" charset="utf-8"></script>
281 <script src="{{ static_url("notebook/js/pager.js") }}" type="text/javascript" charset="utf-8"></script>
268 <script src="{{ static_url("notebook/js/pager.js") }}" type="text/javascript" charset="utf-8"></script>
282 <script src="{{ static_url("notebook/js/menubar.js") }}" type="text/javascript" charset="utf-8"></script>
269 <script src="{{ static_url("notebook/js/menubar.js") }}" type="text/javascript" charset="utf-8"></script>
283 <script src="{{ static_url("notebook/js/toolbar.js") }}" type="text/javascript" charset="utf-8"></script>
270 <script src="{{ static_url("notebook/js/toolbar.js") }}" type="text/javascript" charset="utf-8"></script>
284 <script src="{{ static_url("notebook/js/maintoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
271 <script src="{{ static_url("notebook/js/maintoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
285 <script src="{{ static_url("notebook/js/notebook.js") }}" type="text/javascript" charset="utf-8"></script>
272 <script src="{{ static_url("notebook/js/notebook.js") }}" type="text/javascript" charset="utf-8"></script>
286 <script src="{{ static_url("notebook/js/keyboardmanager.js") }}" type="text/javascript" charset="utf-8"></script>
273 <script src="{{ static_url("notebook/js/keyboardmanager.js") }}" type="text/javascript" charset="utf-8"></script>
287 <script src="{{ static_url("notebook/js/notificationwidget.js") }}" type="text/javascript" charset="utf-8"></script>
274 <script src="{{ static_url("notebook/js/notificationwidget.js") }}" type="text/javascript" charset="utf-8"></script>
288 <script src="{{ static_url("notebook/js/notificationarea.js") }}" type="text/javascript" charset="utf-8"></script>
275 <script src="{{ static_url("notebook/js/notificationarea.js") }}" type="text/javascript" charset="utf-8"></script>
289 <script src="{{ static_url("notebook/js/tooltip.js") }}" type="text/javascript" charset="utf-8"></script>
276 <script src="{{ static_url("notebook/js/tooltip.js") }}" type="text/javascript" charset="utf-8"></script>
290 <script src="{{ static_url("notebook/js/config.js") }}" type="text/javascript" charset="utf-8"></script>
277 <script src="{{ static_url("notebook/js/config.js") }}" type="text/javascript" charset="utf-8"></script>
291 <script src="{{ static_url("notebook/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
278 <script src="{{ static_url("notebook/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
292
279
293 <script src="{{ static_url("notebook/js/contexthint.js") }}" charset="utf-8"></script>
280 <script src="{{ static_url("notebook/js/contexthint.js") }}" charset="utf-8"></script>
294
281
295 <script src="{{ static_url("notebook/js/celltoolbarpresets/default.js") }}" type="text/javascript" charset="utf-8"></script>
282 <script src="{{ static_url("notebook/js/celltoolbarpresets/default.js") }}" type="text/javascript" charset="utf-8"></script>
296 <script src="{{ static_url("notebook/js/celltoolbarpresets/rawcell.js") }}" type="text/javascript" charset="utf-8"></script>
283 <script src="{{ static_url("notebook/js/celltoolbarpresets/rawcell.js") }}" type="text/javascript" charset="utf-8"></script>
297 <script src="{{ static_url("notebook/js/celltoolbarpresets/slideshow.js") }}" type="text/javascript" charset="utf-8"></script>
284 <script src="{{ static_url("notebook/js/celltoolbarpresets/slideshow.js") }}" type="text/javascript" charset="utf-8"></script>
298
285
299 {% endblock %}
286 {% endblock %}
General Comments 0
You need to be logged in to leave comments. Login now