##// END OF EJS Templates
allow splitting and merging of heading cells...
allow splitting and merging of heading cells I consider it a bug that you couldn't merge with heading cells, and that you couldn't split them, either. (So much so that I thought it was a bug in ipython-vimception when I ran into it). This change removes that limitation, so heading cells are on par with the other cells in terms of the kinds of manipulations one can carry out with them.

File last commit:

r17417:811c7332
r17417:811c7332
Show More
textcell.js
447 lines | 14.1 KiB | application/javascript | JavascriptLexer
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
define([
'base/js/namespace',
'jquery',
'notebook/js/cell',
'base/js/security',
'notebook/js/mathjaxutils',
'notebook/js/celltoolbar',
'components/marked/lib/marked',
], function(IPython, $, cell, security, mathjaxutils, celltoolbar, marked) {
"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 = this.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 = new Cell();
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 = $("<div>").addClass('cell text_cell border-box-sizing');
cell.attr('tabindex','2');
var prompt = $('<div/>').addClass('prompt input_prompt');
cell.append(prompt);
var inner_cell = $('<div/>').addClass('inner_cell');
this.celltoolbar = new celltoolbar.CellToolbar({
cell: this,
events: this.events,
notebook: this.notebook});
inner_cell.append(this.celltoolbar.element);
var input_area = $('<div/>').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 = $('<div/>').addClass('text_cell_render border-box-sizing').
addClass('rendered_html').attr('tabindex','-1');
inner_cell.append(input_area).append(render_area);
cell.append(inner_cell);
this.element = cell;
};
/**
* Bind the DOM evet to cell actions
* Need to be called after TextCell.create_element
* @private
* @method bind_event
*/
TextCell.prototype.bind_events = function () {
Cell.prototype.bind_events.apply(this);
var that = this;
this.element.dblclick(function () {
if (that.selected === false) {
this.events.trigger('select.Cell', {'cell':that});
}
var cont = that.unrender();
if (cont) {
that.focus_editor();
}
});
};
// 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");
output.hide();
text_cell.find('div.input_area').show();
if (this.get_text() === this.placeholder) {
this.set_text('');
}
this.refresh();
}
if (this.celltoolbar.ui_controls_list.length) {
this.celltoolbar.show();
}
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.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);
this.celltoolbar.hide();
};
/**
* 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 = this.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 = new TextCell();
/**
* @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));
// links in markdown cells should open in new tabs
html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
this.set_rendered(html);
this.element.find('div.input_area').hide();
this.element.find("div.text_cell_render").show();
this.typeset();
}
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 = this.mergeopt(RawCell, options.config);
TextCell.apply(this, [$.extend({}, options, {config: config})]);
// RawCell should always hide its rendered div
this.element.find('div.text_cell_render').hide();
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 = new TextCell();
/** @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;
};
var HeadingCell = 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 = this.mergeopt(HeadingCell, options.config);
TextCell.apply(this, [$.extend({}, options, {config: config})]);
this.level = 1;
this.cell_type = 'heading';
};
HeadingCell.options_default = {
placeholder: "Type Heading Here"
};
HeadingCell.prototype = new TextCell();
/** @method fromJSON */
HeadingCell.prototype.fromJSON = function (data) {
if (data.level !== undefined){
this.level = data.level;
}
TextCell.prototype.fromJSON.apply(this, arguments);
};
/** @method toJSON */
HeadingCell.prototype.toJSON = function () {
var data = TextCell.prototype.toJSON.apply(this);
data.level = this.get_level();
return data;
};
/**
* Change heading level of cell, and re-render
* @method set_level
*/
HeadingCell.prototype.set_level = function (level) {
this.level = level;
if (this.rendered) {
this.rendered = false;
this.render();
}
};
/** The depth of header cell, based on html (h1 to h6)
* @method get_level
* @return {integer} level - for 1 to 6
*/
HeadingCell.prototype.get_level = function () {
return this.level;
};
HeadingCell.prototype.get_rendered = function () {
var r = this.element.find("div.text_cell_render");
return r.children().first().html();
};
HeadingCell.prototype.render = function () {
var cont = TextCell.prototype.render.apply(this);
if (cont) {
var text = this.get_text();
var math = null;
// Markdown headings must be a single line
text = text.replace(/\n/g, ' ');
if (text === "") { text = this.placeholder; }
text = new Array(this.level + 1).join("#") + " " + text;
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);
var h = $($.parseHTML(html));
// add id and linkback anchor
var hash = h.text().replace(/ /g, '-');
h.attr('id', hash);
h.append(
$('<a/>')
.addClass('anchor-link')
.attr('href', '#' + hash)
.text('ΒΆ')
);
this.set_rendered(h);
this.element.find('div.input_area').hide();
this.element.find("div.text_cell_render").show();
this.typeset();
}
return cont;
};
// Backwards compatability.
IPython.TextCell = TextCell;
IPython.MarkdownCell = MarkdownCell;
IPython.RawCell = RawCell;
IPython.HeadingCell = HeadingCell;
var textcell = {
'TextCell': TextCell,
'MarkdownCell': MarkdownCell,
'RawCell': RawCell,
'HeadingCell': HeadingCell,
};
return textcell;
});