##// END OF EJS Templates
Massive work on the notebook document format....
Brian E. Granger -
Show More
@@ -0,0 +1,195 b''
1 #-----------------------------------------------------------------------------
2 # Copyright (C) 2011 The IPython Development Team
3 #
4 # Distributed under the terms of the BSD License. The full license is in
5 # the file COPYING.txt, distributed as part of this software.
6 #-----------------------------------------------------------------------------
7
8 #-----------------------------------------------------------------------------
9 # Imports
10 #-----------------------------------------------------------------------------
11
12 import datetime
13 import os
14 import uuid
15
16 from tornado import web
17
18 from IPython.config.configurable import Configurable
19 from IPython.nbformat import current
20 from IPython.utils.traitlets import Unicode, List, Dict
21
22
23 #-----------------------------------------------------------------------------
24 # Code
25 #-----------------------------------------------------------------------------
26
27
28 class NotebookManager(Configurable):
29
30 notebook_dir = Unicode(os.getcwd())
31 filename_ext = Unicode(u'.ipynb')
32 allowed_formats = List([u'json',u'xml',u'py'])
33
34 # Map notebook_ids to notebook names
35 mapping = Dict()
36 # Map notebook names to notebook_ids
37 rev_mapping = Dict()
38
39 def list_notebooks(self):
40 """List all notebooks in the notebook dir.
41
42 This returns a list of dicts of the form::
43
44 dict(notebook_id=notebook,name=name)
45 """
46 names = os.listdir(self.notebook_dir)
47 names = [name.strip(self.filename_ext)\
48 for name in names if name.endswith(self.filename_ext)]
49 data = []
50 for name in names:
51 if name not in self.rev_mapping:
52 notebook_id = self.new_notebook_id(name)
53 else:
54 notebook_id = self.rev_mapping[name]
55 data.append(dict(notebook_id=notebook_id,name=name))
56 return data
57
58 def new_notebook_id(self, name):
59 """Generate a new notebook_id for a name and store its mappings."""
60 notebook_id = unicode(uuid.uuid4())
61 self.mapping[notebook_id] = name
62 self.rev_mapping[name] = notebook_id
63 return notebook_id
64
65 def delete_notebook_id(self, notebook_id):
66 """Delete a notebook's id only. This doesn't delete the actual notebook."""
67 name = self.mapping[notebook_id]
68 del self.mapping[notebook_id]
69 del self.rev_mapping[name]
70
71 def notebook_exists(self, notebook_id):
72 """Does a notebook exist?"""
73 if notebook_id not in self.mapping:
74 return False
75 path = self.get_path_by_name(self.mapping[notebook_id])
76 if not os.path.isfile(path):
77 return False
78 return True
79
80 def find_path(self, notebook_id):
81 """Return a full path to a notebook given its notebook_id."""
82 try:
83 name = self.mapping[notebook_id]
84 except KeyError:
85 raise web.HTTPError(404)
86 return self.get_path_by_name(name)
87
88 def get_path_by_name(self, name):
89 """Return a full path to a notebook given its name."""
90 filename = name + self.filename_ext
91 path = os.path.join(self.notebook_dir, filename)
92 return path
93
94 def get_notebook(self, notebook_id, format=u'json'):
95 """Get the representation of a notebook in format by notebook_id."""
96 format = unicode(format)
97 if format not in self.allowed_formats:
98 raise web.HTTPError(415)
99 last_modified, nb = self.get_notebook_object(notebook_id)
100 data = current.writes(nb, format)
101 name = nb.get('name','notebook')
102 return last_modified, name, data
103
104 def get_notebook_object(self, notebook_id):
105 """Get the NotebookNode representation of a notebook by notebook_id."""
106 path = self.find_path(notebook_id)
107 if not os.path.isfile(path):
108 raise web.HTTPError(404)
109 info = os.stat(path)
110 last_modified = datetime.datetime.utcfromtimestamp(info.st_mtime)
111 try:
112 with open(path,'r') as f:
113 s = f.read()
114 try:
115 # v2 and later have xml in the .ipynb files
116 nb = current.reads(s, 'xml')
117 except:
118 # v1 had json in the .ipynb files
119 nb = current.reads(s, 'json')
120 except:
121 raise web.HTTPError(404)
122 return last_modified, nb
123
124 def save_new_notebook(self, data, format=u'json'):
125 """Save a new notebook and return its notebook_id."""
126 if format not in self.allowed_formats:
127 raise web.HTTPError(415)
128 try:
129 nb = current.reads(data, format)
130 except:
131 raise web.HTTPError(400)
132 try:
133 name = nb.name
134 except AttributeError:
135 raise web.HTTPError(400)
136 notebook_id = self.new_notebook_id(name)
137 self.save_notebook_object(notebook_id, nb)
138 return notebook_id
139
140 def save_notebook(self, notebook_id, data, format=u'json'):
141 """Save an existing notebook by notebook_id."""
142 if format not in self.allowed_formats:
143 raise web.HTTPError(415)
144 try:
145 nb = current.reads(data, format)
146 except:
147 raise web.HTTPError(400)
148 self.save_notebook_object(notebook_id, nb)
149
150 def save_notebook_object(self, notebook_id, nb):
151 """Save an existing notebook object by notebook_id."""
152 if notebook_id not in self.mapping:
153 raise web.HTTPError(404)
154 old_name = self.mapping[notebook_id]
155 try:
156 new_name = nb.name
157 except AttributeError:
158 raise web.HTTPError(400)
159 path = self.get_path_by_name(new_name)
160 try:
161 with open(path,'w') as f:
162 current.write(nb, f, u'xml')
163 except:
164 raise web.HTTPError(400)
165 if old_name != new_name:
166 old_path = self.get_path_by_name(old_name)
167 if os.path.isfile(old_path):
168 os.unlink(old_path)
169 self.mapping[notebook_id] = new_name
170 self.rev_mapping[new_name] = notebook_id
171
172 def delete_notebook(self, notebook_id):
173 """Delete notebook by notebook_id."""
174 path = self.find_path(notebook_id)
175 if not os.path.isfile(path):
176 raise web.HTTPError(404)
177 os.unlink(path)
178 self.delete_notebook_id(notebook_id)
179
180 def new_notebook(self):
181 """Create a new notebook and returns its notebook_id."""
182 i = 0
183 while True:
184 name = u'Untitled%i' % i
185 path = self.get_path_by_name(name)
186 if not os.path.isfile(path):
187 break
188 else:
189 i = i+1
190 notebook_id = self.new_notebook_id(name)
191 nb = current.new_notebook(name=name, id=notebook_id)
192 with open(path,'w') as f:
193 current.write(nb, f, u'xml')
194 return notebook_id
195
@@ -4,15 +4,14 b''
4 # Imports
4 # Imports
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6
6
7 import datetime
8 import json
7 import json
9 import logging
8 import logging
10 import os
11 import urllib
9 import urllib
12
10
13 from tornado import web
11 from tornado import web
14 from tornado import websocket
12 from tornado import websocket
15
13
14
16 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
17 # Handlers
16 # Handlers
18 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
@@ -20,7 +19,16 b' from tornado import websocket'
20
19
21 class MainHandler(web.RequestHandler):
20 class MainHandler(web.RequestHandler):
22 def get(self):
21 def get(self):
23 self.render('notebook.html')
22 notebook_id = self.application.notebook_manager.new_notebook()
23 self.render('notebook.html', notebook_id=notebook_id)
24
25
26 class NamedNotebookHandler(web.RequestHandler):
27 def get(self, notebook_id):
28 nbm = self.application.notebook_manager
29 if not nbm.notebook_exists(notebook_id):
30 raise web.HTTPError(404)
31 self.render('notebook.html', notebook_id=notebook_id)
24
32
25
33
26 class KernelHandler(web.RequestHandler):
34 class KernelHandler(web.RequestHandler):
@@ -30,6 +38,7 b' class KernelHandler(web.RequestHandler):'
30
38
31 def post(self):
39 def post(self):
32 kernel_id = self.application.start_kernel()
40 kernel_id = self.application.start_kernel()
41 self.set_header('Location', '/'+kernel_id)
33 self.write(json.dumps(kernel_id))
42 self.write(json.dumps(kernel_id))
34
43
35
44
@@ -65,52 +74,52 b' class ZMQStreamHandler(websocket.WebSocketHandler):'
65 class NotebookRootHandler(web.RequestHandler):
74 class NotebookRootHandler(web.RequestHandler):
66
75
67 def get(self):
76 def get(self):
68 files = os.listdir(os.getcwd())
77 nbm = self.application.notebook_manager
69 files = [file for file in files if file.endswith(".ipynb")]
78 files = nbm.list_notebooks()
70 self.write(json.dumps(files))
79 self.write(json.dumps(files))
71
80
81 def post(self):
82 nbm = self.application.notebook_manager
83 body = self.request.body.strip()
84 format = self.get_argument('format', default='json')
85 if body:
86 notebook_id = nbm.save_new_notebook(body, format)
87 else:
88 notebook_id = nbm.new_notebook()
89 self.set_header('Location', '/'+notebook_id)
90 self.write(json.dumps(notebook_id))
72
91
73 class NotebookHandler(web.RequestHandler):
74
75 SUPPORTED_METHODS = ("GET", "DELETE", "PUT")
76
92
77 def find_path(self, filename):
93 class NotebookHandler(web.RequestHandler):
78 filename = urllib.unquote(filename)
79 if not filename.endswith('.ipynb'):
80 raise web.HTTPError(400)
81 path = os.path.join(os.getcwd(), filename)
82 return path
83
94
84 def get(self, filename):
95 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
85 path = self.find_path(filename)
96
86 if not os.path.isfile(path):
97 def get(self, notebook_id):
87 raise web.HTTPError(404)
98 nbm = self.application.notebook_manager
88 info = os.stat(path)
99 format = self.get_argument('format', default='json')
89 self.set_header("Content-Type", "application/unknown")
100 last_mod, name, data = nbm.get_notebook(notebook_id, format)
90 self.set_header("Last-Modified", datetime.datetime.utcfromtimestamp(
101 if format == u'json':
91 info.st_mtime))
102 self.set_header('Content-Type', 'application/json')
92 f = open(path, "r")
103 self.set_header('Content-Disposition','attachment; filename=%s.json' % name)
93 try:
104 elif format == u'xml':
94 self.finish(f.read())
105 self.set_header('Content-Type', 'text/xml')
95 finally:
106 self.set_header('Content-Disposition','attachment; filename=%s.ipynb' % name)
96 f.close()
107 elif format == u'py':
97
108 self.set_header('Content-Type', 'text/plain')
98 def put(self, filename):
109 self.set_header('Content-Disposition','attachment; filename=%s.py' % name)
99 path = self.find_path(filename)
110 self.set_header('Last-Modified', last_mod)
100 f = open(path, "w")
111 self.finish(data)
101 f.write(self.request.body)
112
102 f.close()
113 def put(self, notebook_id):
114 nbm = self.application.notebook_manager
115 format = self.get_argument('format', default='json')
116 nbm.save_notebook(notebook_id, self.request.body, format)
117 self.set_status(204)
103 self.finish()
118 self.finish()
104
119
105 def delete(self, filename):
120 def delete(self, notebook_id):
106 path = self.find_path(filename)
121 nbm = self.application.notebook_manager
107 if not os.path.isfile(path):
122 nbm.delete_notebook(notebook_id)
108 raise web.HTTPError(404)
109 os.unlink(path)
110 self.set_status(204)
123 self.set_status(204)
111 self.finish()
124 self.finish()
112
125
113
114
115
116
@@ -27,13 +27,15 b' tornado.ioloop = ioloop'
27 from tornado import httpserver
27 from tornado import httpserver
28 from tornado import web
28 from tornado import web
29
29
30 from kernelmanager import KernelManager
30 from .kernelmanager import KernelManager
31 from sessionmanager import SessionManager
31 from .sessionmanager import SessionManager
32 from handlers import (
32 from .handlers import (
33 MainHandler, KernelHandler, KernelActionHandler, ZMQStreamHandler,
33 MainHandler, NamedNotebookHandler,
34 KernelHandler, KernelActionHandler, ZMQStreamHandler,
34 NotebookRootHandler, NotebookHandler
35 NotebookRootHandler, NotebookHandler
35 )
36 )
36 from routers import IOPubStreamRouter, ShellStreamRouter
37 from .routers import IOPubStreamRouter, ShellStreamRouter
38 from .notebookmanager import NotebookManager
37
39
38 from IPython.core.application import BaseIPythonApplication
40 from IPython.core.application import BaseIPythonApplication
39 from IPython.core.profiledir import ProfileDir
41 from IPython.core.profiledir import ProfileDir
@@ -53,6 +55,7 b' from IPython.utils.traitlets import Dict, Unicode, Int, Any, List, Enum'
53
55
54 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
56 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
55 _kernel_action_regex = r"(?P<action>restart|interrupt)"
57 _kernel_action_regex = r"(?P<action>restart|interrupt)"
58 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
56
59
57 LOCALHOST = '127.0.0.1'
60 LOCALHOST = '127.0.0.1'
58
61
@@ -65,12 +68,13 b' class NotebookWebApplication(web.Application):'
65 def __init__(self, kernel_manager, log, kernel_argv, config):
68 def __init__(self, kernel_manager, log, kernel_argv, config):
66 handlers = [
69 handlers = [
67 (r"/", MainHandler),
70 (r"/", MainHandler),
71 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
68 (r"/kernels", KernelHandler),
72 (r"/kernels", KernelHandler),
69 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
73 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
70 (r"/kernels/%s/iopub" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='iopub')),
74 (r"/kernels/%s/iopub" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='iopub')),
71 (r"/kernels/%s/shell" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='shell')),
75 (r"/kernels/%s/shell" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='shell')),
72 (r"/notebooks", NotebookRootHandler),
76 (r"/notebooks", NotebookRootHandler),
73 (r"/notebooks/([^/]+)", NotebookHandler)
77 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler)
74 ]
78 ]
75 settings = dict(
79 settings = dict(
76 template_path=os.path.join(os.path.dirname(__file__), "templates"),
80 template_path=os.path.join(os.path.dirname(__file__), "templates"),
@@ -84,6 +88,7 b' class NotebookWebApplication(web.Application):'
84 self.config = config
88 self.config = config
85 self._routers = {}
89 self._routers = {}
86 self._session_dict = {}
90 self._session_dict = {}
91 self.notebook_manager = NotebookManager(config=self.config)
87
92
88 #-------------------------------------------------------------------------
93 #-------------------------------------------------------------------------
89 # Methods for managing kernels and sessions
94 # Methods for managing kernels and sessions
@@ -112,7 +112,7 b' class ShellStreamRouter(ZMQStreamRouter):'
112 def forward_msg(self, client_id, msg):
112 def forward_msg(self, client_id, msg):
113 if len(msg) < self.max_msg_size:
113 if len(msg) < self.max_msg_size:
114 msg = json.loads(msg)
114 msg = json.loads(msg)
115 to_send = self.session.serialize(msg)
115 # to_send = self.session.serialize(msg)
116 self._request_queue.put(client_id)
116 self._request_queue.put(client_id)
117 self.session.send(self.zmq_stream, msg)
117 self.session.send(self.zmq_stream, msg)
118
118
@@ -206,6 +206,13 b' span.button_label {'
206 font-size: 77%;
206 font-size: 77%;
207 }
207 }
208
208
209 #download_format {
210 float: right;
211 font-size: 85%;
212 width: 60px;
213 margin: 1px 5px;
214 }
215
209 div#left_panel_splitter {
216 div#left_panel_splitter {
210 width: 8px;
217 width: 8px;
211 top: 0px;
218 top: 0px;
@@ -302,18 +302,28 b' var IPython = (function (IPython) {'
302
302
303 CodeCell.prototype.fromJSON = function (data) {
303 CodeCell.prototype.fromJSON = function (data) {
304 if (data.cell_type === 'code') {
304 if (data.cell_type === 'code') {
305 this.set_code(data.code);
305 if (data.input !== undefined) {
306 this.set_input_prompt(data.prompt_number);
306 this.set_code(data.input);
307 }
308 if (data.prompt_number !== undefined) {
309 this.set_input_prompt(data.prompt_number);
310 } else {
311 this.set_input_prompt();
312 };
307 };
313 };
308 };
314 };
309
315
310
316
311 CodeCell.prototype.toJSON = function () {
317 CodeCell.prototype.toJSON = function () {
312 return {
318 var data = {}
313 code : this.get_code(),
319 data.input = this.get_code();
314 cell_type : 'code',
320 data.cell_type = 'code';
315 prompt_number : this.input_prompt_number
321 if (this.input_prompt_number !== ' ') {
322 data.prompt_number = this.input_prompt_number
316 };
323 };
324 data.outputs = [];
325 data.language = 'python';
326 return data;
317 };
327 };
318
328
319 IPython.CodeCell = CodeCell;
329 IPython.CodeCell = CodeCell;
@@ -14,14 +14,9 b' var IPython = (function (IPython) {'
14 this.next_prompt_number = 1;
14 this.next_prompt_number = 1;
15 this.kernel = null;
15 this.kernel = null;
16 this.msg_cell_map = {};
16 this.msg_cell_map = {};
17 this.filename = null;
18 this.notebook_load_re = /%notebook load/
19 this.notebook_save_re = /%notebook save/
20 this.notebook_filename_re = /(\w)+.ipynb/
21 this.style();
17 this.style();
22 this.create_elements();
18 this.create_elements();
23 this.bind_events();
19 this.bind_events();
24 this.start_kernel();
25 };
20 };
26
21
27
22
@@ -473,24 +468,8 b' var IPython = (function (IPython) {'
473 if (cell instanceof IPython.CodeCell) {
468 if (cell instanceof IPython.CodeCell) {
474 cell.clear_output();
469 cell.clear_output();
475 var code = cell.get_code();
470 var code = cell.get_code();
476 if (that.notebook_load_re.test(code)) {
471 var msg_id = that.kernel.execute(cell.get_code());
477 // %notebook load
472 that.msg_cell_map[msg_id] = cell.cell_id;
478 var code_parts = code.split(' ');
479 if (code_parts.length === 3) {
480 that.load_notebook(code_parts[2]);
481 };
482 } else if (that.notebook_save_re.test(code)) {
483 // %notebook save
484 var code_parts = code.split(' ');
485 if (code_parts.length === 3) {
486 that.save_notebook(code_parts[2]);
487 } else {
488 that.save_notebook()
489 };
490 } else {
491 var msg_id = that.kernel.execute(cell.get_code());
492 that.msg_cell_map[msg_id] = cell.cell_id;
493 };
494 } else if (cell instanceof IPython.TextCell) {
473 } else if (cell instanceof IPython.TextCell) {
495 cell.render();
474 cell.render();
496 }
475 }
@@ -532,18 +511,22 b' var IPython = (function (IPython) {'
532 // Always delete cell 0 as they get renumbered as they are deleted.
511 // Always delete cell 0 as they get renumbered as they are deleted.
533 this.delete_cell(0);
512 this.delete_cell(0);
534 };
513 };
535 var new_cells = data.cells;
514 // Only handle 1 worksheet for now.
536 ncells = new_cells.length;
515 var worksheet = data.worksheets[0];
537 var cell_data = null;
516 if (worksheet !== undefined) {
538 for (var i=0; i<ncells; i++) {
517 var new_cells = worksheet.cells;
539 cell_data = new_cells[i];
518 ncells = new_cells.length;
540 if (cell_data.cell_type == 'code') {
519 var cell_data = null;
541 this.insert_code_cell_after();
520 for (var i=0; i<ncells; i++) {
542 this.selected_cell().fromJSON(cell_data);
521 cell_data = new_cells[i];
543 } else if (cell_data.cell_type === 'text') {
522 if (cell_data.cell_type == 'code') {
544 this.insert_text_cell_after();
523 this.insert_code_cell_after();
545 this.selected_cell().fromJSON(cell_data);
524 this.selected_cell().fromJSON(cell_data);
546 };
525 } else if (cell_data.cell_type === 'text') {
526 this.insert_text_cell_after();
527 this.selected_cell().fromJSON(cell_data);
528 };
529 };
547 };
530 };
548 };
531 };
549
532
@@ -555,67 +538,66 b' var IPython = (function (IPython) {'
555 for (var i=0; i<ncells; i++) {
538 for (var i=0; i<ncells; i++) {
556 cell_array[i] = cells[i].toJSON();
539 cell_array[i] = cells[i].toJSON();
557 };
540 };
558 json = {
541 data = {
559 cells : cell_array
542 // Only handle 1 worksheet for now.
543 worksheets : [{cells:cell_array}]
544 }
545 return data
546 };
547
548 Notebook.prototype.save_notebook = function () {
549 if (IPython.save_widget.test_notebook_name()) {
550 var notebook_id = IPython.save_widget.get_notebook_id();
551 var nbname = IPython.save_widget.get_notebook_name();
552 // We may want to move the name/id/nbformat logic inside toJSON?
553 var data = this.toJSON();
554 data.name = nbname;
555 data.nbformat = 2;
556 data.id = notebook_id
557 // We do the call with settings so we can set cache to false.
558 var settings = {
559 processData : false,
560 cache : false,
561 type : "PUT",
562 data : JSON.stringify(data),
563 success : $.proxy(this.notebook_saved,this)
564 };
565 IPython.save_widget.status_saving();
566 $.ajax("/notebooks/" + notebook_id, settings);
560 };
567 };
561 return json
562 };
568 };
563
569
564
570
565 Notebook.prototype.test_filename = function (filename) {
571 Notebook.prototype.notebook_saved = function (data, status, xhr) {
566 if (this.notebook_filename_re.test(filename)) {
572 IPython.save_widget.status_save();
567 return true;
573 }
568 } else {
569 var bad_filename = $('<div/>');
570 bad_filename.html(
571 "The filename you entered (" + filename + ") is not valid. Notebook filenames must have the following form: foo.ipynb"
572 );
573 bad_filename.dialog({title: 'Invalid filename', modal: true});
574 return false;
575 };
576 };
577
578 Notebook.prototype.save_notebook = function (filename) {
579 this.filename = filename || this.filename || '';
580 if (this.filename === '') {
581 var no_filename = $('<div/>');
582 no_filename.html(
583 "This notebook has no filename, please specify a filename of the form: foo.ipynb"
584 );
585 no_filename.dialog({title: 'Missing filename', modal: true});
586 return;
587 }
588 if (!this.test_filename(this.filename)) {return;}
589 var thedata = this.toJSON();
590 var settings = {
591 processData : false,
592 cache : false,
593 type : "PUT",
594 data : JSON.stringify(thedata),
595 success : function (data, status, xhr) {console.log(data);}
596 };
597 $.ajax("/notebooks/" + this.filename, settings);
598 };
599
574
600
575
601 Notebook.prototype.load_notebook = function (filename) {
576 Notebook.prototype.load_notebook = function () {
602 if (!this.test_filename(filename)) {return;}
577 var notebook_id = IPython.save_widget.get_notebook_id();
603 var that = this;
604 // We do the call with settings so we can set cache to false.
578 // We do the call with settings so we can set cache to false.
605 var settings = {
579 var settings = {
606 processData : false,
580 processData : false,
607 cache : false,
581 cache : false,
608 type : "GET",
582 type : "GET",
609 dataType : "json",
583 dataType : "json",
610 success : function (data, status, xhr) {
584 success : $.proxy(this.notebook_loaded,this)
611 that.fromJSON(data);
612 that.filename = filename;
613 that.kernel.restart();
614 }
615 };
585 };
616 $.ajax("/notebooks/" + filename, settings);
586 IPython.save_widget.status_loading();
587 $.ajax("/notebooks/" + notebook_id, settings);
617 }
588 }
618
589
590
591 Notebook.prototype.notebook_loaded = function (data, status, xhr) {
592 this.fromJSON(data);
593 if (this.ncells() === 0) {
594 this.insert_code_cell_after();
595 };
596 IPython.save_widget.status_save();
597 IPython.save_widget.set_notebook_name(data.name);
598 this.start_kernel();
599 };
600
619 IPython.Notebook = Notebook;
601 IPython.Notebook = Notebook;
620
602
621 return IPython;
603 return IPython;
@@ -30,14 +30,18 b' $(document).ready(function () {'
30 IPython.kernel_status_widget.status_idle();
30 IPython.kernel_status_widget.status_idle();
31
31
32 IPython.layout_manager.do_resize();
32 IPython.layout_manager.do_resize();
33 IPython.notebook.insert_code_cell_after();
34 IPython.layout_manager.do_resize();
35
33
36 // These have display: none in the css file and are made visible here to prevent FLOUC.
34 // These have display: none in the css file and are made visible here to prevent FLOUC.
37 $('div#header').css('display','block');
35 $('div#header').css('display','block');
38 $('div#notebook_app').css('display','block');
36 $('div#notebook_app').css('display','block');
39 IPython.layout_manager.do_resize();
37
40 IPython.pager.collapse();
38 IPython.notebook.load_notebook();
41 IPython.layout_manager.do_resize();
39
40 // Perform these actions after the notebook has been loaded.
41 setTimeout(function () {
42 IPython.save_widget.update_url();
43 IPython.layout_manager.do_resize();
44 IPython.pager.collapse();
45 }, 100);
42 });
46 });
43
47
@@ -82,20 +82,31 b' var IPython = (function (IPython) {'
82 this.content.addClass('ui-helper-clearfix');
82 this.content.addClass('ui-helper-clearfix');
83 this.content.find('div.section_row').addClass('ui-helper-clearfix');
83 this.content.find('div.section_row').addClass('ui-helper-clearfix');
84 this.content.find('#new_open').buttonset();
84 this.content.find('#new_open').buttonset();
85 this.content.find('#download_notebook').button();
86 this.content.find('#upload_notebook').button();
87 this.content.find('#download_format').addClass('ui-widget ui-widget-content');
88 this.content.find('#download_format option').addClass('ui-widget ui-widget-content');
85 };
89 };
86
90
87
91
88 NotebookSection.prototype.bind_events = function () {
92 NotebookSection.prototype.bind_events = function () {
89 PanelSection.prototype.bind_events.apply(this);
93 PanelSection.prototype.bind_events.apply(this);
94 var that = this;
90 this.content.find('#new_notebook').click(function () {
95 this.content.find('#new_notebook').click(function () {
91 alert('Not Implemented');
96 console.log('click!')
97 window.open('/');
92 });
98 });
93 this.content.find('#open_notebook').click(function () {
99 this.content.find('#open_notebook').click(function () {
94 alert('Not Implemented');
100 alert('Not Implemented');
95 });
101 });
102 this.content.find('#download_notebook').click(function () {
103 var format = that.content.find('#download_format').val();
104 var notebook_id = IPython.save_widget.get_notebook_id();
105 var url = '/notebooks/' + notebook_id + '?format=' + format;
106 window.open(url,'_newtab');
107 });
96 };
108 };
97
109
98
99 // CellSection
110 // CellSection
100
111
101 var CellSection = function () {
112 var CellSection = function () {
@@ -9,6 +9,7 b' var IPython = (function (IPython) {'
9
9
10 var SaveWidget = function (selector) {
10 var SaveWidget = function (selector) {
11 this.selector = selector;
11 this.selector = selector;
12 this.notebook_name_re = /[^/\\]+/
12 if (this.selector !== undefined) {
13 if (this.selector !== undefined) {
13 this.element = $(selector);
14 this.element = $(selector);
14 this.style();
15 this.style();
@@ -29,7 +30,7 b' var IPython = (function (IPython) {'
29 SaveWidget.prototype.bind_events = function () {
30 SaveWidget.prototype.bind_events = function () {
30 var that = this;
31 var that = this;
31 this.element.find('button#save_notebook').click(function () {
32 this.element.find('button#save_notebook').click(function () {
32 IPython.notebook.save_notebook(that.get_notebook_name());
33 IPython.notebook.save_notebook();
33 });
34 });
34 };
35 };
35
36
@@ -39,11 +40,59 b' var IPython = (function (IPython) {'
39 }
40 }
40
41
41
42
42 SaveWidget.prototype.set_notebook_name = function (name) {
43 SaveWidget.prototype.set_notebook_name = function (nbname) {
43 this.element.find('input#notebook_name').attr('value',name);
44 this.element.find('input#notebook_name').attr('value',nbname);
44 }
45 }
45
46
46
47
48 SaveWidget.prototype.get_notebook_id = function () {
49 return this.element.find('span#notebook_id').text()
50 };
51
52
53 SaveWidget.prototype.update_url = function () {
54 var notebook_id = this.get_notebook_id();
55 if (notebook_id !== '') {
56 window.history.replaceState({}, '', notebook_id);
57 };
58 };
59
60
61 SaveWidget.prototype.test_notebook_name = function () {
62 var nbname = this.get_notebook_name();
63 if (this.notebook_name_re.test(nbname)) {
64 return true;
65 } else {
66 var bad_name = $('<div/>');
67 bad_name.html(
68 "The notebook name you entered (" +
69 nbname +
70 ") is not valid. Notebook names can contain any characters except / and \\"
71 );
72 bad_name.dialog({title: 'Invalid name', modal: true});
73 return false;
74 };
75 };
76
77
78 SaveWidget.prototype.status_save = function () {
79 this.element.find('span.ui-button-text').text('Save');
80 this.element.find('button#save_notebook').button('enable');
81 };
82
83
84 SaveWidget.prototype.status_saving = function () {
85 this.element.find('span.ui-button-text').text('Saving');
86 this.element.find('button#save_notebook').button('disable');
87 };
88
89
90 SaveWidget.prototype.status_loading = function () {
91 this.element.find('span.ui-button-text').text('Loading');
92 this.element.find('button#save_notebook').button('disable');
93 };
94
95
47 IPython.SaveWidget = SaveWidget;
96 IPython.SaveWidget = SaveWidget;
48
97
49 return IPython;
98 return IPython;
@@ -129,17 +129,19 b' var IPython = (function (IPython) {'
129
129
130 TextCell.prototype.fromJSON = function (data) {
130 TextCell.prototype.fromJSON = function (data) {
131 if (data.cell_type === 'text') {
131 if (data.cell_type === 'text') {
132 this.set_text(data.text);
132 if (data.text !== undefined) {
133 this.grow(this.element.find("textarea.text_cell_input"));
133 this.set_text(data.text);
134 this.grow(this.element.find("textarea.text_cell_input"));
135 };
134 };
136 };
135 }
137 }
136
138
137
139
138 TextCell.prototype.toJSON = function () {
140 TextCell.prototype.toJSON = function () {
139 return {
141 var data = {}
140 cell_type : 'text',
142 data.cell_type = 'text';
141 text : this.get_text(),
143 data.text = this.get_text();
142 };
144 return data;
143 };
145 };
144
146
145 IPython.TextCell = TextCell;
147 IPython.TextCell = TextCell;
@@ -33,6 +33,7 b''
33 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
33 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
34 <span id="save_widget">
34 <span id="save_widget">
35 <input type="text" id="notebook_name" size="20"></textarea>
35 <input type="text" id="notebook_name" size="20"></textarea>
36 <span id="notebook_id" style="display:none">{{notebook_id}}</span>
36 <button id="save_notebook">Save</button>
37 <button id="save_notebook">Save</button>
37 </span>
38 </span>
38 <span id="kernel_status">Idle</span>
39 <span id="kernel_status">Idle</span>
@@ -52,6 +53,18 b''
52 </span>
53 </span>
53 <span class="section_row_header">Actions</span>
54 <span class="section_row_header">Actions</span>
54 </div>
55 </div>
56 <div class="section_row">
57 <span class="section_row_buttons">
58 <button id="download_notebook">Export</button>
59 </span>
60 <span>
61 <select id="download_format">
62 <option value="xml">xml</option>
63 <option value="json">json</option>
64 <option value="py">py</option>
65 </select>
66 </span>
67 </div>
55 </div>
68 </div>
56 </div>
69 </div>
57
70
@@ -5,6 +5,11 b' import re'
5 from IPython.nbformat import v2
5 from IPython.nbformat import v2
6 from IPython.nbformat import v1
6 from IPython.nbformat import v1
7
7
8 from IPython.nbformat.v2 import (
9 NotebookNode,
10 new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet
11 )
12
8
13
9 current_nbformat = 2
14 current_nbformat = 2
10
15
@@ -36,10 +36,11 b' class PyWriter(NotebookWriter):'
36 for ws in nb.worksheets:
36 for ws in nb.worksheets:
37 for cell in ws.cells:
37 for cell in ws.cells:
38 if cell.cell_type == 'code':
38 if cell.cell_type == 'code':
39 input = cell.input
39 input = cell.get('input')
40 lines.extend([u'# <codecell>',u''])
40 if input is not None:
41 lines.extend(input.splitlines())
41 lines.extend([u'# <codecell>',u''])
42 lines.extend([u'',u'# </codecell>'])
42 lines.extend(input.splitlines())
43 lines.extend([u'',u'# </codecell>'])
43 lines.append('')
44 lines.append('')
44 return unicode('\n'.join(lines))
45 return unicode('\n'.join(lines))
45
46
General Comments 0
You need to be logged in to leave comments. Login now