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