diff --git a/IPython/html/static/notebook/js/cell.js b/IPython/html/static/notebook/js/cell.js
index 9e2aac1..b54ade3 100644
--- a/IPython/html/static/notebook/js/cell.js
+++ b/IPython/html/static/notebook/js/cell.js
@@ -53,7 +53,9 @@ define([
get: function() { return that._metadata; },
set: function(value) {
that._metadata = value;
- that.celltoolbar.rebuild();
+ if (that.celltoolbar) {
+ that.celltoolbar.rebuild();
+ }
}
});
@@ -194,11 +196,11 @@ define([
if((cur.line !== 0 || cur.ch !==0) && event.keyCode === 38){
event._ipkmIgnore = true;
}
- var nLastLine = editor.lastLine()
- if( ( event.keyCode === 40)
- && (( cur.line !== nLastLine)
- || ( cur.ch !== editor.getLineHandle(nLastLine).text.length))
- ){
+ var nLastLine = editor.lastLine();
+ if ((event.keyCode === 40) &&
+ ((cur.line !== nLastLine) ||
+ (cur.ch !== editor.getLineHandle(nLastLine).text.length))
+ ) {
event._ipkmIgnore = true;
}
// if this is an edit_shortcuts shortcut, the global keyboard/shortcut
@@ -255,6 +257,14 @@ define([
};
/**
+ * should be overritten by subclass
+ * @method execute
+ */
+ Cell.prototype.execute = function () {
+ return;
+ };
+
+ /**
* handle cell level logic when a cell is rendered
* @method render
* @return is the action being taken
@@ -386,7 +396,9 @@ define([
* @method refresh
*/
Cell.prototype.refresh = function () {
- this.code_mirror.refresh();
+ if (this.code_mirror) {
+ this.code_mirror.refresh();
+ }
};
/**
@@ -590,8 +602,74 @@ define([
this.code_mirror.setOption('mode', default_mode);
};
+ var UnrecognizedCell = function (options) {
+ /** Constructor for unrecognized cells */
+ Cell.apply(this, arguments);
+ this.cell_type = 'unrecognized';
+ this.celltoolbar = null;
+ this.data = {};
+
+ Object.seal(this);
+ };
+
+ UnrecognizedCell.prototype = Object.create(Cell.prototype);
+
+
+ // cannot merge or split unrecognized cells
+ UnrecognizedCell.prototype.is_mergeable = function () {
+ return false;
+ };
+
+ UnrecognizedCell.prototype.is_splittable = function () {
+ return false;
+ };
+
+ UnrecognizedCell.prototype.toJSON = function () {
+ // deepcopy the metadata so copied cells don't share the same object
+ return JSON.parse(JSON.stringify(this.data));
+ };
+
+ UnrecognizedCell.prototype.fromJSON = function (data) {
+ this.data = data;
+ if (data.metadata !== undefined) {
+ this.metadata = data.metadata;
+ } else {
+ data.metadata = this.metadata;
+ }
+ this.element.find('.inner_cell').find("a").text("Unrecognized cell type: " + data.cell_type);
+ };
+
+ UnrecognizedCell.prototype.create_element = function () {
+ Cell.prototype.create_element.apply(this, arguments);
+ var cell = this.element = $("
").addClass('cell unrecognized_cell');
+ cell.attr('tabindex','2');
+
+ var prompt = $('
').addClass('prompt input_prompt');
+ cell.append(prompt);
+ var inner_cell = $('
').addClass('inner_cell');
+ inner_cell.append(
+ $("
")
+ .attr("href", "#")
+ .text("Unrecognized cell type")
+ );
+ cell.append(inner_cell);
+ this.element = cell;
+ };
+
+ UnrecognizedCell.prototype.bind_events = function () {
+ Cell.prototype.bind_events.apply(this, arguments);
+ var cell = this;
+
+ this.element.find('.inner_cell').find("a").click(function () {
+ cell.events.trigger('unrecognized_cell.Cell', {cell: cell})
+ });
+ };
+
// Backwards compatibility.
IPython.Cell = Cell;
- return {'Cell': Cell};
+ return {
+ Cell: Cell,
+ UnrecognizedCell: UnrecognizedCell
+ };
});
diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js
index 4eecc19..f3f1c64 100644
--- a/IPython/html/static/notebook/js/notebook.js
+++ b/IPython/html/static/notebook/js/notebook.js
@@ -6,6 +6,7 @@ define([
'jquery',
'base/js/utils',
'base/js/dialog',
+ 'notebook/js/cell',
'notebook/js/textcell',
'notebook/js/codecell',
'services/sessions/session',
@@ -22,13 +23,14 @@ define([
'notebook/js/scrollmanager'
], function (
IPython,
- $,
- utils,
- dialog,
- textcell,
- codecell,
+ $,
+ utils,
+ dialog,
+ cellmod,
+ textcell,
+ codecell,
session,
- celltoolbar,
+ celltoolbar,
marked,
CodeMirror,
runMode,
@@ -147,7 +149,7 @@ define([
this.minimum_autosave_interval = 120000;
this.notebook_name_blacklist_re = /[\/\\:]/;
this.nbformat = 4; // Increment this when changing the nbformat
- this.nbformat_minor = 0; // Increment this when changing the nbformat
+ this.nbformat_minor = this.current_nbformat_minor = 0; // Increment this when changing the nbformat
this.codemirror_mode = 'ipython';
this.create_elements();
this.bind_events();
@@ -211,6 +213,14 @@ define([
that.dirty = true;
});
+ this.events.on('unrecognized_cell.Cell', function () {
+ that.warn_nbformat_minor();
+ });
+
+ this.events.on('unrecognized_output.OutputArea', function () {
+ that.warn_nbformat_minor();
+ });
+
this.events.on('set_dirty.Notebook', function (event, data) {
that.dirty = data.value;
});
@@ -304,6 +314,28 @@ define([
return null;
};
};
+
+ Notebook.prototype.warn_nbformat_minor = function (event) {
+ // trigger a warning dialog about missing functionality from newer minor versions
+ var v = 'v' + this.nbformat + '.';
+ var orig_vs = v + this.nbformat_minor;
+ var this_vs = v + this.current_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 cell and output types " +
+ "introduced in later notebook versions will not be available.";
+
+ dialog.modal({
+ notebook: this,
+ keyboard_manager: this.keyboard_manager,
+ title : "Newer Notebook",
+ body : msg,
+ buttons : {
+ OK : {
+ "class" : "btn-danger"
+ }
+ }
+ });
+ }
/**
* Set the dirty flag, and trigger the set_dirty.Notebook event
@@ -900,7 +932,8 @@ define([
cell = new textcell.RawCell(cell_options);
break;
default:
- console.log("invalid cell type: ", type);
+ console.log("Unrecognized cell type: ", type, cellmod);
+ cell = new cellmod.UnrecognizedCell(cell_options);
}
if(this._insert_element_at_index(cell.element,index)) {
@@ -2222,26 +2255,8 @@ define([
}
}
});
- } else if (orig_nbformat_minor !== undefined && nbmodel.nbformat_minor < orig_nbformat_minor) {
- var that = this;
- var orig_vs = 'v' + nbmodel.nbformat + '.' + orig_nbformat_minor;
- var this_vs = 'v' + nbmodel.nbformat + '.' + this.nbformat_minor;
- 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.";
-
- dialog.modal({
- notebook: this,
- keyboard_manager: this.keyboard_manager,
- title : "Newer Notebook",
- body : msg,
- buttons : {
- OK : {
- class : "btn-danger"
- }
- }
- });
-
+ } else if (this.nbformat_minor < nbmodel.nbformat_minor) {
+ this.nbformat_minor = nbmodel.nbformat_minor;
}
// Create the session after the notebook is completely loaded to prevent
diff --git a/IPython/html/static/notebook/js/outputarea.js b/IPython/html/static/notebook/js/outputarea.js
index 283c491..ae77260 100644
--- a/IPython/html/static/notebook/js/outputarea.js
+++ b/IPython/html/static/notebook/js/outputarea.js
@@ -245,7 +245,7 @@ define([
'text/plain'
];
- OutputArea.prototype.validate_output = function (json) {
+ OutputArea.prototype.validate_mimebundle = function (json) {
// scrub invalid outputs
var data = json.data;
$.map(OutputArea.output_types, function(key){
@@ -263,11 +263,6 @@ define([
OutputArea.prototype.append_output = function (json) {
this.expand();
- // validate output data types
- if (json.data) {
- json = this.validate_output(json);
- }
-
// Clear the output if clear is queued.
var needs_height_reset = false;
if (this.clear_queued) {
@@ -276,14 +271,25 @@ define([
}
var record_output = true;
-
- if (json.output_type === 'execute_result') {
- this.append_execute_result(json);
- } else if (json.output_type === 'error') {
- this.append_error(json);
- } else if (json.output_type === 'stream') {
- // append_stream might have merged the output with earlier stream output
- record_output = this.append_stream(json);
+ switch(json.output_type) {
+ case 'execute_result':
+ json = this.validate_mimebundle(json);
+ this.append_execute_result(json);
+ break;
+ case 'stream':
+ // append_stream might have merged the output with earlier stream output
+ record_output = this.append_stream(json);
+ break;
+ case 'error':
+ this.append_error(json);
+ break;
+ case 'display_data':
+ // append handled below
+ json = this.validate_mimebundle(json);
+ break;
+ default:
+ console.log("unrecognized output type: " + json.output_type);
+ this.append_unrecognized(json);
}
// We must release the animation fixed height in a callback since Gecko
@@ -485,6 +491,23 @@ define([
};
+ OutputArea.prototype.append_unrecognized = function (json) {
+ var that = this;
+ var toinsert = this.create_output_area();
+ var subarea = $('').addClass('output_subarea output_unrecognized');
+ toinsert.append(subarea);
+ subarea.append(
+ $("")
+ .attr("href", "#")
+ .text("Unrecognized output: " + json.output_type)
+ .click(function () {
+ that.events.trigger('unrecognized_output.OutputArea', {output: json})
+ })
+ );
+ this._safe_append(toinsert);
+ };
+
+
OutputArea.prototype.append_display_data = function (json, handle_inserted) {
var toinsert = this.create_output_area();
if (this.append_mime_type(json, toinsert, handle_inserted)) {
diff --git a/IPython/html/static/notebook/js/textcell.js b/IPython/html/static/notebook/js/textcell.js
index 3cb76ab..012e632 100644
--- a/IPython/html/static/notebook/js/textcell.js
+++ b/IPython/html/static/notebook/js/textcell.js
@@ -338,7 +338,7 @@ define([
var textcell = {
TextCell: TextCell,
MarkdownCell: MarkdownCell,
- RawCell: RawCell,
+ RawCell: RawCell
};
return textcell;
});
diff --git a/IPython/html/static/notebook/less/cell.less b/IPython/html/static/notebook/less/cell.less
index 8bf3f85..ed4ad4a 100644
--- a/IPython/html/static/notebook/less/cell.less
+++ b/IPython/html/static/notebook/less/cell.less
@@ -61,3 +61,34 @@ div.prompt:empty {
padding-top: 0;
padding-bottom: 0;
}
+
+div.unrecognized_cell {
+ // from text_cell
+ padding: 5px 5px 5px 0px;
+ .hbox();
+
+ .inner_cell {
+ .border-radius(@border-radius-base);
+ padding: 5px;
+ font-weight: bold;
+ color: red;
+ border: 1px solid @light_border_color;
+ background: darken(@cell_background, 5%);
+ // remove decoration from link
+ a {
+ color: inherit;
+ text-decoration: none;
+
+ &:hover {
+ color: inherit;
+ text-decoration: none;
+ }
+ }
+ }
+}
+@media (max-width: 480px) {
+ // remove prompt indentation on small screens
+ div.unrecognized_cell > div.prompt {
+ display: none;
+ }
+}
diff --git a/IPython/html/static/notebook/less/outputarea.less b/IPython/html/static/notebook/less/outputarea.less
index 033359b..07f8276 100644
--- a/IPython/html/static/notebook/less/outputarea.less
+++ b/IPython/html/static/notebook/less/outputarea.less
@@ -172,3 +172,19 @@ input.raw_input:focus {
p.p-space {
margin-bottom: 10px;
}
+
+div.output_unrecognized {
+ padding: 5px;
+ font-weight: bold;
+ color: red;
+ // remove decoration from link
+ a {
+ color: inherit;
+ text-decoration: none;
+
+ &:hover {
+ color: inherit;
+ text-decoration: none;
+ }
+ }
+}
\ No newline at end of file
diff --git a/IPython/html/static/style/ipython.min.css b/IPython/html/static/style/ipython.min.css
index 09e2df2..5b5c4e3 100644
--- a/IPython/html/static/style/ipython.min.css
+++ b/IPython/html/static/style/ipython.min.css
@@ -419,6 +419,44 @@ div.prompt:empty {
padding-top: 0;
padding-bottom: 0;
}
+div.unrecognized_cell {
+ padding: 5px 5px 5px 0px;
+ /* Old browsers */
+ display: -webkit-box;
+ -webkit-box-orient: horizontal;
+ -webkit-box-align: stretch;
+ display: -moz-box;
+ -moz-box-orient: horizontal;
+ -moz-box-align: stretch;
+ display: box;
+ box-orient: horizontal;
+ box-align: stretch;
+ /* Modern browsers */
+ display: flex;
+ flex-direction: row;
+ align-items: stretch;
+}
+div.unrecognized_cell .inner_cell {
+ border-radius: 4px;
+ padding: 5px;
+ font-weight: bold;
+ color: red;
+ border: 1px solid #cfcfcf;
+ background: #eaeaea;
+}
+div.unrecognized_cell .inner_cell a {
+ color: inherit;
+ text-decoration: none;
+}
+div.unrecognized_cell .inner_cell a:hover {
+ color: inherit;
+ text-decoration: none;
+}
+@media (max-width: 480px) {
+ div.unrecognized_cell > div.prompt {
+ display: none;
+ }
+}
/* any special styling for code cells that are currently running goes here */
div.input {
page-break-inside: avoid;
@@ -888,6 +926,19 @@ input.raw_input:focus {
p.p-space {
margin-bottom: 10px;
}
+div.output_unrecognized {
+ padding: 5px;
+ font-weight: bold;
+ color: red;
+}
+div.output_unrecognized a {
+ color: inherit;
+ text-decoration: none;
+}
+div.output_unrecognized a:hover {
+ color: inherit;
+ text-decoration: none;
+}
.rendered_html {
color: #000000;
/* any extras will just be numbers: */
diff --git a/IPython/html/static/style/style.min.css b/IPython/html/static/style/style.min.css
index 8a9a28b..568261d 100644
--- a/IPython/html/static/style/style.min.css
+++ b/IPython/html/static/style/style.min.css
@@ -8291,6 +8291,44 @@ div.prompt:empty {
padding-top: 0;
padding-bottom: 0;
}
+div.unrecognized_cell {
+ padding: 5px 5px 5px 0px;
+ /* Old browsers */
+ display: -webkit-box;
+ -webkit-box-orient: horizontal;
+ -webkit-box-align: stretch;
+ display: -moz-box;
+ -moz-box-orient: horizontal;
+ -moz-box-align: stretch;
+ display: box;
+ box-orient: horizontal;
+ box-align: stretch;
+ /* Modern browsers */
+ display: flex;
+ flex-direction: row;
+ align-items: stretch;
+}
+div.unrecognized_cell .inner_cell {
+ border-radius: 4px;
+ padding: 5px;
+ font-weight: bold;
+ color: red;
+ border: 1px solid #cfcfcf;
+ background: #eaeaea;
+}
+div.unrecognized_cell .inner_cell a {
+ color: inherit;
+ text-decoration: none;
+}
+div.unrecognized_cell .inner_cell a:hover {
+ color: inherit;
+ text-decoration: none;
+}
+@media (max-width: 480px) {
+ div.unrecognized_cell > div.prompt {
+ display: none;
+ }
+}
/* any special styling for code cells that are currently running goes here */
div.input {
page-break-inside: avoid;
@@ -8760,6 +8798,19 @@ input.raw_input:focus {
p.p-space {
margin-bottom: 10px;
}
+div.output_unrecognized {
+ padding: 5px;
+ font-weight: bold;
+ color: red;
+}
+div.output_unrecognized a {
+ color: inherit;
+ text-decoration: none;
+}
+div.output_unrecognized a:hover {
+ color: inherit;
+ text-decoration: none;
+}
.rendered_html {
color: #000000;
/* any extras will just be numbers: */
diff --git a/IPython/nbformat/tests/test4plus.ipynb b/IPython/nbformat/tests/test4plus.ipynb
index 5ee0da4..38859b5 100644
--- a/IPython/nbformat/tests/test4plus.ipynb
+++ b/IPython/nbformat/tests/test4plus.ipynb
@@ -306,6 +306,41 @@
"from IPython.display import Image\n",
"Image(\"http://ipython.org/_static/IPy_header.png\")"
]
+ },
+ {
+ "cell_type": "future cell",
+ "metadata": {},
+ "key": "value"
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 99,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "hello\n"
+ ]
+ },
+ {
+ "output_type": "future output",
+ "some key": [
+ "some data"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "hello again\n"
+ ]
+ }
+ ],
+ "source": [
+ "future_output()"
+ ]
}
],
"metadata": {},
diff --git a/IPython/nbformat/v4/nbformat.v4.schema.json b/IPython/nbformat/v4/nbformat.v4.schema.json
index 988b6ac..317a80b 100644
--- a/IPython/nbformat/v4/nbformat.v4.schema.json
+++ b/IPython/nbformat/v4/nbformat.v4.schema.json
@@ -54,18 +54,19 @@
"cells": {
"description": "Array of cells of the current notebook.",
"type": "array",
- "items": {
- "type": "object",
- "oneOf": [
- {"$ref": "#/definitions/raw_cell"},
- {"$ref": "#/definitions/markdown_cell"},
- {"$ref": "#/definitions/code_cell"}
- ]
- }
+ "items": {"$ref": "#/definitions/cell"}
}
},
"definitions": {
+ "cell": {
+ "type": "object",
+ "oneOf": [
+ {"$ref": "#/definitions/raw_cell"},
+ {"$ref": "#/definitions/markdown_cell"},
+ {"$ref": "#/definitions/code_cell"}
+ ]
+ },
"raw_cell": {
"description": "Notebook raw nbconvert cell.",
@@ -157,6 +158,31 @@
}
}
},
+
+ "unrecognized_cell": {
+ "description": "Unrecognized cell from a future minor-revision to the notebook format.",
+ "type": "object",
+ "additionalProperties": true,
+ "required": ["cell_type", "metadata"],
+ "properties": {
+ "cell_type": {
+ "description": "String identifying the type of cell.",
+ "not" : {
+ "enum": ["markdown", "code", "raw"]
+ }
+ },
+ "metadata": {
+ "description": "Cell-level metadata.",
+ "type": "object",
+ "properties": {
+ "name": {"$ref": "#/definitions/misc/metadata_name"},
+ "tags": {"$ref": "#/definitions/misc/metadata_tags"}
+ },
+ "additionalProperties": true
+ }
+ }
+ },
+
"output": {
"type": "object",
"oneOf": [
@@ -249,6 +275,21 @@
}
},
+ "unrecognized_output": {
+ "description": "Unrecognized output from a future minor-revision to the notebook format.",
+ "type": "object",
+ "additionalProperties": true,
+ "required": ["output_type"],
+ "properties": {
+ "output_type": {
+ "description": "Type of cell output.",
+ "not": {
+ "enum": ["execute_result", "display_data", "stream", "error"]
+ }
+ }
+ }
+ },
+
"misc": {
"metadata_name": {
"description": "The cell's name. If present, must be a non-empty string.",
diff --git a/IPython/nbformat/validator.py b/IPython/nbformat/validator.py
index a4fcd86..c3f00fc 100644
--- a/IPython/nbformat/validator.py
+++ b/IPython/nbformat/validator.py
@@ -30,7 +30,6 @@ def _relax_additional_properties(obj):
if isinstance(obj, dict):
for key, value in obj.items():
if key == 'additionalProperties':
- print(obj)
value = True
else:
value = _relax_additional_properties(value)
@@ -40,6 +39,15 @@ def _relax_additional_properties(obj):
obj[i] = _relax_additional_properties(value)
return obj
+def _allow_undefined(schema):
+ schema['definitions']['cell']['oneOf'].append(
+ {"$ref": "#/definitions/unrecognized_cell"}
+ )
+ schema['definitions']['output']['oneOf'].append(
+ {"$ref": "#/definitions/unrecognized_output"}
+ )
+ return schema
+
def get_validator(version=None, version_minor=None):
"""Load the JSON schema into a Validator"""
if version is None:
@@ -66,6 +74,8 @@ def get_validator(version=None, version_minor=None):
if current_minor < version_minor:
# notebook from the future, relax all `additionalProperties: False` requirements
schema_json = _relax_additional_properties(schema_json)
+ # and allow undefined cell types and outputs
+ schema_json = _allow_undefined(schema_json)
validators[version_tuple] = Validator(schema_json)
return validators[version_tuple]