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