123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- // 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 {
- utils
- } from '@jupyterlab/services';
- import {
- requireMode
- } from '../codemirror';
- import {
- CodeMirrorEditor
- } from '../codemirror/editor';
- import {
- ICommandLinker
- } from '../commandlinker';
- import {
- CommandIDs
- } from '../filebrowser';
- 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.
- */
- export
- 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,
- tables: true,
- // breaks: true; We can't use GFM breaks as it causes problems with HTML tables
- langPrefix: `cm-s-${CodeMirrorEditor.DEFAULT_THEME} 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.IRendererOptions<string>) {
- 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.IRendererOptions<string>) {
- 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) {
- this._urlResolved = resolveUrls(this.node, options.resolver,
- options.commandLinker);
- }
- }
- /**
- * A message handler invoked on an `'after-attach'` message.
- */
- onAfterAttach(msg: Message): void {
- this._urlResolved.then( () => { typeset(this.node); });
- }
- private _urlResolved: Promise<void> = null;
- }
- /**
- * A widget for displaying Markdown with embeded latex.
- */
- export
- class RenderedMarkdown extends RenderedHTMLCommon {
- /**
- * Construct a new markdown widget.
- */
- constructor(options: RenderMime.IRendererOptions<string>) {
- super(options);
- this.addClass(MARKDOWN_CLASS);
- let parts = removeMath(options.source);
- // Add the markdown content asynchronously.
- marked(parts['text'], (err: any, content: string) => {
- 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) {
- this._urlResolved = resolveUrls(this.node, options.resolver,
- options.commandLinker);
- }
- this.fit();
- this._rendered = true;
- if (this.isAttached) {
- this._urlResolved.then(() => { 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;
- private _urlResolved : Promise<void> = null;
- }
- /**
- * A widget for displaying LaTeX output.
- */
- export
- class RenderedLatex extends Widget {
- /**
- * Construct a new latex widget.
- */
- constructor(options: RenderMime.IRendererOptions<string>) {
- 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.IRendererOptions<string>) {
- 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.IRendererOptions<string>) {
- super();
- let data = escape_for_html(options.source as string);
- 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.IRendererOptions<string>) {
- 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.IRendererOptions<string>) {
- super();
- this.node.innerHTML = options.source;
- let svgElement = this.node.getElementsByTagName('svg')[0];
- if (!svgElement) {
- throw new Error('SVGRender: Error: Failed to create <svg> element');
- }
- if (options.resolver) {
- this._urlResolved = resolveUrls(this.node, options.resolver,
- options.commandLinker);
- }
- this.addClass(SVG_CLASS);
- }
- private _urlResolved: Promise<void> = null;
- }
- export
- class RenderedPDF extends Widget {
- constructor(options: RenderMime.IRendererOptions<string>) {
- 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.
- *
- * @param linker - A command linker.
- *
- * @returns a promise fulfilled when the relative urls have been resolved.
- */
- export
- function resolveUrls(node: HTMLElement, resolver: RenderMime.IResolver,
- linker: ICommandLinker | null): Promise<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) {
- return resolver.resolveUrl(source).then(url => {
- img.src = url;
- return void 0;
- });
- }
- }
- let anchors = node.getElementsByTagName('a');
- for (let i = 0; i < anchors.length; i++) {
- let anchor = anchors[i];
- let href = anchor.getAttribute('href');
- if (href) {
- return resolver.resolveUrl(href).then(url => {
- anchor.href = url;
- if (linker && !utils.urlParse(url).protocol) {
- linker.connectNode(anchor, CommandIDs.open, {
- path: url
- });
- }
- return void 0;
- });
- }
- }
- }
- /**
- * 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;
- }
- }
|