diff --git a/notebook/static/notebook/js/object-to-preact.js b/notebook/static/notebook/js/object-to-preact.js new file mode 100644 index 00000000000..241b322cfb8 --- /dev/null +++ b/notebook/static/notebook/js/object-to-preact.js @@ -0,0 +1,108 @@ +/** + * The original copy of this comes from + * https://github.com/remarkablemark/REON/blob/1f126e71c17f96daad518abffdb2c53b66b8b792/lib/object-to-react.js + * + * MIT License + * + * Copyright (c) 2016 Menglin "Mark" Xu + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +define([], function() { + "use strict"; + // Since apparently this is global rather than part of the UMD loading + var preact = window.preact; + + /** + * Convert an object to preact element(s). + * + * @param {Object} obj - The element object. + * @return {ReactElement} + */ + function objectToPreactElement(obj) { + // Pack args for preact.h + var args = []; + + if (!obj.tagName || typeof obj.tagName !== "string") { + throw new Error("Invalid tagName on ", JSON.stringify(obj, null, 2)); + } + if (!obj.attributes || typeof obj.attributes !== "object") { + throw new Error("Attributes must exist on a VDOM Object"); + } + + // `React.createElement` 1st argument: type + args[0] = obj.tagName; + args[1] = obj.attributes; + + const children = obj.children; + + if (children) { + if (Array.isArray(children)) { + // to be safe (although this should never happen) + if (args[1] === undefined) { + args[1] = null; + } + args = args.concat(arrayToPreactChildren(children)); + } else if (typeof children === "string") { + args[2] = children; + } else if (typeof children === "object") { + args[2] = objectToPreactElement(children); + } else { + console.warn("invalid vdom data passed", children); + } + } + + return preact.h.apply({}, args); + } + + /** + * Convert an array of items to Preact children. + * + * @param {Array} arr - The array. + * @return {Array} - The array of mixed values. + */ + function arrayToPreactChildren(arr) { + // similar to `props.children` + var result = []; + // child of `props.children` + + // iterate through the `children` + for (var i = 0, len = arr.length; i < len; i++) { + // child can have mixed values: text, Preact element, or array + const item = arr[i]; + if (Array.isArray(item)) { + result.push(arrayToPreactChildren(item)); + } else if (typeof item === "string") { + result.push(item); + } else if (typeof item === "object") { + const keyedItem = item; + item.key = i; + result.push(objectToPreactElement(keyedItem)); + } else { + console.warn("invalid vdom data passed", item); + } + } + + return result; + } + + return { objectToPreactElement }; +}); diff --git a/notebook/static/notebook/js/outputarea.js b/notebook/static/notebook/js/outputarea.js index 66a1ffcdbb0..f56e5d9d029 100644 --- a/notebook/static/notebook/js/outputarea.js +++ b/notebook/static/notebook/js/outputarea.js @@ -3,14 +3,16 @@ define([ 'jquery', + 'underscore', 'base/js/utils', 'base/js/i18n', 'base/js/security', 'base/js/keyboard', 'services/config', 'notebook/js/mathjaxutils', + 'notebook/js/object-to-preact', 'components/marked/lib/marked', -], function($, utils, i18n, security, keyboard, configmod, mathjaxutils, marked) { +], function($, _, utils, i18n, security, keyboard, configmod, mathjaxutils, otp, marked) { "use strict"; /** @@ -265,9 +267,10 @@ define([ var MIME_GIF = 'image/gif'; var MIME_PDF = 'application/pdf'; var MIME_TEXT = 'text/plain'; - + var MIME_VDOM = "application/vdom.v1+json"; OutputArea.output_types = [ + MIME_VDOM, MIME_JAVASCRIPT, MIME_HTML, MIME_MARKDOWN, @@ -660,6 +663,7 @@ define([ }; OutputArea.safe_outputs = {}; + OutputArea.safe_outputs[MIME_VDOM] = true; OutputArea.safe_outputs[MIME_TEXT] = true; OutputArea.safe_outputs[MIME_LATEX] = true; OutputArea.safe_outputs[MIME_PNG] = true; @@ -698,6 +702,20 @@ define([ return null; }; + var append_vdom = function(vdom, md, element) { + var type = MIME_VDOM; + var toinsert = this.create_output_subarea( + md, + "output_html rendered_vdom", + type + ); + + element.append(toinsert); + preact.render(otp.objectToPreactElement(_.clone(vdom)), toinsert[0]); + + return toinsert; + }; + var append_html = function (html, md, element) { var type = MIME_HTML; @@ -1096,6 +1114,7 @@ define([ OutputArea.display_order = [ + MIME_VDOM, MIME_JAVASCRIPT, MIME_HTML, MIME_MARKDOWN, @@ -1119,6 +1138,7 @@ define([ OutputArea.append_map[MIME_LATEX] = append_latex; OutputArea.append_map[MIME_JAVASCRIPT] = append_javascript; OutputArea.append_map[MIME_PDF] = append_pdf; + OutputArea.append_map[MIME_VDOM] = append_vdom; OutputArea.prototype.mime_types = function () { return OutputArea.display_order; @@ -1135,3 +1155,4 @@ define([ return {'OutputArea': OutputArea}; }); +