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