// Copyright (c) IPython Development Team. // Distributed under the terms of the Modified BSD License. define([ 'base/js/namespace', 'base/js/utils', 'jquery', 'notebook/js/cell', 'base/js/security', 'notebook/js/mathjaxutils', 'notebook/js/celltoolbar', 'components/marked/lib/marked', 'codemirror/lib/codemirror', 'codemirror/mode/gfm/gfm', 'notebook/js/codemirror-ipythongfm' ], function(IPython,utils , $, cell, security, mathjaxutils, celltoolbar, marked, CodeMirror, gfm, ipgfm) { "use strict"; var Cell = cell.Cell; var TextCell = function (options) { // Constructor // // Construct a new TextCell, codemirror mode is by default 'htmlmixed', // and cell type is 'text' cell start as not redered. // // Parameters: // options: dictionary // Dictionary of keyword arguments. // events: $(Events) instance // config: dictionary // keyboard_manager: KeyboardManager instance // notebook: Notebook instance options = options || {}; // in all TextCell/Cell subclasses // do not assign most of members here, just pass it down // in the options dict potentially overwriting what you wish. // they will be assigned in the base class. this.notebook = options.notebook; this.events = options.events; this.config = options.config; // we cannot put this as a class key as it has handle to "this". var cm_overwrite_options = { onKeyEvent: $.proxy(this.handle_keyevent,this) }; var config = utils.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options}); Cell.apply(this, [{ config: config, keyboard_manager: options.keyboard_manager, events: this.events}]); this.cell_type = this.cell_type || 'text'; mathjaxutils = mathjaxutils; this.rendered = false; }; TextCell.prototype = Object.create(Cell.prototype); TextCell.options_default = { cm_config : { extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"}, mode: 'htmlmixed', lineWrapping : true, } }; /** * Create the DOM element of the TextCell * @method create_element * @private */ TextCell.prototype.create_element = function () { Cell.prototype.create_element.apply(this, arguments); var cell = $("
").addClass('cell text_cell'); cell.attr('tabindex','2'); var prompt = $('
').addClass('prompt input_prompt'); cell.append(prompt); var inner_cell = $('
').addClass('inner_cell'); this.celltoolbar = new celltoolbar.CellToolbar({ cell: this, notebook: this.notebook}); inner_cell.append(this.celltoolbar.element); var input_area = $('
').addClass('input_area'); this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config); // The tabindex=-1 makes this div focusable. var render_area = $('
').addClass('text_cell_render rendered_html') .attr('tabindex','-1'); inner_cell.append(input_area).append(render_area); cell.append(inner_cell); this.element = cell; }; // Cell level actions TextCell.prototype.select = function () { var cont = Cell.prototype.select.apply(this); if (cont) { if (this.mode === 'edit') { this.code_mirror.refresh(); } } return cont; }; TextCell.prototype.unrender = function () { if (this.read_only) return; var cont = Cell.prototype.unrender.apply(this); if (cont) { var text_cell = this.element; var output = text_cell.find("div.text_cell_render"); if (this.get_text() === this.placeholder) { this.set_text(''); } this.refresh(); } return cont; }; TextCell.prototype.execute = function () { this.render(); }; /** * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}} * @method get_text * @retrun {string} CodeMirror current text value */ TextCell.prototype.get_text = function() { return this.code_mirror.getValue(); }; /** * @param {string} text - Codemiror text value * @see TextCell#get_text * @method set_text * */ TextCell.prototype.set_text = function(text) { this.code_mirror.setValue(text); this.unrender(); this.code_mirror.refresh(); }; /** * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}} * @method get_rendered * */ TextCell.prototype.get_rendered = function() { return this.element.find('div.text_cell_render').html(); }; /** * @method set_rendered */ TextCell.prototype.set_rendered = function(text) { this.element.find('div.text_cell_render').html(text); }; /** * Create Text cell from JSON * @param {json} data - JSON serialized text-cell * @method fromJSON */ TextCell.prototype.fromJSON = function (data) { Cell.prototype.fromJSON.apply(this, arguments); if (data.cell_type === this.cell_type) { if (data.source !== undefined) { this.set_text(data.source); // make this value the starting point, so that we can only undo // to this state, instead of a blank cell this.code_mirror.clearHistory(); // TODO: This HTML needs to be treated as potentially dangerous // user input and should be handled before set_rendered. this.set_rendered(data.rendered || ''); this.rendered = false; this.render(); } } }; /** Generate JSON from cell * @return {object} cell data serialised to json */ TextCell.prototype.toJSON = function () { var data = Cell.prototype.toJSON.apply(this); data.source = this.get_text(); if (data.source == this.placeholder) { data.source = ""; } return data; }; var MarkdownCell = function (options) { // Constructor // // Parameters: // options: dictionary // Dictionary of keyword arguments. // events: $(Events) instance // config: dictionary // keyboard_manager: KeyboardManager instance // notebook: Notebook instance options = options || {}; var config = utils.mergeopt(MarkdownCell, options.config); TextCell.apply(this, [$.extend({}, options, {config: config})]); this.cell_type = 'markdown'; }; MarkdownCell.options_default = { cm_config: { mode: 'ipythongfm' }, placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$" }; MarkdownCell.prototype = Object.create(TextCell.prototype); MarkdownCell.prototype.set_heading_level = function (level) { // make a markdown cell a heading level = level || 1; var source = this.get_text(); source = source.replace(/^(#*)\s?/, new Array(level + 1).join('#') + ' '); this.set_text(source); this.refresh(); if (this.rendered) { this.render(); } }; /** * @method render */ MarkdownCell.prototype.render = function () { var cont = TextCell.prototype.render.apply(this); if (cont) { var text = this.get_text(); var math = null; if (text === "") { text = this.placeholder; } var text_and_math = mathjaxutils.remove_math(text); text = text_and_math[0]; math = text_and_math[1]; var html = marked.parser(marked.lexer(text)); html = mathjaxutils.replace_math(html, math); html = security.sanitize_html(html); html = $($.parseHTML(html)); // add anchors to headings // console.log(html); html.find(":header").addBack(":header").each(function (i, h) { h = $(h); var hash = h.text().replace(/ /g, '-'); h.attr('id', hash); h.append( $('') .addClass('anchor-link') .attr('href', '#' + hash) .text('ΒΆ') ); }) // links in markdown cells should open in new tabs html.find("a[href]").not('[href^="#"]').attr("target", "_blank"); this.set_rendered(html); this.typeset(); this.events.trigger("rendered.MarkdownCell", {cell: this}) } return cont; }; var RawCell = function (options) { // Constructor // // Parameters: // options: dictionary // Dictionary of keyword arguments. // events: $(Events) instance // config: dictionary // keyboard_manager: KeyboardManager instance // notebook: Notebook instance options = options || {}; var config = utils.mergeopt(RawCell, options.config); TextCell.apply(this, [$.extend({}, options, {config: config})]); this.cell_type = 'raw'; }; RawCell.options_default = { placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " + "It will not be rendered in the notebook. " + "When passing through nbconvert, a Raw Cell's content is added to the output unmodified." }; RawCell.prototype = Object.create(TextCell.prototype); /** @method bind_events **/ RawCell.prototype.bind_events = function () { TextCell.prototype.bind_events.apply(this); var that = this; this.element.focusout(function() { that.auto_highlight(); that.render(); }); this.code_mirror.on('focus', function() { that.unrender(); }); }; /** * Trigger autodetection of highlight scheme for current cell * @method auto_highlight */ RawCell.prototype.auto_highlight = function () { this._auto_highlight(this.config.raw_cell_highlight); }; /** @method render **/ RawCell.prototype.render = function () { var cont = TextCell.prototype.render.apply(this); if (cont){ var text = this.get_text(); if (text === "") { text = this.placeholder; } this.set_text(text); this.element.removeClass('rendered'); } return cont; }; // Backwards compatability. IPython.TextCell = TextCell; IPython.MarkdownCell = MarkdownCell; IPython.RawCell = RawCell; var textcell = { TextCell: TextCell, MarkdownCell: MarkdownCell, RawCell: RawCell, }; return textcell; });