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