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