Ver Fonte

cleanup

S. Chris Colbert há 7 anos atrás
pai
commit
e708e44d16

+ 19 - 8
packages/rendermime-interfaces/src/index.ts

@@ -22,7 +22,7 @@ namespace IRenderMime {
   export
   interface IMimeModel {
     /**
-     * Whether the model is trusted.
+     * Whether the data in the model is trusted.
      */
     readonly trusted: boolean;
 
@@ -40,9 +40,9 @@ namespace IRenderMime {
      * Set the data associated with the model.
      *
      * #### Notes
-     * Calling this function may trigger an asynchronous operation that could
-     * cause the renderer to be rendered with a new model containing the new
-     * data.
+     * Calling this function may trigger an asynchronous operation
+     * that could cause the renderer to be rendered with a new model
+     * containing the new data.
      */
     setData(options: IMimeModel.ISetDataOptions): void;
   }
@@ -72,8 +72,8 @@ namespace IRenderMime {
   /**
    * The options used to initialize a document widget factory.
    *
-   * This interface is intended to be used by mime renderer extensions to
-   * declaratively define a document opener that uses its renderer factory.
+   * This interface is intended to be used by mime renderer extensions
+   * to define a document opener that uses its renderer factory.
    */
   export
   interface IDocumentWidgetFactoryOptions {
@@ -185,7 +185,7 @@ namespace IRenderMime {
   }
 
   /**
-   * A widget that provides a ready promise.
+   * A widget which dislays the contents of a mime model.
    */
   export
   interface IRenderer extends Widget {
@@ -195,6 +195,10 @@ namespace IRenderMime {
      * @param model - The mime model to render.
      *
      * @returns A promise which resolves when rendering is complete.
+     *
+     * #### Notes
+     * This method may be called multiple times during the lifetime
+     * of the widget to update it if and when new data is available.
      */
     renderModel(model: IMimeModel): Promise<void>;
   }
@@ -205,7 +209,14 @@ namespace IRenderMime {
   export
   interface IRendererFactory {
     /**
-     * Whether the factory can safely handle untrusted data.
+     * Whether the factory is a "safe" factory.
+     *
+     * #### Notes
+     * A "safe" factory produces renderer widgets which can render
+     * untrusted model data in a usable way. *All* renderers must
+     * handle untrusted data safely, but some may simply failover
+     * with a "Run cell to view output" message. A "safe" renderer
+     * is an indication that its sanitized output will be useful.
      */
     readonly safe: boolean;
 

+ 19 - 31
packages/rendermime/src/factories.ts

@@ -6,7 +6,7 @@ import {
   IRenderMime
 } from '@jupyterlab/rendermime-interfaces';
 
-import * as Renderers
+import * as widgets
   from './widgets';
 
 
@@ -17,7 +17,7 @@ export
 const htmlRendererFactory: IRenderMime.IRendererFactory = {
   safe: true,
   mimeTypes: ['text/html'],
-  createRenderer: options => new Renderers.RenderedHTML(options)
+  createRenderer: options => new widgets.RenderedHTML(options)
 };
 
 
@@ -28,7 +28,7 @@ export
 const imageRendererFactory: IRenderMime.IRendererFactory = {
   safe: true,
   mimeTypes: ['image/png', 'image/jpeg', 'image/gif'],
-  createRenderer: options => new Renderers.RenderedImage(options)
+  createRenderer: options => new widgets.RenderedImage(options)
 };
 
 
@@ -39,7 +39,7 @@ export
 const latexRendererFactory: IRenderMime.IRendererFactory = {
   safe: true,
   mimeTypes: ['text/latex'],
-  createRenderer: options => new Renderers.RenderedLatex(options)
+  createRenderer: options => new widgets.RenderedLatex(options)
 };
 
 
@@ -50,29 +50,7 @@ export
 const markdownRendererFactory: IRenderMime.IRendererFactory = {
   safe: true,
   mimeTypes: ['text/markdown'],
-  createRenderer: options => new Renderers.RenderedMarkdown(options)
-};
-
-
-/**
- * A mime renderer factory for text and jupyter console text data.
- */
-export
-const textRendererFactory: IRenderMime.IRendererFactory = {
-  safe: true,
-  mimeTypes: ['text/plain', 'application/vnd.jupyter.stdout', 'application/vnd.jupyter.stderr'],
-  createRenderer: options => new Renderers.RenderedText(options)
-};
-
-
-/**
- * A mime renderer factory for javascript.
- */
-export
-const javaScriptRendererFactory: IRenderMime.IRendererFactory = {
-  safe: false,
-  mimeTypes: ['text/javascript', 'application/javascript'],
-  createRenderer: options => new Renderers.RenderedJavaScript(options)
+  createRenderer: options => new widgets.RenderedMarkdown(options)
 };
 
 
@@ -83,7 +61,7 @@ export
 const pdfRendererFactory: IRenderMime.IRendererFactory = {
   safe: false,
   mimeTypes: ['application/pdf'],
-  createRenderer: options => new Renderers.RenderedPDF(options)
+  createRenderer: options => new widgets.RenderedPDF(options)
 };
 
 
@@ -94,16 +72,26 @@ export
 const svgRendererFactory: IRenderMime.IRendererFactory = {
   safe: false,
   mimeTypes: ['image/svg+xml'],
-  createRenderer: options => new Renderers.RenderedSVG(options)
+  createRenderer: options => new widgets.RenderedSVG(options)
+};
+
+
+/**
+ * A mime renderer factory for plain and jupyter console text data.
+ */
+export
+const textRendererFactory: IRenderMime.IRendererFactory = {
+  safe: true,
+  mimeTypes: ['text/plain', 'application/vnd.jupyter.stdout', 'application/vnd.jupyter.stderr'],
+  createRenderer: options => new widgets.RenderedText(options)
 };
 
 
 /**
- * The builtin factories provided by the package.
+ * The builtin factories provided by the rendermime package.
  */
 export
 const defaultRendererFactories: ReadonlyArray<IRenderMime.IRendererFactory> = [
-  javaScriptRendererFactory,
   htmlRendererFactory,
   markdownRendererFactory,
   latexRendererFactory,

+ 2 - 1
packages/rendermime/src/index.ts

@@ -5,9 +5,10 @@
 import '../style/index.css';  // Why is this first?
 
 export * from '@jupyterlab/rendermime-interfaces';
+export * from './factories';
 export * from './latex';
 export * from './mimemodel';
 export * from './outputmodel';
+export * from './renderers';
 export * from './rendermime';
-export * from './factories';
 export * from './widgets';

+ 801 - 0
packages/rendermime/src/renderers.ts

@@ -0,0 +1,801 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+import {
+  ansi_to_html, escape_for_html
+} from 'ansi_up';
+
+import * as marked
+  from 'marked';
+
+import {
+  ISanitizer
+} from '@jupyterlab/apputils';
+
+import {
+  Mode, CodeMirrorEditor
+} from '@jupyterlab/codemirror';
+
+import {
+  URLExt
+} from '@jupyterlab/coreutils';
+
+import {
+  IRenderMime
+} from '@jupyterlab/rendermime-interfaces';
+
+import {
+  typeset, removeMath, replaceMath
+} from './latex';
+
+
+/**
+ * Render HTML into a host node.
+ *
+ * @params options - The options for rendering.
+ *
+ * @returns A promise which resolves when rendering is complete.
+ */
+export
+function renderHTML(options: renderHTML.IOptions): Promise<void> {
+  // Unpack the options.
+  let {
+    host, source, trusted, sanitizer, resolver, linkHandler, shouldTypeset
+  } = options;
+
+  // Bail early if the source is empty.
+  if (!source) {
+    host.textContent = '';
+    return Promise.resolve(undefined);
+  }
+
+  // Sanitize the source if it is not trusted. This removes all
+  // `<script>` tags as well as other potentially harmful HTML.
+  if (!trusted) {
+    source = sanitizer.sanitize(source);
+  }
+
+  // Set the inner HTML of the host.
+  host.innerHTML = source;
+
+  // TODO - arbitrary script execution is disabled for now.
+  // Eval any script tags contained in the HTML. This is not done
+  // automatically by the browser when script tags are created by
+  // setting `innerHTML`. The santizer should have removed all of
+  // the script tags for untrusted source, but this extra trusted
+  // check is just extra insurance.
+  // if (trusted) {
+  //   // TODO do we really want to run scripts? Because if so, there
+  //   // is really no difference between this and a JS mime renderer.
+  //   Private.evalInnerHTMLScriptTags(host);
+  // }
+
+  // Patch the urls if a resolver is available.
+  let promise: Promise<void>;
+  if (resolver) {
+    promise = Private.handleUrls(host, resolver, linkHandler);
+  } else {
+    promise = Promise.resolve(undefined);
+  }
+
+  // Return the final rendered promise.
+  return promise.then(() => { if (shouldTypeset) { typeset(host); } });
+}
+
+
+/**
+ * The namespace for the `renderHTML` function statics.
+ */
+export
+namespace renderHTML {
+  /**
+   * The options for the `renderHTML` function.
+   */
+  export
+  interface IOptions {
+    /**
+     * The host node for the rendered HTML.
+     */
+    host: HTMLElement;
+
+    /**
+     * The HTML source to render.
+     */
+    source: string;
+
+    /**
+     * Whether the source is trusted.
+     */
+    trusted: boolean;
+
+    /**
+     * The html sanitizer for untrusted source.
+     */
+    sanitizer: ISanitizer;
+
+    /**
+     * An optional url resolver.
+     */
+    resolver: IRenderMime.IResolver | null;
+
+    /**
+     * An optional link handler.
+     */
+    linkHandler: IRenderMime.ILinkHandler | null;
+
+    /**
+     * Whether the node should be typeset.
+     */
+    shouldTypeset: boolean;
+  }
+}
+
+
+/**
+ * Render an image into a host node.
+ *
+ * @params options - The options for rendering.
+ *
+ * @returns A promise which resolves when rendering is complete.
+ */
+export
+function renderImage(options: renderImage.IRenderOptions): Promise<void> {
+  // Unpack the options.
+  let { host, mimeType, source, width, height } = options;
+
+  // Clear the content in the host.
+  host.textContent = '';
+
+  // Create the image element.
+  let img = document.createElement('img');
+
+  // Set the source of the image.
+  img.src = `data:${mimeType};base64,${source}`;
+
+  // Set the size of the image if provided.
+  if (typeof height === 'number') {
+    img.height = height;
+  }
+  if (typeof width === 'number') {
+    img.width = width;
+  }
+
+  // Add the image to the host.
+  host.appendChild(img);
+
+  // Return the rendered promise.
+  return Promise.resolve(undefined);
+}
+
+
+/**
+ * The namespace for the `renderImage` function statics.
+ */
+export
+namespace renderImage {
+  /**
+   * The options for the `renderImage` function.
+   */
+  export
+  interface IRenderOptions {
+    /**
+     * The image node to update with the content.
+     */
+    host: HTMLElement;
+
+    /**
+     * The mime type for the image.
+     */
+    mimeType: string;
+
+    /**
+     * The base64 encoded source for the image.
+     */
+    source: string;
+
+    /**
+     * The optional width for the image.
+     */
+    width?: number;
+
+    /**
+     * The optional height for the image.
+     */
+    height?: number;
+  }
+}
+
+
+/**
+ * Render LaTeX into a host node.
+ *
+ * @params options - The options for rendering.
+ *
+ * @returns A promise which resolves when rendering is complete.
+ */
+export
+function renderLatex(options: renderLatex.IRenderOptions): Promise<void> {
+  // Unpack the options.
+  let { host, source, shouldTypeset } = options;
+
+  // Set the source on the node.
+  host.textContent = source;
+
+  // Typeset the node if needed.
+  if (shouldTypeset) {
+    typeset(host);
+  }
+
+  // Return the rendered promise.
+  return Promise.resolve(undefined);
+}
+
+
+/**
+ * The namespace for the `renderLatex` function statics.
+ */
+export
+namespace renderLatex {
+  /**
+   * The options for the `renderLatex` function.
+   */
+  export
+  interface IRenderOptions {
+    /**
+     * The host node for the rendered LaTeX.
+     */
+    host: HTMLElement;
+
+    /**
+     * The LaTeX source to render.
+     */
+    source: string;
+
+    /**
+     * Whether the node should be typeset.
+     */
+    shouldTypeset: boolean;
+  }
+}
+
+
+/**
+ * Render Markdown into a host node.
+ *
+ * @params options - The options for rendering.
+ *
+ * @returns A promise which resolves when rendering is complete.
+ */
+export
+function renderMarkdown(options: renderMarkdown.IRenderOptions): Promise<void> {
+  // Unpack the options.
+  let {
+    host, source, trusted, sanitizer, resolver, linkHandler, shouldTypeset
+  } = options;
+
+  // Clear the content if there is no source.
+  if (!source) {
+    host.textContent = '';
+    return Promise.resolve(undefined);
+  }
+
+  // Separate math from normal markdown text.
+  let parts = removeMath(source);
+
+  // Render the markdown and handle sanitization.
+  return Private.renderMarked(parts['text']).then(content => {
+    // Restore the math content in the rendered markdown.
+    content = replaceMath(content, parts['math']);
+
+    // Santize the content it is not trusted.
+    if (!trusted) {
+      content = sanitizer.sanitize(content);
+    }
+
+    // Set the inner HTML of the host.
+    host.innerHTML = content;
+
+    // TODO arbitrary script execution is disabled for now.
+    // Eval any script tags contained in the HTML. This is not done
+    // automatically by the browser when script tags are created by
+    // setting `innerHTML`. The santizer should have removed all of
+    // the script tags for untrusted source, but this extra trusted
+    // check is just extra insurance.
+    // if (trusted) {
+    //   // TODO really want to run scripts?
+    //   Private.evalInnerHTMLScriptTags(host);
+    // }
+
+    // Apply ids to the header nodes.
+    Private.headerAnchors(host);
+
+    // TODO - this was in the old code, but why?
+    // <host owner widget>.fit();
+
+    // Patch the urls if a resolver is available.
+    let promise: Promise<void>;
+    if (resolver) {
+      promise = Private.handleUrls(host, resolver, linkHandler);
+    } else {
+      promise = Promise.resolve(undefined);
+    }
+
+    // Return the rendered promise.
+    return promise;
+  }).then(() => { if (shouldTypeset) { typeset(host) } });
+}
+
+
+/**
+ * The namespace for the `renderMarkdown` function statics.
+ */
+export
+namespace renderMarkdown {
+  /**
+   * The options for the `renderMarkdown` function.
+   */
+  export
+  interface IRenderOptions {
+    /**
+     * The host node for the rendered Markdown.
+     */
+    host: HTMLElement;
+
+    /**
+     * The Markdown source to render.
+     */
+    source: string;
+
+    /**
+     * Whether the source is trusted.
+     */
+    trusted: boolean;
+
+    /**
+     * The html sanitizer for untrusted source.
+     */
+    sanitizer: ISanitizer;
+
+    /**
+     * An optional url resolver.
+     */
+    resolver: IRenderMime.IResolver | null;
+
+    /**
+     * An optional link handler.
+     */
+    linkHandler: IRenderMime.ILinkHandler | null;
+
+    /**
+     * Whether the node should be typeset.
+     */
+    shouldTypeset: boolean;
+  }
+}
+
+
+/**
+ * Render a PDF into a host node.
+ *
+ * @params options - The options for rendering.
+ *
+ * @returns A promise which resolves when rendering is complete.
+ */
+export
+function renderPDF(options: renderPDF.IRenderOptions): Promise<void> {
+  // Unpack the options.
+  let { host, source, trusted } = options;
+
+  // Clear the content if there is no source.
+  if (!source) {
+    host.textContent = '';
+    return Promise.resolve(undefined);
+  }
+
+  // Display a message if the source is not trusted.
+  if (!trusted) {
+    host.textContent = 'Execute the cell to display PDF.';
+    return Promise.resolve(undefined);
+  }
+
+  // Update the host with the display content.
+  let href = `data:application/pdf;base64,${source}`;
+  host.innerHTML = `<a target="_blank" href="${href}">View PDF</a>`;
+
+  // Return the final rendered promise.
+  return Promise.resolve(undefined);
+}
+
+
+/**
+ * The namespace for the `renderPDF` function statics.
+ */
+export
+namespace renderPDF {
+  /**
+   * The options for the `renderPDF` function.
+   */
+  export
+  interface IRenderOptions {
+    /**
+     * The host node for the rendered PDF.
+     */
+    host: HTMLElement;
+
+    /**
+     * The base64 encoded source for the PDF.
+     */
+    source: string;
+
+    /**
+     * Whether the source is trusted.
+     */
+    trusted: boolean;
+  }
+}
+
+
+/**
+ * Render SVG into a host node.
+ *
+ * @params options - The options for rendering.
+ *
+ * @returns A promise which resolves when rendering is complete.
+ */
+export
+function renderSVG(options: renderSVG.IRenderOptions): Promise<void> {
+  // Unpack the options.
+  let {
+    host, source, trusted, resolver, linkHandler, shouldTypeset
+  } = options;
+
+  // Clear the content if there is no source.
+  if (!source) {
+    host.textContent = '';
+    return Promise.resolve(undefined);
+  }
+
+  // Display a message if the source is not trusted.
+  if (!trusted) {
+    host.textContent = 'Execute the cell to display SVG.';
+    return Promise.resolve(undefined);
+  }
+
+  // Set the inner HTML of the host.
+  host.innerHTML = source;
+
+  // TODO
+  // what about script tags inside the svg?
+
+  // Patch the urls if a resolver is available.
+  let promise: Promise<void>;
+  if (resolver) {
+    promise = Private.handleUrls(host, resolver, linkHandler);
+  } else {
+    promise = Promise.resolve(undefined);
+  }
+
+  // Return the final rendered promise.
+  return promise.then(() => { if (shouldTypeset) { typeset(host); } });
+}
+
+
+/**
+ * The namespace for the `renderSVG` function statics.
+ */
+export
+namespace renderSVG {
+  /**
+   * The options for the `renderSVG` function.
+   */
+  export
+  interface IRenderOptions {
+    /**
+     * The host node for the rendered SVG.
+     */
+    host: HTMLElement;
+
+    /**
+     * The SVG source.
+     */
+    source: string;
+
+    /**
+     * Whether the source is trusted.
+     */
+    trusted: boolean;
+
+    /**
+     * An optional url resolver.
+     */
+    resolver: IRenderMime.IResolver | null;
+
+    /**
+     * An optional link handler.
+     */
+    linkHandler: IRenderMime.ILinkHandler | null;
+
+    /**
+     * Whether the node should be typeset.
+     */
+    shouldTypeset: boolean;
+  }
+}
+
+
+/**
+ * Render text into a host node.
+ *
+ * @params options - The options for rendering.
+ *
+ * @returns A promise which resolves when rendering is complete.
+ */
+export
+function renderText(options: renderText.IRenderOptions): Promise<void> {
+  // Unpack the options.
+  let { host, source } = options;
+
+  // Escape the terminal codes and HTML tags.
+  let data = escape_for_html(source);
+
+  // Create the HTML content.
+  let content = ansi_to_html(data, { use_classes: true });
+
+  // Set the inner HTML for the host node.
+  host.innerHTML = `<pre>${content}</pre>`;
+
+  // Return the rendered promise.
+  return Promise.resolve(undefined);
+}
+
+
+/**
+ * The namespace for the `renderText` function statics.
+ */
+export
+namespace renderText {
+  /**
+   * The options for the `renderText` function.
+   */
+  export
+  interface IRenderOptions {
+    /**
+     * The host node for the text content.
+     */
+    host: HTMLElement;
+
+    /**
+     * The source text to render.
+     */
+    source: string;
+  }
+}
+
+
+/**
+ * The namespace for module implementation details.
+ */
+namespace Private {
+  // This is disabled for now until we decide we actually really
+  // truly want to allow arbitrary script execution.
+  /**
+   * Eval the script tags contained in a host populated by `innerHTML`.
+   *
+   * When script tags are created via `innerHTML`, the browser does not
+   * evaluate them when they are added to the page. This function works
+   * around that by creating new equivalent script nodes manually, and
+   * replacing the originals.
+   */
+  // export
+  // function evalInnerHTMLScriptTags(host: HTMLElement): void {
+  //   // Create a snapshot of the current script nodes.
+  //   let scripts = toArray(host.getElementsByTagName('script'));
+
+  //   // Loop over each script node.
+  //   for (let script of scripts) {
+  //     // Skip any scripts which no longer have a parent.
+  //     if (!script.parentNode) {
+  //       continue;
+  //     }
+
+  //     // Create a new script node which will be clone.
+  //     let clone = document.createElement('script');
+
+  //     // Copy the attributes into the clone.
+  //     let attrs = script.attributes;
+  //     for (let i = 0, n = attrs.length; i < n; ++i) {
+  //       let { name, value } = attrs[i];
+  //       clone.setAttribute(name, value);
+  //     }
+
+  //     // Copy the text content into the clone.
+  //     clone.textContent = script.textContent;
+
+  //     // Replace the old script in the parent.
+  //     script.parentNode.replaceChild(clone, script);
+  //   }
+  // }
+
+  /**
+   * Render markdown for the specified content.
+   *
+   * @param content - The string of markdown to render.
+   *
+   * @return A promise which resolves with the rendered content.
+   */
+  export
+  function renderMarked(content: string): Promise<string> {
+    initializeMarked();
+    return new Promise<string>((resolve, reject) => {
+      marked(content, (err: any, content: string) => {
+        if (err) {
+          reject(err);
+        } else {
+          resolve(content);
+        }
+      });
+    });
+  }
+
+  /**
+   * Resolve the relative urls in element `src` and `href` attributes.
+   *
+   * @param node - The head html element.
+   *
+   * @param resolver - A url resolver.
+   *
+   * @param linkHandler - An optional link handler for nodes.
+   *
+   * @returns a promise fulfilled when the relative urls have been resolved.
+   */
+  export
+  function handleUrls(node: HTMLElement, resolver: IRenderMime.IResolver, linkHandler: IRenderMime.ILinkHandler | null): Promise<void> {
+    // Set up an array to collect promises.
+    let promises: Promise<void>[] = [];
+
+    // Handle HTML Elements with src attributes.
+    let nodes = node.querySelectorAll('*[src]');
+    for (let i = 0; i < nodes.length; i++) {
+      promises.push(handleAttr(nodes[i] as HTMLElement, 'src', resolver));
+    }
+
+    // Handle achor elements.
+    let anchors = node.getElementsByTagName('a');
+    for (let i = 0; i < anchors.length; i++) {
+      promises.push(handleAnchor(anchors[i], resolver, linkHandler));
+    }
+
+    // Handle link elements.
+    let links = node.getElementsByTagName('link');
+    for (let i = 0; i < links.length; i++) {
+      promises.push(handleAttr(links[i], 'href', resolver));
+    }
+
+    // Wait on all promises.
+    return Promise.all(promises).then(() => undefined);
+  }
+
+  /**
+   * Apply ids to headers.
+   */
+  export
+  function headerAnchors(node: HTMLElement): void {
+    let headerNames = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
+    for (let headerType of headerNames){
+      let headers = node.getElementsByTagName(headerType);
+      for (let i=0; i < headers.length; i++) {
+        let header = headers[i];
+        header.id = header.innerHTML.replace(/ /g, '-');
+        let anchor = document.createElement('a');
+        anchor.target = '_self';
+        anchor.textContent = '¶';
+        anchor.href = '#' + header.id;
+        anchor.classList.add('jp-InternalAnchorLink');
+        header.appendChild(anchor);
+      }
+    }
+  }
+
+  /**
+   * Handle a node with a `src` or `href` attribute.
+   */
+  function handleAttr(node: HTMLElement, name: 'src' | 'href', resolver: IRenderMime.IResolver): Promise<void> {
+    let source = node.getAttribute(name);
+    if (!source) {
+      return Promise.resolve(undefined);
+    }
+    node.setAttribute(name, '');
+    return resolver.resolveUrl(source).then(path => {
+      return resolver.getDownloadUrl(path);
+    }).then(url => {
+      node.setAttribute(name, url);
+    });
+  }
+
+  /**
+   * Handle an anchor node.
+   */
+  function handleAnchor(anchor: HTMLAnchorElement, resolver: IRenderMime.IResolver, linkHandler: IRenderMime.ILinkHandler | null): Promise<void> {
+    anchor.target = '_blank';
+    // Get the link path without the location prepended.
+    // (e.g. "./foo.md#Header 1" vs "http://localhost:8888/foo.md#Header 1")
+    let href = anchor.getAttribute('href');
+    // Bail if it is not a file-like url.
+    if (!href || href.indexOf('://') !== -1 && href.indexOf('//') === 0) {
+      return Promise.resolve(undefined);
+    }
+    // Remove the hash until we can handle it.
+    let hash = anchor.hash;
+    if (hash) {
+      // Handle internal link in the file.
+      if (hash === href) {
+        anchor.target = '_self';
+        return Promise.resolve(undefined);
+      }
+      // For external links, remove the hash until we have hash handling.
+      href = href.replace(hash, '');
+    }
+    // Get the appropriate file path.
+    return resolver.resolveUrl(href).then(path => {
+      // Handle the click override.
+      if (linkHandler && URLExt.isLocal(path)) {
+        linkHandler.handleLink(anchor, path);
+      }
+      // Get the appropriate file download path.
+      return resolver.getDownloadUrl(path);
+    }).then(url => {
+      // Set the visible anchor.
+      anchor.href = url + hash;
+    });
+  }
+
+  let markedInitialized = false;
+
+  /**
+   * Support GitHub flavored Markdown, leave sanitizing to external library.
+   */
+  function initializeMarked(): void {
+    if (markedInitialized) {
+      return;
+    }
+    markedInitialized = true;
+    marked.setOptions({
+      gfm: true,
+      sanitize: false,
+      tables: true,
+      // breaks: true; We can't use GFM breaks as it causes problems with tables
+      langPrefix: `cm-s-${CodeMirrorEditor.defaultConfig.theme} language-`,
+      highlight: (code, lang, callback) => {
+        if (!lang) {
+            // no language, no highlight
+            if (callback) {
+                callback(null, code);
+                return;
+            } else {
+                return code;
+            }
+        }
+        Mode.ensure(lang).then(spec => {
+          let el = document.createElement('div');
+          if (!spec) {
+              console.log(`No CodeMirror mode: ${lang}`);
+              callback(null, code);
+              return;
+          }
+          try {
+            Mode.run(code, spec.mime, el);
+            callback(null, el.innerHTML);
+          } catch (err) {
+            console.log(`Failed to highlight ${lang} code`, err);
+            callback(err, code);
+          }
+        }).catch(err => {
+          console.log(`No CodeMirror mode: ${lang}`);
+          console.log(`Require CodeMirror mode error: ${err}`);
+          callback(null, code);
+        });
+      }
+    });
+  }
+}

+ 0 - 805
packages/rendermime/src/renderhelpers.ts

@@ -1,805 +0,0 @@
-/*-----------------------------------------------------------------------------
-| Copyright (c) Jupyter Development Team.
-| Distributed under the terms of the Modified BSD License.
-|----------------------------------------------------------------------------*/
-import {
-  ansi_to_html, escape_for_html
-} from 'ansi_up';
-
-import * as marked
-  from 'marked';
-
-import {
-  ISanitizer
-} from '@jupyterlab/apputils';
-
-import {
-  Mode, CodeMirrorEditor
-} from '@jupyterlab/codemirror';
-
-import {
-  ReadonlyJSONObject
-} from '@phosphor/coreutils';
-
-import {
-  URLExt
-} from '@jupyterlab/coreutils';
-
-import {
-  IRenderMime
-} from '@jupyterlab/rendermime-interfaces';
-
-import {
-  typeset, removeMath, replaceMath
-} from './latex';
-
-
-/**
- * A collection of helper functions which render common formats.
- */
-export
-namespace RenderHelpers {
-  /**
-   * Render HTML into a host node.
-   *
-   * @params options - The options for rendering.
-   *
-   * @returns A promise which resolves when rendering is complete.
-   */
-  export
-  function renderHTML(options: renderHTML.IOptions): Promise<void> {
-    // Unpack the options.
-    let {
-      node, source, trusted, sanitizer, resolver, linkHandler, shouldTypeset
-    } = options;
-
-    // Clear any existing content.
-    node.textContent = '';
-
-    // Bail if there is no source.
-    if (!source) {
-      return Promise.resolve(undefined);
-    }
-
-    // Sanitize the source if it is not trusted.
-    if (!trusted) {
-      source = sanitizer.sanitize(source);
-    }
-
-    // Add the html to the node.
-    try {
-      let range = document.createRange();
-      node.appendChild(range.createContextualFragment(source));
-    } catch (error) {
-      console.warn('Environment does not support Range ' +
-                   'createContextualFragment, falling back on innerHTML');
-      node.innerHTML = source;
-    }
-
-    // Patch the urls if a resolver is available.
-    let promise: Promise<void>;
-    if (resolver) {
-      promise = Private.handleUrls(node, resolver, linkHandler);
-    } else {
-      promise = Promise.resolve(undefined);
-    }
-
-    // Return the final rendered promise.
-    return promise.then(() => { if (shouldTypeset) { typeset(node); } });
-  }
-
-  /**
-   * The namespace for the `RenderedHTML` class statics.
-   */
-  export
-  namespace renderHTML {
-    /**
-     * The options for the `renderHTML` function.
-     */
-    export
-    interface IOptions {
-      /**
-       * The node to use as the host of the rendered HTML.
-       */
-      node: HTMLElement;
-
-      /**
-       * The HTML source to render.
-       */
-      source: string;
-
-      /**
-       * Whether the source is trusted.
-       */
-      trusted: boolean;
-
-      /**
-       * The html sanitizer.
-       */
-      sanitizer: ISanitizer;
-
-      /**
-       * An optional url resolver.
-       */
-      resolver: IRenderMime.IResolver | null;
-
-      /**
-       * An optional link handler.
-       */
-      linkHandler: IRenderMime.ILinkHandler | null;
-
-      /**
-       * Whether the node should be typeset.
-       */
-      shouldTypeset: boolean;
-    }
-  }
-
-  /**
-   * Render Markdown into a host node.
-   *
-   * @params options - The options for rendering.
-   *
-   * @returns A promise which resolves when rendering is complete.
-   */
-  export
-  function renderMarkdown(options: renderMarkdown.IRenderOptions): Promise<void> {
-    // Unpack the options.
-    let {
-      node, source, trusted, sanitizer, resolver, linkHandler, shouldTypeset
-    } = options;
-
-    // Clear the content if there is no source.
-    if (!source) {
-      node.textContent = '';
-      return Promise.resolve(undefined);
-    }
-
-    // Separate math from normal markdown text.
-    let parts = removeMath(source);
-
-    // Render the markdown and handle sanitization.
-    return Private.renderMarked(parts['text']).then(content => {
-      // Restore the math content in the rendered markdown.
-      content = replaceMath(content, parts['math']);
-
-      // Santize the content it is not trusted.
-      if (!trusted) {
-        content = sanitizer.sanitize(content);
-      }
-
-      // Set the inner HTML to the parsed content.
-      node.innerHTML = content;
-
-      // Apply ids to the header nodes.
-      Private.headerAnchors(node);
-
-      // TODO - this was in the old code, but why?
-      // <node owner widget>.fit();
-
-      // Patch the urls if a resolver is available.
-      let promise: Promise<void>;
-      if (resolver) {
-        promise = Private.handleUrls(node, resolver, linkHandler);
-      } else {
-        promise = Promise.resolve(undefined);
-      }
-
-      // Return the rendered promise.
-      return promise;
-    }).then(() => { if (shouldTypeset) { typeset(node) } });
-  }
-
-  /**
-   * The namespace for the `renderMarkdown` function statics.
-   */
-  export
-  namespace renderMarkdown {
-    /**
-     * The options for the `renderMarkdown` function.
-     */
-    export
-    interface IRenderOptions {
-      /**
-       * The node to use as the host of the rendered Markdown.
-       */
-      node: HTMLElement;
-
-      /**
-       * The Markdown source to render.
-       */
-      source: string;
-
-      /**
-       * Whether the source is trusted.
-       */
-      trusted: boolean;
-
-      /**
-       * The html sanitizer.
-       */
-      sanitizer: ISanitizer;
-
-      /**
-       * An optional url resolver.
-       */
-      resolver: IRenderMime.IResolver | null;
-
-      /**
-       * An optional link handler.
-       */
-      linkHandler: IRenderMime.ILinkHandler | null;
-
-      /**
-       * Whether the node should be typeset.
-       */
-      shouldTypeset: boolean;
-    }
-  }
-
-  /**
-   * Render LaTeX into a host node.
-   *
-   * @params options - The options for rendering.
-   *
-   * @returns A promise which resolves when rendering is complete.
-   */
-  export
-  function renderLatex(options: renderLatex.IRenderOptions): Promise<void> {
-    // Unpack the options.
-    let { node, source, shouldTypeset } = options;
-
-    // Set the source on the node.
-    node.textContent = source;
-
-    // Typeset the node if needed.
-    if (shouldTypeset) {
-      typeset(node);
-    }
-
-    // Return the rendered promise.
-    return Promise.resolve(undefined);
-  }
-
-  /**
-   * The namespace for the `renderLatex` function statics.
-   */
-  export
-  namespace renderLatex {
-    /**
-     * The options for the `renderLatex` function.
-     */
-    export
-    interface IRenderOptions {
-      /**
-       * The node to use as the host of the rendered LaTeX.
-       */
-      node: HTMLElement;
-
-      /**
-       * The LaTeX source to render.
-       */
-      source: string;
-
-      /**
-       * Whether the node should be typeset.
-       */
-      shouldTypeset: boolean;
-    }
-  }
-
-  /**
-   * Render an image into a host node.
-   *
-   * @params options - The options for rendering.
-   *
-   * @returns A promise which resolves when rendering is complete.
-   */
-  export
-  function renderImage(options: renderImage.IRenderOptions): Promise<void> {
-    // Unpack the options.
-    let { mimeType, model, node } = options;
-
-    // Get the source text from the model.
-    let source = String(model.data[mimeType]);
-
-    // Set the source of the image.
-    node.src = `data:${mimeType};base64,${source}`;
-
-    // Look up the metadata for the model.
-    let metadata = model.metadata[mimeType] as ReadonlyJSONObject;
-
-    // Set the size of the image if specified by the metadata.
-    if (metadata) {
-      let { height, width } = metadata;
-      if (typeof height === 'number') {
-        node.height = height;
-      }
-      if (typeof width === 'number') {
-        node.width = width;
-      }
-    }
-
-    // Return the rendered promise.
-    return Promise.resolve(undefined);
-  }
-
-  /**
-   * The namespace for the `renderImage` function statics.
-   */
-  export
-  namespace renderImage {
-    /**
-     * The options for the `renderImage` function.
-     */
-    export
-    interface IRenderOptions {
-      /**
-       * The image node to update with the content.
-       */
-      node: HTMLImageElement;
-
-      /**
-       * The mime type for the image data in the model.
-       */
-      mimeType: string;
-
-      /**
-       * The mime model which holds the data to render.
-       */
-      model: IRenderMime.IMimeModel;
-    }
-  }
-
-  /**
-   * Render text into a host node.
-   *
-   * @params options - The options for rendering.
-   *
-   * @returns A promise which resolves when rendering is complete.
-   */
-  export
-  function renderText(options: renderText.IRenderOptions): Promise<void> {
-    // Unpack the options.
-    let { mimeType, model, node } = options;
-
-    // Get the source text from the model.
-    let source = String(model.data[mimeType]);
-
-    // Escape the terminal codes for HTMl.
-    let data = escape_for_html(source);
-
-    // Set the inner HTML for the host node.
-    node.innerHTML = ansi_to_html(data, { use_classes: true });
-
-    // Return the rendered promise.
-    return Promise.resolve(undefined);
-  }
-
-  /**
-   * The namespace for the `renderText` function statics.
-   */
-  export
-  namespace renderText {
-    /**
-     * The options for the `renderText` function.
-     */
-    export
-    interface IRenderOptions {
-      /**
-       * The node to use as the host for the text content.
-       */
-      node: HTMLElement;
-
-      /**
-       * The mime type for the text data in the model.
-       */
-      mimeType: string;
-
-      /**
-       * The mime model which holds the data to render.
-       */
-      model: IRenderMime.IMimeModel;
-    }
-  }
-
-  /**
-   * Render JavaScript into a host node.
-   *
-   * @params options - The options for rendering.
-   *
-   * @returns A promise which resolves when rendering is complete.
-   */
-  export
-  function renderJavaScript(options: renderJavaScript.IRenderOptions): Promise<void> {
-    // Unpack the options.
-    let { mimeType, model, node } = options;
-
-    // Clear the content of the node.
-    node.textContent = '';
-
-    // Get the source text from the model.
-    let source = String(model.data[mimeType]);
-
-    // Bail early if there is no source.
-    if (!source) {
-      return Promise.resolve(undefined);
-    }
-
-    // Create the "script" node to hold the source.
-    let script: HTMLElement;
-    if (model.trusted) {
-      script = document.createElement('script');
-    } else {
-      script = document.createElement('pre');
-    }
-
-    // Set the source for the script.
-    script.textContent = source;
-
-    // Add the script to the host node.
-    node.appendChild(script);
-
-    // Return the rendered promise.
-    return Promise.resolve(undefined);
-  }
-
-  /**
-   * The namespace for the `renderJavaScript` function statics.
-   */
-  export
-  namespace renderJavaScript {
-    /**
-     * The options for the `renderJavaScript` function.
-     */
-    export
-    interface IRenderOptions {
-      /**
-       * The node to use as the host for the script content.
-       */
-      node: HTMLElement;
-
-      /**
-       * The mime type for the JavaScript source in the model.
-       */
-      mimeType: string;
-
-      /**
-       * The mime model which holds the JavaScript source.
-       */
-      model: IRenderMime.IMimeModel;
-    }
-  }
-
-  /**
-   * Render SVG into a host node.
-   *
-   * @params options - The options for rendering.
-   *
-   * @returns A promise which resolves when rendering is complete.
-   */
-  export
-  function renderSVG(options: renderSVG.IRenderOptions): Promise<void> {
-    // Unpack the options.
-    let {
-      mimeType, model, node, resolver, linkHandler, shouldTypeset
-    } = options;
-
-    // Get the source text from the model.
-    let source = String(model.data[mimeType]);
-
-    // Clear the content if there is no source.
-    if (!source) {
-      node.textContent = '';
-      return Promise.resolve(undefined);
-    }
-
-    // Create the "script" node to hold the source.
-    if (model.trusted) {
-      node.innerHTML = source;
-    } else {
-      node.textContent = 'Run the cell to display SVG.'
-    }
-
-    // Patch the urls if a resolver is available.
-    let promise: Promise<void>;
-    if (resolver) {
-      promise = Private.handleUrls(node, resolver, linkHandler);
-    } else {
-      promise = Promise.resolve(undefined);
-    }
-
-    // Return the final rendered promise.
-    return promise.then(() => { if (shouldTypeset) { typeset(node); } });
-  }
-
-  /**
-   * The namespace for the `renderSVG` function statics.
-   */
-  export
-  namespace renderSVG {
-    /**
-     * The options for the `renderSVG` function.
-     */
-    export
-    interface IRenderOptions {
-      /**
-       * The mimeType to render.
-       */
-      mimeType: string;
-
-      /**
-       * An optional url resolver.
-       */
-      resolver: IRenderMime.IResolver | null;
-
-      /**
-       * An optional link handler.
-       */
-      linkHandler: IRenderMime.ILinkHandler | null;
-
-      /**
-       * The node to use as the host of the rendered SVG.
-       */
-      node: HTMLElement;
-
-      /**
-       * The mime model which holds the data to render.
-       */
-      model: IRenderMime.IMimeModel;
-
-      /**
-       * Whether the node should be typeset.
-       */
-      shouldTypeset: boolean;
-    }
-  }
-
-  /**
-   * Render a PDF into a host node.
-   *
-   * @params options - The options for rendering.
-   *
-   * @returns A promise which resolves when rendering is complete.
-   */
-  export
-  function renderPDF(options: renderPDF.IRenderOptions): Promise<void> {
-    // Unpack the options.
-    let { mimeType, model, node } = options;
-
-    // Get the source text from the model.
-    let source = String(model.data[mimeType]);
-
-    // Clear the content if there is no source.
-    if (!source) {
-      node.textContent = '';
-      return Promise.resolve(undefined);
-    }
-
-    // Update the node with the display content.
-    if (model.trusted) {
-      let href = `data:application/pdf;base64,${source}`;
-      node.innerHTML = `<a target="_blank" href="${href}">View PDF</a>`;
-    } else {
-      node.textContent = 'Run the cell to display PDF.'
-    }
-
-    // Return the final rendered promise.
-    return Promise.resolve(undefined);
-  }
-
-  /**
-   * The namespace for the `renderPDF` function statics.
-   */
-  export
-  namespace renderPDF {
-    /**
-     * The options for the `renderPDF` function.
-     */
-    export
-    interface IRenderOptions {
-      /**
-       * The mimeType to render.
-       */
-      mimeType: string;
-
-      /**
-       * The node to use as the host of the rendered SVG.
-       */
-      node: HTMLElement;
-
-      /**
-       * The mime model which holds the data to render.
-       */
-      model: IRenderMime.IMimeModel;
-    }
-  }
-}
-
-
-/**
- * The namespace for module implementation details.
- */
-namespace Private {
-  /**
-   * Render markdown for the specified content.
-   *
-   * @param content - The string of markdown to render.
-   *
-   * @return A promise which resolves with the rendered content.
-   */
-  export
-  function renderMarked(content: string): Promise<string> {
-    initializeMarked();
-    return new Promise<string>((resolve, reject) => {
-      marked(content, (err: any, content: string) => {
-        if (err) {
-          reject(err);
-        } else {
-          resolve(content);
-        }
-      });
-    });
-  }
-
-  /**
-   * Resolve the relative urls in element `src` and `href` attributes.
-   *
-   * @param node - The head html element.
-   *
-   * @param resolver - A url resolver.
-   *
-   * @param linkHandler - An optional link handler for nodes.
-   *
-   * @returns a promise fulfilled when the relative urls have been resolved.
-   */
-  export
-  function handleUrls(node: HTMLElement, resolver: IRenderMime.IResolver, linkHandler: IRenderMime.ILinkHandler | null): Promise<void> {
-    // Set up an array to collect promises.
-    let promises: Promise<void>[] = [];
-
-    // Handle HTML Elements with src attributes.
-    let nodes = node.querySelectorAll('*[src]');
-    for (let i = 0; i < nodes.length; i++) {
-      promises.push(handleAttr(nodes[i] as HTMLElement, 'src', resolver));
-    }
-
-    // Handle achor elements.
-    let anchors = node.getElementsByTagName('a');
-    for (let i = 0; i < anchors.length; i++) {
-      promises.push(handleAnchor(anchors[i], resolver, linkHandler));
-    }
-
-    // Handle link elements.
-    let links = node.getElementsByTagName('link');
-    for (let i = 0; i < links.length; i++) {
-      promises.push(handleAttr(links[i], 'href', resolver));
-    }
-
-    // Wait on all promises.
-    return Promise.all(promises).then(() => undefined);
-  }
-
-  /**
-   * Handle a node with a `src` or `href` attribute.
-   */
-  function handleAttr(node: HTMLElement, name: 'src' | 'href', resolver: IRenderMime.IResolver): Promise<void> {
-    let source = node.getAttribute(name);
-    if (!source) {
-      return Promise.resolve(undefined);
-    }
-    node.setAttribute(name, '');
-    return resolver.resolveUrl(source).then(path => {
-      return resolver.getDownloadUrl(path);
-    }).then(url => {
-      node.setAttribute(name, url);
-    });
-  }
-
-  /**
-   * Apply ids to headers.
-   */
-  export
-  function headerAnchors(node: HTMLElement): void {
-    let headerNames = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
-    for (let headerType of headerNames){
-      let headers = node.getElementsByTagName(headerType);
-      for (let i=0; i < headers.length; i++) {
-        let header = headers[i];
-        header.id = header.innerHTML.replace(/ /g, '-');
-        let anchor = document.createElement('a');
-        anchor.target = '_self';
-        anchor.textContent = '¶';
-        anchor.href = '#' + header.id;
-        anchor.classList.add('jp-InternalAnchorLink');
-        header.appendChild(anchor);
-      }
-    }
-  }
-
-  /**
-   * Handle an anchor node.
-   */
-  function handleAnchor(anchor: HTMLAnchorElement, resolver: IRenderMime.IResolver, linkHandler: IRenderMime.ILinkHandler | null): Promise<void> {
-    anchor.target = '_blank';
-    // Get the link path without the location prepended.
-    // (e.g. "./foo.md#Header 1" vs "http://localhost:8888/foo.md#Header 1")
-    let href = anchor.getAttribute('href');
-    // Bail if it is not a file-like url.
-    if (!href || href.indexOf('://') !== -1 && href.indexOf('//') === 0) {
-      return Promise.resolve(undefined);
-    }
-    // Remove the hash until we can handle it.
-    let hash = anchor.hash;
-    if (hash) {
-      // Handle internal link in the file.
-      if (hash === href) {
-        anchor.target = '_self';
-        return Promise.resolve(undefined);
-      }
-      // For external links, remove the hash until we have hash handling.
-      href = href.replace(hash, '');
-    }
-    // Get the appropriate file path.
-    return resolver.resolveUrl(href).then(path => {
-      // Handle the click override.
-      if (linkHandler && URLExt.isLocal(path)) {
-        linkHandler.handleLink(anchor, path);
-      }
-      // Get the appropriate file download path.
-      return resolver.getDownloadUrl(path);
-    }).then(url => {
-      // Set the visible anchor.
-      anchor.href = url + hash;
-    });
-  }
-
-  let markedInitialized = false;
-
-  /**
-   * Support GitHub flavored Markdown, leave sanitizing to external library.
-   */
-  function initializeMarked(): void {
-    if (markedInitialized) {
-      return;
-    }
-    markedInitialized = true;
-    marked.setOptions({
-      gfm: true,
-      sanitize: false,
-      tables: true,
-      // breaks: true; We can't use GFM breaks as it causes problems with tables
-      langPrefix: `cm-s-${CodeMirrorEditor.defaultConfig.theme} language-`,
-      highlight: (code, lang, callback) => {
-        if (!lang) {
-            // no language, no highlight
-            if (callback) {
-                callback(null, code);
-                return;
-            } else {
-                return code;
-            }
-        }
-        Mode.ensure(lang).then(spec => {
-          let el = document.createElement('div');
-          if (!spec) {
-              console.log(`No CodeMirror mode: ${lang}`);
-              callback(null, code);
-              return;
-          }
-          try {
-            Mode.run(code, spec.mime, el);
-            callback(null, el.innerHTML);
-          } catch (err) {
-            console.log(`Failed to highlight ${lang} code`, err);
-            callback(err, code);
-          }
-        }).catch(err => {
-          console.log(`No CodeMirror mode: ${lang}`);
-          console.log(`Require CodeMirror mode error: ${err}`);
-          callback(null, code);
-        });
-      }
-    });
-  }
-}

+ 21 - 19
packages/rendermime/src/rendermime.ts

@@ -6,10 +6,6 @@ import {
   Contents, Session
 } from '@jupyterlab/services';
 
-import {
-  find
-} from '@phosphor/algorithm';
-
 import {
   IRenderMime
 } from '@jupyterlab/rendermime-interfaces';
@@ -85,21 +81,30 @@ class RenderMime {
    *
    * @param bundle - The bundle of mime data.
    *
-   * @param safe - Whether to only consider safe factories.
+   * @param preferSafe - Whether to prefer a safe factory.
    *
    * @returns The preferred mime type from the available factories,
    *   or `undefined` if the mime type cannot be rendered.
    */
-  preferredMimeType(bundle: ReadonlyJSONObject, safe: boolean): string | undefined {
-    return find(this.mimeTypes, mt => {
-      if (!(mt in bundle)) {
-        return false;
+  preferredMimeType(bundle: ReadonlyJSONObject, preferSafe: boolean): string | undefined {
+    // Try to find a safe factory first, if preferred.
+    if (preferSafe) {
+      for (let mt of this.mimeTypes) {
+        if (mt in bundle && this._factories[mt].safe) {
+          return mt;
+        }
       }
-      if (safe && !this._factories[mt].safe) {
-        return false;
+    }
+
+    // Otherwise, search for the best factory among all factories.
+    for (let mt of this.mimeTypes) {
+      if (mt in bundle) {
+        return mt;
       }
-      return true;
-    });
+    }
+
+    // Otherwise, no matching mime type exists.
+    return undefined;
   }
 
   /**
@@ -117,16 +122,13 @@ class RenderMime {
       throw new Error(`No factory for mime type: '${mimeType}'`);
     }
 
-    // Create the renderer options for the factory.
-    let options = {
+    // Invoke the best factory for the given mime type.
+    return this._factories[mimeType].createRenderer({
       mimeType,
       resolver: this.resolver,
       sanitizer: this.sanitizer,
       linkHandler: this.linkHandler
-    };
-
-    // Invoke the best factory for the given mime type.
-    return this._factories[mimeType].createRenderer(options);
+    });
   }
 
   /**

+ 59 - 93
packages/rendermime/src/widgets.ts

@@ -2,6 +2,10 @@
 | Copyright (c) Jupyter Development Team.
 | Distributed under the terms of the Modified BSD License.
 |----------------------------------------------------------------------------*/
+import {
+  IRenderMime
+} from '@jupyterlab/rendermime-interfaces';
+
 import {
   Message
 } from '@phosphor/messaging';
@@ -10,17 +14,12 @@ import {
   Widget
 } from '@phosphor/widgets';
 
-import {
-  IRenderMime
-} from '@jupyterlab/rendermime-interfaces';
-
 import {
   typeset
 } from './latex';
 
-import {
-  RenderHelpers
-} from './renderhelpers';
+import * as renderers
+  from './renderers';
 
 
 /**
@@ -72,8 +71,8 @@ abstract class RenderedCommon extends Widget implements IRenderMime.IRenderer {
   renderModel(model: IRenderMime.IMimeModel): Promise<void> {
     // TODO compare model against old model for early bail?
 
-    // Set the trusted flag on the node.
-    this.node.dataset['trusted'] = `${model.trusted}`;
+    // Toggle the trusted class on the widget.
+    this.toggleClass('jp-mod-trusted', model.trusted);
 
     // Render the actual content.
     return this.render(model);
@@ -130,51 +129,8 @@ class RenderedHTML extends RenderedHTMLCommon {
    * @returns A promise which resolves when rendering is complete.
    */
   render(model: IRenderMime.IMimeModel): Promise<void> {
-    return RenderHelpers.renderHTML({
-      node: this.node,
-      source: String(model.data[this.mimeType]),
-      trusted: model.trusted,
-      resolver: this.resolver,
-      sanitizer: this.sanitizer,
-      linkHandler: this.linkHandler,
-      shouldTypeset: this.isAttached
-    });
-  }
-
-  /**
-   * A message handler invoked on an `'after-attach'` message.
-   */
-  onAfterAttach(msg: Message): void {
-    typeset(this.node);
-  }
-}
-
-
-/**
- * A mime renderer for displaying Markdown with embeded latex.
- */
-export
-class RenderedMarkdown extends RenderedHTMLCommon {
-  /**
-   * Construct a new rendered markdown widget.
-   *
-   * @param options - The options for initializing the widget.
-   */
-  constructor(options: IRenderMime.IRendererOptions) {
-    super(options);
-    this.addClass('jp-RenderedMarkdown');
-  }
-
-  /**
-   * Render a mime model.
-   *
-   * @param model - The mime model to render.
-   *
-   * @returns A promise which resolves when rendering is complete.
-   */
-  render(model: IRenderMime.IMimeModel): Promise<void> {
-    return RenderHelpers.renderMarkdown({
-      node: this.node,
+    return renderers.renderHTML({
+      host: this.node,
       source: String(model.data[this.mimeType]),
       trusted: model.trusted,
       resolver: this.resolver,
@@ -216,8 +172,8 @@ class RenderedLatex extends RenderedCommon {
    * @returns A promise which resolves when rendering is complete.
    */
   render(model: IRenderMime.IMimeModel): Promise<void> {
-    return RenderHelpers.renderLatex({
-      node: this.node,
+    return renderers.renderLatex({
+      host: this.node,
       source: String(model.data[this.mimeType]),
       shouldTypeset: this.isAttached
     });
@@ -245,7 +201,6 @@ class RenderedImage extends RenderedCommon {
   constructor(options: IRenderMime.IRendererOptions) {
     super(options);
     this.addClass('jp-RenderedImage');
-    this.node.appendChild(document.createElement('img'));
   }
 
   /**
@@ -256,29 +211,30 @@ class RenderedImage extends RenderedCommon {
    * @returns A promise which resolves when rendering is complete.
    */
   render(model: IRenderMime.IMimeModel): Promise<void> {
-    return RenderHelpers.renderImage({
-      model,
+    return renderers.renderImage({
+      host: this.node,
       mimeType: this.mimeType,
-      node: this.node.firstChild as HTMLImageElement
+      source: String(model.data[this.mimeType]),
+      width: model.metadata.width as number | undefined,
+      height: model.metadata.height as number | undefined
     });
   }
 }
 
 
 /**
- * A widget for displaying plain text and console text.
+ * A mime renderer for displaying Markdown with embeded latex.
  */
 export
-class RenderedText extends RenderedCommon {
+class RenderedMarkdown extends RenderedHTMLCommon {
   /**
-   * Construct a new rendered text widget.
+   * Construct a new rendered markdown widget.
    *
    * @param options - The options for initializing the widget.
    */
   constructor(options: IRenderMime.IRendererOptions) {
     super(options);
-    this.addClass('jp-RenderedText');
-    this.node.appendChild(document.createElement('pre'));
+    this.addClass('jp-RenderedMarkdown');
   }
 
   /**
@@ -289,42 +245,49 @@ class RenderedText extends RenderedCommon {
    * @returns A promise which resolves when rendering is complete.
    */
   render(model: IRenderMime.IMimeModel): Promise<void> {
-    return RenderHelpers.renderText({
-      model,
-      mimeType: this.mimeType,
-      node: this.node.firstChild as HTMLElement
+    return renderers.renderMarkdown({
+      host: this.node,
+      source: String(model.data[this.mimeType]),
+      trusted: model.trusted,
+      resolver: this.resolver,
+      sanitizer: this.sanitizer,
+      linkHandler: this.linkHandler,
+      shouldTypeset: this.isAttached
     });
   }
+
+  /**
+   * A message handler invoked on an `'after-attach'` message.
+   */
+  onAfterAttach(msg: Message): void {
+    typeset(this.node);
+  }
 }
 
 
 /**
- * A widget for displaying/executing JavaScript.
+ * A widget for displaying rendered PDF content.
  */
 export
-class RenderedJavaScript extends RenderedCommon {
+class RenderedPDF extends RenderedCommon {
   /**
-   * Construct a new rendered Javascript widget.
+   * Construct a new rendered PDF widget.
    *
    * @param options - The options for initializing the widget.
    */
   constructor(options: IRenderMime.IRendererOptions) {
     super(options);
-    this.addClass('jp-RenderedJavaScript');
+    this.addClass('jp-RenderedPDF');
   }
 
   /**
    * Render a mime model.
-   *
-   * @param model - The mime model to render.
-   *
-   * @returns A promise which resolves when rendering is complete.
    */
   render(model: IRenderMime.IMimeModel): Promise<void> {
-    return RenderHelpers.renderJavaScript({
-      model,
-      node: this.node,
-      mimeType: this.mimeType
+    return renderers.renderPDF({
+      host: this.node,
+      source: String(model.data[this.mimeType]),
+      trusted: model.trusted
     });
   }
 }
@@ -353,10 +316,10 @@ class RenderedSVG extends RenderedCommon {
    * @returns A promise which resolves when rendering is complete.
    */
   render(model: IRenderMime.IMimeModel): Promise<void> {
-    return RenderHelpers.renderSVG({
-      model,
-      node: this.node,
-      mimeType: this.mimeType,
+    return renderers.renderSVG({
+      host: this.node,
+      source: String(model.data[this.mimeType]),
+      trusted: model.trusted,
       resolver: this.resolver,
       linkHandler: this.linkHandler,
       shouldTypeset: this.isAttached
@@ -373,28 +336,31 @@ class RenderedSVG extends RenderedCommon {
 
 
 /**
- * A widget for displaying rendered PDF content.
+ * A widget for displaying plain text and console text.
  */
 export
-class RenderedPDF extends RenderedCommon {
+class RenderedText extends RenderedCommon {
   /**
-   * Construct a new rendered PDF widget.
+   * Construct a new rendered text widget.
    *
    * @param options - The options for initializing the widget.
    */
   constructor(options: IRenderMime.IRendererOptions) {
     super(options);
-    this.addClass('jp-RenderedPDF');
+    this.addClass('jp-RenderedText');
   }
 
   /**
    * Render a mime model.
+   *
+   * @param model - The mime model to render.
+   *
+   * @returns A promise which resolves when rendering is complete.
    */
   render(model: IRenderMime.IMimeModel): Promise<void> {
-    return RenderHelpers.renderPDF({
-      model,
-      node: this.node,
-      mimeType: this.mimeType
+    return renderers.renderText({
+      host: this.node,
+      source: String(model.data[this.mimeType])
     });
   }
 }