##// END OF EJS Templates
Merge pull request #7088 from Carreau/kernel.js...
Min RK -
r19561:da81b943 merge
parent child Browse files
Show More
@@ -1,111 +1,159 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 ], function(IPython, $, utils) {
8 ], function(IPython, $, utils) {
9 "use strict";
9 "use strict";
10
10
11 var KernelSelector = function(selector, notebook) {
11 var KernelSelector = function(selector, notebook) {
12 this.selector = selector;
12 this.selector = selector;
13 this.notebook = notebook;
13 this.notebook = notebook;
14 this.notebook.set_kernelselector(this);
14 this.events = notebook.events;
15 this.events = notebook.events;
15 this.current_selection = null;
16 this.current_selection = null;
16 this.kernelspecs = {};
17 this.kernelspecs = {};
17 if (this.selector !== undefined) {
18 if (this.selector !== undefined) {
18 this.element = $(selector);
19 this.element = $(selector);
19 this.request_kernelspecs();
20 this.request_kernelspecs();
20 }
21 }
21 this.bind_events();
22 this.bind_events();
22 // Make the object globally available for user convenience & inspection
23 // Make the object globally available for user convenience & inspection
23 IPython.kernelselector = this;
24 IPython.kernelselector = this;
25 Object.seal(this);
24 };
26 };
25
27
26 KernelSelector.prototype.request_kernelspecs = function() {
28 KernelSelector.prototype.request_kernelspecs = function() {
27 var url = utils.url_join_encode(this.notebook.base_url, 'api/kernelspecs');
29 var url = utils.url_join_encode(this.notebook.base_url, 'api/kernelspecs');
28 utils.promising_ajax(url).then($.proxy(this._got_kernelspecs, this));
30 utils.promising_ajax(url).then($.proxy(this._got_kernelspecs, this));
29 };
31 };
30
32
31 KernelSelector.prototype._got_kernelspecs = function(data) {
33 KernelSelector.prototype._got_kernelspecs = function(data) {
32 this.kernelspecs = data.kernelspecs;
34 this.kernelspecs = data.kernelspecs;
33 var menu = this.element.find("#kernel_selector");
35 var menu = this.element.find("#kernel_selector");
34 var change_kernel_submenu = $("#menu-change-kernel-submenu");
36 var change_kernel_submenu = $("#menu-change-kernel-submenu");
35 var keys = Object.keys(data.kernelspecs).sort(function (a, b) {
37 var keys = Object.keys(data.kernelspecs).sort(function (a, b) {
36 // sort by display_name
38 // sort by display_name
37 var da = data.kernelspecs[a].display_name;
39 var da = data.kernelspecs[a].display_name;
38 var db = data.kernelspecs[b].display_name;
40 var db = data.kernelspecs[b].display_name;
39 if (da === db) {
41 if (da === db) {
40 return 0;
42 return 0;
41 } else if (da > db) {
43 } else if (da > db) {
42 return 1;
44 return 1;
43 } else {
45 } else {
44 return -1;
46 return -1;
45 }
47 }
46 });
48 });
47 for (var i = 0; i < keys.length; i++) {
49 for (var i = 0; i < keys.length; i++) {
48 var ks = this.kernelspecs[keys[i]];
50 var ks = this.kernelspecs[keys[i]];
49 var ksentry = $("<li>").attr("id", "kernel-" +ks.name).append($('<a>')
51 var ksentry = $("<li>").attr("id", "kernel-" +ks.name).append($('<a>')
50 .attr('href', '#')
52 .attr('href', '#')
51 .click($.proxy(this.change_kernel, this, ks.name))
53 .click($.proxy(this.change_kernel, this, ks.name))
52 .text(ks.display_name));
54 .text(ks.display_name));
53 menu.append(ksentry);
55 menu.append(ksentry);
54
56
55 var ks_submenu_entry = $("<li>").attr("id", "kernel-submenu-"+ks.name).append($('<a>')
57 var ks_submenu_entry = $("<li>").attr("id", "kernel-submenu-"+ks.name).append($('<a>')
56 .attr('href', '#')
58 .attr('href', '#')
57 .click($.proxy(this.change_kernel, this, ks.name))
59 .click($.proxy(this.change_kernel, this, ks.name))
58 .text(ks.display_name));
60 .text(ks.display_name));
59 change_kernel_submenu.append(ks_submenu_entry);
61 change_kernel_submenu.append(ks_submenu_entry);
60 }
62 }
61 };
63 };
62
64
63 KernelSelector.prototype.change_kernel = function(kernel_name) {
65 KernelSelector.prototype.change_kernel = function(kernel_name) {
66 /**
67 * TODO, have a methods to set kernel spec directly ?
68 **/
69 var that = this;
64 if (kernel_name === this.current_selection) {
70 if (kernel_name === this.current_selection) {
65 return;
71 return;
66 }
72 }
67 var ks = this.kernelspecs[kernel_name];
73 var ks = this.kernelspecs[kernel_name];
74 var new_mode_url = 'kernelspecs/'+ks.name+'/kernel';
75
76 var css_url = this.notebook.base_url+new_mode_url+'.css';
77 $.ajax({
78 type: 'HEAD',
79 url: css_url,
80 success: function(){
81 $('#kernel-css')
82 .attr('href',css_url);
83 },
84 error:function(){
85 console.info("No custom kernel.css at URL:", css_url)
86 }
87 });
88
68 try {
89 try {
69 this.notebook.start_session(kernel_name);
90 this.notebook.start_session(kernel_name);
70 } catch (e) {
91 } catch (e) {
71 if (e.name === 'SessionAlreadyStarting') {
92 if (e.name === 'SessionAlreadyStarting') {
72 console.log("Cannot change kernel while waiting for pending session start.");
93 console.log("Cannot change kernel while waiting for pending session start.");
73 } else {
94 } else {
74 // unhandled error
95 // unhandled error
75 throw e;
96 throw e;
76 }
97 }
77 // only trigger spec_changed if change was successful
98 // only trigger spec_changed if change was successful
78 return;
99 return;
79 }
100 }
80 this.events.trigger('spec_changed.Kernel', ks);
101 this.events.trigger('spec_changed.Kernel', ks);
102
103
104 // load new mode kernel.js if exist
105 require([new_mode_url],
106 // if new mode has custom.js
107 function(new_mode){
108 that.lock_switch();
109 if(new_mode && new_mode.onload){
110 new_mode.onload();
111 } else {
112 console.warn("The current kernel defined a kernel.js file but does not contain "+
113 "any asynchronous module definition. This is undefined behavior "+
114 "which is not recommended");
115 }
116 },
117 function(err){
118 // if new mode does not have custom.js
119 console.info("No custom kernel.css at URL:", new_mode_url)
120 }
121 );
122 };
123
124 KernelSelector.prototype.lock_switch = function() {
125 // should set a flag and display warning+reload if user want to
126 // re-change kernel. As UI discussion never finish
127 // making that a separate PR.
128 console.warn('switching kernel is not guaranteed to work !');
81 };
129 };
82
130
83 KernelSelector.prototype.bind_events = function() {
131 KernelSelector.prototype.bind_events = function() {
84 var that = this;
132 var that = this;
85 this.events.on('spec_changed.Kernel', function(event, data) {
133 this.events.on('spec_changed.Kernel', function(event, data) {
86 that.current_selection = data.name;
134 that.current_selection = data.name;
87 that.element.find("#current_kernel_spec").find('.kernel_name').text(data.display_name);
135 that.element.find("#current_kernel_spec").find('.kernel_name').text(data.display_name);
88 that.element.find("#current_kernel_logo").attr("src", "/kernelspecs/"+data.name+"/logo-64x64.png");
136 that.element.find("#current_kernel_logo").attr("src", "/kernelspecs/"+data.name+"/logo-64x64.png");
89 });
137 });
90
138
91 this.events.on('kernel_created.Session', function(event, data) {
139 this.events.on('kernel_created.Session', function(event, data) {
92 if (data.kernel.name !== that.current_selection) {
140 if (data.kernel.name !== that.current_selection) {
93 // If we created a 'python' session, we only know if it's Python
141 // If we created a 'python' session, we only know if it's Python
94 // 3 or 2 on the server's reply, so we fire the event again to
142 // 3 or 2 on the server's reply, so we fire the event again to
95 // set things up.
143 // set things up.
96 var ks = that.kernelspecs[data.kernel.name];
144 var ks = that.kernelspecs[data.kernel.name];
97 that.events.trigger('spec_changed.Kernel', ks);
145 that.events.trigger('spec_changed.Kernel', ks);
98 }
146 }
99 });
147 });
100
148
101 var logo_img = this.element.find("#current_kernel_logo")
149 var logo_img = this.element.find("#current_kernel_logo")
102 logo_img.on("load", function() {
150 logo_img.on("load", function() {
103 logo_img.show();
151 logo_img.show();
104 });
152 });
105 logo_img.on("error", function() {
153 logo_img.on("error", function() {
106 logo_img.hide();
154 logo_img.hide();
107 });
155 });
108 };
156 };
109
157
110 return {'KernelSelector': KernelSelector};
158 return {'KernelSelector': KernelSelector};
111 });
159 });
@@ -1,2396 +1,2407 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 /**
4 /**
5 * @module notebook
5 * @module notebook
6 */
6 */
7 define([
7 define([
8 'base/js/namespace',
8 'base/js/namespace',
9 'jquery',
9 'jquery',
10 'base/js/utils',
10 'base/js/utils',
11 'base/js/dialog',
11 'base/js/dialog',
12 'notebook/js/cell',
12 'notebook/js/cell',
13 'notebook/js/textcell',
13 'notebook/js/textcell',
14 'notebook/js/codecell',
14 'notebook/js/codecell',
15 'services/config',
15 'services/config',
16 'services/sessions/session',
16 'services/sessions/session',
17 'notebook/js/celltoolbar',
17 'notebook/js/celltoolbar',
18 'components/marked/lib/marked',
18 'components/marked/lib/marked',
19 'codemirror/lib/codemirror',
19 'codemirror/lib/codemirror',
20 'codemirror/addon/runmode/runmode',
20 'codemirror/addon/runmode/runmode',
21 'notebook/js/mathjaxutils',
21 'notebook/js/mathjaxutils',
22 'base/js/keyboard',
22 'base/js/keyboard',
23 'notebook/js/tooltip',
23 'notebook/js/tooltip',
24 'notebook/js/celltoolbarpresets/default',
24 'notebook/js/celltoolbarpresets/default',
25 'notebook/js/celltoolbarpresets/rawcell',
25 'notebook/js/celltoolbarpresets/rawcell',
26 'notebook/js/celltoolbarpresets/slideshow',
26 'notebook/js/celltoolbarpresets/slideshow',
27 'notebook/js/scrollmanager'
27 'notebook/js/scrollmanager'
28 ], function (
28 ], function (
29 IPython,
29 IPython,
30 $,
30 $,
31 utils,
31 utils,
32 dialog,
32 dialog,
33 cellmod,
33 cellmod,
34 textcell,
34 textcell,
35 codecell,
35 codecell,
36 configmod,
36 configmod,
37 session,
37 session,
38 celltoolbar,
38 celltoolbar,
39 marked,
39 marked,
40 CodeMirror,
40 CodeMirror,
41 runMode,
41 runMode,
42 mathjaxutils,
42 mathjaxutils,
43 keyboard,
43 keyboard,
44 tooltip,
44 tooltip,
45 default_celltoolbar,
45 default_celltoolbar,
46 rawcell_celltoolbar,
46 rawcell_celltoolbar,
47 slideshow_celltoolbar,
47 slideshow_celltoolbar,
48 scrollmanager
48 scrollmanager
49 ) {
49 ) {
50 "use strict";
50 "use strict";
51
51
52 /**
52 /**
53 * Contains and manages cells.
53 * Contains and manages cells.
54 *
54 *
55 * @class Notebook
55 * @class Notebook
56 * @param {string} selector
56 * @param {string} selector
57 * @param {object} options - Dictionary of keyword arguments.
57 * @param {object} options - Dictionary of keyword arguments.
58 * @param {jQuery} options.events - selector of Events
58 * @param {jQuery} options.events - selector of Events
59 * @param {KeyboardManager} options.keyboard_manager
59 * @param {KeyboardManager} options.keyboard_manager
60 * @param {Contents} options.contents
60 * @param {Contents} options.contents
61 * @param {SaveWidget} options.save_widget
61 * @param {SaveWidget} options.save_widget
62 * @param {object} options.config
62 * @param {object} options.config
63 * @param {string} options.base_url
63 * @param {string} options.base_url
64 * @param {string} options.notebook_path
64 * @param {string} options.notebook_path
65 * @param {string} options.notebook_name
65 * @param {string} options.notebook_name
66 */
66 */
67 var Notebook = function (selector, options) {
67 var Notebook = function (selector, options) {
68 this.config = options.config;
68 this.config = options.config;
69 this.class_config = new configmod.ConfigWithDefaults(this.config,
69 this.class_config = new configmod.ConfigWithDefaults(this.config,
70 Notebook.options_default, 'Notebook');
70 Notebook.options_default, 'Notebook');
71 this.base_url = options.base_url;
71 this.base_url = options.base_url;
72 this.notebook_path = options.notebook_path;
72 this.notebook_path = options.notebook_path;
73 this.notebook_name = options.notebook_name;
73 this.notebook_name = options.notebook_name;
74 this.events = options.events;
74 this.events = options.events;
75 this.keyboard_manager = options.keyboard_manager;
75 this.keyboard_manager = options.keyboard_manager;
76 this.contents = options.contents;
76 this.contents = options.contents;
77 this.save_widget = options.save_widget;
77 this.save_widget = options.save_widget;
78 this.tooltip = new tooltip.Tooltip(this.events);
78 this.tooltip = new tooltip.Tooltip(this.events);
79 this.ws_url = options.ws_url;
79 this.ws_url = options.ws_url;
80 this._session_starting = false;
80 this._session_starting = false;
81
81
82 // Create default scroll manager.
82 // Create default scroll manager.
83 this.scroll_manager = new scrollmanager.ScrollManager(this);
83 this.scroll_manager = new scrollmanager.ScrollManager(this);
84
84
85 // TODO: This code smells (and the other `= this` line a couple lines down)
85 // TODO: This code smells (and the other `= this` line a couple lines down)
86 // We need a better way to deal with circular instance references.
86 // We need a better way to deal with circular instance references.
87 this.keyboard_manager.notebook = this;
87 this.keyboard_manager.notebook = this;
88 this.save_widget.notebook = this;
88 this.save_widget.notebook = this;
89
89
90 mathjaxutils.init();
90 mathjaxutils.init();
91
91
92 if (marked) {
92 if (marked) {
93 marked.setOptions({
93 marked.setOptions({
94 gfm : true,
94 gfm : true,
95 tables: true,
95 tables: true,
96 // FIXME: probably want central config for CodeMirror theme when we have js config
96 // FIXME: probably want central config for CodeMirror theme when we have js config
97 langPrefix: "cm-s-ipython language-",
97 langPrefix: "cm-s-ipython language-",
98 highlight: function(code, lang, callback) {
98 highlight: function(code, lang, callback) {
99 if (!lang) {
99 if (!lang) {
100 // no language, no highlight
100 // no language, no highlight
101 if (callback) {
101 if (callback) {
102 callback(null, code);
102 callback(null, code);
103 return;
103 return;
104 } else {
104 } else {
105 return code;
105 return code;
106 }
106 }
107 }
107 }
108 utils.requireCodeMirrorMode(lang, function (spec) {
108 utils.requireCodeMirrorMode(lang, function (spec) {
109 var el = document.createElement("div");
109 var el = document.createElement("div");
110 var mode = CodeMirror.getMode({}, spec);
110 var mode = CodeMirror.getMode({}, spec);
111 if (!mode) {
111 if (!mode) {
112 console.log("No CodeMirror mode: " + lang);
112 console.log("No CodeMirror mode: " + lang);
113 callback(null, code);
113 callback(null, code);
114 return;
114 return;
115 }
115 }
116 try {
116 try {
117 CodeMirror.runMode(code, spec, el);
117 CodeMirror.runMode(code, spec, el);
118 callback(null, el.innerHTML);
118 callback(null, el.innerHTML);
119 } catch (err) {
119 } catch (err) {
120 console.log("Failed to highlight " + lang + " code", err);
120 console.log("Failed to highlight " + lang + " code", err);
121 callback(err, code);
121 callback(err, code);
122 }
122 }
123 }, function (err) {
123 }, function (err) {
124 console.log("No CodeMirror mode: " + lang);
124 console.log("No CodeMirror mode: " + lang);
125 callback(err, code);
125 callback(err, code);
126 });
126 });
127 }
127 }
128 });
128 });
129 }
129 }
130
130
131 this.element = $(selector);
131 this.element = $(selector);
132 this.element.scroll();
132 this.element.scroll();
133 this.element.data("notebook", this);
133 this.element.data("notebook", this);
134 this.next_prompt_number = 1;
134 this.next_prompt_number = 1;
135 this.session = null;
135 this.session = null;
136 this.kernel = null;
136 this.kernel = null;
137 this.clipboard = null;
137 this.clipboard = null;
138 this.undelete_backup = null;
138 this.undelete_backup = null;
139 this.undelete_index = null;
139 this.undelete_index = null;
140 this.undelete_below = false;
140 this.undelete_below = false;
141 this.paste_enabled = false;
141 this.paste_enabled = false;
142 this.writable = false;
142 this.writable = false;
143 // It is important to start out in command mode to match the intial mode
143 // It is important to start out in command mode to match the intial mode
144 // of the KeyboardManager.
144 // of the KeyboardManager.
145 this.mode = 'command';
145 this.mode = 'command';
146 this.set_dirty(false);
146 this.set_dirty(false);
147 this.metadata = {};
147 this.metadata = {};
148 this._checkpoint_after_save = false;
148 this._checkpoint_after_save = false;
149 this.last_checkpoint = null;
149 this.last_checkpoint = null;
150 this.checkpoints = [];
150 this.checkpoints = [];
151 this.autosave_interval = 0;
151 this.autosave_interval = 0;
152 this.autosave_timer = null;
152 this.autosave_timer = null;
153 // autosave *at most* every two minutes
153 // autosave *at most* every two minutes
154 this.minimum_autosave_interval = 120000;
154 this.minimum_autosave_interval = 120000;
155 this.notebook_name_blacklist_re = /[\/\\:]/;
155 this.notebook_name_blacklist_re = /[\/\\:]/;
156 this.nbformat = 4; // Increment this when changing the nbformat
156 this.nbformat = 4; // Increment this when changing the nbformat
157 this.nbformat_minor = this.current_nbformat_minor = 0; // Increment this when changing the nbformat
157 this.nbformat_minor = this.current_nbformat_minor = 0; // Increment this when changing the nbformat
158 this.codemirror_mode = 'ipython';
158 this.codemirror_mode = 'ipython';
159 this.create_elements();
159 this.create_elements();
160 this.bind_events();
160 this.bind_events();
161 this.kernel_selector = null;
161 this.kernel_selector = null;
162 this.dirty = null;
162 this.dirty = null;
163 this.trusted = null;
163 this.trusted = null;
164 this._fully_loaded = false;
164 this._fully_loaded = false;
165
165
166 // Trigger cell toolbar registration.
166 // Trigger cell toolbar registration.
167 default_celltoolbar.register(this);
167 default_celltoolbar.register(this);
168 rawcell_celltoolbar.register(this);
168 rawcell_celltoolbar.register(this);
169 slideshow_celltoolbar.register(this);
169 slideshow_celltoolbar.register(this);
170
170
171 // prevent assign to miss-typed properties.
171 // prevent assign to miss-typed properties.
172 Object.seal(this);
172 Object.seal(this);
173 };
173 };
174
174
175 Notebook.options_default = {
175 Notebook.options_default = {
176 // can be any cell type, or the special values of
176 // can be any cell type, or the special values of
177 // 'above', 'below', or 'selected' to get the value from another cell.
177 // 'above', 'below', or 'selected' to get the value from another cell.
178 default_cell_type: 'code'
178 default_cell_type: 'code'
179 };
179 };
180
180
181 /**
181 /**
182 * Create an HTML and CSS representation of the notebook.
182 * Create an HTML and CSS representation of the notebook.
183 */
183 */
184 Notebook.prototype.create_elements = function () {
184 Notebook.prototype.create_elements = function () {
185 var that = this;
185 var that = this;
186 this.element.attr('tabindex','-1');
186 this.element.attr('tabindex','-1');
187 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
187 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
188 // We add this end_space div to the end of the notebook div to:
188 // We add this end_space div to the end of the notebook div to:
189 // i) provide a margin between the last cell and the end of the notebook
189 // i) provide a margin between the last cell and the end of the notebook
190 // ii) to prevent the div from scrolling up when the last cell is being
190 // ii) to prevent the div from scrolling up when the last cell is being
191 // edited, but is too low on the page, which browsers will do automatically.
191 // edited, but is too low on the page, which browsers will do automatically.
192 var end_space = $('<div/>').addClass('end_space');
192 var end_space = $('<div/>').addClass('end_space');
193 end_space.dblclick(function (e) {
193 end_space.dblclick(function (e) {
194 var ncells = that.ncells();
194 var ncells = that.ncells();
195 that.insert_cell_below('code',ncells-1);
195 that.insert_cell_below('code',ncells-1);
196 });
196 });
197 this.element.append(this.container);
197 this.element.append(this.container);
198 this.container.append(end_space);
198 this.container.append(end_space);
199 };
199 };
200
200
201 /**
201 /**
202 * Bind JavaScript events: key presses and custom IPython events.
202 * Bind JavaScript events: key presses and custom IPython events.
203 */
203 */
204 Notebook.prototype.bind_events = function () {
204 Notebook.prototype.bind_events = function () {
205 var that = this;
205 var that = this;
206
206
207 this.events.on('set_next_input.Notebook', function (event, data) {
207 this.events.on('set_next_input.Notebook', function (event, data) {
208 if (data.replace) {
208 if (data.replace) {
209 data.cell.set_text(data.text);
209 data.cell.set_text(data.text);
210 data.cell.clear_output();
210 data.cell.clear_output();
211 } else {
211 } else {
212 var index = that.find_cell_index(data.cell);
212 var index = that.find_cell_index(data.cell);
213 var new_cell = that.insert_cell_below('code',index);
213 var new_cell = that.insert_cell_below('code',index);
214 new_cell.set_text(data.text);
214 new_cell.set_text(data.text);
215 }
215 }
216 that.dirty = true;
216 that.dirty = true;
217 });
217 });
218
218
219 this.events.on('unrecognized_cell.Cell', function () {
219 this.events.on('unrecognized_cell.Cell', function () {
220 that.warn_nbformat_minor();
220 that.warn_nbformat_minor();
221 });
221 });
222
222
223 this.events.on('unrecognized_output.OutputArea', function () {
223 this.events.on('unrecognized_output.OutputArea', function () {
224 that.warn_nbformat_minor();
224 that.warn_nbformat_minor();
225 });
225 });
226
226
227 this.events.on('set_dirty.Notebook', function (event, data) {
227 this.events.on('set_dirty.Notebook', function (event, data) {
228 that.dirty = data.value;
228 that.dirty = data.value;
229 });
229 });
230
230
231 this.events.on('trust_changed.Notebook', function (event, trusted) {
231 this.events.on('trust_changed.Notebook', function (event, trusted) {
232 that.trusted = trusted;
232 that.trusted = trusted;
233 });
233 });
234
234
235 this.events.on('select.Cell', function (event, data) {
235 this.events.on('select.Cell', function (event, data) {
236 var index = that.find_cell_index(data.cell);
236 var index = that.find_cell_index(data.cell);
237 that.select(index);
237 that.select(index);
238 });
238 });
239
239
240 this.events.on('edit_mode.Cell', function (event, data) {
240 this.events.on('edit_mode.Cell', function (event, data) {
241 that.handle_edit_mode(data.cell);
241 that.handle_edit_mode(data.cell);
242 });
242 });
243
243
244 this.events.on('command_mode.Cell', function (event, data) {
244 this.events.on('command_mode.Cell', function (event, data) {
245 that.handle_command_mode(data.cell);
245 that.handle_command_mode(data.cell);
246 });
246 });
247
247
248 this.events.on('spec_changed.Kernel', function(event, data) {
248 this.events.on('spec_changed.Kernel', function(event, data) {
249 that.metadata.kernelspec =
249 that.metadata.kernelspec =
250 {name: data.name, display_name: data.display_name};
250 {name: data.name, display_name: data.display_name};
251 });
251 });
252
252
253 this.events.on('kernel_ready.Kernel', function(event, data) {
253 this.events.on('kernel_ready.Kernel', function(event, data) {
254 var kinfo = data.kernel.info_reply;
254 var kinfo = data.kernel.info_reply;
255 var langinfo = kinfo.language_info || {};
255 var langinfo = kinfo.language_info || {};
256 that.metadata.language_info = langinfo;
256 that.metadata.language_info = langinfo;
257 // Mode 'null' should be plain, unhighlighted text.
257 // Mode 'null' should be plain, unhighlighted text.
258 var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
258 var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
259 that.set_codemirror_mode(cm_mode);
259 that.set_codemirror_mode(cm_mode);
260 });
260 });
261
261
262 var collapse_time = function (time) {
262 var collapse_time = function (time) {
263 var app_height = $('#ipython-main-app').height(); // content height
263 var app_height = $('#ipython-main-app').height(); // content height
264 var splitter_height = $('div#pager_splitter').outerHeight(true);
264 var splitter_height = $('div#pager_splitter').outerHeight(true);
265 var new_height = app_height - splitter_height;
265 var new_height = app_height - splitter_height;
266 that.element.animate({height : new_height + 'px'}, time);
266 that.element.animate({height : new_height + 'px'}, time);
267 };
267 };
268
268
269 this.element.bind('collapse_pager', function (event, extrap) {
269 this.element.bind('collapse_pager', function (event, extrap) {
270 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
270 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
271 collapse_time(time);
271 collapse_time(time);
272 });
272 });
273
273
274 var expand_time = function (time) {
274 var expand_time = function (time) {
275 var app_height = $('#ipython-main-app').height(); // content height
275 var app_height = $('#ipython-main-app').height(); // content height
276 var splitter_height = $('div#pager_splitter').outerHeight(true);
276 var splitter_height = $('div#pager_splitter').outerHeight(true);
277 var pager_height = $('div#pager').outerHeight(true);
277 var pager_height = $('div#pager').outerHeight(true);
278 var new_height = app_height - pager_height - splitter_height;
278 var new_height = app_height - pager_height - splitter_height;
279 that.element.animate({height : new_height + 'px'}, time);
279 that.element.animate({height : new_height + 'px'}, time);
280 };
280 };
281
281
282 this.element.bind('expand_pager', function (event, extrap) {
282 this.element.bind('expand_pager', function (event, extrap) {
283 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
283 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
284 expand_time(time);
284 expand_time(time);
285 });
285 });
286
286
287 // Firefox 22 broke $(window).on("beforeunload")
287 // Firefox 22 broke $(window).on("beforeunload")
288 // I'm not sure why or how.
288 // I'm not sure why or how.
289 window.onbeforeunload = function (e) {
289 window.onbeforeunload = function (e) {
290 // TODO: Make killing the kernel configurable.
290 // TODO: Make killing the kernel configurable.
291 var kill_kernel = false;
291 var kill_kernel = false;
292 if (kill_kernel) {
292 if (kill_kernel) {
293 that.session.delete();
293 that.session.delete();
294 }
294 }
295 // if we are autosaving, trigger an autosave on nav-away.
295 // if we are autosaving, trigger an autosave on nav-away.
296 // still warn, because if we don't the autosave may fail.
296 // still warn, because if we don't the autosave may fail.
297 if (that.dirty) {
297 if (that.dirty) {
298 if ( that.autosave_interval ) {
298 if ( that.autosave_interval ) {
299 // schedule autosave in a timeout
299 // schedule autosave in a timeout
300 // this gives you a chance to forcefully discard changes
300 // this gives you a chance to forcefully discard changes
301 // by reloading the page if you *really* want to.
301 // by reloading the page if you *really* want to.
302 // the timer doesn't start until you *dismiss* the dialog.
302 // the timer doesn't start until you *dismiss* the dialog.
303 setTimeout(function () {
303 setTimeout(function () {
304 if (that.dirty) {
304 if (that.dirty) {
305 that.save_notebook();
305 that.save_notebook();
306 }
306 }
307 }, 1000);
307 }, 1000);
308 return "Autosave in progress, latest changes may be lost.";
308 return "Autosave in progress, latest changes may be lost.";
309 } else {
309 } else {
310 return "Unsaved changes will be lost.";
310 return "Unsaved changes will be lost.";
311 }
311 }
312 }
312 }
313 // Null is the *only* return value that will make the browser not
313 // Null is the *only* return value that will make the browser not
314 // pop up the "don't leave" dialog.
314 // pop up the "don't leave" dialog.
315 return null;
315 return null;
316 };
316 };
317 };
317 };
318
318
319 /**
319 /**
320 * Trigger a warning dialog about missing functionality from newer minor versions
320 * Trigger a warning dialog about missing functionality from newer minor versions
321 */
321 */
322 Notebook.prototype.warn_nbformat_minor = function (event) {
322 Notebook.prototype.warn_nbformat_minor = function (event) {
323 var v = 'v' + this.nbformat + '.';
323 var v = 'v' + this.nbformat + '.';
324 var orig_vs = v + this.nbformat_minor;
324 var orig_vs = v + this.nbformat_minor;
325 var this_vs = v + this.current_nbformat_minor;
325 var this_vs = v + this.current_nbformat_minor;
326 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
326 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
327 this_vs + ". You can still work with this notebook, but cell and output types " +
327 this_vs + ". You can still work with this notebook, but cell and output types " +
328 "introduced in later notebook versions will not be available.";
328 "introduced in later notebook versions will not be available.";
329
329
330 dialog.modal({
330 dialog.modal({
331 notebook: this,
331 notebook: this,
332 keyboard_manager: this.keyboard_manager,
332 keyboard_manager: this.keyboard_manager,
333 title : "Newer Notebook",
333 title : "Newer Notebook",
334 body : msg,
334 body : msg,
335 buttons : {
335 buttons : {
336 OK : {
336 OK : {
337 "class" : "btn-danger"
337 "class" : "btn-danger"
338 }
338 }
339 }
339 }
340 });
340 });
341 };
341 };
342
342
343 /**
343 /**
344 * Set the dirty flag, and trigger the set_dirty.Notebook event
344 * Set the dirty flag, and trigger the set_dirty.Notebook event
345 */
345 */
346 Notebook.prototype.set_dirty = function (value) {
346 Notebook.prototype.set_dirty = function (value) {
347 if (value === undefined) {
347 if (value === undefined) {
348 value = true;
348 value = true;
349 }
349 }
350 if (this.dirty == value) {
350 if (this.dirty == value) {
351 return;
351 return;
352 }
352 }
353 this.events.trigger('set_dirty.Notebook', {value: value});
353 this.events.trigger('set_dirty.Notebook', {value: value});
354 };
354 };
355
355
356 /**
356 /**
357 * Scroll the top of the page to a given cell.
357 * Scroll the top of the page to a given cell.
358 *
358 *
359 * @param {integer} index - An index of the cell to view
359 * @param {integer} index - An index of the cell to view
360 * @param {integer} time - Animation time in milliseconds
360 * @param {integer} time - Animation time in milliseconds
361 * @return {integer} Pixel offset from the top of the container
361 * @return {integer} Pixel offset from the top of the container
362 */
362 */
363 Notebook.prototype.scroll_to_cell = function (index, time) {
363 Notebook.prototype.scroll_to_cell = function (index, time) {
364 var cells = this.get_cells();
364 var cells = this.get_cells();
365 time = time || 0;
365 time = time || 0;
366 index = Math.min(cells.length-1,index);
366 index = Math.min(cells.length-1,index);
367 index = Math.max(0 ,index);
367 index = Math.max(0 ,index);
368 var scroll_value = cells[index].element.position().top-cells[0].element.position().top ;
368 var scroll_value = cells[index].element.position().top-cells[0].element.position().top ;
369 this.element.animate({scrollTop:scroll_value}, time);
369 this.element.animate({scrollTop:scroll_value}, time);
370 return scroll_value;
370 return scroll_value;
371 };
371 };
372
372
373 /**
373 /**
374 * Scroll to the bottom of the page.
374 * Scroll to the bottom of the page.
375 */
375 */
376 Notebook.prototype.scroll_to_bottom = function () {
376 Notebook.prototype.scroll_to_bottom = function () {
377 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
377 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
378 };
378 };
379
379
380 /**
380 /**
381 * Scroll to the top of the page.
381 * Scroll to the top of the page.
382 */
382 */
383 Notebook.prototype.scroll_to_top = function () {
383 Notebook.prototype.scroll_to_top = function () {
384 this.element.animate({scrollTop:0}, 0);
384 this.element.animate({scrollTop:0}, 0);
385 };
385 };
386
386
387 // Edit Notebook metadata
387 // Edit Notebook metadata
388
388
389 /**
389 /**
390 * Display a dialog that allows the user to edit the Notebook's metadata.
390 * Display a dialog that allows the user to edit the Notebook's metadata.
391 */
391 */
392 Notebook.prototype.edit_metadata = function () {
392 Notebook.prototype.edit_metadata = function () {
393 var that = this;
393 var that = this;
394 dialog.edit_metadata({
394 dialog.edit_metadata({
395 md: this.metadata,
395 md: this.metadata,
396 callback: function (md) {
396 callback: function (md) {
397 that.metadata = md;
397 that.metadata = md;
398 },
398 },
399 name: 'Notebook',
399 name: 'Notebook',
400 notebook: this,
400 notebook: this,
401 keyboard_manager: this.keyboard_manager});
401 keyboard_manager: this.keyboard_manager});
402 };
402 };
403
403
404 // Cell indexing, retrieval, etc.
404 // Cell indexing, retrieval, etc.
405
405
406 /**
406 /**
407 * Get all cell elements in the notebook.
407 * Get all cell elements in the notebook.
408 *
408 *
409 * @return {jQuery} A selector of all cell elements
409 * @return {jQuery} A selector of all cell elements
410 */
410 */
411 Notebook.prototype.get_cell_elements = function () {
411 Notebook.prototype.get_cell_elements = function () {
412 return this.container.find(".cell").not('.cell .cell');
412 return this.container.find(".cell").not('.cell .cell');
413 };
413 };
414
414
415 /**
415 /**
416 * Get a particular cell element.
416 * Get a particular cell element.
417 *
417 *
418 * @param {integer} index An index of a cell to select
418 * @param {integer} index An index of a cell to select
419 * @return {jQuery} A selector of the given cell.
419 * @return {jQuery} A selector of the given cell.
420 */
420 */
421 Notebook.prototype.get_cell_element = function (index) {
421 Notebook.prototype.get_cell_element = function (index) {
422 var result = null;
422 var result = null;
423 var e = this.get_cell_elements().eq(index);
423 var e = this.get_cell_elements().eq(index);
424 if (e.length !== 0) {
424 if (e.length !== 0) {
425 result = e;
425 result = e;
426 }
426 }
427 return result;
427 return result;
428 };
428 };
429
429
430 /**
430 /**
431 * Try to get a particular cell by msg_id.
431 * Try to get a particular cell by msg_id.
432 *
432 *
433 * @param {string} msg_id A message UUID
433 * @param {string} msg_id A message UUID
434 * @return {Cell} Cell or null if no cell was found.
434 * @return {Cell} Cell or null if no cell was found.
435 */
435 */
436 Notebook.prototype.get_msg_cell = function (msg_id) {
436 Notebook.prototype.get_msg_cell = function (msg_id) {
437 return codecell.CodeCell.msg_cells[msg_id] || null;
437 return codecell.CodeCell.msg_cells[msg_id] || null;
438 };
438 };
439
439
440 /**
440 /**
441 * Count the cells in this notebook.
441 * Count the cells in this notebook.
442 *
442 *
443 * @return {integer} The number of cells in this notebook
443 * @return {integer} The number of cells in this notebook
444 */
444 */
445 Notebook.prototype.ncells = function () {
445 Notebook.prototype.ncells = function () {
446 return this.get_cell_elements().length;
446 return this.get_cell_elements().length;
447 };
447 };
448
448
449 /**
449 /**
450 * Get all Cell objects in this notebook.
450 * Get all Cell objects in this notebook.
451 *
451 *
452 * @return {Array} This notebook's Cell objects
452 * @return {Array} This notebook's Cell objects
453 */
453 */
454 Notebook.prototype.get_cells = function () {
454 Notebook.prototype.get_cells = function () {
455 // TODO: we are often calling cells as cells()[i], which we should optimize
455 // TODO: we are often calling cells as cells()[i], which we should optimize
456 // to cells(i) or a new method.
456 // to cells(i) or a new method.
457 return this.get_cell_elements().toArray().map(function (e) {
457 return this.get_cell_elements().toArray().map(function (e) {
458 return $(e).data("cell");
458 return $(e).data("cell");
459 });
459 });
460 };
460 };
461
461
462 /**
462 /**
463 * Get a Cell objects from this notebook.
463 * Get a Cell objects from this notebook.
464 *
464 *
465 * @param {integer} index - An index of a cell to retrieve
465 * @param {integer} index - An index of a cell to retrieve
466 * @return {Cell} Cell or null if no cell was found.
466 * @return {Cell} Cell or null if no cell was found.
467 */
467 */
468 Notebook.prototype.get_cell = function (index) {
468 Notebook.prototype.get_cell = function (index) {
469 var result = null;
469 var result = null;
470 var ce = this.get_cell_element(index);
470 var ce = this.get_cell_element(index);
471 if (ce !== null) {
471 if (ce !== null) {
472 result = ce.data('cell');
472 result = ce.data('cell');
473 }
473 }
474 return result;
474 return result;
475 };
475 };
476
476
477 /**
477 /**
478 * Get the cell below a given cell.
478 * Get the cell below a given cell.
479 *
479 *
480 * @param {Cell} cell
480 * @param {Cell} cell
481 * @return {Cell} the next cell or null if no cell was found.
481 * @return {Cell} the next cell or null if no cell was found.
482 */
482 */
483 Notebook.prototype.get_next_cell = function (cell) {
483 Notebook.prototype.get_next_cell = function (cell) {
484 var result = null;
484 var result = null;
485 var index = this.find_cell_index(cell);
485 var index = this.find_cell_index(cell);
486 if (this.is_valid_cell_index(index+1)) {
486 if (this.is_valid_cell_index(index+1)) {
487 result = this.get_cell(index+1);
487 result = this.get_cell(index+1);
488 }
488 }
489 return result;
489 return result;
490 };
490 };
491
491
492 /**
492 /**
493 * Get the cell above a given cell.
493 * Get the cell above a given cell.
494 *
494 *
495 * @param {Cell} cell
495 * @param {Cell} cell
496 * @return {Cell} The previous cell or null if no cell was found.
496 * @return {Cell} The previous cell or null if no cell was found.
497 */
497 */
498 Notebook.prototype.get_prev_cell = function (cell) {
498 Notebook.prototype.get_prev_cell = function (cell) {
499 var result = null;
499 var result = null;
500 var index = this.find_cell_index(cell);
500 var index = this.find_cell_index(cell);
501 if (index !== null && index > 0) {
501 if (index !== null && index > 0) {
502 result = this.get_cell(index-1);
502 result = this.get_cell(index-1);
503 }
503 }
504 return result;
504 return result;
505 };
505 };
506
506
507 /**
507 /**
508 * Get the numeric index of a given cell.
508 * Get the numeric index of a given cell.
509 *
509 *
510 * @param {Cell} cell
510 * @param {Cell} cell
511 * @return {integer} The cell's numeric index or null if no cell was found.
511 * @return {integer} The cell's numeric index or null if no cell was found.
512 */
512 */
513 Notebook.prototype.find_cell_index = function (cell) {
513 Notebook.prototype.find_cell_index = function (cell) {
514 var result = null;
514 var result = null;
515 this.get_cell_elements().filter(function (index) {
515 this.get_cell_elements().filter(function (index) {
516 if ($(this).data("cell") === cell) {
516 if ($(this).data("cell") === cell) {
517 result = index;
517 result = index;
518 }
518 }
519 });
519 });
520 return result;
520 return result;
521 };
521 };
522
522
523 /**
523 /**
524 * Return given index if defined, or the selected index if not.
524 * Return given index if defined, or the selected index if not.
525 *
525 *
526 * @param {integer} [index] - A cell's index
526 * @param {integer} [index] - A cell's index
527 * @return {integer} cell index
527 * @return {integer} cell index
528 */
528 */
529 Notebook.prototype.index_or_selected = function (index) {
529 Notebook.prototype.index_or_selected = function (index) {
530 var i;
530 var i;
531 if (index === undefined || index === null) {
531 if (index === undefined || index === null) {
532 i = this.get_selected_index();
532 i = this.get_selected_index();
533 if (i === null) {
533 if (i === null) {
534 i = 0;
534 i = 0;
535 }
535 }
536 } else {
536 } else {
537 i = index;
537 i = index;
538 }
538 }
539 return i;
539 return i;
540 };
540 };
541
541
542 /**
542 /**
543 * Get the currently selected cell.
543 * Get the currently selected cell.
544 *
544 *
545 * @return {Cell} The selected cell
545 * @return {Cell} The selected cell
546 */
546 */
547 Notebook.prototype.get_selected_cell = function () {
547 Notebook.prototype.get_selected_cell = function () {
548 var index = this.get_selected_index();
548 var index = this.get_selected_index();
549 return this.get_cell(index);
549 return this.get_cell(index);
550 };
550 };
551
551
552 /**
552 /**
553 * Check whether a cell index is valid.
553 * Check whether a cell index is valid.
554 *
554 *
555 * @param {integer} index - A cell index
555 * @param {integer} index - A cell index
556 * @return True if the index is valid, false otherwise
556 * @return True if the index is valid, false otherwise
557 */
557 */
558 Notebook.prototype.is_valid_cell_index = function (index) {
558 Notebook.prototype.is_valid_cell_index = function (index) {
559 if (index !== null && index >= 0 && index < this.ncells()) {
559 if (index !== null && index >= 0 && index < this.ncells()) {
560 return true;
560 return true;
561 } else {
561 } else {
562 return false;
562 return false;
563 }
563 }
564 };
564 };
565
565
566 /**
566 /**
567 * Get the index of the currently selected cell.
567 * Get the index of the currently selected cell.
568 *
568 *
569 * @return {integer} The selected cell's numeric index
569 * @return {integer} The selected cell's numeric index
570 */
570 */
571 Notebook.prototype.get_selected_index = function () {
571 Notebook.prototype.get_selected_index = function () {
572 var result = null;
572 var result = null;
573 this.get_cell_elements().filter(function (index) {
573 this.get_cell_elements().filter(function (index) {
574 if ($(this).data("cell").selected === true) {
574 if ($(this).data("cell").selected === true) {
575 result = index;
575 result = index;
576 }
576 }
577 });
577 });
578 return result;
578 return result;
579 };
579 };
580
580
581
581
582 // Cell selection.
582 // Cell selection.
583
583
584 /**
584 /**
585 * Programmatically select a cell.
585 * Programmatically select a cell.
586 *
586 *
587 * @param {integer} index - A cell's index
587 * @param {integer} index - A cell's index
588 * @return {Notebook} This notebook
588 * @return {Notebook} This notebook
589 */
589 */
590 Notebook.prototype.select = function (index) {
590 Notebook.prototype.select = function (index) {
591 if (this.is_valid_cell_index(index)) {
591 if (this.is_valid_cell_index(index)) {
592 var sindex = this.get_selected_index();
592 var sindex = this.get_selected_index();
593 if (sindex !== null && index !== sindex) {
593 if (sindex !== null && index !== sindex) {
594 // If we are about to select a different cell, make sure we are
594 // If we are about to select a different cell, make sure we are
595 // first in command mode.
595 // first in command mode.
596 if (this.mode !== 'command') {
596 if (this.mode !== 'command') {
597 this.command_mode();
597 this.command_mode();
598 }
598 }
599 this.get_cell(sindex).unselect();
599 this.get_cell(sindex).unselect();
600 }
600 }
601 var cell = this.get_cell(index);
601 var cell = this.get_cell(index);
602 cell.select();
602 cell.select();
603 if (cell.cell_type === 'heading') {
603 if (cell.cell_type === 'heading') {
604 this.events.trigger('selected_cell_type_changed.Notebook',
604 this.events.trigger('selected_cell_type_changed.Notebook',
605 {'cell_type':cell.cell_type,level:cell.level}
605 {'cell_type':cell.cell_type,level:cell.level}
606 );
606 );
607 } else {
607 } else {
608 this.events.trigger('selected_cell_type_changed.Notebook',
608 this.events.trigger('selected_cell_type_changed.Notebook',
609 {'cell_type':cell.cell_type}
609 {'cell_type':cell.cell_type}
610 );
610 );
611 }
611 }
612 }
612 }
613 return this;
613 return this;
614 };
614 };
615
615
616 /**
616 /**
617 * Programmatically select the next cell.
617 * Programmatically select the next cell.
618 *
618 *
619 * @return {Notebook} This notebook
619 * @return {Notebook} This notebook
620 */
620 */
621 Notebook.prototype.select_next = function () {
621 Notebook.prototype.select_next = function () {
622 var index = this.get_selected_index();
622 var index = this.get_selected_index();
623 this.select(index+1);
623 this.select(index+1);
624 return this;
624 return this;
625 };
625 };
626
626
627 /**
627 /**
628 * Programmatically select the previous cell.
628 * Programmatically select the previous cell.
629 *
629 *
630 * @return {Notebook} This notebook
630 * @return {Notebook} This notebook
631 */
631 */
632 Notebook.prototype.select_prev = function () {
632 Notebook.prototype.select_prev = function () {
633 var index = this.get_selected_index();
633 var index = this.get_selected_index();
634 this.select(index-1);
634 this.select(index-1);
635 return this;
635 return this;
636 };
636 };
637
637
638
638
639 // Edit/Command mode
639 // Edit/Command mode
640
640
641 /**
641 /**
642 * Gets the index of the cell that is in edit mode.
642 * Gets the index of the cell that is in edit mode.
643 *
643 *
644 * @return {integer} index
644 * @return {integer} index
645 */
645 */
646 Notebook.prototype.get_edit_index = function () {
646 Notebook.prototype.get_edit_index = function () {
647 var result = null;
647 var result = null;
648 this.get_cell_elements().filter(function (index) {
648 this.get_cell_elements().filter(function (index) {
649 if ($(this).data("cell").mode === 'edit') {
649 if ($(this).data("cell").mode === 'edit') {
650 result = index;
650 result = index;
651 }
651 }
652 });
652 });
653 return result;
653 return result;
654 };
654 };
655
655
656 /**
656 /**
657 * Handle when a a cell blurs and the notebook should enter command mode.
657 * Handle when a a cell blurs and the notebook should enter command mode.
658 *
658 *
659 * @param {Cell} [cell] - Cell to enter command mode on.
659 * @param {Cell} [cell] - Cell to enter command mode on.
660 */
660 */
661 Notebook.prototype.handle_command_mode = function (cell) {
661 Notebook.prototype.handle_command_mode = function (cell) {
662 if (this.mode !== 'command') {
662 if (this.mode !== 'command') {
663 cell.command_mode();
663 cell.command_mode();
664 this.mode = 'command';
664 this.mode = 'command';
665 this.events.trigger('command_mode.Notebook');
665 this.events.trigger('command_mode.Notebook');
666 this.keyboard_manager.command_mode();
666 this.keyboard_manager.command_mode();
667 }
667 }
668 };
668 };
669
669
670 /**
670 /**
671 * Make the notebook enter command mode.
671 * Make the notebook enter command mode.
672 */
672 */
673 Notebook.prototype.command_mode = function () {
673 Notebook.prototype.command_mode = function () {
674 var cell = this.get_cell(this.get_edit_index());
674 var cell = this.get_cell(this.get_edit_index());
675 if (cell && this.mode !== 'command') {
675 if (cell && this.mode !== 'command') {
676 // We don't call cell.command_mode, but rather call cell.focus_cell()
676 // We don't call cell.command_mode, but rather call cell.focus_cell()
677 // which will blur and CM editor and trigger the call to
677 // which will blur and CM editor and trigger the call to
678 // handle_command_mode.
678 // handle_command_mode.
679 cell.focus_cell();
679 cell.focus_cell();
680 }
680 }
681 };
681 };
682
682
683 /**
683 /**
684 * Handle when a cell fires it's edit_mode event.
684 * Handle when a cell fires it's edit_mode event.
685 *
685 *
686 * @param {Cell} [cell] Cell to enter edit mode on.
686 * @param {Cell} [cell] Cell to enter edit mode on.
687 */
687 */
688 Notebook.prototype.handle_edit_mode = function (cell) {
688 Notebook.prototype.handle_edit_mode = function (cell) {
689 if (cell && this.mode !== 'edit') {
689 if (cell && this.mode !== 'edit') {
690 cell.edit_mode();
690 cell.edit_mode();
691 this.mode = 'edit';
691 this.mode = 'edit';
692 this.events.trigger('edit_mode.Notebook');
692 this.events.trigger('edit_mode.Notebook');
693 this.keyboard_manager.edit_mode();
693 this.keyboard_manager.edit_mode();
694 }
694 }
695 };
695 };
696
696
697 /**
697 /**
698 * Make a cell enter edit mode.
698 * Make a cell enter edit mode.
699 */
699 */
700 Notebook.prototype.edit_mode = function () {
700 Notebook.prototype.edit_mode = function () {
701 var cell = this.get_selected_cell();
701 var cell = this.get_selected_cell();
702 if (cell && this.mode !== 'edit') {
702 if (cell && this.mode !== 'edit') {
703 cell.unrender();
703 cell.unrender();
704 cell.focus_editor();
704 cell.focus_editor();
705 }
705 }
706 };
706 };
707
707
708 /**
708 /**
709 * Focus the currently selected cell.
709 * Focus the currently selected cell.
710 */
710 */
711 Notebook.prototype.focus_cell = function () {
711 Notebook.prototype.focus_cell = function () {
712 var cell = this.get_selected_cell();
712 var cell = this.get_selected_cell();
713 if (cell === null) {return;} // No cell is selected
713 if (cell === null) {return;} // No cell is selected
714 cell.focus_cell();
714 cell.focus_cell();
715 };
715 };
716
716
717 // Cell movement
717 // Cell movement
718
718
719 /**
719 /**
720 * Move given (or selected) cell up and select it.
720 * Move given (or selected) cell up and select it.
721 *
721 *
722 * @param {integer} [index] - cell index
722 * @param {integer} [index] - cell index
723 * @return {Notebook} This notebook
723 * @return {Notebook} This notebook
724 */
724 */
725 Notebook.prototype.move_cell_up = function (index) {
725 Notebook.prototype.move_cell_up = function (index) {
726 var i = this.index_or_selected(index);
726 var i = this.index_or_selected(index);
727 if (this.is_valid_cell_index(i) && i > 0) {
727 if (this.is_valid_cell_index(i) && i > 0) {
728 var pivot = this.get_cell_element(i-1);
728 var pivot = this.get_cell_element(i-1);
729 var tomove = this.get_cell_element(i);
729 var tomove = this.get_cell_element(i);
730 if (pivot !== null && tomove !== null) {
730 if (pivot !== null && tomove !== null) {
731 tomove.detach();
731 tomove.detach();
732 pivot.before(tomove);
732 pivot.before(tomove);
733 this.select(i-1);
733 this.select(i-1);
734 var cell = this.get_selected_cell();
734 var cell = this.get_selected_cell();
735 cell.focus_cell();
735 cell.focus_cell();
736 }
736 }
737 this.set_dirty(true);
737 this.set_dirty(true);
738 }
738 }
739 return this;
739 return this;
740 };
740 };
741
741
742
742
743 /**
743 /**
744 * Move given (or selected) cell down and select it.
744 * Move given (or selected) cell down and select it.
745 *
745 *
746 * @param {integer} [index] - cell index
746 * @param {integer} [index] - cell index
747 * @return {Notebook} This notebook
747 * @return {Notebook} This notebook
748 */
748 */
749 Notebook.prototype.move_cell_down = function (index) {
749 Notebook.prototype.move_cell_down = function (index) {
750 var i = this.index_or_selected(index);
750 var i = this.index_or_selected(index);
751 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
751 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
752 var pivot = this.get_cell_element(i+1);
752 var pivot = this.get_cell_element(i+1);
753 var tomove = this.get_cell_element(i);
753 var tomove = this.get_cell_element(i);
754 if (pivot !== null && tomove !== null) {
754 if (pivot !== null && tomove !== null) {
755 tomove.detach();
755 tomove.detach();
756 pivot.after(tomove);
756 pivot.after(tomove);
757 this.select(i+1);
757 this.select(i+1);
758 var cell = this.get_selected_cell();
758 var cell = this.get_selected_cell();
759 cell.focus_cell();
759 cell.focus_cell();
760 }
760 }
761 }
761 }
762 this.set_dirty();
762 this.set_dirty();
763 return this;
763 return this;
764 };
764 };
765
765
766
766
767 // Insertion, deletion.
767 // Insertion, deletion.
768
768
769 /**
769 /**
770 * Delete a cell from the notebook.
770 * Delete a cell from the notebook.
771 *
771 *
772 * @param {integer} [index] - cell's numeric index
772 * @param {integer} [index] - cell's numeric index
773 * @return {Notebook} This notebook
773 * @return {Notebook} This notebook
774 */
774 */
775 Notebook.prototype.delete_cell = function (index) {
775 Notebook.prototype.delete_cell = function (index) {
776 var i = this.index_or_selected(index);
776 var i = this.index_or_selected(index);
777 var cell = this.get_cell(i);
777 var cell = this.get_cell(i);
778 if (!cell.is_deletable()) {
778 if (!cell.is_deletable()) {
779 return this;
779 return this;
780 }
780 }
781
781
782 this.undelete_backup = cell.toJSON();
782 this.undelete_backup = cell.toJSON();
783 $('#undelete_cell').removeClass('disabled');
783 $('#undelete_cell').removeClass('disabled');
784 if (this.is_valid_cell_index(i)) {
784 if (this.is_valid_cell_index(i)) {
785 var old_ncells = this.ncells();
785 var old_ncells = this.ncells();
786 var ce = this.get_cell_element(i);
786 var ce = this.get_cell_element(i);
787 ce.remove();
787 ce.remove();
788 if (i === 0) {
788 if (i === 0) {
789 // Always make sure we have at least one cell.
789 // Always make sure we have at least one cell.
790 if (old_ncells === 1) {
790 if (old_ncells === 1) {
791 this.insert_cell_below('code');
791 this.insert_cell_below('code');
792 }
792 }
793 this.select(0);
793 this.select(0);
794 this.undelete_index = 0;
794 this.undelete_index = 0;
795 this.undelete_below = false;
795 this.undelete_below = false;
796 } else if (i === old_ncells-1 && i !== 0) {
796 } else if (i === old_ncells-1 && i !== 0) {
797 this.select(i-1);
797 this.select(i-1);
798 this.undelete_index = i - 1;
798 this.undelete_index = i - 1;
799 this.undelete_below = true;
799 this.undelete_below = true;
800 } else {
800 } else {
801 this.select(i);
801 this.select(i);
802 this.undelete_index = i;
802 this.undelete_index = i;
803 this.undelete_below = false;
803 this.undelete_below = false;
804 }
804 }
805 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
805 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
806 this.set_dirty(true);
806 this.set_dirty(true);
807 }
807 }
808 return this;
808 return this;
809 };
809 };
810
810
811 /**
811 /**
812 * Restore the most recently deleted cell.
812 * Restore the most recently deleted cell.
813 */
813 */
814 Notebook.prototype.undelete_cell = function() {
814 Notebook.prototype.undelete_cell = function() {
815 if (this.undelete_backup !== null && this.undelete_index !== null) {
815 if (this.undelete_backup !== null && this.undelete_index !== null) {
816 var current_index = this.get_selected_index();
816 var current_index = this.get_selected_index();
817 if (this.undelete_index < current_index) {
817 if (this.undelete_index < current_index) {
818 current_index = current_index + 1;
818 current_index = current_index + 1;
819 }
819 }
820 if (this.undelete_index >= this.ncells()) {
820 if (this.undelete_index >= this.ncells()) {
821 this.select(this.ncells() - 1);
821 this.select(this.ncells() - 1);
822 }
822 }
823 else {
823 else {
824 this.select(this.undelete_index);
824 this.select(this.undelete_index);
825 }
825 }
826 var cell_data = this.undelete_backup;
826 var cell_data = this.undelete_backup;
827 var new_cell = null;
827 var new_cell = null;
828 if (this.undelete_below) {
828 if (this.undelete_below) {
829 new_cell = this.insert_cell_below(cell_data.cell_type);
829 new_cell = this.insert_cell_below(cell_data.cell_type);
830 } else {
830 } else {
831 new_cell = this.insert_cell_above(cell_data.cell_type);
831 new_cell = this.insert_cell_above(cell_data.cell_type);
832 }
832 }
833 new_cell.fromJSON(cell_data);
833 new_cell.fromJSON(cell_data);
834 if (this.undelete_below) {
834 if (this.undelete_below) {
835 this.select(current_index+1);
835 this.select(current_index+1);
836 } else {
836 } else {
837 this.select(current_index);
837 this.select(current_index);
838 }
838 }
839 this.undelete_backup = null;
839 this.undelete_backup = null;
840 this.undelete_index = null;
840 this.undelete_index = null;
841 }
841 }
842 $('#undelete_cell').addClass('disabled');
842 $('#undelete_cell').addClass('disabled');
843 };
843 };
844
844
845 /**
845 /**
846 * Insert a cell so that after insertion the cell is at given index.
846 * Insert a cell so that after insertion the cell is at given index.
847 *
847 *
848 * If cell type is not provided, it will default to the type of the
848 * If cell type is not provided, it will default to the type of the
849 * currently active cell.
849 * currently active cell.
850 *
850 *
851 * Similar to insert_above, but index parameter is mandatory.
851 * Similar to insert_above, but index parameter is mandatory.
852 *
852 *
853 * Index will be brought back into the accessible range [0,n].
853 * Index will be brought back into the accessible range [0,n].
854 *
854 *
855 * @param {string} [type] - in ['code','markdown', 'raw'], defaults to 'code'
855 * @param {string} [type] - in ['code','markdown', 'raw'], defaults to 'code'
856 * @param {integer} [index] - a valid index where to insert cell
856 * @param {integer} [index] - a valid index where to insert cell
857 * @return {Cell|null} created cell or null
857 * @return {Cell|null} created cell or null
858 */
858 */
859 Notebook.prototype.insert_cell_at_index = function(type, index){
859 Notebook.prototype.insert_cell_at_index = function(type, index){
860
860
861 var ncells = this.ncells();
861 var ncells = this.ncells();
862 index = Math.min(index, ncells);
862 index = Math.min(index, ncells);
863 index = Math.max(index, 0);
863 index = Math.max(index, 0);
864 var cell = null;
864 var cell = null;
865 type = type || this.class_config.get_sync('default_cell_type');
865 type = type || this.class_config.get_sync('default_cell_type');
866 if (type === 'above') {
866 if (type === 'above') {
867 if (index > 0) {
867 if (index > 0) {
868 type = this.get_cell(index-1).cell_type;
868 type = this.get_cell(index-1).cell_type;
869 } else {
869 } else {
870 type = 'code';
870 type = 'code';
871 }
871 }
872 } else if (type === 'below') {
872 } else if (type === 'below') {
873 if (index < ncells) {
873 if (index < ncells) {
874 type = this.get_cell(index).cell_type;
874 type = this.get_cell(index).cell_type;
875 } else {
875 } else {
876 type = 'code';
876 type = 'code';
877 }
877 }
878 } else if (type === 'selected') {
878 } else if (type === 'selected') {
879 type = this.get_selected_cell().cell_type;
879 type = this.get_selected_cell().cell_type;
880 }
880 }
881
881
882 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
882 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
883 var cell_options = {
883 var cell_options = {
884 events: this.events,
884 events: this.events,
885 config: this.config,
885 config: this.config,
886 keyboard_manager: this.keyboard_manager,
886 keyboard_manager: this.keyboard_manager,
887 notebook: this,
887 notebook: this,
888 tooltip: this.tooltip
888 tooltip: this.tooltip
889 };
889 };
890 switch(type) {
890 switch(type) {
891 case 'code':
891 case 'code':
892 cell = new codecell.CodeCell(this.kernel, cell_options);
892 cell = new codecell.CodeCell(this.kernel, cell_options);
893 cell.set_input_prompt();
893 cell.set_input_prompt();
894 break;
894 break;
895 case 'markdown':
895 case 'markdown':
896 cell = new textcell.MarkdownCell(cell_options);
896 cell = new textcell.MarkdownCell(cell_options);
897 break;
897 break;
898 case 'raw':
898 case 'raw':
899 cell = new textcell.RawCell(cell_options);
899 cell = new textcell.RawCell(cell_options);
900 break;
900 break;
901 default:
901 default:
902 console.log("Unrecognized cell type: ", type, cellmod);
902 console.log("Unrecognized cell type: ", type, cellmod);
903 cell = new cellmod.UnrecognizedCell(cell_options);
903 cell = new cellmod.UnrecognizedCell(cell_options);
904 }
904 }
905
905
906 if(this._insert_element_at_index(cell.element,index)) {
906 if(this._insert_element_at_index(cell.element,index)) {
907 cell.render();
907 cell.render();
908 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
908 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
909 cell.refresh();
909 cell.refresh();
910 // We used to select the cell after we refresh it, but there
910 // We used to select the cell after we refresh it, but there
911 // are now cases were this method is called where select is
911 // are now cases were this method is called where select is
912 // not appropriate. The selection logic should be handled by the
912 // not appropriate. The selection logic should be handled by the
913 // caller of the the top level insert_cell methods.
913 // caller of the the top level insert_cell methods.
914 this.set_dirty(true);
914 this.set_dirty(true);
915 }
915 }
916 }
916 }
917 return cell;
917 return cell;
918
918
919 };
919 };
920
920
921 /**
921 /**
922 * Insert an element at given cell index.
922 * Insert an element at given cell index.
923 *
923 *
924 * @param {HTMLElement} element - a cell element
924 * @param {HTMLElement} element - a cell element
925 * @param {integer} [index] - a valid index where to inser cell
925 * @param {integer} [index] - a valid index where to inser cell
926 * @returns {boolean} success
926 * @returns {boolean} success
927 */
927 */
928 Notebook.prototype._insert_element_at_index = function(element, index){
928 Notebook.prototype._insert_element_at_index = function(element, index){
929 if (element === undefined){
929 if (element === undefined){
930 return false;
930 return false;
931 }
931 }
932
932
933 var ncells = this.ncells();
933 var ncells = this.ncells();
934
934
935 if (ncells === 0) {
935 if (ncells === 0) {
936 // special case append if empty
936 // special case append if empty
937 this.element.find('div.end_space').before(element);
937 this.element.find('div.end_space').before(element);
938 } else if ( ncells === index ) {
938 } else if ( ncells === index ) {
939 // special case append it the end, but not empty
939 // special case append it the end, but not empty
940 this.get_cell_element(index-1).after(element);
940 this.get_cell_element(index-1).after(element);
941 } else if (this.is_valid_cell_index(index)) {
941 } else if (this.is_valid_cell_index(index)) {
942 // otherwise always somewhere to append to
942 // otherwise always somewhere to append to
943 this.get_cell_element(index).before(element);
943 this.get_cell_element(index).before(element);
944 } else {
944 } else {
945 return false;
945 return false;
946 }
946 }
947
947
948 if (this.undelete_index !== null && index <= this.undelete_index) {
948 if (this.undelete_index !== null && index <= this.undelete_index) {
949 this.undelete_index = this.undelete_index + 1;
949 this.undelete_index = this.undelete_index + 1;
950 this.set_dirty(true);
950 this.set_dirty(true);
951 }
951 }
952 return true;
952 return true;
953 };
953 };
954
954
955 /**
955 /**
956 * Insert a cell of given type above given index, or at top
956 * Insert a cell of given type above given index, or at top
957 * of notebook if index smaller than 0.
957 * of notebook if index smaller than 0.
958 *
958 *
959 * @param {string} [type] - cell type
959 * @param {string} [type] - cell type
960 * @param {integer} [index] - defaults to the currently selected cell
960 * @param {integer} [index] - defaults to the currently selected cell
961 * @return {Cell|null} handle to created cell or null
961 * @return {Cell|null} handle to created cell or null
962 */
962 */
963 Notebook.prototype.insert_cell_above = function (type, index) {
963 Notebook.prototype.insert_cell_above = function (type, index) {
964 index = this.index_or_selected(index);
964 index = this.index_or_selected(index);
965 return this.insert_cell_at_index(type, index);
965 return this.insert_cell_at_index(type, index);
966 };
966 };
967
967
968 /**
968 /**
969 * Insert a cell of given type below given index, or at bottom
969 * Insert a cell of given type below given index, or at bottom
970 * of notebook if index greater than number of cells
970 * of notebook if index greater than number of cells
971 *
971 *
972 * @param {string} [type] - cell type
972 * @param {string} [type] - cell type
973 * @param {integer} [index] - defaults to the currently selected cell
973 * @param {integer} [index] - defaults to the currently selected cell
974 * @return {Cell|null} handle to created cell or null
974 * @return {Cell|null} handle to created cell or null
975 */
975 */
976 Notebook.prototype.insert_cell_below = function (type, index) {
976 Notebook.prototype.insert_cell_below = function (type, index) {
977 index = this.index_or_selected(index);
977 index = this.index_or_selected(index);
978 return this.insert_cell_at_index(type, index+1);
978 return this.insert_cell_at_index(type, index+1);
979 };
979 };
980
980
981
981
982 /**
982 /**
983 * Insert cell at end of notebook
983 * Insert cell at end of notebook
984 *
984 *
985 * @param {string} type - cell type
985 * @param {string} type - cell type
986 * @return {Cell|null} handle to created cell or null
986 * @return {Cell|null} handle to created cell or null
987 */
987 */
988 Notebook.prototype.insert_cell_at_bottom = function (type){
988 Notebook.prototype.insert_cell_at_bottom = function (type){
989 var len = this.ncells();
989 var len = this.ncells();
990 return this.insert_cell_below(type,len-1);
990 return this.insert_cell_below(type,len-1);
991 };
991 };
992
992
993 /**
993 /**
994 * Turn a cell into a code cell.
994 * Turn a cell into a code cell.
995 *
995 *
996 * @param {integer} [index] - cell index
996 * @param {integer} [index] - cell index
997 */
997 */
998 Notebook.prototype.to_code = function (index) {
998 Notebook.prototype.to_code = function (index) {
999 var i = this.index_or_selected(index);
999 var i = this.index_or_selected(index);
1000 if (this.is_valid_cell_index(i)) {
1000 if (this.is_valid_cell_index(i)) {
1001 var source_cell = this.get_cell(i);
1001 var source_cell = this.get_cell(i);
1002 if (!(source_cell instanceof codecell.CodeCell)) {
1002 if (!(source_cell instanceof codecell.CodeCell)) {
1003 var target_cell = this.insert_cell_below('code',i);
1003 var target_cell = this.insert_cell_below('code',i);
1004 var text = source_cell.get_text();
1004 var text = source_cell.get_text();
1005 if (text === source_cell.placeholder) {
1005 if (text === source_cell.placeholder) {
1006 text = '';
1006 text = '';
1007 }
1007 }
1008 //metadata
1008 //metadata
1009 target_cell.metadata = source_cell.metadata;
1009 target_cell.metadata = source_cell.metadata;
1010
1010
1011 target_cell.set_text(text);
1011 target_cell.set_text(text);
1012 // make this value the starting point, so that we can only undo
1012 // make this value the starting point, so that we can only undo
1013 // to this state, instead of a blank cell
1013 // to this state, instead of a blank cell
1014 target_cell.code_mirror.clearHistory();
1014 target_cell.code_mirror.clearHistory();
1015 source_cell.element.remove();
1015 source_cell.element.remove();
1016 this.select(i);
1016 this.select(i);
1017 var cursor = source_cell.code_mirror.getCursor();
1017 var cursor = source_cell.code_mirror.getCursor();
1018 target_cell.code_mirror.setCursor(cursor);
1018 target_cell.code_mirror.setCursor(cursor);
1019 this.set_dirty(true);
1019 this.set_dirty(true);
1020 }
1020 }
1021 }
1021 }
1022 };
1022 };
1023
1023
1024 /**
1024 /**
1025 * Turn a cell into a Markdown cell.
1025 * Turn a cell into a Markdown cell.
1026 *
1026 *
1027 * @param {integer} [index] - cell index
1027 * @param {integer} [index] - cell index
1028 */
1028 */
1029 Notebook.prototype.to_markdown = function (index) {
1029 Notebook.prototype.to_markdown = function (index) {
1030 var i = this.index_or_selected(index);
1030 var i = this.index_or_selected(index);
1031 if (this.is_valid_cell_index(i)) {
1031 if (this.is_valid_cell_index(i)) {
1032 var source_cell = this.get_cell(i);
1032 var source_cell = this.get_cell(i);
1033
1033
1034 if (!(source_cell instanceof textcell.MarkdownCell)) {
1034 if (!(source_cell instanceof textcell.MarkdownCell)) {
1035 var target_cell = this.insert_cell_below('markdown',i);
1035 var target_cell = this.insert_cell_below('markdown',i);
1036 var text = source_cell.get_text();
1036 var text = source_cell.get_text();
1037
1037
1038 if (text === source_cell.placeholder) {
1038 if (text === source_cell.placeholder) {
1039 text = '';
1039 text = '';
1040 }
1040 }
1041 // metadata
1041 // metadata
1042 target_cell.metadata = source_cell.metadata;
1042 target_cell.metadata = source_cell.metadata;
1043 // We must show the editor before setting its contents
1043 // We must show the editor before setting its contents
1044 target_cell.unrender();
1044 target_cell.unrender();
1045 target_cell.set_text(text);
1045 target_cell.set_text(text);
1046 // make this value the starting point, so that we can only undo
1046 // make this value the starting point, so that we can only undo
1047 // to this state, instead of a blank cell
1047 // to this state, instead of a blank cell
1048 target_cell.code_mirror.clearHistory();
1048 target_cell.code_mirror.clearHistory();
1049 source_cell.element.remove();
1049 source_cell.element.remove();
1050 this.select(i);
1050 this.select(i);
1051 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1051 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1052 target_cell.render();
1052 target_cell.render();
1053 }
1053 }
1054 var cursor = source_cell.code_mirror.getCursor();
1054 var cursor = source_cell.code_mirror.getCursor();
1055 target_cell.code_mirror.setCursor(cursor);
1055 target_cell.code_mirror.setCursor(cursor);
1056 this.set_dirty(true);
1056 this.set_dirty(true);
1057 }
1057 }
1058 }
1058 }
1059 };
1059 };
1060
1060
1061 /**
1061 /**
1062 * Turn a cell into a raw text cell.
1062 * Turn a cell into a raw text cell.
1063 *
1063 *
1064 * @param {integer} [index] - cell index
1064 * @param {integer} [index] - cell index
1065 */
1065 */
1066 Notebook.prototype.to_raw = function (index) {
1066 Notebook.prototype.to_raw = function (index) {
1067 var i = this.index_or_selected(index);
1067 var i = this.index_or_selected(index);
1068 if (this.is_valid_cell_index(i)) {
1068 if (this.is_valid_cell_index(i)) {
1069 var target_cell = null;
1069 var target_cell = null;
1070 var source_cell = this.get_cell(i);
1070 var source_cell = this.get_cell(i);
1071
1071
1072 if (!(source_cell instanceof textcell.RawCell)) {
1072 if (!(source_cell instanceof textcell.RawCell)) {
1073 target_cell = this.insert_cell_below('raw',i);
1073 target_cell = this.insert_cell_below('raw',i);
1074 var text = source_cell.get_text();
1074 var text = source_cell.get_text();
1075 if (text === source_cell.placeholder) {
1075 if (text === source_cell.placeholder) {
1076 text = '';
1076 text = '';
1077 }
1077 }
1078 //metadata
1078 //metadata
1079 target_cell.metadata = source_cell.metadata;
1079 target_cell.metadata = source_cell.metadata;
1080 // We must show the editor before setting its contents
1080 // We must show the editor before setting its contents
1081 target_cell.unrender();
1081 target_cell.unrender();
1082 target_cell.set_text(text);
1082 target_cell.set_text(text);
1083 // make this value the starting point, so that we can only undo
1083 // make this value the starting point, so that we can only undo
1084 // to this state, instead of a blank cell
1084 // to this state, instead of a blank cell
1085 target_cell.code_mirror.clearHistory();
1085 target_cell.code_mirror.clearHistory();
1086 source_cell.element.remove();
1086 source_cell.element.remove();
1087 this.select(i);
1087 this.select(i);
1088 var cursor = source_cell.code_mirror.getCursor();
1088 var cursor = source_cell.code_mirror.getCursor();
1089 target_cell.code_mirror.setCursor(cursor);
1089 target_cell.code_mirror.setCursor(cursor);
1090 this.set_dirty(true);
1090 this.set_dirty(true);
1091 }
1091 }
1092 }
1092 }
1093 };
1093 };
1094
1094
1095 /**
1095 /**
1096 * Warn about heading cell support removal.
1096 * Warn about heading cell support removal.
1097 */
1097 */
1098 Notebook.prototype._warn_heading = function () {
1098 Notebook.prototype._warn_heading = function () {
1099 dialog.modal({
1099 dialog.modal({
1100 notebook: this,
1100 notebook: this,
1101 keyboard_manager: this.keyboard_manager,
1101 keyboard_manager: this.keyboard_manager,
1102 title : "Use markdown headings",
1102 title : "Use markdown headings",
1103 body : $("<p/>").text(
1103 body : $("<p/>").text(
1104 'IPython no longer uses special heading cells. ' +
1104 'IPython no longer uses special heading cells. ' +
1105 'Instead, write your headings in Markdown cells using # characters:'
1105 'Instead, write your headings in Markdown cells using # characters:'
1106 ).append($('<pre/>').text(
1106 ).append($('<pre/>').text(
1107 '## This is a level 2 heading'
1107 '## This is a level 2 heading'
1108 )),
1108 )),
1109 buttons : {
1109 buttons : {
1110 "OK" : {}
1110 "OK" : {}
1111 }
1111 }
1112 });
1112 });
1113 };
1113 };
1114
1114
1115 /**
1115 /**
1116 * Turn a cell into a heading containing markdown cell.
1116 * Turn a cell into a heading containing markdown cell.
1117 *
1117 *
1118 * @param {integer} [index] - cell index
1118 * @param {integer} [index] - cell index
1119 * @param {integer} [level] - heading level (e.g., 1 for h1)
1119 * @param {integer} [level] - heading level (e.g., 1 for h1)
1120 */
1120 */
1121 Notebook.prototype.to_heading = function (index, level) {
1121 Notebook.prototype.to_heading = function (index, level) {
1122 this.to_markdown(index);
1122 this.to_markdown(index);
1123 level = level || 1;
1123 level = level || 1;
1124 var i = this.index_or_selected(index);
1124 var i = this.index_or_selected(index);
1125 if (this.is_valid_cell_index(i)) {
1125 if (this.is_valid_cell_index(i)) {
1126 var cell = this.get_cell(i);
1126 var cell = this.get_cell(i);
1127 cell.set_heading_level(level);
1127 cell.set_heading_level(level);
1128 this.set_dirty(true);
1128 this.set_dirty(true);
1129 }
1129 }
1130 };
1130 };
1131
1131
1132
1132
1133 // Cut/Copy/Paste
1133 // Cut/Copy/Paste
1134
1134
1135 /**
1135 /**
1136 * Enable the UI elements for pasting cells.
1136 * Enable the UI elements for pasting cells.
1137 */
1137 */
1138 Notebook.prototype.enable_paste = function () {
1138 Notebook.prototype.enable_paste = function () {
1139 var that = this;
1139 var that = this;
1140 if (!this.paste_enabled) {
1140 if (!this.paste_enabled) {
1141 $('#paste_cell_replace').removeClass('disabled')
1141 $('#paste_cell_replace').removeClass('disabled')
1142 .on('click', function () {that.paste_cell_replace();});
1142 .on('click', function () {that.paste_cell_replace();});
1143 $('#paste_cell_above').removeClass('disabled')
1143 $('#paste_cell_above').removeClass('disabled')
1144 .on('click', function () {that.paste_cell_above();});
1144 .on('click', function () {that.paste_cell_above();});
1145 $('#paste_cell_below').removeClass('disabled')
1145 $('#paste_cell_below').removeClass('disabled')
1146 .on('click', function () {that.paste_cell_below();});
1146 .on('click', function () {that.paste_cell_below();});
1147 this.paste_enabled = true;
1147 this.paste_enabled = true;
1148 }
1148 }
1149 };
1149 };
1150
1150
1151 /**
1151 /**
1152 * Disable the UI elements for pasting cells.
1152 * Disable the UI elements for pasting cells.
1153 */
1153 */
1154 Notebook.prototype.disable_paste = function () {
1154 Notebook.prototype.disable_paste = function () {
1155 if (this.paste_enabled) {
1155 if (this.paste_enabled) {
1156 $('#paste_cell_replace').addClass('disabled').off('click');
1156 $('#paste_cell_replace').addClass('disabled').off('click');
1157 $('#paste_cell_above').addClass('disabled').off('click');
1157 $('#paste_cell_above').addClass('disabled').off('click');
1158 $('#paste_cell_below').addClass('disabled').off('click');
1158 $('#paste_cell_below').addClass('disabled').off('click');
1159 this.paste_enabled = false;
1159 this.paste_enabled = false;
1160 }
1160 }
1161 };
1161 };
1162
1162
1163 /**
1163 /**
1164 * Cut a cell.
1164 * Cut a cell.
1165 */
1165 */
1166 Notebook.prototype.cut_cell = function () {
1166 Notebook.prototype.cut_cell = function () {
1167 this.copy_cell();
1167 this.copy_cell();
1168 this.delete_cell();
1168 this.delete_cell();
1169 };
1169 };
1170
1170
1171 /**
1171 /**
1172 * Copy a cell.
1172 * Copy a cell.
1173 */
1173 */
1174 Notebook.prototype.copy_cell = function () {
1174 Notebook.prototype.copy_cell = function () {
1175 var cell = this.get_selected_cell();
1175 var cell = this.get_selected_cell();
1176 this.clipboard = cell.toJSON();
1176 this.clipboard = cell.toJSON();
1177 // remove undeletable status from the copied cell
1177 // remove undeletable status from the copied cell
1178 if (this.clipboard.metadata.deletable !== undefined) {
1178 if (this.clipboard.metadata.deletable !== undefined) {
1179 delete this.clipboard.metadata.deletable;
1179 delete this.clipboard.metadata.deletable;
1180 }
1180 }
1181 this.enable_paste();
1181 this.enable_paste();
1182 };
1182 };
1183
1183
1184 /**
1184 /**
1185 * Replace the selected cell with the cell in the clipboard.
1185 * Replace the selected cell with the cell in the clipboard.
1186 */
1186 */
1187 Notebook.prototype.paste_cell_replace = function () {
1187 Notebook.prototype.paste_cell_replace = function () {
1188 if (this.clipboard !== null && this.paste_enabled) {
1188 if (this.clipboard !== null && this.paste_enabled) {
1189 var cell_data = this.clipboard;
1189 var cell_data = this.clipboard;
1190 var new_cell = this.insert_cell_above(cell_data.cell_type);
1190 var new_cell = this.insert_cell_above(cell_data.cell_type);
1191 new_cell.fromJSON(cell_data);
1191 new_cell.fromJSON(cell_data);
1192 var old_cell = this.get_next_cell(new_cell);
1192 var old_cell = this.get_next_cell(new_cell);
1193 this.delete_cell(this.find_cell_index(old_cell));
1193 this.delete_cell(this.find_cell_index(old_cell));
1194 this.select(this.find_cell_index(new_cell));
1194 this.select(this.find_cell_index(new_cell));
1195 }
1195 }
1196 };
1196 };
1197
1197
1198 /**
1198 /**
1199 * Paste a cell from the clipboard above the selected cell.
1199 * Paste a cell from the clipboard above the selected cell.
1200 */
1200 */
1201 Notebook.prototype.paste_cell_above = function () {
1201 Notebook.prototype.paste_cell_above = function () {
1202 if (this.clipboard !== null && this.paste_enabled) {
1202 if (this.clipboard !== null && this.paste_enabled) {
1203 var cell_data = this.clipboard;
1203 var cell_data = this.clipboard;
1204 var new_cell = this.insert_cell_above(cell_data.cell_type);
1204 var new_cell = this.insert_cell_above(cell_data.cell_type);
1205 new_cell.fromJSON(cell_data);
1205 new_cell.fromJSON(cell_data);
1206 new_cell.focus_cell();
1206 new_cell.focus_cell();
1207 }
1207 }
1208 };
1208 };
1209
1209
1210 /**
1210 /**
1211 * Paste a cell from the clipboard below the selected cell.
1211 * Paste a cell from the clipboard below the selected cell.
1212 */
1212 */
1213 Notebook.prototype.paste_cell_below = function () {
1213 Notebook.prototype.paste_cell_below = function () {
1214 if (this.clipboard !== null && this.paste_enabled) {
1214 if (this.clipboard !== null && this.paste_enabled) {
1215 var cell_data = this.clipboard;
1215 var cell_data = this.clipboard;
1216 var new_cell = this.insert_cell_below(cell_data.cell_type);
1216 var new_cell = this.insert_cell_below(cell_data.cell_type);
1217 new_cell.fromJSON(cell_data);
1217 new_cell.fromJSON(cell_data);
1218 new_cell.focus_cell();
1218 new_cell.focus_cell();
1219 }
1219 }
1220 };
1220 };
1221
1221
1222 // Split/merge
1222 // Split/merge
1223
1223
1224 /**
1224 /**
1225 * Split the selected cell into two cells.
1225 * Split the selected cell into two cells.
1226 */
1226 */
1227 Notebook.prototype.split_cell = function () {
1227 Notebook.prototype.split_cell = function () {
1228 var cell = this.get_selected_cell();
1228 var cell = this.get_selected_cell();
1229 if (cell.is_splittable()) {
1229 if (cell.is_splittable()) {
1230 var texta = cell.get_pre_cursor();
1230 var texta = cell.get_pre_cursor();
1231 var textb = cell.get_post_cursor();
1231 var textb = cell.get_post_cursor();
1232 cell.set_text(textb);
1232 cell.set_text(textb);
1233 var new_cell = this.insert_cell_above(cell.cell_type);
1233 var new_cell = this.insert_cell_above(cell.cell_type);
1234 // Unrender the new cell so we can call set_text.
1234 // Unrender the new cell so we can call set_text.
1235 new_cell.unrender();
1235 new_cell.unrender();
1236 new_cell.set_text(texta);
1236 new_cell.set_text(texta);
1237 }
1237 }
1238 };
1238 };
1239
1239
1240 /**
1240 /**
1241 * Merge the selected cell into the cell above it.
1241 * Merge the selected cell into the cell above it.
1242 */
1242 */
1243 Notebook.prototype.merge_cell_above = function () {
1243 Notebook.prototype.merge_cell_above = function () {
1244 var index = this.get_selected_index();
1244 var index = this.get_selected_index();
1245 var cell = this.get_cell(index);
1245 var cell = this.get_cell(index);
1246 var render = cell.rendered;
1246 var render = cell.rendered;
1247 if (!cell.is_mergeable()) {
1247 if (!cell.is_mergeable()) {
1248 return;
1248 return;
1249 }
1249 }
1250 if (index > 0) {
1250 if (index > 0) {
1251 var upper_cell = this.get_cell(index-1);
1251 var upper_cell = this.get_cell(index-1);
1252 if (!upper_cell.is_mergeable()) {
1252 if (!upper_cell.is_mergeable()) {
1253 return;
1253 return;
1254 }
1254 }
1255 var upper_text = upper_cell.get_text();
1255 var upper_text = upper_cell.get_text();
1256 var text = cell.get_text();
1256 var text = cell.get_text();
1257 if (cell instanceof codecell.CodeCell) {
1257 if (cell instanceof codecell.CodeCell) {
1258 cell.set_text(upper_text+'\n'+text);
1258 cell.set_text(upper_text+'\n'+text);
1259 } else {
1259 } else {
1260 cell.unrender(); // Must unrender before we set_text.
1260 cell.unrender(); // Must unrender before we set_text.
1261 cell.set_text(upper_text+'\n\n'+text);
1261 cell.set_text(upper_text+'\n\n'+text);
1262 if (render) {
1262 if (render) {
1263 // The rendered state of the final cell should match
1263 // The rendered state of the final cell should match
1264 // that of the original selected cell;
1264 // that of the original selected cell;
1265 cell.render();
1265 cell.render();
1266 }
1266 }
1267 }
1267 }
1268 this.delete_cell(index-1);
1268 this.delete_cell(index-1);
1269 this.select(this.find_cell_index(cell));
1269 this.select(this.find_cell_index(cell));
1270 }
1270 }
1271 };
1271 };
1272
1272
1273 /**
1273 /**
1274 * Merge the selected cell into the cell below it.
1274 * Merge the selected cell into the cell below it.
1275 */
1275 */
1276 Notebook.prototype.merge_cell_below = function () {
1276 Notebook.prototype.merge_cell_below = function () {
1277 var index = this.get_selected_index();
1277 var index = this.get_selected_index();
1278 var cell = this.get_cell(index);
1278 var cell = this.get_cell(index);
1279 var render = cell.rendered;
1279 var render = cell.rendered;
1280 if (!cell.is_mergeable()) {
1280 if (!cell.is_mergeable()) {
1281 return;
1281 return;
1282 }
1282 }
1283 if (index < this.ncells()-1) {
1283 if (index < this.ncells()-1) {
1284 var lower_cell = this.get_cell(index+1);
1284 var lower_cell = this.get_cell(index+1);
1285 if (!lower_cell.is_mergeable()) {
1285 if (!lower_cell.is_mergeable()) {
1286 return;
1286 return;
1287 }
1287 }
1288 var lower_text = lower_cell.get_text();
1288 var lower_text = lower_cell.get_text();
1289 var text = cell.get_text();
1289 var text = cell.get_text();
1290 if (cell instanceof codecell.CodeCell) {
1290 if (cell instanceof codecell.CodeCell) {
1291 cell.set_text(text+'\n'+lower_text);
1291 cell.set_text(text+'\n'+lower_text);
1292 } else {
1292 } else {
1293 cell.unrender(); // Must unrender before we set_text.
1293 cell.unrender(); // Must unrender before we set_text.
1294 cell.set_text(text+'\n\n'+lower_text);
1294 cell.set_text(text+'\n\n'+lower_text);
1295 if (render) {
1295 if (render) {
1296 // The rendered state of the final cell should match
1296 // The rendered state of the final cell should match
1297 // that of the original selected cell;
1297 // that of the original selected cell;
1298 cell.render();
1298 cell.render();
1299 }
1299 }
1300 }
1300 }
1301 this.delete_cell(index+1);
1301 this.delete_cell(index+1);
1302 this.select(this.find_cell_index(cell));
1302 this.select(this.find_cell_index(cell));
1303 }
1303 }
1304 };
1304 };
1305
1305
1306
1306
1307 // Cell collapsing and output clearing
1307 // Cell collapsing and output clearing
1308
1308
1309 /**
1309 /**
1310 * Hide a cell's output.
1310 * Hide a cell's output.
1311 *
1311 *
1312 * @param {integer} index - cell index
1312 * @param {integer} index - cell index
1313 */
1313 */
1314 Notebook.prototype.collapse_output = function (index) {
1314 Notebook.prototype.collapse_output = function (index) {
1315 var i = this.index_or_selected(index);
1315 var i = this.index_or_selected(index);
1316 var cell = this.get_cell(i);
1316 var cell = this.get_cell(i);
1317 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1317 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1318 cell.collapse_output();
1318 cell.collapse_output();
1319 this.set_dirty(true);
1319 this.set_dirty(true);
1320 }
1320 }
1321 };
1321 };
1322
1322
1323 /**
1323 /**
1324 * Hide each code cell's output area.
1324 * Hide each code cell's output area.
1325 */
1325 */
1326 Notebook.prototype.collapse_all_output = function () {
1326 Notebook.prototype.collapse_all_output = function () {
1327 this.get_cells().map(function (cell, i) {
1327 this.get_cells().map(function (cell, i) {
1328 if (cell instanceof codecell.CodeCell) {
1328 if (cell instanceof codecell.CodeCell) {
1329 cell.collapse_output();
1329 cell.collapse_output();
1330 }
1330 }
1331 });
1331 });
1332 // this should not be set if the `collapse` key is removed from nbformat
1332 // this should not be set if the `collapse` key is removed from nbformat
1333 this.set_dirty(true);
1333 this.set_dirty(true);
1334 };
1334 };
1335
1335
1336 /**
1336 /**
1337 * Show a cell's output.
1337 * Show a cell's output.
1338 *
1338 *
1339 * @param {integer} index - cell index
1339 * @param {integer} index - cell index
1340 */
1340 */
1341 Notebook.prototype.expand_output = function (index) {
1341 Notebook.prototype.expand_output = function (index) {
1342 var i = this.index_or_selected(index);
1342 var i = this.index_or_selected(index);
1343 var cell = this.get_cell(i);
1343 var cell = this.get_cell(i);
1344 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1344 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1345 cell.expand_output();
1345 cell.expand_output();
1346 this.set_dirty(true);
1346 this.set_dirty(true);
1347 }
1347 }
1348 };
1348 };
1349
1349
1350 /**
1350 /**
1351 * Expand each code cell's output area, and remove scrollbars.
1351 * Expand each code cell's output area, and remove scrollbars.
1352 */
1352 */
1353 Notebook.prototype.expand_all_output = function () {
1353 Notebook.prototype.expand_all_output = function () {
1354 this.get_cells().map(function (cell, i) {
1354 this.get_cells().map(function (cell, i) {
1355 if (cell instanceof codecell.CodeCell) {
1355 if (cell instanceof codecell.CodeCell) {
1356 cell.expand_output();
1356 cell.expand_output();
1357 }
1357 }
1358 });
1358 });
1359 // this should not be set if the `collapse` key is removed from nbformat
1359 // this should not be set if the `collapse` key is removed from nbformat
1360 this.set_dirty(true);
1360 this.set_dirty(true);
1361 };
1361 };
1362
1362
1363 /**
1363 /**
1364 * Clear the selected CodeCell's output area.
1364 * Clear the selected CodeCell's output area.
1365 *
1365 *
1366 * @param {integer} index - cell index
1366 * @param {integer} index - cell index
1367 */
1367 */
1368 Notebook.prototype.clear_output = function (index) {
1368 Notebook.prototype.clear_output = function (index) {
1369 var i = this.index_or_selected(index);
1369 var i = this.index_or_selected(index);
1370 var cell = this.get_cell(i);
1370 var cell = this.get_cell(i);
1371 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1371 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1372 cell.clear_output();
1372 cell.clear_output();
1373 this.set_dirty(true);
1373 this.set_dirty(true);
1374 }
1374 }
1375 };
1375 };
1376
1376
1377 /**
1377 /**
1378 * Clear each code cell's output area.
1378 * Clear each code cell's output area.
1379 */
1379 */
1380 Notebook.prototype.clear_all_output = function () {
1380 Notebook.prototype.clear_all_output = function () {
1381 this.get_cells().map(function (cell, i) {
1381 this.get_cells().map(function (cell, i) {
1382 if (cell instanceof codecell.CodeCell) {
1382 if (cell instanceof codecell.CodeCell) {
1383 cell.clear_output();
1383 cell.clear_output();
1384 }
1384 }
1385 });
1385 });
1386 this.set_dirty(true);
1386 this.set_dirty(true);
1387 };
1387 };
1388
1388
1389 /**
1389 /**
1390 * Scroll the selected CodeCell's output area.
1390 * Scroll the selected CodeCell's output area.
1391 *
1391 *
1392 * @param {integer} index - cell index
1392 * @param {integer} index - cell index
1393 */
1393 */
1394 Notebook.prototype.scroll_output = function (index) {
1394 Notebook.prototype.scroll_output = function (index) {
1395 var i = this.index_or_selected(index);
1395 var i = this.index_or_selected(index);
1396 var cell = this.get_cell(i);
1396 var cell = this.get_cell(i);
1397 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1397 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1398 cell.scroll_output();
1398 cell.scroll_output();
1399 this.set_dirty(true);
1399 this.set_dirty(true);
1400 }
1400 }
1401 };
1401 };
1402
1402
1403 /**
1403 /**
1404 * Expand each code cell's output area and add a scrollbar for long output.
1404 * Expand each code cell's output area and add a scrollbar for long output.
1405 */
1405 */
1406 Notebook.prototype.scroll_all_output = function () {
1406 Notebook.prototype.scroll_all_output = function () {
1407 this.get_cells().map(function (cell, i) {
1407 this.get_cells().map(function (cell, i) {
1408 if (cell instanceof codecell.CodeCell) {
1408 if (cell instanceof codecell.CodeCell) {
1409 cell.scroll_output();
1409 cell.scroll_output();
1410 }
1410 }
1411 });
1411 });
1412 // this should not be set if the `collapse` key is removed from nbformat
1412 // this should not be set if the `collapse` key is removed from nbformat
1413 this.set_dirty(true);
1413 this.set_dirty(true);
1414 };
1414 };
1415
1415
1416 /**
1416 /**
1417 * Toggle whether a cell's output is collapsed or expanded.
1417 * Toggle whether a cell's output is collapsed or expanded.
1418 *
1418 *
1419 * @param {integer} index - cell index
1419 * @param {integer} index - cell index
1420 */
1420 */
1421 Notebook.prototype.toggle_output = function (index) {
1421 Notebook.prototype.toggle_output = function (index) {
1422 var i = this.index_or_selected(index);
1422 var i = this.index_or_selected(index);
1423 var cell = this.get_cell(i);
1423 var cell = this.get_cell(i);
1424 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1424 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1425 cell.toggle_output();
1425 cell.toggle_output();
1426 this.set_dirty(true);
1426 this.set_dirty(true);
1427 }
1427 }
1428 };
1428 };
1429
1429
1430 /**
1430 /**
1431 * Toggle the output of all cells.
1431 * Toggle the output of all cells.
1432 */
1432 */
1433 Notebook.prototype.toggle_all_output = function () {
1433 Notebook.prototype.toggle_all_output = function () {
1434 this.get_cells().map(function (cell, i) {
1434 this.get_cells().map(function (cell, i) {
1435 if (cell instanceof codecell.CodeCell) {
1435 if (cell instanceof codecell.CodeCell) {
1436 cell.toggle_output();
1436 cell.toggle_output();
1437 }
1437 }
1438 });
1438 });
1439 // this should not be set if the `collapse` key is removed from nbformat
1439 // this should not be set if the `collapse` key is removed from nbformat
1440 this.set_dirty(true);
1440 this.set_dirty(true);
1441 };
1441 };
1442
1442
1443 /**
1443 /**
1444 * Toggle a scrollbar for long cell outputs.
1444 * Toggle a scrollbar for long cell outputs.
1445 *
1445 *
1446 * @param {integer} index - cell index
1446 * @param {integer} index - cell index
1447 */
1447 */
1448 Notebook.prototype.toggle_output_scroll = function (index) {
1448 Notebook.prototype.toggle_output_scroll = function (index) {
1449 var i = this.index_or_selected(index);
1449 var i = this.index_or_selected(index);
1450 var cell = this.get_cell(i);
1450 var cell = this.get_cell(i);
1451 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1451 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1452 cell.toggle_output_scroll();
1452 cell.toggle_output_scroll();
1453 this.set_dirty(true);
1453 this.set_dirty(true);
1454 }
1454 }
1455 };
1455 };
1456
1456
1457 /**
1457 /**
1458 * Toggle the scrolling of long output on all cells.
1458 * Toggle the scrolling of long output on all cells.
1459 */
1459 */
1460 Notebook.prototype.toggle_all_output_scroll = function () {
1460 Notebook.prototype.toggle_all_output_scroll = function () {
1461 this.get_cells().map(function (cell, i) {
1461 this.get_cells().map(function (cell, i) {
1462 if (cell instanceof codecell.CodeCell) {
1462 if (cell instanceof codecell.CodeCell) {
1463 cell.toggle_output_scroll();
1463 cell.toggle_output_scroll();
1464 }
1464 }
1465 });
1465 });
1466 // this should not be set if the `collapse` key is removed from nbformat
1466 // this should not be set if the `collapse` key is removed from nbformat
1467 this.set_dirty(true);
1467 this.set_dirty(true);
1468 };
1468 };
1469
1469
1470 // Other cell functions: line numbers, ...
1470 // Other cell functions: line numbers, ...
1471
1471
1472 /**
1472 /**
1473 * Toggle line numbers in the selected cell's input area.
1473 * Toggle line numbers in the selected cell's input area.
1474 */
1474 */
1475 Notebook.prototype.cell_toggle_line_numbers = function() {
1475 Notebook.prototype.cell_toggle_line_numbers = function() {
1476 this.get_selected_cell().toggle_line_numbers();
1476 this.get_selected_cell().toggle_line_numbers();
1477 };
1477 };
1478
1478
1479 /**
1479 /**
1480 * Set the codemirror mode for all code cells, including the default for
1480 * Set the codemirror mode for all code cells, including the default for
1481 * new code cells.
1481 * new code cells.
1482 */
1482 */
1483 Notebook.prototype.set_codemirror_mode = function(newmode){
1483 Notebook.prototype.set_codemirror_mode = function(newmode){
1484 if (newmode === this.codemirror_mode) {
1484 if (newmode === this.codemirror_mode) {
1485 return;
1485 return;
1486 }
1486 }
1487 this.codemirror_mode = newmode;
1487 this.codemirror_mode = newmode;
1488 codecell.CodeCell.options_default.cm_config.mode = newmode;
1488 codecell.CodeCell.options_default.cm_config.mode = newmode;
1489
1489
1490 var that = this;
1490 var that = this;
1491 utils.requireCodeMirrorMode(newmode, function (spec) {
1491 utils.requireCodeMirrorMode(newmode, function (spec) {
1492 that.get_cells().map(function(cell, i) {
1492 that.get_cells().map(function(cell, i) {
1493 if (cell.cell_type === 'code'){
1493 if (cell.cell_type === 'code'){
1494 cell.code_mirror.setOption('mode', spec);
1494 cell.code_mirror.setOption('mode', spec);
1495 // This is currently redundant, because cm_config ends up as
1495 // This is currently redundant, because cm_config ends up as
1496 // codemirror's own .options object, but I don't want to
1496 // codemirror's own .options object, but I don't want to
1497 // rely on that.
1497 // rely on that.
1498 cell.cm_config.mode = spec;
1498 cell.cm_config.mode = spec;
1499 }
1499 }
1500 });
1500 });
1501 });
1501 });
1502 };
1502 };
1503
1503
1504 // Session related things
1504 // Session related things
1505
1505
1506 /**
1506 /**
1507 * Start a new session and set it on each code cell.
1507 * Start a new session and set it on each code cell.
1508 */
1508 */
1509 Notebook.prototype.start_session = function (kernel_name) {
1509 Notebook.prototype.start_session = function (kernel_name) {
1510 if (this._session_starting) {
1510 if (this._session_starting) {
1511 throw new session.SessionAlreadyStarting();
1511 throw new session.SessionAlreadyStarting();
1512 }
1512 }
1513 this._session_starting = true;
1513 this._session_starting = true;
1514
1514
1515 var options = {
1515 var options = {
1516 base_url: this.base_url,
1516 base_url: this.base_url,
1517 ws_url: this.ws_url,
1517 ws_url: this.ws_url,
1518 notebook_path: this.notebook_path,
1518 notebook_path: this.notebook_path,
1519 notebook_name: this.notebook_name,
1519 notebook_name: this.notebook_name,
1520 kernel_name: kernel_name,
1520 kernel_name: kernel_name,
1521 notebook: this
1521 notebook: this
1522 };
1522 };
1523
1523
1524 var success = $.proxy(this._session_started, this);
1524 var success = $.proxy(this._session_started, this);
1525 var failure = $.proxy(this._session_start_failed, this);
1525 var failure = $.proxy(this._session_start_failed, this);
1526
1526
1527 if (this.session !== null) {
1527 if (this.session !== null) {
1528 this.session.restart(options, success, failure);
1528 this.session.restart(options, success, failure);
1529 } else {
1529 } else {
1530 this.session = new session.Session(options);
1530 this.session = new session.Session(options);
1531 this.session.start(success, failure);
1531 this.session.start(success, failure);
1532 }
1532 }
1533 };
1533 };
1534
1534
1535
1535
1536 /**
1536 /**
1537 * Once a session is started, link the code cells to the kernel and pass the
1537 * Once a session is started, link the code cells to the kernel and pass the
1538 * comm manager to the widget manager.
1538 * comm manager to the widget manager.
1539 */
1539 */
1540 Notebook.prototype._session_started = function (){
1540 Notebook.prototype._session_started = function (){
1541 this._session_starting = false;
1541 this._session_starting = false;
1542 this.kernel = this.session.kernel;
1542 this.kernel = this.session.kernel;
1543 var ncells = this.ncells();
1543 var ncells = this.ncells();
1544 for (var i=0; i<ncells; i++) {
1544 for (var i=0; i<ncells; i++) {
1545 var cell = this.get_cell(i);
1545 var cell = this.get_cell(i);
1546 if (cell instanceof codecell.CodeCell) {
1546 if (cell instanceof codecell.CodeCell) {
1547 cell.set_kernel(this.session.kernel);
1547 cell.set_kernel(this.session.kernel);
1548 }
1548 }
1549 }
1549 }
1550 };
1550 };
1551
1551
1552 /**
1552 /**
1553 * Called when the session fails to start.
1553 * Called when the session fails to start.
1554 */
1554 */
1555 Notebook.prototype._session_start_failed = function(jqxhr, status, error){
1555 Notebook.prototype._session_start_failed = function(jqxhr, status, error){
1556 this._session_starting = false;
1556 this._session_starting = false;
1557 utils.log_ajax_error(jqxhr, status, error);
1557 utils.log_ajax_error(jqxhr, status, error);
1558 };
1558 };
1559
1559
1560 /**
1560 /**
1561 * Prompt the user to restart the IPython kernel.
1561 * Prompt the user to restart the IPython kernel.
1562 */
1562 */
1563 Notebook.prototype.restart_kernel = function () {
1563 Notebook.prototype.restart_kernel = function () {
1564 var that = this;
1564 var that = this;
1565 dialog.modal({
1565 dialog.modal({
1566 notebook: this,
1566 notebook: this,
1567 keyboard_manager: this.keyboard_manager,
1567 keyboard_manager: this.keyboard_manager,
1568 title : "Restart kernel or continue running?",
1568 title : "Restart kernel or continue running?",
1569 body : $("<p/>").text(
1569 body : $("<p/>").text(
1570 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1570 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1571 ),
1571 ),
1572 buttons : {
1572 buttons : {
1573 "Continue running" : {},
1573 "Continue running" : {},
1574 "Restart" : {
1574 "Restart" : {
1575 "class" : "btn-danger",
1575 "class" : "btn-danger",
1576 "click" : function() {
1576 "click" : function() {
1577 that.kernel.restart();
1577 that.kernel.restart();
1578 }
1578 }
1579 }
1579 }
1580 }
1580 }
1581 });
1581 });
1582 };
1582 };
1583
1583
1584 /**
1584 /**
1585 * Execute or render cell outputs and go into command mode.
1585 * Execute or render cell outputs and go into command mode.
1586 */
1586 */
1587 Notebook.prototype.execute_cell = function () {
1587 Notebook.prototype.execute_cell = function () {
1588 // mode = shift, ctrl, alt
1588 // mode = shift, ctrl, alt
1589 var cell = this.get_selected_cell();
1589 var cell = this.get_selected_cell();
1590
1590
1591 cell.execute();
1591 cell.execute();
1592 this.command_mode();
1592 this.command_mode();
1593 this.set_dirty(true);
1593 this.set_dirty(true);
1594 };
1594 };
1595
1595
1596 /**
1596 /**
1597 * Execute or render cell outputs and insert a new cell below.
1597 * Execute or render cell outputs and insert a new cell below.
1598 */
1598 */
1599 Notebook.prototype.execute_cell_and_insert_below = function () {
1599 Notebook.prototype.execute_cell_and_insert_below = function () {
1600 var cell = this.get_selected_cell();
1600 var cell = this.get_selected_cell();
1601 var cell_index = this.find_cell_index(cell);
1601 var cell_index = this.find_cell_index(cell);
1602
1602
1603 cell.execute();
1603 cell.execute();
1604
1604
1605 // If we are at the end always insert a new cell and return
1605 // If we are at the end always insert a new cell and return
1606 if (cell_index === (this.ncells()-1)) {
1606 if (cell_index === (this.ncells()-1)) {
1607 this.command_mode();
1607 this.command_mode();
1608 this.insert_cell_below();
1608 this.insert_cell_below();
1609 this.select(cell_index+1);
1609 this.select(cell_index+1);
1610 this.edit_mode();
1610 this.edit_mode();
1611 this.scroll_to_bottom();
1611 this.scroll_to_bottom();
1612 this.set_dirty(true);
1612 this.set_dirty(true);
1613 return;
1613 return;
1614 }
1614 }
1615
1615
1616 this.command_mode();
1616 this.command_mode();
1617 this.insert_cell_below();
1617 this.insert_cell_below();
1618 this.select(cell_index+1);
1618 this.select(cell_index+1);
1619 this.edit_mode();
1619 this.edit_mode();
1620 this.set_dirty(true);
1620 this.set_dirty(true);
1621 };
1621 };
1622
1622
1623 /**
1623 /**
1624 * Execute or render cell outputs and select the next cell.
1624 * Execute or render cell outputs and select the next cell.
1625 */
1625 */
1626 Notebook.prototype.execute_cell_and_select_below = function () {
1626 Notebook.prototype.execute_cell_and_select_below = function () {
1627
1627
1628 var cell = this.get_selected_cell();
1628 var cell = this.get_selected_cell();
1629 var cell_index = this.find_cell_index(cell);
1629 var cell_index = this.find_cell_index(cell);
1630
1630
1631 cell.execute();
1631 cell.execute();
1632
1632
1633 // If we are at the end always insert a new cell and return
1633 // If we are at the end always insert a new cell and return
1634 if (cell_index === (this.ncells()-1)) {
1634 if (cell_index === (this.ncells()-1)) {
1635 this.command_mode();
1635 this.command_mode();
1636 this.insert_cell_below();
1636 this.insert_cell_below();
1637 this.select(cell_index+1);
1637 this.select(cell_index+1);
1638 this.edit_mode();
1638 this.edit_mode();
1639 this.scroll_to_bottom();
1639 this.scroll_to_bottom();
1640 this.set_dirty(true);
1640 this.set_dirty(true);
1641 return;
1641 return;
1642 }
1642 }
1643
1643
1644 this.command_mode();
1644 this.command_mode();
1645 this.select(cell_index+1);
1645 this.select(cell_index+1);
1646 this.focus_cell();
1646 this.focus_cell();
1647 this.set_dirty(true);
1647 this.set_dirty(true);
1648 };
1648 };
1649
1649
1650 /**
1650 /**
1651 * Execute all cells below the selected cell.
1651 * Execute all cells below the selected cell.
1652 */
1652 */
1653 Notebook.prototype.execute_cells_below = function () {
1653 Notebook.prototype.execute_cells_below = function () {
1654 this.execute_cell_range(this.get_selected_index(), this.ncells());
1654 this.execute_cell_range(this.get_selected_index(), this.ncells());
1655 this.scroll_to_bottom();
1655 this.scroll_to_bottom();
1656 };
1656 };
1657
1657
1658 /**
1658 /**
1659 * Execute all cells above the selected cell.
1659 * Execute all cells above the selected cell.
1660 */
1660 */
1661 Notebook.prototype.execute_cells_above = function () {
1661 Notebook.prototype.execute_cells_above = function () {
1662 this.execute_cell_range(0, this.get_selected_index());
1662 this.execute_cell_range(0, this.get_selected_index());
1663 };
1663 };
1664
1664
1665 /**
1665 /**
1666 * Execute all cells.
1666 * Execute all cells.
1667 */
1667 */
1668 Notebook.prototype.execute_all_cells = function () {
1668 Notebook.prototype.execute_all_cells = function () {
1669 this.execute_cell_range(0, this.ncells());
1669 this.execute_cell_range(0, this.ncells());
1670 this.scroll_to_bottom();
1670 this.scroll_to_bottom();
1671 };
1671 };
1672
1672
1673 /**
1673 /**
1674 * Execute a contiguous range of cells.
1674 * Execute a contiguous range of cells.
1675 *
1675 *
1676 * @param {integer} start - index of the first cell to execute (inclusive)
1676 * @param {integer} start - index of the first cell to execute (inclusive)
1677 * @param {integer} end - index of the last cell to execute (exclusive)
1677 * @param {integer} end - index of the last cell to execute (exclusive)
1678 */
1678 */
1679 Notebook.prototype.execute_cell_range = function (start, end) {
1679 Notebook.prototype.execute_cell_range = function (start, end) {
1680 this.command_mode();
1680 this.command_mode();
1681 for (var i=start; i<end; i++) {
1681 for (var i=start; i<end; i++) {
1682 this.select(i);
1682 this.select(i);
1683 this.execute_cell();
1683 this.execute_cell();
1684 }
1684 }
1685 };
1685 };
1686
1686
1687 // Persistance and loading
1687 // Persistance and loading
1688
1688
1689 /**
1689 /**
1690 * Getter method for this notebook's name.
1690 * Getter method for this notebook's name.
1691 *
1691 *
1692 * @return {string} This notebook's name (excluding file extension)
1692 * @return {string} This notebook's name (excluding file extension)
1693 */
1693 */
1694 Notebook.prototype.get_notebook_name = function () {
1694 Notebook.prototype.get_notebook_name = function () {
1695 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1695 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1696 return nbname;
1696 return nbname;
1697 };
1697 };
1698
1698
1699 /**
1699 /**
1700 * Setter method for this notebook's name.
1700 * Setter method for this notebook's name.
1701 *
1701 *
1702 * @param {string} name
1702 * @param {string} name
1703 */
1703 */
1704 Notebook.prototype.set_notebook_name = function (name) {
1704 Notebook.prototype.set_notebook_name = function (name) {
1705 var parent = utils.url_path_split(this.notebook_path)[0];
1705 var parent = utils.url_path_split(this.notebook_path)[0];
1706 this.notebook_name = name;
1706 this.notebook_name = name;
1707 this.notebook_path = utils.url_path_join(parent, name);
1707 this.notebook_path = utils.url_path_join(parent, name);
1708 };
1708 };
1709
1709
1710 /**
1710 /**
1711 * Check that a notebook's name is valid.
1711 * Check that a notebook's name is valid.
1712 *
1712 *
1713 * @param {string} nbname - A name for this notebook
1713 * @param {string} nbname - A name for this notebook
1714 * @return {boolean} True if the name is valid, false if invalid
1714 * @return {boolean} True if the name is valid, false if invalid
1715 */
1715 */
1716 Notebook.prototype.test_notebook_name = function (nbname) {
1716 Notebook.prototype.test_notebook_name = function (nbname) {
1717 nbname = nbname || '';
1717 nbname = nbname || '';
1718 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1718 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1719 return true;
1719 return true;
1720 } else {
1720 } else {
1721 return false;
1721 return false;
1722 }
1722 }
1723 };
1723 };
1724
1724
1725 /**
1725 /**
1726 * Load a notebook from JSON (.ipynb).
1726 * Load a notebook from JSON (.ipynb).
1727 *
1727 *
1728 * @param {object} data - JSON representation of a notebook
1728 * @param {object} data - JSON representation of a notebook
1729 */
1729 */
1730 Notebook.prototype.fromJSON = function (data) {
1730 Notebook.prototype.fromJSON = function (data) {
1731
1731
1732 var content = data.content;
1732 var content = data.content;
1733 var ncells = this.ncells();
1733 var ncells = this.ncells();
1734 var i;
1734 var i;
1735 for (i=0; i<ncells; i++) {
1735 for (i=0; i<ncells; i++) {
1736 // Always delete cell 0 as they get renumbered as they are deleted.
1736 // Always delete cell 0 as they get renumbered as they are deleted.
1737 this.delete_cell(0);
1737 this.delete_cell(0);
1738 }
1738 }
1739 // Save the metadata and name.
1739 // Save the metadata and name.
1740 this.metadata = content.metadata;
1740 this.metadata = content.metadata;
1741 this.notebook_name = data.name;
1741 this.notebook_name = data.name;
1742 this.notebook_path = data.path;
1742 this.notebook_path = data.path;
1743 var trusted = true;
1743 var trusted = true;
1744
1744
1745 // Trigger an event changing the kernel spec - this will set the default
1745 // Trigger an event changing the kernel spec - this will set the default
1746 // codemirror mode
1746 // codemirror mode
1747 if (this.metadata.kernelspec !== undefined) {
1747 if (this.metadata.kernelspec !== undefined) {
1748 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1748 // TODO shoudl probably not trigger here,
1749 // should call the kernel selector, or custom.{js|css} not loaded.
1750 if(this.kernel_selector){
1751 // technically not perfect, we should check that the kernelspec matches
1752 this.kernel_selector.change_kernel(this.metadata.kernelspec.name);
1753 } else {
1754 console.log('do not have handle on kernel_selector');
1755 }
1749 }
1756 }
1750
1757
1751 // Set the codemirror mode from language_info metadata
1758 // Set the codemirror mode from language_info metadata
1752 if (this.metadata.language_info !== undefined) {
1759 if (this.metadata.language_info !== undefined) {
1753 var langinfo = this.metadata.language_info;
1760 var langinfo = this.metadata.language_info;
1754 // Mode 'null' should be plain, unhighlighted text.
1761 // Mode 'null' should be plain, unhighlighted text.
1755 var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
1762 var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
1756 this.set_codemirror_mode(cm_mode);
1763 this.set_codemirror_mode(cm_mode);
1757 }
1764 }
1758
1765
1759 var new_cells = content.cells;
1766 var new_cells = content.cells;
1760 ncells = new_cells.length;
1767 ncells = new_cells.length;
1761 var cell_data = null;
1768 var cell_data = null;
1762 var new_cell = null;
1769 var new_cell = null;
1763 for (i=0; i<ncells; i++) {
1770 for (i=0; i<ncells; i++) {
1764 cell_data = new_cells[i];
1771 cell_data = new_cells[i];
1765 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1772 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1766 new_cell.fromJSON(cell_data);
1773 new_cell.fromJSON(cell_data);
1767 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1774 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1768 trusted = false;
1775 trusted = false;
1769 }
1776 }
1770 }
1777 }
1771 if (trusted !== this.trusted) {
1778 if (trusted !== this.trusted) {
1772 this.trusted = trusted;
1779 this.trusted = trusted;
1773 this.events.trigger("trust_changed.Notebook", trusted);
1780 this.events.trigger("trust_changed.Notebook", trusted);
1774 }
1781 }
1775 };
1782 };
1776
1783
1777 /**
1784 /**
1778 * Dump this notebook into a JSON-friendly object.
1785 * Dump this notebook into a JSON-friendly object.
1779 *
1786 *
1780 * @return {object} A JSON-friendly representation of this notebook.
1787 * @return {object} A JSON-friendly representation of this notebook.
1781 */
1788 */
1782 Notebook.prototype.toJSON = function () {
1789 Notebook.prototype.toJSON = function () {
1783 // remove the conversion indicator, which only belongs in-memory
1790 // remove the conversion indicator, which only belongs in-memory
1784 delete this.metadata.orig_nbformat;
1791 delete this.metadata.orig_nbformat;
1785 delete this.metadata.orig_nbformat_minor;
1792 delete this.metadata.orig_nbformat_minor;
1786
1793
1787 var cells = this.get_cells();
1794 var cells = this.get_cells();
1788 var ncells = cells.length;
1795 var ncells = cells.length;
1789 var cell_array = new Array(ncells);
1796 var cell_array = new Array(ncells);
1790 var trusted = true;
1797 var trusted = true;
1791 for (var i=0; i<ncells; i++) {
1798 for (var i=0; i<ncells; i++) {
1792 var cell = cells[i];
1799 var cell = cells[i];
1793 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1800 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1794 trusted = false;
1801 trusted = false;
1795 }
1802 }
1796 cell_array[i] = cell.toJSON();
1803 cell_array[i] = cell.toJSON();
1797 }
1804 }
1798 var data = {
1805 var data = {
1799 cells: cell_array,
1806 cells: cell_array,
1800 metadata: this.metadata,
1807 metadata: this.metadata,
1801 nbformat: this.nbformat,
1808 nbformat: this.nbformat,
1802 nbformat_minor: this.nbformat_minor
1809 nbformat_minor: this.nbformat_minor
1803 };
1810 };
1804 if (trusted != this.trusted) {
1811 if (trusted != this.trusted) {
1805 this.trusted = trusted;
1812 this.trusted = trusted;
1806 this.events.trigger("trust_changed.Notebook", trusted);
1813 this.events.trigger("trust_changed.Notebook", trusted);
1807 }
1814 }
1808 return data;
1815 return data;
1809 };
1816 };
1810
1817
1811 /**
1818 /**
1812 * Start an autosave timer which periodically saves the notebook.
1819 * Start an autosave timer which periodically saves the notebook.
1813 *
1820 *
1814 * @param {integer} interval - the autosave interval in milliseconds
1821 * @param {integer} interval - the autosave interval in milliseconds
1815 */
1822 */
1816 Notebook.prototype.set_autosave_interval = function (interval) {
1823 Notebook.prototype.set_autosave_interval = function (interval) {
1817 var that = this;
1824 var that = this;
1818 // clear previous interval, so we don't get simultaneous timers
1825 // clear previous interval, so we don't get simultaneous timers
1819 if (this.autosave_timer) {
1826 if (this.autosave_timer) {
1820 clearInterval(this.autosave_timer);
1827 clearInterval(this.autosave_timer);
1821 }
1828 }
1822 if (!this.writable) {
1829 if (!this.writable) {
1823 // disable autosave if not writable
1830 // disable autosave if not writable
1824 interval = 0;
1831 interval = 0;
1825 }
1832 }
1826
1833
1827 this.autosave_interval = this.minimum_autosave_interval = interval;
1834 this.autosave_interval = this.minimum_autosave_interval = interval;
1828 if (interval) {
1835 if (interval) {
1829 this.autosave_timer = setInterval(function() {
1836 this.autosave_timer = setInterval(function() {
1830 if (that.dirty) {
1837 if (that.dirty) {
1831 that.save_notebook();
1838 that.save_notebook();
1832 }
1839 }
1833 }, interval);
1840 }, interval);
1834 this.events.trigger("autosave_enabled.Notebook", interval);
1841 this.events.trigger("autosave_enabled.Notebook", interval);
1835 } else {
1842 } else {
1836 this.autosave_timer = null;
1843 this.autosave_timer = null;
1837 this.events.trigger("autosave_disabled.Notebook");
1844 this.events.trigger("autosave_disabled.Notebook");
1838 }
1845 }
1839 };
1846 };
1840
1847
1841 /**
1848 /**
1842 * Save this notebook on the server. This becomes a notebook instance's
1849 * Save this notebook on the server. This becomes a notebook instance's
1843 * .save_notebook method *after* the entire notebook has been loaded.
1850 * .save_notebook method *after* the entire notebook has been loaded.
1844 */
1851 */
1845 Notebook.prototype.save_notebook = function () {
1852 Notebook.prototype.save_notebook = function () {
1846 if (!this._fully_loaded) {
1853 if (!this._fully_loaded) {
1847 this.events.trigger('notebook_save_failed.Notebook',
1854 this.events.trigger('notebook_save_failed.Notebook',
1848 new Error("Load failed, save is disabled")
1855 new Error("Load failed, save is disabled")
1849 );
1856 );
1850 return;
1857 return;
1851 } else if (!this.writable) {
1858 } else if (!this.writable) {
1852 this.events.trigger('notebook_save_failed.Notebook',
1859 this.events.trigger('notebook_save_failed.Notebook',
1853 new Error("Notebook is read-only")
1860 new Error("Notebook is read-only")
1854 );
1861 );
1855 return;
1862 return;
1856 }
1863 }
1857
1864
1858 // Trigger an event before save, which allows listeners to modify
1865 // Trigger an event before save, which allows listeners to modify
1859 // the notebook as needed.
1866 // the notebook as needed.
1860 this.events.trigger('before_save.Notebook');
1867 this.events.trigger('before_save.Notebook');
1861
1868
1862 // Create a JSON model to be sent to the server.
1869 // Create a JSON model to be sent to the server.
1863 var model = {
1870 var model = {
1864 type : "notebook",
1871 type : "notebook",
1865 content : this.toJSON()
1872 content : this.toJSON()
1866 };
1873 };
1867 // time the ajax call for autosave tuning purposes.
1874 // time the ajax call for autosave tuning purposes.
1868 var start = new Date().getTime();
1875 var start = new Date().getTime();
1869
1876
1870 var that = this;
1877 var that = this;
1871 return this.contents.save(this.notebook_path, model).then(
1878 return this.contents.save(this.notebook_path, model).then(
1872 $.proxy(this.save_notebook_success, this, start),
1879 $.proxy(this.save_notebook_success, this, start),
1873 function (error) {
1880 function (error) {
1874 that.events.trigger('notebook_save_failed.Notebook', error);
1881 that.events.trigger('notebook_save_failed.Notebook', error);
1875 }
1882 }
1876 );
1883 );
1877 };
1884 };
1878
1885
1879 /**
1886 /**
1880 * Success callback for saving a notebook.
1887 * Success callback for saving a notebook.
1881 *
1888 *
1882 * @param {integer} start - Time when the save request start
1889 * @param {integer} start - Time when the save request start
1883 * @param {object} data - JSON representation of a notebook
1890 * @param {object} data - JSON representation of a notebook
1884 */
1891 */
1885 Notebook.prototype.save_notebook_success = function (start, data) {
1892 Notebook.prototype.save_notebook_success = function (start, data) {
1886 this.set_dirty(false);
1893 this.set_dirty(false);
1887 if (data.message) {
1894 if (data.message) {
1888 // save succeeded, but validation failed.
1895 // save succeeded, but validation failed.
1889 var body = $("<div>");
1896 var body = $("<div>");
1890 var title = "Notebook validation failed";
1897 var title = "Notebook validation failed";
1891
1898
1892 body.append($("<p>").text(
1899 body.append($("<p>").text(
1893 "The save operation succeeded," +
1900 "The save operation succeeded," +
1894 " but the notebook does not appear to be valid." +
1901 " but the notebook does not appear to be valid." +
1895 " The validation error was:"
1902 " The validation error was:"
1896 )).append($("<div>").addClass("validation-error").append(
1903 )).append($("<div>").addClass("validation-error").append(
1897 $("<pre>").text(data.message)
1904 $("<pre>").text(data.message)
1898 ));
1905 ));
1899 dialog.modal({
1906 dialog.modal({
1900 notebook: this,
1907 notebook: this,
1901 keyboard_manager: this.keyboard_manager,
1908 keyboard_manager: this.keyboard_manager,
1902 title: title,
1909 title: title,
1903 body: body,
1910 body: body,
1904 buttons : {
1911 buttons : {
1905 OK : {
1912 OK : {
1906 "class" : "btn-primary"
1913 "class" : "btn-primary"
1907 }
1914 }
1908 }
1915 }
1909 });
1916 });
1910 }
1917 }
1911 this.events.trigger('notebook_saved.Notebook');
1918 this.events.trigger('notebook_saved.Notebook');
1912 this._update_autosave_interval(start);
1919 this._update_autosave_interval(start);
1913 if (this._checkpoint_after_save) {
1920 if (this._checkpoint_after_save) {
1914 this.create_checkpoint();
1921 this.create_checkpoint();
1915 this._checkpoint_after_save = false;
1922 this._checkpoint_after_save = false;
1916 }
1923 }
1917 };
1924 };
1918
1925
1919 /**
1926 /**
1920 * Update the autosave interval based on the duration of the last save.
1927 * Update the autosave interval based on the duration of the last save.
1921 *
1928 *
1922 * @param {integer} timestamp - when the save request started
1929 * @param {integer} timestamp - when the save request started
1923 */
1930 */
1924 Notebook.prototype._update_autosave_interval = function (start) {
1931 Notebook.prototype._update_autosave_interval = function (start) {
1925 var duration = (new Date().getTime() - start);
1932 var duration = (new Date().getTime() - start);
1926 if (this.autosave_interval) {
1933 if (this.autosave_interval) {
1927 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1934 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1928 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1935 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1929 // round to 10 seconds, otherwise we will be setting a new interval too often
1936 // round to 10 seconds, otherwise we will be setting a new interval too often
1930 interval = 10000 * Math.round(interval / 10000);
1937 interval = 10000 * Math.round(interval / 10000);
1931 // set new interval, if it's changed
1938 // set new interval, if it's changed
1932 if (interval != this.autosave_interval) {
1939 if (interval != this.autosave_interval) {
1933 this.set_autosave_interval(interval);
1940 this.set_autosave_interval(interval);
1934 }
1941 }
1935 }
1942 }
1936 };
1943 };
1937
1944
1938 /**
1945 /**
1939 * Explicitly trust the output of this notebook.
1946 * Explicitly trust the output of this notebook.
1940 */
1947 */
1941 Notebook.prototype.trust_notebook = function () {
1948 Notebook.prototype.trust_notebook = function () {
1942 var body = $("<div>").append($("<p>")
1949 var body = $("<div>").append($("<p>")
1943 .text("A trusted IPython notebook may execute hidden malicious code ")
1950 .text("A trusted IPython notebook may execute hidden malicious code ")
1944 .append($("<strong>")
1951 .append($("<strong>")
1945 .append(
1952 .append(
1946 $("<em>").text("when you open it")
1953 $("<em>").text("when you open it")
1947 )
1954 )
1948 ).append(".").append(
1955 ).append(".").append(
1949 " Selecting trust will immediately reload this notebook in a trusted state."
1956 " Selecting trust will immediately reload this notebook in a trusted state."
1950 ).append(
1957 ).append(
1951 " For more information, see the "
1958 " For more information, see the "
1952 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1959 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1953 .text("IPython security documentation")
1960 .text("IPython security documentation")
1954 ).append(".")
1961 ).append(".")
1955 );
1962 );
1956
1963
1957 var nb = this;
1964 var nb = this;
1958 dialog.modal({
1965 dialog.modal({
1959 notebook: this,
1966 notebook: this,
1960 keyboard_manager: this.keyboard_manager,
1967 keyboard_manager: this.keyboard_manager,
1961 title: "Trust this notebook?",
1968 title: "Trust this notebook?",
1962 body: body,
1969 body: body,
1963
1970
1964 buttons: {
1971 buttons: {
1965 Cancel : {},
1972 Cancel : {},
1966 Trust : {
1973 Trust : {
1967 class : "btn-danger",
1974 class : "btn-danger",
1968 click : function () {
1975 click : function () {
1969 var cells = nb.get_cells();
1976 var cells = nb.get_cells();
1970 for (var i = 0; i < cells.length; i++) {
1977 for (var i = 0; i < cells.length; i++) {
1971 var cell = cells[i];
1978 var cell = cells[i];
1972 if (cell.cell_type == 'code') {
1979 if (cell.cell_type == 'code') {
1973 cell.output_area.trusted = true;
1980 cell.output_area.trusted = true;
1974 }
1981 }
1975 }
1982 }
1976 nb.events.on('notebook_saved.Notebook', function () {
1983 nb.events.on('notebook_saved.Notebook', function () {
1977 window.location.reload();
1984 window.location.reload();
1978 });
1985 });
1979 nb.save_notebook();
1986 nb.save_notebook();
1980 }
1987 }
1981 }
1988 }
1982 }
1989 }
1983 });
1990 });
1984 };
1991 };
1985
1992
1986 /**
1993 /**
1987 * Make a copy of the current notebook.
1994 * Make a copy of the current notebook.
1988 */
1995 */
1989 Notebook.prototype.copy_notebook = function () {
1996 Notebook.prototype.copy_notebook = function () {
1990 var that = this;
1997 var that = this;
1991 var base_url = this.base_url;
1998 var base_url = this.base_url;
1992 var w = window.open();
1999 var w = window.open();
1993 var parent = utils.url_path_split(this.notebook_path)[0];
2000 var parent = utils.url_path_split(this.notebook_path)[0];
1994 this.contents.copy(this.notebook_path, parent).then(
2001 this.contents.copy(this.notebook_path, parent).then(
1995 function (data) {
2002 function (data) {
1996 w.location = utils.url_join_encode(
2003 w.location = utils.url_join_encode(
1997 base_url, 'notebooks', data.path
2004 base_url, 'notebooks', data.path
1998 );
2005 );
1999 },
2006 },
2000 function(error) {
2007 function(error) {
2001 w.close();
2008 w.close();
2002 that.events.trigger('notebook_copy_failed', error);
2009 that.events.trigger('notebook_copy_failed', error);
2003 }
2010 }
2004 );
2011 );
2005 };
2012 };
2006
2013
2007 /**
2014 /**
2008 * Rename the notebook.
2015 * Rename the notebook.
2009 * @param {string} new_name
2016 * @param {string} new_name
2010 * @return {Promise} promise that resolves when the notebook is renamed.
2017 * @return {Promise} promise that resolves when the notebook is renamed.
2011 */
2018 */
2012 Notebook.prototype.rename = function (new_name) {
2019 Notebook.prototype.rename = function (new_name) {
2013 if (!new_name.match(/\.ipynb$/)) {
2020 if (!new_name.match(/\.ipynb$/)) {
2014 new_name = new_name + ".ipynb";
2021 new_name = new_name + ".ipynb";
2015 }
2022 }
2016
2023
2017 var that = this;
2024 var that = this;
2018 var parent = utils.url_path_split(this.notebook_path)[0];
2025 var parent = utils.url_path_split(this.notebook_path)[0];
2019 var new_path = utils.url_path_join(parent, new_name);
2026 var new_path = utils.url_path_join(parent, new_name);
2020 return this.contents.rename(this.notebook_path, new_path).then(
2027 return this.contents.rename(this.notebook_path, new_path).then(
2021 function (json) {
2028 function (json) {
2022 that.notebook_name = json.name;
2029 that.notebook_name = json.name;
2023 that.notebook_path = json.path;
2030 that.notebook_path = json.path;
2024 that.session.rename_notebook(json.path);
2031 that.session.rename_notebook(json.path);
2025 that.events.trigger('notebook_renamed.Notebook', json);
2032 that.events.trigger('notebook_renamed.Notebook', json);
2026 }
2033 }
2027 );
2034 );
2028 };
2035 };
2029
2036
2030 /**
2037 /**
2031 * Delete this notebook
2038 * Delete this notebook
2032 */
2039 */
2033 Notebook.prototype.delete = function () {
2040 Notebook.prototype.delete = function () {
2034 this.contents.delete(this.notebook_path);
2041 this.contents.delete(this.notebook_path);
2035 };
2042 };
2036
2043
2037 /**
2044 /**
2038 * Request a notebook's data from the server.
2045 * Request a notebook's data from the server.
2039 *
2046 *
2040 * @param {string} notebook_path - A notebook to load
2047 * @param {string} notebook_path - A notebook to load
2041 */
2048 */
2042 Notebook.prototype.load_notebook = function (notebook_path) {
2049 Notebook.prototype.load_notebook = function (notebook_path) {
2043 this.notebook_path = notebook_path;
2050 this.notebook_path = notebook_path;
2044 this.notebook_name = utils.url_path_split(this.notebook_path)[1];
2051 this.notebook_name = utils.url_path_split(this.notebook_path)[1];
2045 this.events.trigger('notebook_loading.Notebook');
2052 this.events.trigger('notebook_loading.Notebook');
2046 this.contents.get(notebook_path, {type: 'notebook'}).then(
2053 this.contents.get(notebook_path, {type: 'notebook'}).then(
2047 $.proxy(this.load_notebook_success, this),
2054 $.proxy(this.load_notebook_success, this),
2048 $.proxy(this.load_notebook_error, this)
2055 $.proxy(this.load_notebook_error, this)
2049 );
2056 );
2050 };
2057 };
2051
2058
2052 /**
2059 /**
2053 * Success callback for loading a notebook from the server.
2060 * Success callback for loading a notebook from the server.
2054 *
2061 *
2055 * Load notebook data from the JSON response.
2062 * Load notebook data from the JSON response.
2056 *
2063 *
2057 * @param {object} data JSON representation of a notebook
2064 * @param {object} data JSON representation of a notebook
2058 */
2065 */
2059 Notebook.prototype.load_notebook_success = function (data) {
2066 Notebook.prototype.load_notebook_success = function (data) {
2060 var failed, msg;
2067 var failed, msg;
2061 try {
2068 try {
2062 this.fromJSON(data);
2069 this.fromJSON(data);
2063 } catch (e) {
2070 } catch (e) {
2064 failed = e;
2071 failed = e;
2065 console.log("Notebook failed to load from JSON:", e);
2072 console.log("Notebook failed to load from JSON:", e);
2066 }
2073 }
2067 if (failed || data.message) {
2074 if (failed || data.message) {
2068 // *either* fromJSON failed or validation failed
2075 // *either* fromJSON failed or validation failed
2069 var body = $("<div>");
2076 var body = $("<div>");
2070 var title;
2077 var title;
2071 if (failed) {
2078 if (failed) {
2072 title = "Notebook failed to load";
2079 title = "Notebook failed to load";
2073 body.append($("<p>").text(
2080 body.append($("<p>").text(
2074 "The error was: "
2081 "The error was: "
2075 )).append($("<div>").addClass("js-error").text(
2082 )).append($("<div>").addClass("js-error").text(
2076 failed.toString()
2083 failed.toString()
2077 )).append($("<p>").text(
2084 )).append($("<p>").text(
2078 "See the error console for details."
2085 "See the error console for details."
2079 ));
2086 ));
2080 } else {
2087 } else {
2081 title = "Notebook validation failed";
2088 title = "Notebook validation failed";
2082 }
2089 }
2083
2090
2084 if (data.message) {
2091 if (data.message) {
2085 if (failed) {
2092 if (failed) {
2086 msg = "The notebook also failed validation:";
2093 msg = "The notebook also failed validation:";
2087 } else {
2094 } else {
2088 msg = "An invalid notebook may not function properly." +
2095 msg = "An invalid notebook may not function properly." +
2089 " The validation error was:";
2096 " The validation error was:";
2090 }
2097 }
2091 body.append($("<p>").text(
2098 body.append($("<p>").text(
2092 msg
2099 msg
2093 )).append($("<div>").addClass("validation-error").append(
2100 )).append($("<div>").addClass("validation-error").append(
2094 $("<pre>").text(data.message)
2101 $("<pre>").text(data.message)
2095 ));
2102 ));
2096 }
2103 }
2097
2104
2098 dialog.modal({
2105 dialog.modal({
2099 notebook: this,
2106 notebook: this,
2100 keyboard_manager: this.keyboard_manager,
2107 keyboard_manager: this.keyboard_manager,
2101 title: title,
2108 title: title,
2102 body: body,
2109 body: body,
2103 buttons : {
2110 buttons : {
2104 OK : {
2111 OK : {
2105 "class" : "btn-primary"
2112 "class" : "btn-primary"
2106 }
2113 }
2107 }
2114 }
2108 });
2115 });
2109 }
2116 }
2110 if (this.ncells() === 0) {
2117 if (this.ncells() === 0) {
2111 this.insert_cell_below('code');
2118 this.insert_cell_below('code');
2112 this.edit_mode(0);
2119 this.edit_mode(0);
2113 } else {
2120 } else {
2114 this.select(0);
2121 this.select(0);
2115 this.handle_command_mode(this.get_cell(0));
2122 this.handle_command_mode(this.get_cell(0));
2116 }
2123 }
2117 this.set_dirty(false);
2124 this.set_dirty(false);
2118 this.scroll_to_top();
2125 this.scroll_to_top();
2119 this.writable = data.writable || false;
2126 this.writable = data.writable || false;
2120 var nbmodel = data.content;
2127 var nbmodel = data.content;
2121 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2128 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2122 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2129 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2123 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2130 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2124 var src;
2131 var src;
2125 if (nbmodel.nbformat > orig_nbformat) {
2132 if (nbmodel.nbformat > orig_nbformat) {
2126 src = " an older notebook format ";
2133 src = " an older notebook format ";
2127 } else {
2134 } else {
2128 src = " a newer notebook format ";
2135 src = " a newer notebook format ";
2129 }
2136 }
2130
2137
2131 msg = "This notebook has been converted from" + src +
2138 msg = "This notebook has been converted from" + src +
2132 "(v"+orig_nbformat+") to the current notebook " +
2139 "(v"+orig_nbformat+") to the current notebook " +
2133 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2140 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2134 "current notebook format will be used.";
2141 "current notebook format will be used.";
2135
2142
2136 if (nbmodel.nbformat > orig_nbformat) {
2143 if (nbmodel.nbformat > orig_nbformat) {
2137 msg += " Older versions of IPython may not be able to read the new format.";
2144 msg += " Older versions of IPython may not be able to read the new format.";
2138 } else {
2145 } else {
2139 msg += " Some features of the original notebook may not be available.";
2146 msg += " Some features of the original notebook may not be available.";
2140 }
2147 }
2141 msg += " To preserve the original version, close the " +
2148 msg += " To preserve the original version, close the " +
2142 "notebook without saving it.";
2149 "notebook without saving it.";
2143 dialog.modal({
2150 dialog.modal({
2144 notebook: this,
2151 notebook: this,
2145 keyboard_manager: this.keyboard_manager,
2152 keyboard_manager: this.keyboard_manager,
2146 title : "Notebook converted",
2153 title : "Notebook converted",
2147 body : msg,
2154 body : msg,
2148 buttons : {
2155 buttons : {
2149 OK : {
2156 OK : {
2150 class : "btn-primary"
2157 class : "btn-primary"
2151 }
2158 }
2152 }
2159 }
2153 });
2160 });
2154 } else if (this.nbformat_minor < nbmodel.nbformat_minor) {
2161 } else if (this.nbformat_minor < nbmodel.nbformat_minor) {
2155 this.nbformat_minor = nbmodel.nbformat_minor;
2162 this.nbformat_minor = nbmodel.nbformat_minor;
2156 }
2163 }
2157
2164
2158 // Create the session after the notebook is completely loaded to prevent
2165 // Create the session after the notebook is completely loaded to prevent
2159 // code execution upon loading, which is a security risk.
2166 // code execution upon loading, which is a security risk.
2160 if (this.session === null) {
2167 if (this.session === null) {
2161 var kernel_name;
2168 var kernel_name;
2162 if (this.metadata.kernelspec) {
2169 if (this.metadata.kernelspec) {
2163 var kernelspec = this.metadata.kernelspec || {};
2170 var kernelspec = this.metadata.kernelspec || {};
2164 kernel_name = kernelspec.name;
2171 kernel_name = kernelspec.name;
2165 } else {
2172 } else {
2166 kernel_name = utils.get_url_param('kernel_name');
2173 kernel_name = utils.get_url_param('kernel_name');
2167 }
2174 }
2168 this.start_session(kernel_name);
2175 this.start_session(kernel_name);
2169 }
2176 }
2170 // load our checkpoint list
2177 // load our checkpoint list
2171 this.list_checkpoints();
2178 this.list_checkpoints();
2172
2179
2173 // load toolbar state
2180 // load toolbar state
2174 if (this.metadata.celltoolbar) {
2181 if (this.metadata.celltoolbar) {
2175 celltoolbar.CellToolbar.global_show();
2182 celltoolbar.CellToolbar.global_show();
2176 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2183 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2177 } else {
2184 } else {
2178 celltoolbar.CellToolbar.global_hide();
2185 celltoolbar.CellToolbar.global_hide();
2179 }
2186 }
2180
2187
2181 if (!this.writable) {
2188 if (!this.writable) {
2182 this.set_autosave_interval(0);
2189 this.set_autosave_interval(0);
2183 this.events.trigger('notebook_read_only.Notebook');
2190 this.events.trigger('notebook_read_only.Notebook');
2184 }
2191 }
2185
2192
2186 // now that we're fully loaded, it is safe to restore save functionality
2193 // now that we're fully loaded, it is safe to restore save functionality
2187 this._fully_loaded = true;
2194 this._fully_loaded = true;
2188 this.events.trigger('notebook_loaded.Notebook');
2195 this.events.trigger('notebook_loaded.Notebook');
2189 };
2196 };
2190
2197
2198 Notebook.prototype.set_kernelselector = function(k_selector){
2199 this.kernel_selector = k_selector;
2200 };
2201
2191 /**
2202 /**
2192 * Failure callback for loading a notebook from the server.
2203 * Failure callback for loading a notebook from the server.
2193 *
2204 *
2194 * @param {Error} error
2205 * @param {Error} error
2195 */
2206 */
2196 Notebook.prototype.load_notebook_error = function (error) {
2207 Notebook.prototype.load_notebook_error = function (error) {
2197 this.events.trigger('notebook_load_failed.Notebook', error);
2208 this.events.trigger('notebook_load_failed.Notebook', error);
2198 var msg;
2209 var msg;
2199 if (error.name === utils.XHR_ERROR && error.xhr.status === 500) {
2210 if (error.name === utils.XHR_ERROR && error.xhr.status === 500) {
2200 utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
2211 utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
2201 msg = "An unknown error occurred while loading this notebook. " +
2212 msg = "An unknown error occurred while loading this notebook. " +
2202 "This version can load notebook formats " +
2213 "This version can load notebook formats " +
2203 "v" + this.nbformat + " or earlier. See the server log for details.";
2214 "v" + this.nbformat + " or earlier. See the server log for details.";
2204 } else {
2215 } else {
2205 msg = error.message;
2216 msg = error.message;
2206 }
2217 }
2207 dialog.modal({
2218 dialog.modal({
2208 notebook: this,
2219 notebook: this,
2209 keyboard_manager: this.keyboard_manager,
2220 keyboard_manager: this.keyboard_manager,
2210 title: "Error loading notebook",
2221 title: "Error loading notebook",
2211 body : msg,
2222 body : msg,
2212 buttons : {
2223 buttons : {
2213 "OK": {}
2224 "OK": {}
2214 }
2225 }
2215 });
2226 });
2216 };
2227 };
2217
2228
2218 /********************* checkpoint-related ********************/
2229 /********************* checkpoint-related ********************/
2219
2230
2220 /**
2231 /**
2221 * Save the notebook then immediately create a checkpoint.
2232 * Save the notebook then immediately create a checkpoint.
2222 */
2233 */
2223 Notebook.prototype.save_checkpoint = function () {
2234 Notebook.prototype.save_checkpoint = function () {
2224 this._checkpoint_after_save = true;
2235 this._checkpoint_after_save = true;
2225 this.save_notebook();
2236 this.save_notebook();
2226 };
2237 };
2227
2238
2228 /**
2239 /**
2229 * Add a checkpoint for this notebook.
2240 * Add a checkpoint for this notebook.
2230 */
2241 */
2231 Notebook.prototype.add_checkpoint = function (checkpoint) {
2242 Notebook.prototype.add_checkpoint = function (checkpoint) {
2232 var found = false;
2243 var found = false;
2233 for (var i = 0; i < this.checkpoints.length; i++) {
2244 for (var i = 0; i < this.checkpoints.length; i++) {
2234 var existing = this.checkpoints[i];
2245 var existing = this.checkpoints[i];
2235 if (existing.id == checkpoint.id) {
2246 if (existing.id == checkpoint.id) {
2236 found = true;
2247 found = true;
2237 this.checkpoints[i] = checkpoint;
2248 this.checkpoints[i] = checkpoint;
2238 break;
2249 break;
2239 }
2250 }
2240 }
2251 }
2241 if (!found) {
2252 if (!found) {
2242 this.checkpoints.push(checkpoint);
2253 this.checkpoints.push(checkpoint);
2243 }
2254 }
2244 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2255 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2245 };
2256 };
2246
2257
2247 /**
2258 /**
2248 * List checkpoints for this notebook.
2259 * List checkpoints for this notebook.
2249 */
2260 */
2250 Notebook.prototype.list_checkpoints = function () {
2261 Notebook.prototype.list_checkpoints = function () {
2251 var that = this;
2262 var that = this;
2252 this.contents.list_checkpoints(this.notebook_path).then(
2263 this.contents.list_checkpoints(this.notebook_path).then(
2253 $.proxy(this.list_checkpoints_success, this),
2264 $.proxy(this.list_checkpoints_success, this),
2254 function(error) {
2265 function(error) {
2255 that.events.trigger('list_checkpoints_failed.Notebook', error);
2266 that.events.trigger('list_checkpoints_failed.Notebook', error);
2256 }
2267 }
2257 );
2268 );
2258 };
2269 };
2259
2270
2260 /**
2271 /**
2261 * Success callback for listing checkpoints.
2272 * Success callback for listing checkpoints.
2262 *
2273 *
2263 * @param {object} data - JSON representation of a checkpoint
2274 * @param {object} data - JSON representation of a checkpoint
2264 */
2275 */
2265 Notebook.prototype.list_checkpoints_success = function (data) {
2276 Notebook.prototype.list_checkpoints_success = function (data) {
2266 this.checkpoints = data;
2277 this.checkpoints = data;
2267 if (data.length) {
2278 if (data.length) {
2268 this.last_checkpoint = data[data.length - 1];
2279 this.last_checkpoint = data[data.length - 1];
2269 } else {
2280 } else {
2270 this.last_checkpoint = null;
2281 this.last_checkpoint = null;
2271 }
2282 }
2272 this.events.trigger('checkpoints_listed.Notebook', [data]);
2283 this.events.trigger('checkpoints_listed.Notebook', [data]);
2273 };
2284 };
2274
2285
2275 /**
2286 /**
2276 * Create a checkpoint of this notebook on the server from the most recent save.
2287 * Create a checkpoint of this notebook on the server from the most recent save.
2277 */
2288 */
2278 Notebook.prototype.create_checkpoint = function () {
2289 Notebook.prototype.create_checkpoint = function () {
2279 var that = this;
2290 var that = this;
2280 this.contents.create_checkpoint(this.notebook_path).then(
2291 this.contents.create_checkpoint(this.notebook_path).then(
2281 $.proxy(this.create_checkpoint_success, this),
2292 $.proxy(this.create_checkpoint_success, this),
2282 function (error) {
2293 function (error) {
2283 that.events.trigger('checkpoint_failed.Notebook', error);
2294 that.events.trigger('checkpoint_failed.Notebook', error);
2284 }
2295 }
2285 );
2296 );
2286 };
2297 };
2287
2298
2288 /**
2299 /**
2289 * Success callback for creating a checkpoint.
2300 * Success callback for creating a checkpoint.
2290 *
2301 *
2291 * @param {object} data - JSON representation of a checkpoint
2302 * @param {object} data - JSON representation of a checkpoint
2292 */
2303 */
2293 Notebook.prototype.create_checkpoint_success = function (data) {
2304 Notebook.prototype.create_checkpoint_success = function (data) {
2294 this.add_checkpoint(data);
2305 this.add_checkpoint(data);
2295 this.events.trigger('checkpoint_created.Notebook', data);
2306 this.events.trigger('checkpoint_created.Notebook', data);
2296 };
2307 };
2297
2308
2298 /**
2309 /**
2299 * Display the restore checkpoint dialog
2310 * Display the restore checkpoint dialog
2300 * @param {string} checkpoint ID
2311 * @param {string} checkpoint ID
2301 */
2312 */
2302 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2313 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2303 var that = this;
2314 var that = this;
2304 checkpoint = checkpoint || this.last_checkpoint;
2315 checkpoint = checkpoint || this.last_checkpoint;
2305 if ( ! checkpoint ) {
2316 if ( ! checkpoint ) {
2306 console.log("restore dialog, but no checkpoint to restore to!");
2317 console.log("restore dialog, but no checkpoint to restore to!");
2307 return;
2318 return;
2308 }
2319 }
2309 var body = $('<div/>').append(
2320 var body = $('<div/>').append(
2310 $('<p/>').addClass("p-space").text(
2321 $('<p/>').addClass("p-space").text(
2311 "Are you sure you want to revert the notebook to " +
2322 "Are you sure you want to revert the notebook to " +
2312 "the latest checkpoint?"
2323 "the latest checkpoint?"
2313 ).append(
2324 ).append(
2314 $("<strong/>").text(
2325 $("<strong/>").text(
2315 " This cannot be undone."
2326 " This cannot be undone."
2316 )
2327 )
2317 )
2328 )
2318 ).append(
2329 ).append(
2319 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2330 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2320 ).append(
2331 ).append(
2321 $('<p/>').addClass("p-space").text(
2332 $('<p/>').addClass("p-space").text(
2322 Date(checkpoint.last_modified)
2333 Date(checkpoint.last_modified)
2323 ).css("text-align", "center")
2334 ).css("text-align", "center")
2324 );
2335 );
2325
2336
2326 dialog.modal({
2337 dialog.modal({
2327 notebook: this,
2338 notebook: this,
2328 keyboard_manager: this.keyboard_manager,
2339 keyboard_manager: this.keyboard_manager,
2329 title : "Revert notebook to checkpoint",
2340 title : "Revert notebook to checkpoint",
2330 body : body,
2341 body : body,
2331 buttons : {
2342 buttons : {
2332 Revert : {
2343 Revert : {
2333 class : "btn-danger",
2344 class : "btn-danger",
2334 click : function () {
2345 click : function () {
2335 that.restore_checkpoint(checkpoint.id);
2346 that.restore_checkpoint(checkpoint.id);
2336 }
2347 }
2337 },
2348 },
2338 Cancel : {}
2349 Cancel : {}
2339 }
2350 }
2340 });
2351 });
2341 };
2352 };
2342
2353
2343 /**
2354 /**
2344 * Restore the notebook to a checkpoint state.
2355 * Restore the notebook to a checkpoint state.
2345 *
2356 *
2346 * @param {string} checkpoint ID
2357 * @param {string} checkpoint ID
2347 */
2358 */
2348 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2359 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2349 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2360 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2350 var that = this;
2361 var that = this;
2351 this.contents.restore_checkpoint(this.notebook_path, checkpoint).then(
2362 this.contents.restore_checkpoint(this.notebook_path, checkpoint).then(
2352 $.proxy(this.restore_checkpoint_success, this),
2363 $.proxy(this.restore_checkpoint_success, this),
2353 function (error) {
2364 function (error) {
2354 that.events.trigger('checkpoint_restore_failed.Notebook', error);
2365 that.events.trigger('checkpoint_restore_failed.Notebook', error);
2355 }
2366 }
2356 );
2367 );
2357 };
2368 };
2358
2369
2359 /**
2370 /**
2360 * Success callback for restoring a notebook to a checkpoint.
2371 * Success callback for restoring a notebook to a checkpoint.
2361 */
2372 */
2362 Notebook.prototype.restore_checkpoint_success = function () {
2373 Notebook.prototype.restore_checkpoint_success = function () {
2363 this.events.trigger('checkpoint_restored.Notebook');
2374 this.events.trigger('checkpoint_restored.Notebook');
2364 this.load_notebook(this.notebook_path);
2375 this.load_notebook(this.notebook_path);
2365 };
2376 };
2366
2377
2367 /**
2378 /**
2368 * Delete a notebook checkpoint.
2379 * Delete a notebook checkpoint.
2369 *
2380 *
2370 * @param {string} checkpoint ID
2381 * @param {string} checkpoint ID
2371 */
2382 */
2372 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2383 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2373 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2384 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2374 var that = this;
2385 var that = this;
2375 this.contents.delete_checkpoint(this.notebook_path, checkpoint).then(
2386 this.contents.delete_checkpoint(this.notebook_path, checkpoint).then(
2376 $.proxy(this.delete_checkpoint_success, this),
2387 $.proxy(this.delete_checkpoint_success, this),
2377 function (error) {
2388 function (error) {
2378 that.events.trigger('checkpoint_delete_failed.Notebook', error);
2389 that.events.trigger('checkpoint_delete_failed.Notebook', error);
2379 }
2390 }
2380 );
2391 );
2381 };
2392 };
2382
2393
2383 /**
2394 /**
2384 * Success callback for deleting a notebook checkpoint.
2395 * Success callback for deleting a notebook checkpoint.
2385 */
2396 */
2386 Notebook.prototype.delete_checkpoint_success = function () {
2397 Notebook.prototype.delete_checkpoint_success = function () {
2387 this.events.trigger('checkpoint_deleted.Notebook');
2398 this.events.trigger('checkpoint_deleted.Notebook');
2388 this.load_notebook(this.notebook_path);
2399 this.load_notebook(this.notebook_path);
2389 };
2400 };
2390
2401
2391
2402
2392 // For backwards compatability.
2403 // For backwards compatability.
2393 IPython.Notebook = Notebook;
2404 IPython.Notebook = Notebook;
2394
2405
2395 return {'Notebook': Notebook};
2406 return {'Notebook': Notebook};
2396 });
2407 });
@@ -1,333 +1,334 b''
1 {% extends "page.html" %}
1 {% extends "page.html" %}
2
2
3 {% block stylesheet %}
3 {% block stylesheet %}
4
4
5 {% if mathjax_url %}
5 {% if mathjax_url %}
6 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script>
6 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script>
7 {% endif %}
7 {% endif %}
8 <script type="text/javascript">
8 <script type="text/javascript">
9 // MathJax disabled, set as null to distingish from *missing* MathJax,
9 // MathJax disabled, set as null to distingish from *missing* MathJax,
10 // where it will be undefined, and should prompt a dialog later.
10 // where it will be undefined, and should prompt a dialog later.
11 window.mathjax_url = "{{mathjax_url}}";
11 window.mathjax_url = "{{mathjax_url}}";
12 </script>
12 </script>
13
13
14 <link rel="stylesheet" href="{{ static_url("components/bootstrap-tour/build/css/bootstrap-tour.min.css") }}" type="text/css" />
14 <link rel="stylesheet" href="{{ static_url("components/bootstrap-tour/build/css/bootstrap-tour.min.css") }}" type="text/css" />
15 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
15 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
16
16
17 {{super()}}
17 {{super()}}
18
18
19 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
19 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
20 <link rel="stylesheet" href="" id='kernel-css' type="text/css" />
20
21
21 {% endblock %}
22 {% endblock %}
22
23
23 {% block params %}
24 {% block params %}
24
25
25 data-project="{{project}}"
26 data-project="{{project}}"
26 data-base-url="{{base_url}}"
27 data-base-url="{{base_url}}"
27 data-ws-url="{{ws_url}}"
28 data-ws-url="{{ws_url}}"
28 data-notebook-name="{{notebook_name}}"
29 data-notebook-name="{{notebook_name}}"
29 data-notebook-path="{{notebook_path}}"
30 data-notebook-path="{{notebook_path}}"
30 class="notebook_app"
31 class="notebook_app"
31
32
32 {% endblock %}
33 {% endblock %}
33
34
34
35
35 {% block headercontainer %}
36 {% block headercontainer %}
36
37
37
38
38 <span id="save_widget" class="pull-left save_widget">
39 <span id="save_widget" class="pull-left save_widget">
39 <span class="filename"></span>
40 <span class="filename"></span>
40 <span class="checkpoint_status"></span>
41 <span class="checkpoint_status"></span>
41 <span class="autosave_status"></span>
42 <span class="autosave_status"></span>
42 </span>
43 </span>
43
44
44 <div id="kernel_selector_widget" class="pull-right dropdown">
45 <div id="kernel_selector_widget" class="pull-right dropdown">
45 <!-- empty png in 26 bytes below-->
46 <!-- empty png in 26 bytes below-->
46 <img id="current_kernel_logo" src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D" alt='current kernel logo'>
47 <img id="current_kernel_logo" src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D" alt='current kernel logo'>
47 <button class="dropdown-toggle btn btn-sm navbar-btn" data-toggle="dropdown" type='button' id="current_kernel_spec">
48 <button class="dropdown-toggle btn btn-sm navbar-btn" data-toggle="dropdown" type='button' id="current_kernel_spec">
48 <span class='kernel_name'>Kernel</span>
49 <span class='kernel_name'>Kernel</span>
49 <span class="caret"></span>
50 <span class="caret"></span>
50 </button>
51 </button>
51 <ul id="kernel_selector" class="dropdown-menu">
52 <ul id="kernel_selector" class="dropdown-menu">
52 </ul>
53 </ul>
53 </div>
54 </div>
54
55
55 {% endblock headercontainer %}
56 {% endblock headercontainer %}
56
57
57 {% block header %}
58 {% block header %}
58 <div id="menubar-container" class="container">
59 <div id="menubar-container" class="container">
59 <div id="menubar">
60 <div id="menubar">
60 <div id="menus" class="navbar navbar-default" role="navigation">
61 <div id="menus" class="navbar navbar-default" role="navigation">
61 <div class="container-fluid">
62 <div class="container-fluid">
62 <button type="button" class="btn btn-default navbar-btn navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
63 <button type="button" class="btn btn-default navbar-btn navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
63 <i class="fa fa-bars"></i>
64 <i class="fa fa-bars"></i>
64 <span class="navbar-text">Menu</span>
65 <span class="navbar-text">Menu</span>
65 </button>
66 </button>
66 <p id="kernel_indicator" class="navbar-text">
67 <p id="kernel_indicator" class="navbar-text">
67 <i id="kernel_indicator_icon"></i>
68 <i id="kernel_indicator_icon"></i>
68 </p>
69 </p>
69 <p id="modal_indicator" class="navbar-text">
70 <p id="modal_indicator" class="navbar-text">
70 <i id="modal_indicator_icon"></i>
71 <i id="modal_indicator_icon"></i>
71 </p>
72 </p>
72 <span id="notification_area"></span>
73 <span id="notification_area"></span>
73 <div class="navbar-collapse collapse">
74 <div class="navbar-collapse collapse">
74 <ul class="nav navbar-nav">
75 <ul class="nav navbar-nav">
75 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
76 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
76 <ul id="file_menu" class="dropdown-menu">
77 <ul id="file_menu" class="dropdown-menu">
77 <li id="new_notebook"
78 <li id="new_notebook"
78 title="Make a new notebook (Opens a new window)">
79 title="Make a new notebook (Opens a new window)">
79 <a href="#">New</a></li>
80 <a href="#">New</a></li>
80 <li id="open_notebook"
81 <li id="open_notebook"
81 title="Opens a new window with the Dashboard view">
82 title="Opens a new window with the Dashboard view">
82 <a href="#">Open...</a></li>
83 <a href="#">Open...</a></li>
83 <!-- <hr/> -->
84 <!-- <hr/> -->
84 <li class="divider"></li>
85 <li class="divider"></li>
85 <li id="copy_notebook"
86 <li id="copy_notebook"
86 title="Open a copy of this notebook's contents and start a new kernel">
87 title="Open a copy of this notebook's contents and start a new kernel">
87 <a href="#">Make a Copy...</a></li>
88 <a href="#">Make a Copy...</a></li>
88 <li id="rename_notebook"><a href="#">Rename...</a></li>
89 <li id="rename_notebook"><a href="#">Rename...</a></li>
89 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
90 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
90 <!-- <hr/> -->
91 <!-- <hr/> -->
91 <li class="divider"></li>
92 <li class="divider"></li>
92 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
93 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
93 <ul class="dropdown-menu">
94 <ul class="dropdown-menu">
94 <li><a href="#"></a></li>
95 <li><a href="#"></a></li>
95 <li><a href="#"></a></li>
96 <li><a href="#"></a></li>
96 <li><a href="#"></a></li>
97 <li><a href="#"></a></li>
97 <li><a href="#"></a></li>
98 <li><a href="#"></a></li>
98 <li><a href="#"></a></li>
99 <li><a href="#"></a></li>
99 </ul>
100 </ul>
100 </li>
101 </li>
101 <li class="divider"></li>
102 <li class="divider"></li>
102 <li id="print_preview"><a href="#">Print Preview</a></li>
103 <li id="print_preview"><a href="#">Print Preview</a></li>
103 <li class="dropdown-submenu"><a href="#">Download as</a>
104 <li class="dropdown-submenu"><a href="#">Download as</a>
104 <ul class="dropdown-menu">
105 <ul class="dropdown-menu">
105 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
106 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
106 <li id="download_script"><a href="#">Script</a></li>
107 <li id="download_script"><a href="#">Script</a></li>
107 <li id="download_html"><a href="#">HTML (.html)</a></li>
108 <li id="download_html"><a href="#">HTML (.html)</a></li>
108 <li id="download_rst"><a href="#">reST (.rst)</a></li>
109 <li id="download_rst"><a href="#">reST (.rst)</a></li>
109 <li id="download_pdf"><a href="#">PDF (.pdf)</a></li>
110 <li id="download_pdf"><a href="#">PDF (.pdf)</a></li>
110 </ul>
111 </ul>
111 </li>
112 </li>
112 <li class="divider"></li>
113 <li class="divider"></li>
113 <li id="trust_notebook"
114 <li id="trust_notebook"
114 title="Trust the output of this notebook">
115 title="Trust the output of this notebook">
115 <a href="#" >Trust Notebook</a></li>
116 <a href="#" >Trust Notebook</a></li>
116 <li class="divider"></li>
117 <li class="divider"></li>
117 <li id="kill_and_exit"
118 <li id="kill_and_exit"
118 title="Shutdown this notebook's kernel, and close this window">
119 title="Shutdown this notebook's kernel, and close this window">
119 <a href="#" >Close and halt</a></li>
120 <a href="#" >Close and halt</a></li>
120 </ul>
121 </ul>
121 </li>
122 </li>
122 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
123 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
123 <ul id="edit_menu" class="dropdown-menu">
124 <ul id="edit_menu" class="dropdown-menu">
124 <li id="cut_cell"><a href="#">Cut Cell</a></li>
125 <li id="cut_cell"><a href="#">Cut Cell</a></li>
125 <li id="copy_cell"><a href="#">Copy Cell</a></li>
126 <li id="copy_cell"><a href="#">Copy Cell</a></li>
126 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
127 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
127 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
128 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
128 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
129 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
129 <li id="delete_cell"><a href="#">Delete Cell</a></li>
130 <li id="delete_cell"><a href="#">Delete Cell</a></li>
130 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
131 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
131 <li class="divider"></li>
132 <li class="divider"></li>
132 <li id="split_cell"><a href="#">Split Cell</a></li>
133 <li id="split_cell"><a href="#">Split Cell</a></li>
133 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
134 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
134 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
135 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
135 <li class="divider"></li>
136 <li class="divider"></li>
136 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
137 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
137 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
138 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
138 <li class="divider"></li>
139 <li class="divider"></li>
139 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
140 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
140 </ul>
141 </ul>
141 </li>
142 </li>
142 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
143 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
143 <ul id="view_menu" class="dropdown-menu">
144 <ul id="view_menu" class="dropdown-menu">
144 <li id="toggle_header"
145 <li id="toggle_header"
145 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
146 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
146 <a href="#">Toggle Header</a></li>
147 <a href="#">Toggle Header</a></li>
147 <li id="toggle_toolbar"
148 <li id="toggle_toolbar"
148 title="Show/Hide the action icons (below menu bar)">
149 title="Show/Hide the action icons (below menu bar)">
149 <a href="#">Toggle Toolbar</a></li>
150 <a href="#">Toggle Toolbar</a></li>
150 </ul>
151 </ul>
151 </li>
152 </li>
152 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
153 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
153 <ul id="insert_menu" class="dropdown-menu">
154 <ul id="insert_menu" class="dropdown-menu">
154 <li id="insert_cell_above"
155 <li id="insert_cell_above"
155 title="Insert an empty Code cell above the currently active cell">
156 title="Insert an empty Code cell above the currently active cell">
156 <a href="#">Insert Cell Above</a></li>
157 <a href="#">Insert Cell Above</a></li>
157 <li id="insert_cell_below"
158 <li id="insert_cell_below"
158 title="Insert an empty Code cell below the currently active cell">
159 title="Insert an empty Code cell below the currently active cell">
159 <a href="#">Insert Cell Below</a></li>
160 <a href="#">Insert Cell Below</a></li>
160 </ul>
161 </ul>
161 </li>
162 </li>
162 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
163 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
163 <ul id="cell_menu" class="dropdown-menu">
164 <ul id="cell_menu" class="dropdown-menu">
164 <li id="run_cell" title="Run this cell, and move cursor to the next one">
165 <li id="run_cell" title="Run this cell, and move cursor to the next one">
165 <a href="#">Run</a></li>
166 <a href="#">Run</a></li>
166 <li id="run_cell_select_below" title="Run this cell, select below">
167 <li id="run_cell_select_below" title="Run this cell, select below">
167 <a href="#">Run and Select Below</a></li>
168 <a href="#">Run and Select Below</a></li>
168 <li id="run_cell_insert_below" title="Run this cell, insert below">
169 <li id="run_cell_insert_below" title="Run this cell, insert below">
169 <a href="#">Run and Insert Below</a></li>
170 <a href="#">Run and Insert Below</a></li>
170 <li id="run_all_cells" title="Run all cells in the notebook">
171 <li id="run_all_cells" title="Run all cells in the notebook">
171 <a href="#">Run All</a></li>
172 <a href="#">Run All</a></li>
172 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
173 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
173 <a href="#">Run All Above</a></li>
174 <a href="#">Run All Above</a></li>
174 <li id="run_all_cells_below" title="Run this cell and all cells below it">
175 <li id="run_all_cells_below" title="Run this cell and all cells below it">
175 <a href="#">Run All Below</a></li>
176 <a href="#">Run All Below</a></li>
176 <li class="divider"></li>
177 <li class="divider"></li>
177 <li id="change_cell_type" class="dropdown-submenu"
178 <li id="change_cell_type" class="dropdown-submenu"
178 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
179 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
179 <a href="#">Cell Type</a>
180 <a href="#">Cell Type</a>
180 <ul class="dropdown-menu">
181 <ul class="dropdown-menu">
181 <li id="to_code"
182 <li id="to_code"
182 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
183 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
183 <a href="#">Code</a></li>
184 <a href="#">Code</a></li>
184 <li id="to_markdown"
185 <li id="to_markdown"
185 title="Contents will be rendered as HTML and serve as explanatory text">
186 title="Contents will be rendered as HTML and serve as explanatory text">
186 <a href="#">Markdown</a></li>
187 <a href="#">Markdown</a></li>
187 <li id="to_raw"
188 <li id="to_raw"
188 title="Contents will pass through nbconvert unmodified">
189 title="Contents will pass through nbconvert unmodified">
189 <a href="#">Raw NBConvert</a></li>
190 <a href="#">Raw NBConvert</a></li>
190 </ul>
191 </ul>
191 </li>
192 </li>
192 <li class="divider"></li>
193 <li class="divider"></li>
193 <li id="current_outputs" class="dropdown-submenu"><a href="#">Current Output</a>
194 <li id="current_outputs" class="dropdown-submenu"><a href="#">Current Output</a>
194 <ul class="dropdown-menu">
195 <ul class="dropdown-menu">
195 <li id="toggle_current_output"
196 <li id="toggle_current_output"
196 title="Hide/Show the output of the current cell">
197 title="Hide/Show the output of the current cell">
197 <a href="#">Toggle</a>
198 <a href="#">Toggle</a>
198 </li>
199 </li>
199 <li id="toggle_current_output_scroll"
200 <li id="toggle_current_output_scroll"
200 title="Scroll the output of the current cell">
201 title="Scroll the output of the current cell">
201 <a href="#">Toggle Scrolling</a>
202 <a href="#">Toggle Scrolling</a>
202 </li>
203 </li>
203 <li id="clear_current_output"
204 <li id="clear_current_output"
204 title="Clear the output of the current cell">
205 title="Clear the output of the current cell">
205 <a href="#">Clear</a>
206 <a href="#">Clear</a>
206 </li>
207 </li>
207 </ul>
208 </ul>
208 </li>
209 </li>
209 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
210 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
210 <ul class="dropdown-menu">
211 <ul class="dropdown-menu">
211 <li id="toggle_all_output"
212 <li id="toggle_all_output"
212 title="Hide/Show the output of all cells">
213 title="Hide/Show the output of all cells">
213 <a href="#">Toggle</a>
214 <a href="#">Toggle</a>
214 </li>
215 </li>
215 <li id="toggle_all_output_scroll"
216 <li id="toggle_all_output_scroll"
216 title="Scroll the output of all cells">
217 title="Scroll the output of all cells">
217 <a href="#">Toggle Scrolling</a>
218 <a href="#">Toggle Scrolling</a>
218 </li>
219 </li>
219 <li id="clear_all_output"
220 <li id="clear_all_output"
220 title="Clear the output of all cells">
221 title="Clear the output of all cells">
221 <a href="#">Clear</a>
222 <a href="#">Clear</a>
222 </li>
223 </li>
223 </ul>
224 </ul>
224 </li>
225 </li>
225 </ul>
226 </ul>
226 </li>
227 </li>
227 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
228 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
228 <ul id="kernel_menu" class="dropdown-menu">
229 <ul id="kernel_menu" class="dropdown-menu">
229 <li id="int_kernel"
230 <li id="int_kernel"
230 title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
231 title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
231 <a href="#">Interrupt</a>
232 <a href="#">Interrupt</a>
232 </li>
233 </li>
233 <li id="restart_kernel"
234 <li id="restart_kernel"
234 title="Restart the Kernel">
235 title="Restart the Kernel">
235 <a href="#">Restart</a>
236 <a href="#">Restart</a>
236 </li>
237 </li>
237 <li id="reconnect_kernel"
238 <li id="reconnect_kernel"
238 title="Reconnect to the Kernel">
239 title="Reconnect to the Kernel">
239 <a href="#">Reconnect</a>
240 <a href="#">Reconnect</a>
240 </li>
241 </li>
241 <li class="divider"></li>
242 <li class="divider"></li>
242 <li id="menu-change-kernel" class="dropdown-submenu">
243 <li id="menu-change-kernel" class="dropdown-submenu">
243 <a href="#">Change kernel</a>
244 <a href="#">Change kernel</a>
244 <ul class="dropdown-menu" id="menu-change-kernel-submenu"></ul>
245 <ul class="dropdown-menu" id="menu-change-kernel-submenu"></ul>
245 </li>
246 </li>
246 </ul>
247 </ul>
247 </li>
248 </li>
248 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
249 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
249 <ul id="help_menu" class="dropdown-menu">
250 <ul id="help_menu" class="dropdown-menu">
250 <li id="notebook_tour" title="A quick tour of the notebook user interface"><a href="#">User Interface Tour</a></li>
251 <li id="notebook_tour" title="A quick tour of the notebook user interface"><a href="#">User Interface Tour</a></li>
251 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
252 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
252 <li class="divider"></li>
253 <li class="divider"></li>
253 {% set
254 {% set
254 sections = (
255 sections = (
255 (
256 (
256 ("http://ipython.org/documentation.html","IPython Help",True),
257 ("http://ipython.org/documentation.html","IPython Help",True),
257 ("http://nbviewer.ipython.org/github/ipython/ipython/tree/2.x/examples/Index.ipynb", "Notebook Help", True),
258 ("http://nbviewer.ipython.org/github/ipython/ipython/tree/2.x/examples/Index.ipynb", "Notebook Help", True),
258 ),(
259 ),(
259 ("http://docs.python.org","Python",True),
260 ("http://docs.python.org","Python",True),
260 ("http://help.github.com/articles/github-flavored-markdown","Markdown",True),
261 ("http://help.github.com/articles/github-flavored-markdown","Markdown",True),
261 ("http://docs.scipy.org/doc/numpy/reference/","NumPy",True),
262 ("http://docs.scipy.org/doc/numpy/reference/","NumPy",True),
262 ("http://docs.scipy.org/doc/scipy/reference/","SciPy",True),
263 ("http://docs.scipy.org/doc/scipy/reference/","SciPy",True),
263 ("http://matplotlib.org/contents.html","Matplotlib",True),
264 ("http://matplotlib.org/contents.html","Matplotlib",True),
264 ("http://docs.sympy.org/latest/index.html","SymPy",True),
265 ("http://docs.sympy.org/latest/index.html","SymPy",True),
265 ("http://pandas.pydata.org/pandas-docs/stable/","pandas", True)
266 ("http://pandas.pydata.org/pandas-docs/stable/","pandas", True)
266 )
267 )
267 )
268 )
268 %}
269 %}
269
270
270 {% for helplinks in sections %}
271 {% for helplinks in sections %}
271 {% for link in helplinks %}
272 {% for link in helplinks %}
272 <li><a href="{{link[0]}}" {{'target="_blank" title="Opens in a new window"' if link[2]}}>
273 <li><a href="{{link[0]}}" {{'target="_blank" title="Opens in a new window"' if link[2]}}>
273 {{'<i class="fa fa-external-link menu-icon pull-right"></i>' if link[2]}}
274 {{'<i class="fa fa-external-link menu-icon pull-right"></i>' if link[2]}}
274 {{link[1]}}
275 {{link[1]}}
275 </a></li>
276 </a></li>
276 {% endfor %}
277 {% endfor %}
277 {% if not loop.last %}
278 {% if not loop.last %}
278 <li class="divider"></li>
279 <li class="divider"></li>
279 {% endif %}
280 {% endif %}
280 {% endfor %}
281 {% endfor %}
281 <li class="divider"></li>
282 <li class="divider"></li>
282 <li title="About IPython Notebook"><a id="notebook_about" href="#">About</a></li>
283 <li title="About IPython Notebook"><a id="notebook_about" href="#">About</a></li>
283 </ul>
284 </ul>
284 </li>
285 </li>
285 </ul>
286 </ul>
286 </div>
287 </div>
287 </div>
288 </div>
288 </div>
289 </div>
289 </div>
290 </div>
290
291
291 <div id="maintoolbar" class="navbar">
292 <div id="maintoolbar" class="navbar">
292 <div class="toolbar-inner navbar-inner navbar-nobg">
293 <div class="toolbar-inner navbar-inner navbar-nobg">
293 <div id="maintoolbar-container" class="container"></div>
294 <div id="maintoolbar-container" class="container"></div>
294 </div>
295 </div>
295 </div>
296 </div>
296 </div>
297 </div>
297
298
298 <div class="lower-header-bar"></div>
299 <div class="lower-header-bar"></div>
299 {% endblock header %}
300 {% endblock header %}
300
301
301 {% block site %}
302 {% block site %}
302
303
303
304
304 <div id="ipython-main-app">
305 <div id="ipython-main-app">
305 <div id="notebook_panel">
306 <div id="notebook_panel">
306 <div id="notebook"></div>
307 <div id="notebook"></div>
307 </div>
308 </div>
308 </div>
309 </div>
309
310
310 <div id="pager">
311 <div id="pager">
311 <div id="pager-contents">
312 <div id="pager-contents">
312 <div id="pager-container" class="container"></div>
313 <div id="pager-container" class="container"></div>
313 </div>
314 </div>
314 <div id='pager-button-area'></div>
315 <div id='pager-button-area'></div>
315 </div>
316 </div>
316
317
317 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
318 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
318
319
319
320
320 {% endblock %}
321 {% endblock %}
321
322
322
323
323 {% block script %}
324 {% block script %}
324 {{super()}}
325 {{super()}}
325 <script type="text/javascript">
326 <script type="text/javascript">
326 sys_info = {{sys_info}};
327 sys_info = {{sys_info}};
327 </script>
328 </script>
328
329
329 <script src="{{ static_url("components/text-encoding/lib/encoding.js") }}" charset="utf-8"></script>
330 <script src="{{ static_url("components/text-encoding/lib/encoding.js") }}" charset="utf-8"></script>
330
331
331 <script src="{{ static_url("notebook/js/main.js") }}" charset="utf-8"></script>
332 <script src="{{ static_url("notebook/js/main.js") }}" charset="utf-8"></script>
332
333
333 {% endblock %}
334 {% endblock %}
@@ -1,119 +1,120 b''
1 <!DOCTYPE HTML>
1 <!DOCTYPE HTML>
2 <html>
2 <html>
3
3
4 <head>
4 <head>
5 <meta charset="utf-8">
5 <meta charset="utf-8">
6
6
7 <title>{% block title %}IPython Notebook{% endblock %}</title>
7 <title>{% block title %}IPython Notebook{% endblock %}</title>
8 {% block favicon %}<link rel="shortcut icon" type="image/x-icon" href="{{static_url("base/images/favicon.ico") }}">{% endblock %}
8 {% block favicon %}<link rel="shortcut icon" type="image/x-icon" href="{{static_url("base/images/favicon.ico") }}">{% endblock %}
9 <meta http-equiv="X-UA-Compatible" content="IE=edge" />
9 <meta http-equiv="X-UA-Compatible" content="IE=edge" />
10 <link rel="stylesheet" href="{{static_url("components/jquery-ui/themes/smoothness/jquery-ui.min.css") }}" type="text/css" />
10 <link rel="stylesheet" href="{{static_url("components/jquery-ui/themes/smoothness/jquery-ui.min.css") }}" type="text/css" />
11 <meta name="viewport" content="width=device-width, initial-scale=1.0">
11 <meta name="viewport" content="width=device-width, initial-scale=1.0">
12
12
13 {% block stylesheet %}
13 {% block stylesheet %}
14 <link rel="stylesheet" href="{{ static_url("style/style.min.css") }}" type="text/css"/>
14 <link rel="stylesheet" href="{{ static_url("style/style.min.css") }}" type="text/css"/>
15 {% endblock %}
15 {% endblock %}
16 <link rel="stylesheet" href="{{ static_url("custom/custom.css") }}" type="text/css" />
16 <link rel="stylesheet" href="{{ static_url("custom/custom.css") }}" type="text/css" />
17 <script src="{{static_url("components/es6-promise/promise.min.js")}}" type="text/javascript" charset="utf-8"></script>
17 <script src="{{static_url("components/es6-promise/promise.min.js")}}" type="text/javascript" charset="utf-8"></script>
18 <script src="{{static_url("components/requirejs/require.js") }}" type="text/javascript" charset="utf-8"></script>
18 <script src="{{static_url("components/requirejs/require.js") }}" type="text/javascript" charset="utf-8"></script>
19 <script>
19 <script>
20 require.config({
20 require.config({
21 {% if version_hash %}
21 {% if version_hash %}
22 urlArgs: "v={{version_hash}}",
22 urlArgs: "v={{version_hash}}",
23 {% endif %}
23 {% endif %}
24 baseUrl: '{{static_url("", include_version=False)}}',
24 baseUrl: '{{static_url("", include_version=False)}}',
25 paths: {
25 paths: {
26 nbextensions : '{{ base_url }}nbextensions',
26 nbextensions : '{{ base_url }}nbextensions',
27 kernelspecs : '{{ base_url }}kernelspecs',
27 underscore : 'components/underscore/underscore-min',
28 underscore : 'components/underscore/underscore-min',
28 backbone : 'components/backbone/backbone-min',
29 backbone : 'components/backbone/backbone-min',
29 jquery: 'components/jquery/jquery.min',
30 jquery: 'components/jquery/jquery.min',
30 bootstrap: 'components/bootstrap/js/bootstrap.min',
31 bootstrap: 'components/bootstrap/js/bootstrap.min',
31 bootstraptour: 'components/bootstrap-tour/build/js/bootstrap-tour.min',
32 bootstraptour: 'components/bootstrap-tour/build/js/bootstrap-tour.min',
32 jqueryui: 'components/jquery-ui/ui/minified/jquery-ui.min',
33 jqueryui: 'components/jquery-ui/ui/minified/jquery-ui.min',
33 moment: 'components/moment/moment',
34 moment: 'components/moment/moment',
34 codemirror: 'components/codemirror',
35 codemirror: 'components/codemirror',
35 termjs: 'components/term.js/src/term',
36 termjs: 'components/term.js/src/term',
36 },
37 },
37 shim: {
38 shim: {
38 underscore: {
39 underscore: {
39 exports: '_'
40 exports: '_'
40 },
41 },
41 backbone: {
42 backbone: {
42 deps: ["underscore", "jquery"],
43 deps: ["underscore", "jquery"],
43 exports: "Backbone"
44 exports: "Backbone"
44 },
45 },
45 bootstrap: {
46 bootstrap: {
46 deps: ["jquery"],
47 deps: ["jquery"],
47 exports: "bootstrap"
48 exports: "bootstrap"
48 },
49 },
49 bootstraptour: {
50 bootstraptour: {
50 deps: ["bootstrap"],
51 deps: ["bootstrap"],
51 exports: "Tour"
52 exports: "Tour"
52 },
53 },
53 jqueryui: {
54 jqueryui: {
54 deps: ["jquery"],
55 deps: ["jquery"],
55 exports: "$"
56 exports: "$"
56 }
57 }
57 }
58 }
58 });
59 });
59
60
60 require.config({
61 require.config({
61 map: {
62 map: {
62 '*':{
63 '*':{
63 'contents': '{{ contents_js_source }}',
64 'contents': '{{ contents_js_source }}',
64 }
65 }
65 }
66 }
66 });
67 });
67 </script>
68 </script>
68
69
69 {% block meta %}
70 {% block meta %}
70 {% endblock %}
71 {% endblock %}
71
72
72 </head>
73 </head>
73
74
74 <body {% block params %}{% endblock %}>
75 <body {% block params %}{% endblock %}>
75
76
76 <noscript>
77 <noscript>
77 <div id='noscript'>
78 <div id='noscript'>
78 IPython Notebook requires JavaScript.<br>
79 IPython Notebook requires JavaScript.<br>
79 Please enable it to proceed.
80 Please enable it to proceed.
80 </div>
81 </div>
81 </noscript>
82 </noscript>
82
83
83 <div id="header" class="navbar navbar-fixed-top">
84 <div id="header" class="navbar navbar-fixed-top">
84 <div id="header-container" class="container">
85 <div id="header-container" class="container">
85 <div id="ipython_notebook" class="nav navbar-brand pull-left"><a href="{{base_url}}tree" title='dashboard'>{% block logo %}<img src='{{static_url("base/images/logo.png") }}' alt='Jupyter Notebook'/>{% endblock %}</a></div>
86 <div id="ipython_notebook" class="nav navbar-brand pull-left"><a href="{{base_url}}tree" title='dashboard'>{% block logo %}<img src='{{static_url("base/images/logo.png") }}' alt='Jupyter Notebook'/>{% endblock %}</a></div>
86
87
87 {% block login_widget %}
88 {% block login_widget %}
88
89
89 <span id="login_widget">
90 <span id="login_widget">
90 {% if logged_in %}
91 {% if logged_in %}
91 <button id="logout" class="btn btn-sm navbar-btn">Logout</button>
92 <button id="logout" class="btn btn-sm navbar-btn">Logout</button>
92 {% elif login_available and not logged_in %}
93 {% elif login_available and not logged_in %}
93 <button id="login" class="btn btn-sm navbar-btn">Login</button>
94 <button id="login" class="btn btn-sm navbar-btn">Login</button>
94 {% endif %}
95 {% endif %}
95 </span>
96 </span>
96
97
97 {% endblock %}
98 {% endblock %}
98
99
99 {% block headercontainer %}
100 {% block headercontainer %}
100 {% endblock %}
101 {% endblock %}
101 </div>
102 </div>
102 <div class="header-bar"></div>
103 <div class="header-bar"></div>
103
104
104 {% block header %}
105 {% block header %}
105 {% endblock %}
106 {% endblock %}
106 </div>
107 </div>
107 <div id="header-spacer"></div>
108 <div id="header-spacer"></div>
108
109
109 <div id="site">
110 <div id="site">
110 {% block site %}
111 {% block site %}
111 {% endblock %}
112 {% endblock %}
112 </div>
113 </div>
113
114
114 {% block script %}
115 {% block script %}
115 {% endblock %}
116 {% endblock %}
116
117
117 </body>
118 </body>
118
119
119 </html>
120 </html>
General Comments 0
You need to be logged in to leave comments. Login now