textcell.js
464 lines
| 14.4 KiB
| application/javascript
|
JavascriptLexer
|
r17196 | // Copyright (c) IPython Development Team. | ||
// Distributed under the terms of the Modified BSD License. | ||||
define([ | ||||
'base/js/namespace', | ||||
|
r17200 | 'jquery', | ||
|
r17196 | 'notebook/js/cell', | ||
'base/js/security', | ||||
|
r17200 | 'notebook/js/mathjaxutils', | ||
'notebook/js/celltoolbar', | ||||
|
r17205 | 'components/marked/lib/marked', | ||
], function(IPython, $, cell, security, mathjaxutils, celltoolbar, marked) { | ||||
|
r11526 | "use strict"; | ||
|
r17203 | var Cell = cell.Cell; | ||
|
r4349 | |||
|
r17210 | var TextCell = function (options) { | ||
|
r17211 | // 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 | ||||
|
r17210 | options = options || {}; | ||
|
r17211 | |||
|
r10165 | // 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. | ||||
|
r17210 | this.notebook = options.notebook; | ||
this.events = options.events; | ||||
this.config = options.config; | ||||
|
r17200 | |||
|
r10165 | // we cannot put this as a class key as it has handle to "this". | ||
|
r9537 | var cm_overwrite_options = { | ||
|
r14021 | onKeyEvent: $.proxy(this.handle_keyevent,this) | ||
|
r9537 | }; | ||
|
r17210 | var config = this.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options}); | ||
Cell.apply(this, [{ | ||||
config: config, | ||||
keyboard_manager: options.keyboard_manager, | ||||
|
r17212 | events: this.events}]); | ||
|
r9537 | |||
|
r13668 | this.cell_type = this.cell_type || 'text'; | ||
|
r17200 | mathjaxutils = mathjaxutils; | ||
|
r4352 | this.rendered = false; | ||
}; | ||||
|
r17203 | TextCell.prototype = new Cell(); | ||
|
r10165 | |||
TextCell.options_default = { | ||||
cm_config : { | ||||
extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"}, | ||||
mode: 'htmlmixed', | ||||
|
r9537 | lineWrapping : true, | ||
} | ||||
|
r10165 | }; | ||
|
r9537 | |||
|
r8711 | /** | ||
* Create the DOM element of the TextCell | ||||
|
r8709 | * @method create_element | ||
* @private | ||||
*/ | ||||
|
r4508 | TextCell.prototype.create_element = function () { | ||
|
r17203 | Cell.prototype.create_element.apply(this, arguments); | ||
|
r13776 | |||
|
r10217 | var cell = $("<div>").addClass('cell text_cell border-box-sizing'); | ||
|
r4629 | cell.attr('tabindex','2'); | ||
|
r9144 | |||
|
r13776 | var prompt = $('<div/>').addClass('prompt input_prompt'); | ||
cell.append(prompt); | ||||
var inner_cell = $('<div/>').addClass('inner_cell'); | ||||
|
r17214 | this.celltoolbar = new celltoolbar.CellToolbar({ | ||
cell: this, | ||||
events: this.events, | ||||
notebook: this.notebook}); | ||||
|
r13776 | inner_cell.append(this.celltoolbar.element); | ||
|
r15552 | var input_area = $('<div/>').addClass('input_area'); | ||
|
r15497 | this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config); | ||
|
r4499 | // The tabindex=-1 makes this div focusable. | ||
|
r5946 | var render_area = $('<div/>').addClass('text_cell_render border-box-sizing'). | ||
|
r8661 | addClass('rendered_html').attr('tabindex','-1'); | ||
|
r13776 | inner_cell.append(input_area).append(render_area); | ||
cell.append(inner_cell); | ||||
|
r4352 | this.element = cell; | ||
}; | ||||
|
r8711 | /** | ||
* Bind the DOM evet to cell actions | ||||
|
r8709 | * Need to be called after TextCell.create_element | ||
* @private | ||||
* @method bind_event | ||||
*/ | ||||
|
r4508 | TextCell.prototype.bind_events = function () { | ||
|
r17203 | Cell.prototype.bind_events.apply(this); | ||
|
r4352 | var that = this; | ||
|
r14015 | |||
|
r5946 | this.element.dblclick(function () { | ||
|
r14016 | if (that.selected === false) { | ||
|
r17196 | this.events.trigger('select.Cell', {'cell':that}); | ||
|
r15497 | } | ||
|
r15531 | var cont = that.unrender(); | ||
if (cont) { | ||||
that.focus_editor(); | ||||
} | ||||
|
r4352 | }); | ||
}; | ||||
|
r4349 | |||
|
r14014 | // Cell level actions | ||
|
r4508 | TextCell.prototype.select = function () { | ||
|
r17203 | var cont = Cell.prototype.select.apply(this); | ||
|
r14015 | if (cont) { | ||
|
r14014 | if (this.mode === 'edit') { | ||
this.code_mirror.refresh(); | ||||
} | ||||
|
r15497 | } | ||
|
r14015 | return cont; | ||
|
r5833 | }; | ||
|
r14014 | TextCell.prototype.unrender = function () { | ||
if (this.read_only) return; | ||||
|
r17203 | var cont = Cell.prototype.unrender.apply(this); | ||
|
r14015 | if (cont) { | ||
|
r4508 | var text_cell = this.element; | ||
|
r8709 | var output = text_cell.find("div.text_cell_render"); | ||
|
r4352 | output.hide(); | ||
|
r15552 | text_cell.find('div.input_area').show(); | ||
|
r5943 | if (this.get_text() === this.placeholder) { | ||
this.set_text(''); | ||||
|
r5479 | } | ||
|
r15502 | this.refresh(); | ||
|
r15497 | } | ||
|
r16287 | if (this.celltoolbar.ui_controls_list.length) { | ||
this.celltoolbar.show(); | ||||
} | ||||
|
r14015 | return cont; | ||
}; | ||||
TextCell.prototype.execute = function () { | ||||
this.render(); | ||||
|
r14014 | }; | ||
|
r4349 | |||
|
r8709 | /** | ||
* setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}} | ||||
* @method get_text | ||||
* @retrun {string} CodeMirror current text value | ||||
*/ | ||||
|
r5943 | TextCell.prototype.get_text = function() { | ||
|
r4499 | return this.code_mirror.getValue(); | ||
|
r4352 | }; | ||
|
r4349 | |||
|
r8709 | /** | ||
* @param {string} text - Codemiror text value | ||||
* @see TextCell#get_text | ||||
* @method set_text | ||||
* */ | ||||
|
r5943 | TextCell.prototype.set_text = function(text) { | ||
|
r4499 | this.code_mirror.setValue(text); | ||
this.code_mirror.refresh(); | ||||
|
r4352 | }; | ||
|
r4349 | |||
|
r8709 | /** | ||
* setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}} | ||||
* @method get_rendered | ||||
* */ | ||||
|
r4513 | TextCell.prototype.get_rendered = function() { | ||
|
r4508 | return this.element.find('div.text_cell_render').html(); | ||
}; | ||||
|
r8709 | /** | ||
* @method set_rendered | ||||
*/ | ||||
|
r4508 | TextCell.prototype.set_rendered = function(text) { | ||
this.element.find('div.text_cell_render').html(text); | ||||
|
r16287 | this.celltoolbar.hide(); | ||
|
r4352 | }; | ||
|
r4349 | |||
|
r8711 | /** | ||
* Create Text cell from JSON | ||||
|
r8709 | * @param {json} data - JSON serialized text-cell | ||
|
r8711 | * @method fromJSON | ||
|
r8709 | */ | ||
|
r4508 | TextCell.prototype.fromJSON = function (data) { | ||
|
r17203 | Cell.prototype.fromJSON.apply(this, arguments); | ||
|
r4508 | if (data.cell_type === this.cell_type) { | ||
|
r4499 | if (data.source !== undefined) { | ||
|
r5943 | this.set_text(data.source); | ||
|
r7587 | // make this value the starting point, so that we can only undo | ||
// to this state, instead of a blank cell | ||||
this.code_mirror.clearHistory(); | ||||
|
r15407 | // TODO: This HTML needs to be treated as potentially dangerous | ||
// user input and should be handled before set_rendered. | ||||
|
r4513 | this.set_rendered(data.rendered || ''); | ||
this.rendered = false; | ||||
this.render(); | ||||
|
r5479 | } | ||
} | ||||
|
r4513 | }; | ||
|
r4349 | |||
|
r8711 | /** Generate JSON from cell | ||
* @return {object} cell data serialised to json | ||||
*/ | ||||
|
r4508 | TextCell.prototype.toJSON = function () { | ||
|
r17203 | var data = Cell.prototype.toJSON.apply(this); | ||
|
r5943 | data.source = this.get_text(); | ||
|
r13668 | if (data.source == this.placeholder) { | ||
data.source = ""; | ||||
} | ||||
|
r4484 | return data; | ||
|
r4349 | }; | ||
|
r4508 | |||
|
r17210 | var MarkdownCell = function (options) { | ||
|
r17211 | // Constructor | ||
// | ||||
// Parameters: | ||||
// options: dictionary | ||||
// Dictionary of keyword arguments. | ||||
// events: $(Events) instance | ||||
// config: dictionary | ||||
// keyboard_manager: KeyboardManager instance | ||||
// notebook: Notebook instance | ||||
|
r17210 | options = options || {}; | ||
var config = this.mergeopt(MarkdownCell, options.config); | ||||
TextCell.apply(this, [$.extend({}, options, {config: config})]); | ||||
|
r10165 | |||
|
r4508 | this.cell_type = 'markdown'; | ||
}; | ||||
|
r10165 | MarkdownCell.options_default = { | ||
cm_config: { | ||||
|
r16787 | mode: 'ipythongfm' | ||
|
r10165 | }, | ||
placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$" | ||||
|
r15497 | }; | ||
|
r10165 | |||
|
r4508 | MarkdownCell.prototype = new TextCell(); | ||
|
r8711 | /** | ||
* @method render | ||||
*/ | ||||
|
r4508 | MarkdownCell.prototype.render = function () { | ||
|
r17196 | var cont = TextCell.prototype.render.apply(this); | ||
|
r14015 | if (cont) { | ||
|
r5943 | var text = this.get_text(); | ||
|
r11843 | var math = null; | ||
|
r5479 | if (text === "") { text = this.placeholder; } | ||
|
r17200 | var text_and_math = mathjaxutils.remove_math(text); | ||
|
r11843 | text = text_and_math[0]; | ||
math = text_and_math[1]; | ||||
|
r10536 | var html = marked.parser(marked.lexer(text)); | ||
|
r17200 | html = mathjaxutils.replace_math(html, math); | ||
|
r17203 | html = security.sanitize_html(html); | ||
|
r16821 | html = $($.parseHTML(html)); | ||
|
r15637 | // links in markdown cells should open in new tabs | ||
html.find("a[href]").not('[href^="#"]').attr("target", "_blank"); | ||||
this.set_rendered(html); | ||||
|
r15552 | this.element.find('div.input_area').hide(); | ||
|
r8662 | this.element.find("div.text_cell_render").show(); | ||
|
r15637 | this.typeset(); | ||
|
r15497 | } | ||
|
r14015 | return cont; | ||
|
r4508 | }; | ||
|
r17210 | var RawCell = function (options) { | ||
|
r17211 | // Constructor | ||
// | ||||
// Parameters: | ||||
// options: dictionary | ||||
// Dictionary of keyword arguments. | ||||
// events: $(Events) instance | ||||
// config: dictionary | ||||
// keyboard_manager: KeyboardManager instance | ||||
// notebook: Notebook instance | ||||
|
r17210 | options = options || {}; | ||
var config = this.mergeopt(RawCell, options.config); | ||||
TextCell.apply(this, [$.extend({}, options, {config: config})]); | ||||
|
r14016 | |||
// RawCell should always hide its rendered div | ||||
this.element.find('div.text_cell_render').hide(); | ||||
|
r17210 | this.cell_type = 'raw'; | ||
|
r4508 | }; | ||
|
r10165 | RawCell.options_default = { | ||
|
r15859 | placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " + | ||
"It will not be rendered in the notebook. " + | ||||
|
r13668 | "When passing through nbconvert, a Raw Cell's content is added to the output unmodified." | ||
|
r10165 | }; | ||
|
r6248 | RawCell.prototype = new TextCell(); | ||
|
r4508 | |||
|
r14015 | /** @method bind_events **/ | ||
RawCell.prototype.bind_events = function () { | ||||
TextCell.prototype.bind_events.apply(this); | ||||
|
r15497 | var that = this; | ||
|
r14015 | this.element.focusout(function() { | ||
that.auto_highlight(); | ||||
|
r15858 | that.render(); | ||
|
r14015 | }); | ||
|
r15768 | |||
this.code_mirror.on('focus', function() { that.unrender(); }); | ||||
|
r14015 | }; | ||
|
r8711 | /** | ||
* Trigger autodetection of highlight scheme for current cell | ||||
* @method auto_highlight | ||||
*/ | ||||
|
r8202 | RawCell.prototype.auto_highlight = function () { | ||
|
r17200 | this._auto_highlight(this.config.raw_cell_highlight); | ||
|
r8202 | }; | ||
|
r4508 | |||
|
r8711 | /** @method render **/ | ||
|
r6248 | RawCell.prototype.render = function () { | ||
|
r17196 | var cont = TextCell.prototype.render.apply(this); | ||
|
r15768 | if (cont){ | ||
var text = this.get_text(); | ||||
if (text === "") { text = this.placeholder; } | ||||
this.set_text(text); | ||||
|
r15858 | this.element.removeClass('rendered'); | ||
|
r14015 | } | ||
|
r15768 | return cont; | ||
|
r6017 | }; | ||
|
r17210 | var HeadingCell = function (options) { | ||
|
r17211 | // Constructor | ||
// | ||||
// Parameters: | ||||
// options: dictionary | ||||
// Dictionary of keyword arguments. | ||||
// events: $(Events) instance | ||||
// config: dictionary | ||||
// keyboard_manager: KeyboardManager instance | ||||
// notebook: Notebook instance | ||||
|
r17210 | options = options || {}; | ||
var config = this.mergeopt(HeadingCell, options.config); | ||||
TextCell.apply(this, [$.extend({}, options, {config: config})]); | ||||
|
r10165 | |||
|
r13668 | this.level = 1; | ||
this.cell_type = 'heading'; | ||||
|
r6018 | }; | ||
|
r10165 | HeadingCell.options_default = { | ||
placeholder: "Type Heading Here" | ||||
}; | ||||
|
r6018 | |||
HeadingCell.prototype = new TextCell(); | ||||
|
r8711 | /** @method fromJSON */ | ||
|
r6156 | HeadingCell.prototype.fromJSON = function (data) { | ||
|
r15497 | if (data.level !== undefined){ | ||
|
r6156 | this.level = data.level; | ||
} | ||||
|
r10165 | TextCell.prototype.fromJSON.apply(this, arguments); | ||
|
r6156 | }; | ||
|
r8711 | /** @method toJSON */ | ||
|
r6156 | HeadingCell.prototype.toJSON = function () { | ||
|
r10165 | var data = TextCell.prototype.toJSON.apply(this); | ||
|
r6156 | data.level = this.get_level(); | ||
return data; | ||||
}; | ||||
|
r12510 | /** | ||
* can the cell be split into two cells | ||||
* @method is_splittable | ||||
**/ | ||||
HeadingCell.prototype.is_splittable = function () { | ||||
return false; | ||||
}; | ||||
/** | ||||
* can the cell be merged with other cells | ||||
* @method is_mergeable | ||||
**/ | ||||
HeadingCell.prototype.is_mergeable = function () { | ||||
return false; | ||||
}; | ||||
|
r6156 | |||
|
r8711 | /** | ||
* Change heading level of cell, and re-render | ||||
* @method set_level | ||||
*/ | ||||
|
r6019 | HeadingCell.prototype.set_level = function (level) { | ||
this.level = level; | ||||
if (this.rendered) { | ||||
this.rendered = false; | ||||
this.render(); | ||||
|
r15497 | } | ||
|
r6019 | }; | ||
|
r8709 | /** The depth of header cell, based on html (h1 to h6) | ||
|
r8711 | * @method get_level | ||
|
r8709 | * @return {integer} level - for 1 to 6 | ||
*/ | ||||
|
r6019 | HeadingCell.prototype.get_level = function () { | ||
return this.level; | ||||
}; | ||||
|
r6018 | HeadingCell.prototype.get_rendered = function () { | ||
var r = this.element.find("div.text_cell_render"); | ||||
return r.children().first().html(); | ||||
|
r6019 | }; | ||
|
r6018 | |||
HeadingCell.prototype.render = function () { | ||||
|
r17196 | var cont = TextCell.prototype.render.apply(this); | ||
|
r14015 | if (cont) { | ||
|
r6018 | var text = this.get_text(); | ||
|
r11843 | var math = null; | ||
|
r11290 | // Markdown headings must be a single line | ||
text = text.replace(/\n/g, ' '); | ||||
|
r6018 | if (text === "") { text = this.placeholder; } | ||
|
r17196 | text = new Array(this.level + 1).join("#") + " " + text; | ||
|
r17200 | var text_and_math = mathjaxutils.remove_math(text); | ||
|
r11843 | text = text_and_math[0]; | ||
math = text_and_math[1]; | ||||
|
r11288 | var html = marked.parser(marked.lexer(text)); | ||
|
r17200 | html = mathjaxutils.replace_math(html, math); | ||
|
r17203 | html = security.sanitize_html(html); | ||
|
r16821 | var h = $($.parseHTML(html)); | ||
|
r15637 | // 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); | ||||
|
r15698 | this.element.find('div.input_area').hide(); | ||
|
r6018 | this.element.find("div.text_cell_render").show(); | ||
|
r15637 | this.typeset(); | ||
} | ||||
|
r14015 | return cont; | ||
|
r6018 | }; | ||
|
r17196 | // Backwards compatability. | ||
|
r4508 | IPython.TextCell = TextCell; | ||
IPython.MarkdownCell = MarkdownCell; | ||||
|
r6248 | IPython.RawCell = RawCell; | ||
|
r6019 | IPython.HeadingCell = HeadingCell; | ||
|
r4508 | |||
|
r17214 | var textcell = { | ||
|
r17196 | 'TextCell': TextCell, | ||
'MarkdownCell': MarkdownCell, | ||||
'RawCell': RawCell, | ||||
'HeadingCell': HeadingCell, | ||||
}; | ||||
|
r17214 | return textcell; | ||
|
r17196 | }); | ||