// 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',
'services/config',
'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,
configmod,
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 config = utils.mergeopt(TextCell, this.config);
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);
this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this))
// 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;
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: ConfigSection instance
* keyboard_manager: KeyboardManager instance
* notebook: Notebook instance
*/
options = options || {};
var config = utils.mergeopt(MarkdownCell, {});
TextCell.apply(this, [$.extend({}, options, {config: config})]);
this.class_config = new configmod.ConfigWithDefaults(options.config,
{}, 'MarkdownCell');
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 that = this;
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];
marked(text, function (err, html) {
html = mathjaxutils.replace_math(html, math);
html = security.sanitize_html(html);
html = $($.parseHTML(html));
// add anchors to headings
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");
that.set_rendered(html);
that.typeset();
that.events.trigger("rendered.MarkdownCell", {cell: that});
});
}
return cont;
};
var RawCell = function (options) {
/**
* Constructor
*
* Parameters:
* options: dictionary
* Dictionary of keyword arguments.
* events: $(Events) instance
* config: ConfigSection instance
* keyboard_manager: KeyboardManager instance
* notebook: Notebook instance
*/
options = options || {};
var config = utils.mergeopt(RawCell, {});
TextCell.apply(this, [$.extend({}, options, {config: config})]);
this.class_config = new configmod.ConfigWithDefaults(options.config,
RawCell.config_defaults, 'RawCell');
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.config_defaults = {
highlight_modes : {
'diff' :{'reg':[/^diff/]}
},
};
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.class_config.get_sync('highlight_modes'));
};
/** @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;
});