|
@@ -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);
|
|
|
}
|
|
|
}
|