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