diff --git a/IPython/html/nbconvert/handlers.py b/IPython/html/nbconvert/handlers.py
index f6e1094..40c820e 100644
--- a/IPython/html/nbconvert/handlers.py
+++ b/IPython/html/nbconvert/handlers.py
@@ -114,6 +114,7 @@ class NbconvertPostHandler(IPythonHandler):
exporter = get_exporter(format, config=self.config)
model = self.get_json_body()
+ name = model.get('name', 'notebook.ipynb')
nbnode = to_notebook_json(model['content'])
try:
@@ -121,7 +122,7 @@ class NbconvertPostHandler(IPythonHandler):
except Exception as e:
raise web.HTTPError(500, "nbconvert failed: %s" % e)
- if respond_zip(self, nbnode.metadata.name, output, resources):
+ if respond_zip(self, name, output, resources):
return
# MIME type
diff --git a/IPython/html/nbconvert/tests/test_nbconvert_handlers.py b/IPython/html/nbconvert/tests/test_nbconvert_handlers.py
index ea44217..904bc98 100644
--- a/IPython/html/nbconvert/tests/test_nbconvert_handlers.py
+++ b/IPython/html/nbconvert/tests/test_nbconvert_handlers.py
@@ -10,7 +10,7 @@ import requests
from IPython.html.utils import url_path_join
from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
-from IPython.nbformat.current import (new_notebook, write, new_worksheet,
+from IPython.nbformat.current import (new_notebook, write,
new_heading_cell, new_code_cell,
new_output)
@@ -43,7 +43,8 @@ class NbconvertAPI(object):
png_green_pixel = base64.encodestring(b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00'
b'\x00\x00\x01\x00\x00x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDAT'
-b'\x08\xd7c\x90\xfb\xcf\x00\x00\x02\\\x01\x1e.~d\x87\x00\x00\x00\x00IEND\xaeB`\x82')
+b'\x08\xd7c\x90\xfb\xcf\x00\x00\x02\\\x01\x1e.~d\x87\x00\x00\x00\x00IEND\xaeB`\x82'
+).decode('ascii')
class APITest(NotebookTestBase):
def setUp(self):
@@ -52,19 +53,20 @@ class APITest(NotebookTestBase):
if not os.path.isdir(pjoin(nbdir, 'foo')):
os.mkdir(pjoin(nbdir, 'foo'))
- nb = new_notebook(name='testnb')
+ nb = new_notebook()
- ws = new_worksheet()
- nb.worksheets = [ws]
- ws.cells.append(new_heading_cell(u'Created by test ³'))
- cc1 = new_code_cell(input=u'print(2*6)')
- cc1.outputs.append(new_output(output_text=u'12', output_type='stream'))
- cc1.outputs.append(new_output(output_png=png_green_pixel, output_type='pyout'))
- ws.cells.append(cc1)
+ nb.cells.append(new_heading_cell(u'Created by test ³'))
+ cc1 = new_code_cell(source=u'print(2*6)')
+ cc1.outputs.append(new_output(output_type="stream", data=u'12'))
+ cc1.outputs.append(new_output(output_type="execute_result",
+ mime_bundle={'image/png' : png_green_pixel},
+ prompt_number=1,
+ ))
+ nb.cells.append(cc1)
with io.open(pjoin(nbdir, 'foo', 'testnb.ipynb'), 'w',
encoding='utf-8') as f:
- write(nb, f, format='ipynb')
+ write(nb, f)
self.nbconvert_api = NbconvertAPI(self.base_url())
diff --git a/IPython/html/services/contents/manager.py b/IPython/html/services/contents/manager.py
index bdff6e8..ba06072 100644
--- a/IPython/html/services/contents/manager.py
+++ b/IPython/html/services/contents/manager.py
@@ -234,8 +234,7 @@ class ContentsManager(LoggingConfigurable):
model = {}
if 'content' not in model and model.get('type', None) != 'directory':
if ext == '.ipynb':
- metadata = current.new_metadata(name=u'')
- model['content'] = current.new_notebook(metadata=metadata)
+ model['content'] = current.new_notebook()
model['type'] = 'notebook'
model['format'] = 'json'
else:
diff --git a/IPython/html/services/contents/tests/test_contents_api.py b/IPython/html/services/contents/tests/test_contents_api.py
index bac91de..bb5a175 100644
--- a/IPython/html/services/contents/tests/test_contents_api.py
+++ b/IPython/html/services/contents/tests/test_contents_api.py
@@ -15,7 +15,7 @@ import requests
from IPython.html.utils import url_path_join, url_escape
from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
from IPython.nbformat import current
-from IPython.nbformat.current import (new_notebook, write, read, new_worksheet,
+from IPython.nbformat.current import (new_notebook, write, read,
new_heading_cell, to_notebook_json)
from IPython.nbformat import v2
from IPython.utils import py3compat
@@ -142,7 +142,7 @@ class APITest(NotebookTestBase):
# create a notebook
with io.open(pjoin(nbdir, d, '%s.ipynb' % name), 'w',
encoding='utf-8') as f:
- nb = new_notebook(name=name)
+ nb = new_notebook()
write(nb, f, format='ipynb')
# create a text file
@@ -286,14 +286,14 @@ class APITest(NotebookTestBase):
self.assertEqual(model['content'], '')
def test_upload_untitled(self):
- nb = new_notebook(name='Upload test')
+ nb = new_notebook()
nbmodel = {'content': nb, 'type': 'notebook'}
resp = self.api.upload_untitled(path=u'å b',
body=json.dumps(nbmodel))
self._check_created(resp, 'Untitled0.ipynb', u'å b')
def test_upload(self):
- nb = new_notebook(name=u'ignored')
+ nb = new_notebook()
nbmodel = {'content': nb, 'type': 'notebook'}
resp = self.api.upload(u'Upload tést.ipynb', path=u'å b',
body=json.dumps(nbmodel))
@@ -355,7 +355,6 @@ class APITest(NotebookTestBase):
resp = self.api.read(u'Upload tést.ipynb', u'å b')
data = resp.json()
self.assertEqual(data['content']['nbformat'], current.nbformat)
- self.assertEqual(data['content']['orig_nbformat'], 2)
def test_copy_untitled(self):
resp = self.api.copy_untitled(u'ç d.ipynb', path=u'å b')
@@ -416,9 +415,7 @@ class APITest(NotebookTestBase):
resp = self.api.read('a.ipynb', 'foo')
nbcontent = json.loads(resp.text)['content']
nb = to_notebook_json(nbcontent)
- ws = new_worksheet()
- nb.worksheets = [ws]
- ws.cells.append(new_heading_cell(u'Created by test ³'))
+ nb.cells.append(new_heading_cell(u'Created by test ³'))
nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'}
resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
@@ -426,11 +423,11 @@ class APITest(NotebookTestBase):
nbfile = pjoin(self.notebook_dir.name, 'foo', 'a.ipynb')
with io.open(nbfile, 'r', encoding='utf-8') as f:
newnb = read(f, format='ipynb')
- self.assertEqual(newnb.worksheets[0].cells[0].source,
+ self.assertEqual(newnb.cells[0].source,
u'Created by test ³')
nbcontent = self.api.read('a.ipynb', 'foo').json()['content']
newnb = to_notebook_json(nbcontent)
- self.assertEqual(newnb.worksheets[0].cells[0].source,
+ self.assertEqual(newnb.cells[0].source,
u'Created by test ³')
# Save and rename
@@ -455,10 +452,8 @@ class APITest(NotebookTestBase):
# Modify it
nbcontent = json.loads(resp.text)['content']
nb = to_notebook_json(nbcontent)
- ws = new_worksheet()
- nb.worksheets = [ws]
hcell = new_heading_cell('Created by test')
- ws.cells.append(hcell)
+ nb.cells.append(hcell)
# Save
nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'}
resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
@@ -469,14 +464,14 @@ class APITest(NotebookTestBase):
nbcontent = self.api.read('a.ipynb', 'foo').json()['content']
nb = to_notebook_json(nbcontent)
- self.assertEqual(nb.worksheets[0].cells[0].source, 'Created by test')
+ self.assertEqual(nb.cells[0].source, 'Created by test')
# Restore cp1
r = self.api.restore_checkpoint('a.ipynb', 'foo', cp1['id'])
self.assertEqual(r.status_code, 204)
nbcontent = self.api.read('a.ipynb', 'foo').json()['content']
nb = to_notebook_json(nbcontent)
- self.assertEqual(nb.worksheets, [])
+ self.assertEqual(nb.cells, [])
# Delete cp1
r = self.api.delete_checkpoint('a.ipynb', 'foo', cp1['id'])
diff --git a/IPython/html/services/contents/tests/test_manager.py b/IPython/html/services/contents/tests/test_manager.py
index d5dfff4..558f73a 100644
--- a/IPython/html/services/contents/tests/test_manager.py
+++ b/IPython/html/services/contents/tests/test_manager.py
@@ -95,11 +95,9 @@ class TestContentsManager(TestCase):
return os_path
def add_code_cell(self, nb):
- output = current.new_output("display_data", output_javascript="alert('hi');")
+ output = current.new_output("display_data", {'application/javascript': "alert('hi');"})
cell = current.new_code_cell("print('hi')", outputs=[output])
- if not nb.worksheets:
- nb.worksheets.append(current.new_worksheet())
- nb.worksheets[0].cells.append(cell)
+ nb.cells.append(cell)
def new_notebook(self):
cm = self.contents_manager
@@ -309,13 +307,13 @@ class TestContentsManager(TestCase):
nb, name, path = self.new_notebook()
cm.mark_trusted_cells(nb, name, path)
- for cell in nb.worksheets[0].cells:
+ for cell in nb.cells:
if cell.cell_type == 'code':
assert not cell.metadata.trusted
cm.trust_notebook(name, path)
nb = cm.get_model(name, path)['content']
- for cell in nb.worksheets[0].cells:
+ for cell in nb.cells:
if cell.cell_type == 'code':
assert cell.metadata.trusted
diff --git a/IPython/html/services/sessions/tests/test_sessions_api.py b/IPython/html/services/sessions/tests/test_sessions_api.py
index 9623415..643fd68 100644
--- a/IPython/html/services/sessions/tests/test_sessions_api.py
+++ b/IPython/html/services/sessions/tests/test_sessions_api.py
@@ -62,7 +62,7 @@ class SessionAPITest(NotebookTestBase):
with io.open(pjoin(nbdir, 'foo', 'nb1.ipynb'), 'w',
encoding='utf-8') as f:
- nb = new_notebook(name='nb1')
+ nb = new_notebook()
write(nb, f, format='ipynb')
self.sess_api = SessionAPI(self.base_url())
diff --git a/IPython/html/static/notebook/js/codecell.js b/IPython/html/static/notebook/js/codecell.js
index c6d19c8..ca5b6f3 100644
--- a/IPython/html/static/notebook/js/codecell.js
+++ b/IPython/html/static/notebook/js/codecell.js
@@ -382,13 +382,11 @@ define([
CodeCell.prototype.collapse_output = function () {
- this.collapsed = true;
this.output_area.collapse();
};
CodeCell.prototype.expand_output = function () {
- this.collapsed = false;
this.output_area.expand();
this.output_area.unscroll_area();
};
@@ -399,7 +397,6 @@ define([
};
CodeCell.prototype.toggle_output = function () {
- this.collapsed = Boolean(1 - this.collapsed);
this.output_area.toggle_output();
};
@@ -467,22 +464,18 @@ define([
CodeCell.prototype.fromJSON = function (data) {
Cell.prototype.fromJSON.apply(this, arguments);
if (data.cell_type === 'code') {
- if (data.input !== undefined) {
- this.set_text(data.input);
+ if (data.source !== undefined) {
+ this.set_text(data.source);
// make this value the starting point, so that we can only undo
// to this state, instead of a blank cell
this.code_mirror.clearHistory();
this.auto_highlight();
}
- if (data.prompt_number !== undefined) {
- this.set_input_prompt(data.prompt_number);
- } else {
- this.set_input_prompt();
- }
+ this.set_input_prompt(data.prompt_number);
this.output_area.trusted = data.metadata.trusted || false;
this.output_area.fromJSON(data.outputs);
- if (data.collapsed !== undefined) {
- if (data.collapsed) {
+ if (data.metadata.collapsed !== undefined) {
+ if (data.metadata.collapsed) {
this.collapse_output();
} else {
this.expand_output();
@@ -494,16 +487,17 @@ define([
CodeCell.prototype.toJSON = function () {
var data = Cell.prototype.toJSON.apply(this);
- data.input = this.get_text();
+ data.source = this.get_text();
// is finite protect against undefined and '*' value
if (isFinite(this.input_prompt_number)) {
data.prompt_number = this.input_prompt_number;
+ } else {
+ data.prompt_number = null;
}
var outputs = this.output_area.toJSON();
data.outputs = outputs;
- data.language = 'python';
data.metadata.trusted = this.output_area.trusted;
- data.collapsed = this.output_area.collapsed;
+ data.metadata.collapsed = this.output_area.collapsed;
return data;
};
diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js
index 8181138..98c6111 100644
--- a/IPython/html/static/notebook/js/notebook.js
+++ b/IPython/html/static/notebook/js/notebook.js
@@ -121,10 +121,8 @@ define([
this.autosave_timer = null;
// autosave *at most* every two minutes
this.minimum_autosave_interval = 120000;
- // single worksheet for now
- this.worksheet_metadata = {};
this.notebook_name_blacklist_re = /[\/\\:]/;
- this.nbformat = 3; // Increment this when changing the nbformat
+ this.nbformat = 4; // Increment this when changing the nbformat
this.nbformat_minor = 0; // Increment this when changing the nbformat
this.codemirror_mode = 'ipython';
this.create_elements();
@@ -1785,8 +1783,6 @@ define([
/**
* Load a notebook from JSON (.ipynb).
*
- * This currently handles one worksheet: others are deleted.
- *
* @method fromJSON
* @param {Object} data JSON representation of a notebook
*/
@@ -1818,50 +1814,22 @@ define([
this.set_codemirror_mode(cm_mode);
}
- // Only handle 1 worksheet for now.
- var worksheet = content.worksheets[0];
- if (worksheet !== undefined) {
- if (worksheet.metadata) {
- this.worksheet_metadata = worksheet.metadata;
- }
- var new_cells = worksheet.cells;
- ncells = new_cells.length;
- var cell_data = null;
- var new_cell = null;
- for (i=0; i raw
- // handle never-released plaintext name for raw cells
- if (cell_data.cell_type === 'plaintext'){
- cell_data.cell_type = 'raw';
- }
-
- new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
- new_cell.fromJSON(cell_data);
- if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
- trusted = false;
- }
+ var new_cells = content.cells;
+ ncells = new_cells.length;
+ var cell_data = null;
+ var new_cell = null;
+ for (i=0; i 1) {
- dialog.modal({
- notebook: this,
- keyboard_manager: this.keyboard_manager,
- title : "Multiple worksheets",
- body : "This notebook has " + data.worksheets.length + " worksheets, " +
- "but this version of IPython can only handle the first. " +
- "If you save this notebook, worksheets after the first will be lost.",
- buttons : {
- OK : {
- class : "btn-danger"
- }
- }
- });
- }
};
/**
@@ -1871,6 +1839,10 @@ define([
* @return {Object} A JSON-friendly representation of this notebook.
*/
Notebook.prototype.toJSON = function () {
+ // remove the conversion indicator, which only belongs in-memory
+ delete this.metadata.orig_nbformat;
+ delete this.metadata.orig_nbformat_minor;
+
var cells = this.get_cells();
var ncells = cells.length;
var cell_array = new Array(ncells);
@@ -1883,11 +1855,7 @@ define([
cell_array[i] = cell.toJSON();
}
var data = {
- // Only handle 1 worksheet for now.
- worksheets : [{
- cells: cell_array,
- metadata: this.worksheet_metadata
- }],
+ cells: cell_array,
metadata : this.metadata
};
if (trusted != this.trusted) {
@@ -2337,10 +2305,13 @@ define([
}
this.set_dirty(false);
this.scroll_to_top();
- if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
+ var nbmodel = data.content;
+ var orig_nbformat = nbmodel.metadata.orig_nbformat;
+ var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
+ if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
var msg = "This notebook has been converted from an older " +
- "notebook format (v"+data.orig_nbformat+") to the current notebook " +
- "format (v"+data.nbformat+"). The next time you save this notebook, the " +
+ "notebook format (v"+orig_nbformat+") to the current notebook " +
+ "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
"newer notebook format will be used and older versions of IPython " +
"may not be able to read it. To keep the older version, close the " +
"notebook without saving it.";
@@ -2355,10 +2326,10 @@ define([
}
}
});
- } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
+ } else if (orig_nbformat_minor !== undefined && nbmodel.nbformat_minor !== orig_nbformat_minor) {
var that = this;
- var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
- var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
+ var orig_vs = 'v' + nbmodel.nbformat + '.' + orig_nbformat_minor;
+ var this_vs = 'v' + nbmodel.nbformat + '.' + this.nbformat_minor;
var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
this_vs + ". You can still work with this notebook, but some features " +
"introduced in later notebook versions may not be available.";
diff --git a/IPython/html/static/notebook/js/outputarea.js b/IPython/html/static/notebook/js/outputarea.js
index 3be41f5..3732563 100644
--- a/IPython/html/static/notebook/js/outputarea.js
+++ b/IPython/html/static/notebook/js/outputarea.js
@@ -211,7 +211,7 @@ define([
var content = msg.content;
if (msg_type === "stream") {
json.text = content.text;
- json.stream = content.name;
+ json.name = content.name;
} else if (msg_type === "display_data") {
json = content.data;
json.output_type = msg_type;
@@ -234,6 +234,7 @@ define([
OutputArea.prototype.rename_keys = function (data, key_map) {
+ // TODO: This is now unused, should it be removed?
var remapped = {};
for (var key in data) {
var new_key = key_map[key] || key;
@@ -260,7 +261,10 @@ define([
// TODO: right now everything is a string, but JSON really shouldn't be.
// nbformat 4 will fix that.
$.map(OutputArea.output_types, function(key){
- if (json[key] !== undefined && typeof json[key] !== 'string') {
+ if (key !== 'application/json' &&
+ json[key] !== undefined &&
+ typeof json[key] !== 'string'
+ ) {
console.log("Invalid type for " + key, json[key]);
delete json[key];
}
@@ -449,23 +453,18 @@ define([
OutputArea.prototype.append_stream = function (json) {
- // temporary fix: if stream undefined (json file written prior to this patch),
- // default to most likely stdout:
- if (json.stream === undefined){
- json.stream = 'stdout';
- }
- var text = json.text;
- var subclass = "output_"+json.stream;
+ var text = json.data;
+ var subclass = "output_"+json.name;
if (this.outputs.length > 0){
// have at least one output to consider
var last = this.outputs[this.outputs.length-1];
- if (last.output_type == 'stream' && json.stream == last.stream){
+ if (last.output_type == 'stream' && json.name == last.name){
// latest output was in the same stream,
// so append directly into its pre tag
// escape ANSI & HTML specials:
- last.text = utils.fixCarriageReturn(last.text + json.text);
+ last.data = utils.fixCarriageReturn(last.data + json.data);
var pre = this.element.find('div.'+subclass).last().find('pre');
- var html = utils.fixConsole(last.text);
+ var html = utils.fixConsole(last.data);
// The only user content injected with this HTML call is
// escaped by the fixConsole() method.
pre.html(html);
@@ -852,70 +851,33 @@ define([
// JSON serialization
- OutputArea.prototype.fromJSON = function (outputs) {
+ OutputArea.prototype.fromJSON = function (outputs, metadata) {
var len = outputs.length;
- var data;
+ metadata = metadata || {};
for (var i=0; i\"";
@@ -24,8 +24,10 @@ var svg = "\"