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