From bb23105fdd38ddc24c90fe82204e85234434b2f9 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 19 Nov 2014 16:22:37 -0800 Subject: [PATCH 1/2] Handle unrecognized outputs and cells from the future --- IPython/html/static/notebook/js/cell.js | 81 +++++++++++++++++-- IPython/html/static/notebook/js/notebook.js | 17 ++-- IPython/html/static/notebook/js/outputarea.js | 44 ++++++---- IPython/html/static/notebook/js/textcell.js | 2 +- IPython/html/static/notebook/less/cell.less | 21 +++++ .../html/static/notebook/less/outputarea.less | 6 ++ IPython/html/static/style/ipython.min.css | 36 +++++++++ IPython/html/static/style/style.min.css | 36 +++++++++ 8 files changed, 213 insertions(+), 30 deletions(-) diff --git a/IPython/html/static/notebook/js/cell.js b/IPython/html/static/notebook/js/cell.js index 9e2aac1d1..26ecbd286 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 @@ -254,6 +256,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 @@ -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,61 @@ 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').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.text("Unrecognized cell type"); + cell.append(inner_cell); + this.element = 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 5bd4315a3..9bb3b00b9 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, @@ -894,7 +896,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)) { diff --git a/IPython/html/static/notebook/js/outputarea.js b/IPython/html/static/notebook/js/outputarea.js index ae56b948a..55bac957d 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,26 @@ 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); + console.log("appending", 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 @@ -482,6 +489,15 @@ define([ }; + OutputArea.prototype.append_unrecognized = function (json) { + var toinsert = this.create_output_area(); + var subarea = $('
').addClass('output_subarea output_unrecognized'); + toinsert.append(subarea); + subarea.text("Unrecognized output: " + json.output_type); + 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 24bd1e497..fb466b7ed 100644 --- a/IPython/html/static/notebook/js/textcell.js +++ b/IPython/html/static/notebook/js/textcell.js @@ -340,7 +340,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 8bf3f8594..1a5c54190 100644 --- a/IPython/html/static/notebook/less/cell.less +++ b/IPython/html/static/notebook/less/cell.less @@ -61,3 +61,24 @@ 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%); + } +} +@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 033359b20..a9f15ed06 100644 --- a/IPython/html/static/notebook/less/outputarea.less +++ b/IPython/html/static/notebook/less/outputarea.less @@ -172,3 +172,9 @@ input.raw_input:focus { p.p-space { margin-bottom: 10px; } + +div.output_unrecognized { + padding: 5px; + font-weight: bold; + color: red; +} \ 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 09e2df27a..0acc29b9d 100644 --- a/IPython/html/static/style/ipython.min.css +++ b/IPython/html/static/style/ipython.min.css @@ -419,6 +419,36 @@ 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; +} +@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 +918,12 @@ input.raw_input:focus { p.p-space { margin-bottom: 10px; } +div.output_unrecognized { + border-radius: 4px; + padding: 5px; + font-weight: bold; + color: red; +} .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 2430e1f85..f0b94db47 100644 --- a/IPython/html/static/style/style.min.css +++ b/IPython/html/static/style/style.min.css @@ -8288,6 +8288,36 @@ 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; +} +@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; @@ -8757,6 +8787,12 @@ input.raw_input:focus { p.p-space { margin-bottom: 10px; } +div.output_unrecognized { + border-radius: 4px; + padding: 5px; + font-weight: bold; + color: red; +} .rendered_html { color: #000000; /* any extras will just be numbers: */ From 88219df674e0ebbd95be4fc8868267ea27cfda22 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 19 Nov 2014 17:13:55 -0800 Subject: [PATCH 2/2] Preserve nbformat_minor from the future warn on click of unrecognized elements, rather than on load. --- IPython/html/static/notebook/js/cell.js | 17 +++++- IPython/html/static/notebook/js/notebook.js | 54 +++++++++++-------- IPython/html/static/notebook/js/outputarea.js | 11 +++- IPython/html/static/notebook/less/cell.less | 10 ++++ .../html/static/notebook/less/outputarea.less | 10 ++++ IPython/html/static/style/ipython.min.css | 17 +++++- IPython/html/static/style/style.min.css | 17 +++++- 7 files changed, 109 insertions(+), 27 deletions(-) diff --git a/IPython/html/static/notebook/js/cell.js b/IPython/html/static/notebook/js/cell.js index 26ecbd286..b54ade31c 100644 --- a/IPython/html/static/notebook/js/cell.js +++ b/IPython/html/static/notebook/js/cell.js @@ -636,7 +636,7 @@ define([ } else { data.metadata = this.metadata; } - this.element.find('.inner_cell').text("Unrecognized cell type: " + data.cell_type); + this.element.find('.inner_cell').find("a").text("Unrecognized cell type: " + data.cell_type); }; UnrecognizedCell.prototype.create_element = function () { @@ -647,10 +647,23 @@ define([ var prompt = $('
').addClass('prompt input_prompt'); cell.append(prompt); var inner_cell = $('
').addClass('inner_cell'); - inner_cell.text("Unrecognized cell type"); + 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; diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js index 9bb3b00b9..04eb318f7 100644 --- a/IPython/html/static/notebook/js/notebook.js +++ b/IPython/html/static/notebook/js/notebook.js @@ -147,7 +147,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(); @@ -207,6 +207,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; }); @@ -300,6 +308,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 @@ -2234,26 +2264,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 55bac957d..30af73ac0 100644 --- a/IPython/html/static/notebook/js/outputarea.js +++ b/IPython/html/static/notebook/js/outputarea.js @@ -271,7 +271,6 @@ define([ } var record_output = true; - console.log("appending", json); switch(json.output_type) { case 'execute_result': json = this.validate_mimebundle(json); @@ -490,10 +489,18 @@ 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.text("Unrecognized output: " + json.output_type); + subarea.append( + $("") + .attr("href", "#") + .text("Unrecognized output: " + json.output_type) + .click(function () { + that.events.trigger('unrecognized_output.OutputArea', {output: json}) + }) + ); this._safe_append(toinsert); }; diff --git a/IPython/html/static/notebook/less/cell.less b/IPython/html/static/notebook/less/cell.less index 1a5c54190..ed4ad4a12 100644 --- a/IPython/html/static/notebook/less/cell.less +++ b/IPython/html/static/notebook/less/cell.less @@ -74,6 +74,16 @@ div.unrecognized_cell { 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) { diff --git a/IPython/html/static/notebook/less/outputarea.less b/IPython/html/static/notebook/less/outputarea.less index a9f15ed06..07f827634 100644 --- a/IPython/html/static/notebook/less/outputarea.less +++ b/IPython/html/static/notebook/less/outputarea.less @@ -177,4 +177,14 @@ 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 0acc29b9d..5b5c4e313 100644 --- a/IPython/html/static/style/ipython.min.css +++ b/IPython/html/static/style/ipython.min.css @@ -444,6 +444,14 @@ div.unrecognized_cell .inner_cell { 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; @@ -919,11 +927,18 @@ p.p-space { margin-bottom: 10px; } div.output_unrecognized { - border-radius: 4px; 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 f0b94db47..9d239f185 100644 --- a/IPython/html/static/style/style.min.css +++ b/IPython/html/static/style/style.min.css @@ -8313,6 +8313,14 @@ div.unrecognized_cell .inner_cell { 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; @@ -8788,11 +8796,18 @@ p.p-space { margin-bottom: 10px; } div.output_unrecognized { - border-radius: 4px; 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: */