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