Browse Source

Merge pull request #718 from ellisonbg/rendered-html-2

Cleanup renderers
Afshin Darian 8 years ago
parent
commit
96bcb7da27

+ 1 - 0
src/basestyle/index.less

@@ -12,4 +12,5 @@
 @import '../filebrowser/base.less';
 @import '../iframe/base.less';
 @import '../notebook/base.less';
+@import '../renderers/base.less';
 @import '../running/base.less';

+ 1 - 1
src/default-theme/index.less

@@ -24,6 +24,6 @@
 @import './mainmenu.less';
 @import './markdownwidget.less';
 @import './notebook.less';
-@import './rendermime.less';
+@import './renderers.less';
 @import './running.less';
 @import './terminal.less';

+ 13 - 0
src/default-theme/renderers.less

@@ -0,0 +1,13 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+
+.jp-RenderedHTMLCommon {
+
+  blockquote {
+    border-left: 5px solid #eeeeee;
+  }
+
+}

+ 0 - 231
src/default-theme/rendermime.less

@@ -1,231 +0,0 @@
-/*-----------------------------------------------------------------------------
-| Copyright (c) Jupyter Development Team.
-| Distributed under the terms of the Modified BSD License.
-|----------------------------------------------------------------------------*/
-
-.jp-Rendered-html {
-  color: #000;
-  /* any extras will just be numbers: */
-}
-
-.jp-Rendered-html em {
-  font-style: italic;
-}
-
-.jp-Rendered-html strong {
-  font-weight: bold;
-}
-
-.jp-Rendered-html u {
-  text-decoration: underline;
-}
-
-.jp-Rendered-html :link {
-  text-decoration: underline;
-}
-
-.jp-Rendered-html :visited {
-  text-decoration: underline;
-}
-
-.jp-Rendered-html h1 {
-  font-size: 185.7%;
-  margin: 1.08em 0 0 0;
-  font-weight: bold;
-  line-height: 1.0;
-}
-
-.jp-Rendered-html h2 {
-  font-size: 157.1%;
-  margin: 1.27em 0 0 0;
-  font-weight: bold;
-  line-height: 1.0;
-}
-
-.jp-Rendered-html h3 {
-  font-size: 128.6%;
-  margin: 1.55em 0 0 0;
-  font-weight: bold;
-  line-height: 1.0;
-}
-
-.jp-Rendered-html h4 {
-  font-size: 100%;
-  margin: 2em 0 0 0;
-  font-weight: bold;
-  line-height: 1.0;
-}
-
-.jp-Rendered-html h5 {
-  font-size: 100%;
-  margin: 2em 0 0 0;
-  font-weight: bold;
-  line-height: 1.0;
-  font-style: italic;
-}
-
-.jp-Rendered-html h6 {
-  font-size: 100%;
-  margin: 2em 0 0 0;
-  font-weight: bold;
-  line-height: 1.0;
-  font-style: italic;
-}
-
-.jp-Rendered-html h1:first-child {
-  margin-top: 0.538em;
-}
-
-.jp-Rendered-html h2:first-child {
-  margin-top: 0.636em;
-}
-
-.jp-Rendered-html h3:first-child {
-  margin-top: 0.777em;
-}
-
-.jp-Rendered-html h4:first-child {
-  margin-top: 1em;
-}
-
-.jp-Rendered-html h5:first-child {
-  margin-top: 1em;
-}
-
-.jp-Rendered-html h6:first-child {
-  margin-top: 1em;
-}
-
-.jp-Rendered-html ul:not(.list-inline),
-.jp-Rendered-html ol:not(.list-inline) {
-  padding-left: 2em;
-}
-
-.jp-Rendered-html ul {
-  list-style: disc;
-}
-
-.jp-Rendered-html ul ul {
-  list-style: square;
-}
-
-.jp-Rendered-html ul ul ul {
-  list-style: circle;
-}
-
-.jp-Rendered-html ol {
-  list-style: decimal;
-}
-
-.jp-Rendered-html ol ol {
-  list-style: upper-alpha;
-}
-
-.jp-Rendered-html ol ol ol {
-  list-style: lower-alpha;
-}
-
-.jp-Rendered-html ol ol ol ol {
-  list-style: lower-roman;
-}
-
-.jp-Rendered-html ol ol ol ol ol {
-  list-style: decimal;
-}
-
-.jp-Rendered-html * + ul {
-  margin-top: 1em;
-}
-
-.jp-Rendered-html * + ol {
-  margin-top: 1em;
-}
-
-.jp-Rendered-html hr {
-  color: black;
-  background-color: black;
-}
-
-.jp-Rendered-html pre {
-  margin: 1em 2em;
-}
-
-.jp-Rendered-html pre,
-.jp-Rendered-html code {
-  border: 0;
-  background-color: #fff;
-  color: #000;
-  font-size: 100%;
-  padding: 0px;
-}
-
-.jp-Rendered-html blockquote {
-  margin: 1em 2em;
-}
-
-.jp-Rendered-html table {
-  margin-left: auto;
-  margin-right: auto;
-  border: 1px solid black;
-  border-collapse: collapse;
-}
-
-.jp-Rendered-html tr,
-.jp-Rendered-html th,
-.jp-Rendered-html td {
-  border: 1px solid black;
-  border-collapse: collapse;
-  margin: 1em 2em;
-}
-
-.jp-Rendered-html td,
-.jp-Rendered-html th {
-  text-align: left;
-  vertical-align: middle;
-  padding: 4px;
-}
-
-.jp-Rendered-html th {
-  font-weight: bold;
-}
-
-.jp-Rendered-html * + table {
-  margin-top: 1em;
-}
-
-.jp-Rendered-html p {
-  text-align: left;
-}
-
-.jp-Rendered-html * + p {
-  margin-top: 1em;
-}
-
-.jp-Rendered-html img {
-  display: block;
-  margin-left: auto;
-  margin-right: auto;
-}
-
-.jp-Rendered-html * + img {
-  margin-top: 1em;
-}
-
-.jp-Rendered-html img,
-.jp-Rendered-html svg {
-  max-width: 100%;
-  height: auto;
-}
-
-.jp-Rendered-html img.unconfined,
-.jp-Rendered-html svg.unconfined {
-  max-width: none;
-}
-
-.jp-Rendered-html .alert {
-  margin-bottom: initial;
-}
-
-.jp-Rendered-html * + .alert {
-  margin-top: 1em;
-}

