diff --git a/packages/base/src/widget.ts b/packages/base/src/widget.ts index 981264fc31..a1f847f3b2 100644 --- a/packages/base/src/widget.ts +++ b/packages/base/src/widget.ts @@ -88,6 +88,21 @@ export class WidgetModel extends Backbone.Model { }; } + /** + * Generate extra mime bundle for this widget + */ + async generateMimeBundle() { + return {}; + } + + /** + * Whether the `generateMimeBundle` output should + * overwrite the mimeBundle or extend it + */ + shouldOverwriteMimeBundle(): boolean { + return false; + } + /** * Test to see if the model has been synced with the server. * diff --git a/packages/controls/src/widget_image.ts b/packages/controls/src/widget_image.ts index a1169840ab..a06b377422 100644 --- a/packages/controls/src/widget_image.ts +++ b/packages/controls/src/widget_image.ts @@ -26,6 +26,15 @@ export class ImageModel extends CoreDOMWidgetModel { }, }, }; + + async generateMimeBundle() { + const view: ImageView = await this.widget_manager.create_view( + this + ); + return Promise.resolve({ + 'text/html': view.el.outerHTML, + }); + } } export class ImageView extends DOMWidgetView { diff --git a/packages/controls/src/widget_string.ts b/packages/controls/src/widget_string.ts index 43e7e50e3d..d593b81830 100644 --- a/packages/controls/src/widget_string.ts +++ b/packages/controls/src/widget_string.ts @@ -184,6 +184,11 @@ export class HTMLModel extends StringModel { _model_name: 'HTMLModel', }; } + generateMimeBundle() { + return Promise.resolve({ + 'text/html': this.get('value'), + }); + } } export class HTMLView extends StringView { diff --git a/widgetsnbextension/src/manager.js b/widgetsnbextension/src/manager.js index b49537c366..6d641b61c3 100644 --- a/widgetsnbextension/src/manager.js +++ b/widgetsnbextension/src/manager.js @@ -219,6 +219,49 @@ export class WidgetManager extends ManagerBase { */ _init_actions() { var notifier = Jupyter.notification_area.widget('widgets'); + this.notebook.events.on('before_save.Notebook', async () => { + var cells = Jupyter.notebook.get_cells(); + // notebook.js save_notebook doesn't want for this promise, we are simply lucky when this + // finishes before saving. + await Promise.all( + cells.map(async (cell) => { + if (!cell.output_area) { + return; + } + + var widget_output = cell.output_area.outputs.find((output) => { + return ( + (output.data && output.data[MIME_TYPE]) || + (output.metadata && output.metadata[MIME_TYPE]) + ); + }); + if (widget_output) { + var model_id = widget_output.data[MIME_TYPE] + ? widget_output.data[MIME_TYPE].model_id + : widget_output.metadata[MIME_TYPE].model_id; + var model = await this.get_model(model_id); + if (model) { + var bundle = await model.generateMimeBundle(); + + if ( + widget_output.data[MIME_TYPE] && + model.shouldOverwriteMimeBundle() + ) { + widget_output.metadata[MIME_TYPE] = + widget_output.data[MIME_TYPE]; + widget_output.metadata['text/plain'] = + widget_output.data['text/plain']; + + delete widget_output.data[MIME_TYPE]; + delete widget_output.data['text/plain']; + } + + _.extend(widget_output.data, bundle); + } + } + }) + ); + }); this.saveWidgetsAction = { handler: function () { this.get_state({