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