+ 4 - 63
src/notebook/base.less

@@ -3,6 +3,8 @@
 |
 | Distributed under the terms of the Modified BSD License.
 |----------------------------------------------------------------------------*/
+
+
 .jp-Notebook-panel {
   display: flex;
   flex-direction: column;
@@ -38,68 +40,7 @@
 
 
 .jp-MarkdownCell-content {
-  padding: 7px;
-  user-select: text;
-  -moz-user-select: text;
-  -webkit-user-select: text;
-  -ms-user-select: text;
-}
-
-
-.jp-MarkdownCell-content h1 {
-  font-size: 26px;
-  line-height: 1.0;
-  margin: 14px 0 0 0;
-}
-
-
-.jp-MarkdownCell-content h2 {
-  font-size: 22px;
-  line-height: 1.0;
-  margin: 14px 0 0 0;
-}
-
-
-.jp-MarkdownCell-content h3 {
-  font-size: 18px;
-  line-height: 1.0;
-  margin: 14px 0 0 0;
-}
-
-
-.jp-MarkdownCell-content h4 {
-  font-size: 14px;
-  line-height: 1.0;
-  margin: 14px 0 0 0;
-}
-
-
-.jp-MarkdownCell-content h5 {
-  font-size: 14px;
-  font-style: italic;
-  line-height: 1.0;
-  margin: 14px 0 0 0;
-}
-
-
-.jp-MarkdownCell-content h6 {
-  font-size: 14px;
-  font-style: italic;
-  line-height: 1.0;
-  margin: 14px 0 0 0;
-}
-
-
-.jp-MarkdownCell-content p {
-  font-size: 14px;
-  line-height: 20px;
-  margin: 0 0 0 0;
-}
-
-
-.jp-MarkdownCell-content li {
-  line-height: 20px;
-  margin: 14px 0 0 0;
+  padding: 0.5em;
 }
 
 
@@ -110,7 +51,7 @@
   -ms-user-select: text;
 }
 
-.jp-Output-result {
+.p-Widget.jp-Output-result {
   overflow: auto;
 }
 

+ 291 - 0
src/renderers/base.less

@@ -0,0 +1,291 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+.jp-RenderedHTMLCommon {
+
+  color: black;
+  font-size: 14px;
+  line-height: 20px;
+
+  .MathJax_Display {
+    margin: 0;
+  }
+
+
+  em {
+    font-style: italic;
+  }
+
+  strong {
+    font-weight: bold;
+  }
+
+  u {
+    text-decoration: underline;
+  }
+
+  :link {
+    text-decoration: underline;
+  }
+
+  :visited {
+    text-decoration: underline;
+  }
+
+  /*For a 14px base font size this goes as:
+  font-size = 26, 22, 18, 14, 12, 12
+  margin-top = 14, 14, 14, 14, 8, 8
+  */
+
+  h1 {
+    font-size: 185.7%;
+    margin: 1.08em 0 0 0;
+    font-weight: bold;
+    line-height: 1.0;
+  }
+
+
+  h2 {
+    font-size: 157.1%;
+    margin: 1.27em 0 0 0;
+    font-weight: bold;
+    line-height: 1.0;
+  }
+
+
+  h3 {
+    font-size: 128.6%;
+    margin: 1.55em 0 0 0;
+    font-weight: bold;
+    line-height: 1.0;
+  }
+
+
+  h4 {
+    font-size: 100%;
+    margin: 2em 0 0 0;
+    font-weight: bold;
+    line-height: 1.0;
+  }
+
+
+  h5 {
+    font-size: 100%;
+    margin: 2em 0 0 0;
+    font-weight: bold;
+    line-height: 1.0;
+    font-style: italic;
+  }
+
+
+  h6 {
+    font-size: 100%;
+    margin: 2em 0 0 0;
+    font-weight: bold;
+    line-height: 1.0;
+    font-style: italic;
+  }
+
+
+  h1:first-child {
+    margin-top: 0.538em;
+  }
+
+
+  h2:first-child {
+    margin-top: 0.636em;
+  }
+
+
+  h3:first-child {
+    margin-top: 0.777em;
+  }
+
+
+  h4:first-child {
+    margin-top: 1em;
+  }
+
+
+  h5:first-child {
+    margin-top: 1em;
+  }
+
+
+  h6:first-child {
+    margin-top: 1em;
+  }
+
+
+  ul:not(.list-inline),
+  ol:not(.list-inline) {
+    padding-left: 2em;
+  }
+
+
+  ul {
+    list-style: disc;
+  }
+
+
+  ul ul {
+    list-style: square;
+  }
+
+
+  ul ul ul {
+    list-style: circle;
+  }
+
+
+  ol {
+    list-style: decimal;
+  }
+
+
+  ol ol {
+    list-style: upper-alpha;
+  }
+
+
+  ol ol ol {
+    list-style: lower-alpha;
+  }
+
+
+  ol ol ol ol {
+    list-style: lower-roman;
+  }
+
+
+  ol ol ol ol ol {
+    list-style: decimal;
+  }
+
+
+  * + ul {
+    margin-top: 1em;
+  }
+
+
+  * + ol {
+    margin-top: 1em;
+  }
+
+
+  hr {
+    color: black;
+    background-color: black;
+    margin-top: 1em;
+    margin-bottom: 1em;
+  }
+
+
+  pre {
+    margin: 1em 2em;
+  }
+
+
+  pre,
+  code {
+    border: 0;
+    background-color: white;
+    color: black;
+    font-size: 100%;
+    padding: 0px;
+  }
+
+
+  blockquote {
+    margin: 1em 2em;
+  }
+
+
+  table {
+    margin-left: auto;
+    margin-right: auto;
+    border: 1px solid black;
+    border-collapse: collapse;
+  }
+
+
+  tr,
+  th,
+  td {
+    border: 1px solid black;
+    border-collapse: collapse;
+    margin: 1em 2em;
+  }
+
+
+  td,
+  th {
+    text-align: left;
+    vertical-align: middle;
+    padding: 4px;
+  }
+
+
+  th {
+    font-weight: bold;
+  }
+
+  * + table {
+    margin-top: 1em;
+  }
+
+
+  p {
+    text-align: left;
+    margin: 0px;
+  }
+
+
+  * + p {
+    margin-top: 1em;
+  }
+
+
+  img {
+    display: block;
+    margin-left: auto;
+    margin-right: auto;
+  }
+
+
+  * + img {
+    margin-top: 1em;
+  }
+
+
+  img,
+  svg {
+    max-width: 100%;
+    height: auto;
+  }
+
+
+  img.unconfined,
+  svg.unconfined {
+    max-width: none;
+  }
+
+  .alert {
+    margin-bottom: initial;
+  }
+
+
+  * + .alert {
+    margin-top: 1em;
+  }
+
+
+  blockquote {
+    margin: 1em 2em;
+    padding: 0 1em;
+    border-left: 5px solid black;
+  }
+
+}

+ 11 - 259
src/renderers/index.ts

@@ -1,186 +1,19 @@
 // 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 CodeMirror
-  from 'codemirror';
-
-import 'codemirror/addon/runmode/runmode';
-
-import * as marked
-  from 'marked';
-
-import {
-  Message
-} from 'phosphor/lib/core/messaging';
 
 import {
   Widget
 } from 'phosphor/lib/ui/widget';
 
-import {
-  requireMode
-} from '../codemirror';
-
 import {
   RenderMime
 } from '../rendermime';
 
 import {
-  typeset, removeMath, replaceMath
-} from './latex';
-
-
-/**
- * The class name added to rendered widgets.
- */
-const RENDERED_CLASS = 'jp-Rendered';
-
-/**
- * The class name added to rendered html widgets.
- */
-const RENDERED_HTML = 'jp-Rendered-html';
-
-
-// Support GitHub flavored Markdown, leave sanitizing to external library.
-marked.setOptions({
-  gfm: true,
-  sanitize: false,
-  breaks: true,
-  langPrefix: 'cm-s-default language-',
-  highlight: (code, lang, callback) => {
-    if (!lang) {
-        // no language, no highlight
-        if (callback) {
-            callback(null, code);
-            return;
-        } else {
-            return code;
-        }
-    }
-    requireMode(lang).then(spec => {
-      let el = document.createElement('div');
-      if (!spec) {
-          console.log(`No CodeMirror mode: ${lang}`);
-          callback(null, code);
-          return;
-      }
-      try {
-        CodeMirror.runMode(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);
-    });
-  }
-});
-
-
-/**
- * A widget for displaying HTML and rendering math.
- */
-class HTMLWidget extends Widget {
-  /**
-   * Construct a new html widget.
-   */
-  constructor(options: RenderMime.IRenderOptions) {
-    super();
-    this.addClass(RENDERED_HTML);
-    this.addClass(RENDERED_CLASS);
-    let source = options.source;
-    if (options.sanitizer) {
-      source = options.sanitizer.sanitize(source);
-    }
-    appendHtml(this.node, source);
-    if (options.resolver) {
-      resolveUrls(this.node, options.resolver);
-    }
-  }
-
-  /**
-   * A message handler invoked on an `'after-attach'` message.
-   */
-  onAfterAttach(msg: Message): void {
-    typeset(this.node);
-  }
-}
-
-
-/**
- * A widget for displaying Markdown.
- */
-class MarkdownWidget extends Widget {
-  /**
-   * Construct a new markdown widget.
-   */
-  constructor(options: RenderMime.IRenderOptions) {
-    super();
-    this.addClass(RENDERED_HTML);
-    this.addClass(RENDERED_CLASS);
-    let parts = removeMath(options.source);
-    // Add the markdown content asynchronously.
-    marked(parts['text'], (err, content) => {
-      if (err) {
-        console.error(err);
-        return;
-      }
-      content = replaceMath(content, parts['math']);
-      if (options.sanitizer) {
-        content = options.sanitizer.sanitize(content);
-      }
-      appendHtml(this.node, content);
-      if (options.resolver) {
-        resolveUrls(this.node, options.resolver);
-      }
-      this.fit();
-      this._rendered = true;
-      if (this.isAttached) {
-        typeset(this.node);
-      }
-    });
-  }
-
-  /**
-   * A message handler invoked on an `'after-attach'` message.
-   */
-  onAfterAttach(msg: Message): void {
-    if (this._rendered) {
-      typeset(this.node);
-    }
-  }
-
-  private _rendered = false;
-}
-
-
-/**
- * A widget for displaying LaTeX output.
- */
-class LatexWidget extends Widget {
-  /**
-   * Construct a new latex widget.
-   */
-  constructor(text: string) {
-    super();
-    this.node.textContent = text;
-    this.addClass(RENDERED_CLASS);
-  }
-
-  /**
-   * A message handler invoked on an `'after-attach'` message.
-   */
-  onAfterAttach(msg: Message): void {
-    typeset(this.node);
-  }
-}
+  RenderedHTML, RenderedMarkdown, RenderedText, RenderedImage,
+  RenderedJavascript, RenderedSVG, RenderedPDF, RenderedLatex
+} from './widget';
 
 
 /**
@@ -211,7 +44,7 @@ class HTMLRenderer implements RenderMime.IRenderer {
    * Render the transformed mime bundle.
    */
   render(options: RenderMime.IRenderOptions): Widget {
-    return new HTMLWidget(options);
+    return new RenderedHTML(options);
   }
 }
 
@@ -244,12 +77,7 @@ class ImageRenderer implements RenderMime.IRenderer {
    * Render the transformed mime bundle.
    */
   render(options: RenderMime.IRenderOptions): Widget {
-    let w = new Widget();
-    let img = document.createElement('img');
-    img.src = `data:${options.mimetype};base64,${options.source}`;
-    w.node.appendChild(img);
-    w.addClass(RENDERED_CLASS);
-    return w;
+    return new RenderedImage(options);
   }
 }
 
@@ -282,13 +110,7 @@ class TextRenderer implements RenderMime.IRenderer {
    * Render the transformed mime bundle.
    */
   render(options: RenderMime.IRenderOptions): Widget {
-    let w = new Widget();
-    let data = escape_for_html(options.source);
-    let pre = document.createElement('pre');
-    pre.innerHTML = ansi_to_html(data);
-    w.node.appendChild(pre);
-    w.addClass(RENDERED_CLASS);
-    return w;
+    return new RenderedText(options);
   }
 }
 
@@ -321,13 +143,7 @@ class JavascriptRenderer implements RenderMime.IRenderer {
    * Render the transformed mime bundle.
    */
   render(options: RenderMime.IRenderOptions): Widget {
-    let w = new Widget();
-    let s = document.createElement('script');
-    s.type = options.mimetype;
-    s.textContent = options.source;
-    w.node.appendChild(s);
-    w.addClass(RENDERED_CLASS);
-    return w;
+    return new RenderedJavascript(options);
   }
 }
 
@@ -360,21 +176,7 @@ class SVGRenderer implements RenderMime.IRenderer {
    * Render the transformed mime bundle.
    */
   render(options: RenderMime.IRenderOptions): Widget {
-    let source = options.source;
-    if (options.sanitizer) {
-      source = options.sanitizer.sanitize(source);
-    }
-    let w = new Widget();
-    w.node.innerHTML = source;
-    let svgElement = w.node.getElementsByTagName('svg')[0];
-    if (!svgElement) {
-      throw new Error('SVGRender: Error: Failed to create <svg> element');
-    }
-    if (options.resolver) {
-      resolveUrls(w.node, options.resolver);
-    }
-    w.addClass(RENDERED_CLASS);
-    return w;
+    return new RenderedSVG(options);
   }
 }
 
@@ -407,14 +209,7 @@ class PDFRenderer implements RenderMime.IRenderer {
    * Render the transformed mime bundle.
    */
   render(options: RenderMime.IRenderOptions): Widget {
-    let w = new Widget();
-    let a = document.createElement('a');
-    a.target = '_blank';
-    a.textContent = 'View PDF';
-    a.href = 'data:application/pdf;base64,' + options.source;
-    w.node.appendChild(a);
-    w.addClass(RENDERED_CLASS);
-    return w;
+    return new RenderedPDF(options);
   }
 }
 
@@ -447,7 +242,7 @@ class LatexRenderer implements RenderMime.IRenderer  {
    * Render the mime bundle.
    */
   render(options: RenderMime.IRenderOptions): Widget {
-    return new LatexWidget(options.source);
+    return new RenderedLatex(options);
   }
 }
 
@@ -480,49 +275,6 @@ class MarkdownRenderer implements RenderMime.IRenderer {
    * Render the mime bundle.
    */
   render(options: RenderMime.IRenderOptions): Widget {
-    return new MarkdownWidget(options);
-  }
-}
-
-
-/**
- * Resolve the relative urls in the image and anchor tags of a node tree.
- *
- * @param node - The head html element.
- *
- * @param resolver - A url resolver.
- */
-export
-function resolveUrls(node: HTMLElement, resolver: RenderMime.IResolver): void {
-  let imgs = node.getElementsByTagName('img');
-  for (let i = 0; i < imgs.length; i++) {
-    let img = imgs[i];
-    let source = img.getAttribute('src');
-    if (source) {
-      img.src = resolver.resolveUrl(source);
-    }
-  }
-  let anchors = node.getElementsByTagName('a');
-  for (let i = 0; i < anchors.length; i++) {
-    let anchor = anchors[i];
-    let href = anchor.getAttribute('href');
-    if (href) {
-      anchor.href = resolver.resolveUrl(href);
-    }
-  }
-}
-
-
-/**
- * Append trusted html to a node.
- */
-function appendHtml(node: HTMLElement, html: string): void {
-  try {
-    let range = document.createRange();
-    node.appendChild(range.createContextualFragment(html));
-  } catch (error) {
-    console.warn('Environment does not support Range ' +
-                 'createContextualFragment, falling back on innerHTML');
-    node.innerHTML = html;
+    return new RenderedMarkdown(options);
   }
 }

+ 354 - 0
src/renderers/widget.ts

@@ -0,0 +1,354 @@
+// 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 CodeMirror
+  from 'codemirror';
+
+import 'codemirror/addon/runmode/runmode';
+
+import {
+  requireMode
+} from '../codemirror';
+
+import * as marked
+  from 'marked';
+
+import {
+  RenderMime
+} from '../rendermime';
+
+import {
+  Message
+} from 'phosphor/lib/core/messaging';
+
+import {
+  Widget
+} from 'phosphor/lib/ui/widget';
+
+import {
+  typeset, removeMath, replaceMath
+} from './latex';
+
+
+/*
+ * The class name added to common rendered HTML. 
+ */
+const HTML_COMMON_CLASS = 'jp-RenderedHTMLCommon';
+
+/*
+ * The class name added to rendered HTML. 
+ */
+const HTML_CLASS = 'jp-RenderedHTML';
+
+/*
+ * The class name added to rendered markdown. 
+ */
+const MARKDOWN_CLASS = 'jp-RenderedMarkdown';
+
+/*
+ * The class name added to rendered Latex. 
+ */
+const LATEX_CLASS = 'jp-RenderedLatex';
+
+/*
+ * The class name added to rendered images. 
+ */
+const IMAGE_CLASS = 'jp-RenderedImage';
+
+/*
+ * The class name added to rendered text. 
+ */
+const TEXT_CLASS = 'jp-RenderedText';
+
+/*
+ * The class name added to rendered javascript. 
+ */
+const JAVASCRIPT_CLASS = 'jp-RenderedJavascript';
+
+/*
+ * The class name added to rendered SVG. 
+ */
+const SVG_CLASS = 'jp-RenderedSVG';
+
+/*
+ * The class name added to rendered PDF. 
+ */
+const PDF_CLASS = 'jp-RenderedPDF';
+
+
+// Support GitHub flavored Markdown, leave sanitizing to external library.
+marked.setOptions({
+  gfm: true,
+  sanitize: false,
+  breaks: true,
+  langPrefix: 'cm-s-default language-',
+  highlight: (code, lang, callback) => {
+    if (!lang) {
+        // no language, no highlight
+        if (callback) {
+            callback(null, code);
+            return;
+        } else {
+            return code;
+        }
+    }
+    requireMode(lang).then(spec => {
+      let el = document.createElement('div');
+      if (!spec) {
+          console.log(`No CodeMirror mode: ${lang}`);
+          callback(null, code);
+          return;
+      }
+      try {
+        CodeMirror.runMode(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);
+    });
+  }
+});
+
+
+/*
+ * A widget for displaying any widget whoes representation is rendered HTML
+ * */
+export
+class RenderedHTMLCommon extends Widget {
+  /* Construct a new rendered HTML common widget.*/
+  constructor(options: RenderMime.IRenderOptions) {
+    super();
+    this.addClass(HTML_COMMON_CLASS);
+  }
+}
+
+
+/**
+ * A widget for displaying HTML and rendering math.
+ */
+export
+class RenderedHTML extends RenderedHTMLCommon {
+  /**
+   * Construct a new html widget.
+   */
+  constructor(options: RenderMime.IRenderOptions) {
+    super(options);
+    this.addClass(HTML_CLASS);
+    let source = options.source;
+    if (options.sanitizer) {
+      source = options.sanitizer.sanitize(source);
+    }
+    appendHtml(this.node, source);
+    if (options.resolver) {
+      resolveUrls(this.node, options.resolver);
+    }
+  }
+
+  /**
+   * A message handler invoked on an `'after-attach'` message.
+   */
+  onAfterAttach(msg: Message): void {
+    typeset(this.node);
+  }
+}
+
+
+/**
+ * A widget for displaying Markdown with embeded latex.
+ */
+export
+class RenderedMarkdown extends RenderedHTMLCommon {
+  /**
+   * Construct a new markdown widget.
+   */
+  constructor(options: RenderMime.IRenderOptions) {
+    super(options);
+    this.addClass(MARKDOWN_CLASS);
+    let parts = removeMath(options.source);
+    // Add the markdown content asynchronously.
+    marked(parts['text'], (err, content) => {
+      if (err) {
+        console.error(err);
+        return;
+      }
+      content = replaceMath(content, parts['math']);
+      if (options.sanitizer) {
+        content = options.sanitizer.sanitize(content);
+      }
+      appendHtml(this.node, content);
+      if (options.resolver) {
+        resolveUrls(this.node, options.resolver);
+      }
+      this.fit();
+      this._rendered = true;
+      if (this.isAttached) {
+        typeset(this.node);
+      }
+    });
+  }
+
+  /**
+   * A message handler invoked on an `'after-attach'` message.
+   */
+  onAfterAttach(msg: Message): void {
+    if (this._rendered) {
+      typeset(this.node);
+    }
+  }
+
+  private _rendered = false;
+}
+
+
+/**
+ * A widget for displaying LaTeX output.
+ */
+export
+class RenderedLatex extends Widget {
+  /**
+   * Construct a new latex widget.
+   */
+  constructor(options: RenderMime.IRenderOptions) {
+    super();
+    this.node.textContent = options.source;
+    this.addClass(LATEX_CLASS);
+  }
+
+  /**
+   * A message handler invoked on an `'after-attach'` message.
+   */
+  onAfterAttach(msg: Message): void {
+    typeset(this.node);
+  }
+}
+
+
+export
+class RenderedImage extends Widget {
+
+  constructor(options: RenderMime.IRenderOptions) {
+    super();
+    let img = document.createElement('img');
+    img.src = `data:${options.mimetype};base64,${options.source}`;
+    this.node.appendChild(img);
+    this.addClass(IMAGE_CLASS);
+  }
+}
+
+
+export
+class RenderedText extends Widget {
+
+  constructor(options: RenderMime.IRenderOptions) {
+    super();
+    let data = escape_for_html(options.source);
+    let pre = document.createElement('pre');
+    pre.innerHTML = ansi_to_html(data);
+    this.node.appendChild(pre);
+    this.addClass(TEXT_CLASS);
+  }
+}
+
+
+export
+class RenderedJavascript extends Widget {
+
+  constructor(options: RenderMime.IRenderOptions) {
+    super();
+    let s = document.createElement('script');
+    s.type = options.mimetype;
+    s.textContent = options.source;
+    this.node.appendChild(s);
+    this.addClass(JAVASCRIPT_CLASS);
+  }
+}
+
+
+export
+class RenderedSVG extends Widget {
+
+  constructor(options: RenderMime.IRenderOptions) {
+    super();
+    let source = options.source;
+    if (options.sanitizer) {
+      source = options.sanitizer.sanitize(source);
+    }
+    this.node.innerHTML = source;
+    let svgElement = this.node.getElementsByTagName('svg')[0];
+    if (!svgElement) {
+      throw new Error('SVGRender: Error: Failed to create <svg> element');
+    }
+    if (options.resolver) {
+      resolveUrls(this.node, options.resolver);
+    }
+    this.addClass(SVG_CLASS);
+  }
+}
+
+
+export
+class RenderedPDF extends Widget {
+
+  constructor(options: RenderMime.IRenderOptions) {
+    super();
+    let a = document.createElement('a');
+    a.target = '_blank';
+    a.textContent = 'View PDF';
+    a.href = 'data:application/pdf;base64,' + options.source;
+    this.node.appendChild(a);
+    this.addClass(PDF_CLASS);
+  }
+}
+
+
+/**
+ * Resolve the relative urls in the image and anchor tags of a node tree.
+ *
+ * @param node - The head html element.
+ *
+ * @param resolver - A url resolver.
+ */
+export
+function resolveUrls(node: HTMLElement, resolver: RenderMime.IResolver): void {
+  let imgs = node.getElementsByTagName('img');
+  for (let i = 0; i < imgs.length; i++) {
+    let img = imgs[i];
+    let source = img.getAttribute('src');
+    if (source) {
+      img.src = resolver.resolveUrl(source);
+    }
+  }
+  let anchors = node.getElementsByTagName('a');
+  for (let i = 0; i < anchors.length; i++) {
+    let anchor = anchors[i];
+    let href = anchor.getAttribute('href');
+    if (href) {
+      anchor.href = resolver.resolveUrl(href);
+    }
+  }
+}
+
+
+/**
+ * Append trusted html to a node.
+ */
+function appendHtml(node: HTMLElement, html: string): void {
+  try {
+    let range = document.createRange();
+    node.appendChild(range.createContextualFragment(html));
+  } catch (error) {
+    console.warn('Environment does not support Range ' +
+                 'createContextualFragment, falling back on innerHTML');
+    node.innerHTML = html;
+  }
+}