textcell.js
352 lines
| 11.1 KiB
| application/javascript
|
JavascriptLexer
Jonathan Frederic
|
r17196 | // Copyright (c) IPython Development Team. | ||
// Distributed under the terms of the Modified BSD License. | ||||
define([ | ||||
'base/js/namespace', | ||||
MinRK
|
r17764 | 'base/js/utils', | ||
Jonathan Frederic
|
r17200 | 'jquery', | ||
Jonathan Frederic
|
r17196 | 'notebook/js/cell', | ||
'base/js/security', | ||||
Jonathan Frederic
|
r17200 | 'notebook/js/mathjaxutils', | ||
'notebook/js/celltoolbar', | ||||
Jonathan Frederic
|
r17205 | 'components/marked/lib/marked', | ||
Matthias BUSSONNIER
|
r18280 | 'codemirror/lib/codemirror', | ||
'codemirror/mode/gfm/gfm', | ||||
'notebook/js/codemirror-ipythongfm' | ||||
], function(IPython,utils , $, cell, security, mathjaxutils, celltoolbar, marked, CodeMirror, gfm, ipgfm) { | ||||
Matthias BUSSONNIER
|
r11526 | "use strict"; | ||
Jonathan Frederic
|
r17203 | var Cell = cell.Cell; | ||
Brian E. Granger
|
r4349 | |||
jon
|
r17210 | var TextCell = function (options) { | ||
Jonathan Frederic
|
r19176 | /** | ||
* 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 | ||||
*/ | ||||
jon
|
r17210 | options = options || {}; | ||
jon
|
r17211 | |||
Matthias BUSSONNIER
|
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. | ||||
jon
|
r17210 | this.notebook = options.notebook; | ||
this.events = options.events; | ||||
this.config = options.config; | ||||
Jonathan Frederic
|
r17200 | |||
Matthias BUSSONNIER
|
r10165 | // we cannot put this as a class key as it has handle to "this". | ||
foogunlana
|
r19098 | var config = utils.mergeopt(TextCell, this.config); | ||
jon
|
r17210 | Cell.apply(this, [{ | ||
config: config, | ||||
keyboard_manager: options.keyboard_manager, | ||||
Jonathan Frederic
|
r17212 | events: this.events}]); | ||
Matthias BUSSONNIER
|
r9537 | |||
MinRK
|
r13668 | this.cell_type = this.cell_type || 'text'; | ||
Jonathan Frederic
|
r17200 | mathjaxutils = mathjaxutils; | ||
Brian E. Granger
|
r4352 | this.rendered = false; | ||
}; | ||||
Matthias Bussonnier
|
r18377 | TextCell.prototype = Object.create(Cell.prototype); | ||
Matthias BUSSONNIER
|
r10165 | |||
TextCell.options_default = { | ||||
cm_config : { | ||||
extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"}, | ||||
mode: 'htmlmixed', | ||||
Matthias BUSSONNIER
|
r9537 | lineWrapping : true, | ||
} | ||||
Matthias BUSSONNIER
|
r10165 | }; | ||
Matthias BUSSONNIER
|
r9537 | |||
Matthias BUSSONNIER
|
r8711 | /** | ||
* Create the DOM element of the TextCell | ||||
Matthias BUSSONNIER
|
r8709 | * @method create_element | ||
* @private | ||||
*/ | ||||
Brian Granger
|
r4508 | TextCell.prototype.create_element = function () { | ||
Jonathan Frederic
|
r17203 | Cell.prototype.create_element.apply(this, arguments); | ||
Brian E. Granger
|
r13776 | |||
Matthias BUSSONNIER
|
r17422 | var cell = $("<div>").addClass('cell text_cell'); | ||
Brian E. Granger
|
r4629 | cell.attr('tabindex','2'); | ||
Brian Granger
|
r9144 | |||
Brian E. Granger
|
r13776 | var prompt = $('<div/>').addClass('prompt input_prompt'); | ||
cell.append(prompt); | ||||
var inner_cell = $('<div/>').addClass('inner_cell'); | ||||
Jonathan Frederic
|
r17214 | this.celltoolbar = new celltoolbar.CellToolbar({ | ||
cell: this, | ||||
notebook: this.notebook}); | ||||
Brian E. Granger
|
r13776 | inner_cell.append(this.celltoolbar.element); | ||
MinRK
|
r15552 | var input_area = $('<div/>').addClass('input_area'); | ||
Jonathan Frederic
|
r15497 | this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config); | ||
foogunlana
|
r19098 | this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this)) | ||
Brian E. Granger
|
r4499 | // The tabindex=-1 makes this div focusable. | ||
Matthias BUSSONNIER
|
r17426 | var render_area = $('<div/>').addClass('text_cell_render rendered_html') | ||
.attr('tabindex','-1'); | ||||
Brian E. Granger
|
r13776 | inner_cell.append(input_area).append(render_area); | ||
cell.append(inner_cell); | ||||
Brian E. Granger
|
r4352 | this.element = cell; | ||
}; | ||||
Brian E. Granger
|
r14014 | // Cell level actions | ||
Brian Granger
|
r4508 | TextCell.prototype.select = function () { | ||
Jonathan Frederic
|
r17203 | var cont = Cell.prototype.select.apply(this); | ||
Brian E. Granger
|
r14015 | if (cont) { | ||
Brian E. Granger
|
r14014 | if (this.mode === 'edit') { | ||
this.code_mirror.refresh(); | ||||
} | ||||
Jonathan Frederic
|
r15497 | } | ||
Brian E. Granger
|
r14015 | return cont; | ||
MinRK
|
r5833 | }; | ||
Brian E. Granger
|
r14014 | TextCell.prototype.unrender = function () { | ||
if (this.read_only) return; | ||||
Jonathan Frederic
|
r17203 | var cont = Cell.prototype.unrender.apply(this); | ||
Brian E. Granger
|
r14015 | if (cont) { | ||
Brian Granger
|
r4508 | var text_cell = this.element; | ||
Matthias BUSSONNIER
|
r8709 | var output = text_cell.find("div.text_cell_render"); | ||
Brian Granger
|
r5943 | if (this.get_text() === this.placeholder) { | ||
this.set_text(''); | ||||
Stefan van der Walt
|
r5479 | } | ||
Brian E. Granger
|
r15502 | this.refresh(); | ||
Jonathan Frederic
|
r15497 | } | ||
Brian E. Granger
|
r14015 | return cont; | ||
}; | ||||
TextCell.prototype.execute = function () { | ||||
this.render(); | ||||
Brian E. Granger
|
r14014 | }; | ||
Brian E. Granger
|
r4349 | |||
Matthias BUSSONNIER
|
r8709 | /** | ||
* setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}} | ||||
* @method get_text | ||||
* @retrun {string} CodeMirror current text value | ||||
*/ | ||||
Brian Granger
|
r5943 | TextCell.prototype.get_text = function() { | ||
Brian E. Granger
|
r4499 | return this.code_mirror.getValue(); | ||
Brian E. Granger
|
r4352 | }; | ||
Brian E. Granger
|
r4349 | |||
Matthias BUSSONNIER
|
r8709 | /** | ||
* @param {string} text - Codemiror text value | ||||
* @see TextCell#get_text | ||||
* @method set_text | ||||
* */ | ||||
Brian Granger
|
r5943 | TextCell.prototype.set_text = function(text) { | ||
Brian E. Granger
|
r4499 | this.code_mirror.setValue(text); | ||
Paul Ivanov
|
r17464 | this.unrender(); | ||
Brian E. Granger
|
r4499 | this.code_mirror.refresh(); | ||
Brian E. Granger
|
r4352 | }; | ||
Brian E. Granger
|
r4349 | |||
Matthias BUSSONNIER
|
r8709 | /** | ||
* setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}} | ||||
* @method get_rendered | ||||
* */ | ||||
Brian E. Granger
|
r4513 | TextCell.prototype.get_rendered = function() { | ||
Brian Granger
|
r4508 | return this.element.find('div.text_cell_render').html(); | ||
}; | ||||
Matthias BUSSONNIER
|
r8709 | /** | ||
* @method set_rendered | ||||
*/ | ||||
Brian Granger
|
r4508 | TextCell.prototype.set_rendered = function(text) { | ||
this.element.find('div.text_cell_render').html(text); | ||||
Brian E. Granger
|
r4352 | }; | ||
Brian E. Granger
|
r4349 | |||
Matthias BUSSONNIER
|
r8711 | /** | ||
* Create Text cell from JSON | ||||
Matthias BUSSONNIER
|
r8709 | * @param {json} data - JSON serialized text-cell | ||
Matthias BUSSONNIER
|
r8711 | * @method fromJSON | ||
Matthias BUSSONNIER
|
r8709 | */ | ||
Brian Granger
|
r4508 | TextCell.prototype.fromJSON = function (data) { | ||
Jonathan Frederic
|
r17203 | Cell.prototype.fromJSON.apply(this, arguments); | ||
Brian Granger
|
r4508 | if (data.cell_type === this.cell_type) { | ||
Brian E. Granger
|
r4499 | if (data.source !== undefined) { | ||
Brian Granger
|
r5943 | this.set_text(data.source); | ||
Paul Ivanov
|
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(); | ||||
Jonathan Frederic
|
r15407 | // TODO: This HTML needs to be treated as potentially dangerous | ||
// user input and should be handled before set_rendered. | ||||
Brian E. Granger
|
r4513 | this.set_rendered(data.rendered || ''); | ||
this.rendered = false; | ||||
this.render(); | ||||
Stefan van der Walt
|
r5479 | } | ||
} | ||||
Brian E. Granger
|
r4513 | }; | ||
Brian E. Granger
|
r4349 | |||
Matthias BUSSONNIER
|
r8711 | /** Generate JSON from cell | ||
* @return {object} cell data serialised to json | ||||
*/ | ||||
Brian Granger
|
r4508 | TextCell.prototype.toJSON = function () { | ||
Jonathan Frederic
|
r17203 | var data = Cell.prototype.toJSON.apply(this); | ||
Brian Granger
|
r5943 | data.source = this.get_text(); | ||
MinRK
|
r13668 | if (data.source == this.placeholder) { | ||
data.source = ""; | ||||
} | ||||
Brian E. Granger
|
r4484 | return data; | ||
Brian E. Granger
|
r4349 | }; | ||
Brian Granger
|
r4508 | |||
jon
|
r17210 | var MarkdownCell = function (options) { | ||
Jonathan Frederic
|
r19176 | /** | ||
* Constructor | ||||
* | ||||
* Parameters: | ||||
* options: dictionary | ||||
* Dictionary of keyword arguments. | ||||
* events: $(Events) instance | ||||
* config: dictionary | ||||
* keyboard_manager: KeyboardManager instance | ||||
* notebook: Notebook instance | ||||
*/ | ||||
jon
|
r17210 | options = options || {}; | ||
MinRK
|
r17764 | var config = utils.mergeopt(MarkdownCell, options.config); | ||
jon
|
r17210 | TextCell.apply(this, [$.extend({}, options, {config: config})]); | ||
Matthias BUSSONNIER
|
r10165 | |||
Brian Granger
|
r4508 | this.cell_type = 'markdown'; | ||
}; | ||||
Matthias BUSSONNIER
|
r10165 | MarkdownCell.options_default = { | ||
cm_config: { | ||||
Jonathan Frederic
|
r16787 | mode: 'ipythongfm' | ||
Matthias BUSSONNIER
|
r10165 | }, | ||
placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$" | ||||
Jonathan Frederic
|
r15497 | }; | ||
Matthias BUSSONNIER
|
r10165 | |||
Matthias Bussonnier
|
r18377 | MarkdownCell.prototype = Object.create(TextCell.prototype); | ||
Brian Granger
|
r4508 | |||
MinRK
|
r18596 | MarkdownCell.prototype.set_heading_level = function (level) { | ||
Jonathan Frederic
|
r19176 | /** | ||
* make a markdown cell a heading | ||||
*/ | ||||
MinRK
|
r18596 | level = level || 1; | ||
var source = this.get_text(); | ||||
MinRK
|
r18597 | source = source.replace(/^(#*)\s?/, | ||
new Array(level + 1).join('#') + ' '); | ||||
this.set_text(source); | ||||
MinRK
|
r18596 | this.refresh(); | ||
if (this.rendered) { | ||||
this.render(); | ||||
} | ||||
}; | ||||
Matthias BUSSONNIER
|
r8711 | /** | ||
* @method render | ||||
*/ | ||||
Brian Granger
|
r4508 | MarkdownCell.prototype.render = function () { | ||
Jonathan Frederic
|
r17196 | var cont = TextCell.prototype.render.apply(this); | ||
Brian E. Granger
|
r14015 | if (cont) { | ||
MinRK
|
r18864 | var that = this; | ||
Brian Granger
|
r5943 | var text = this.get_text(); | ||
Jessica B. Hamrick
|
r11843 | var math = null; | ||
Stefan van der Walt
|
r5479 | if (text === "") { text = this.placeholder; } | ||
Jonathan Frederic
|
r17200 | var text_and_math = mathjaxutils.remove_math(text); | ||
Jessica B. Hamrick
|
r11843 | text = text_and_math[0]; | ||
math = text_and_math[1]; | ||||
MinRK
|
r18864 | 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( | ||||
$('<a/>') | ||||
.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}); | ||||
}); | ||||
Jonathan Frederic
|
r15497 | } | ||
Brian E. Granger
|
r14015 | return cont; | ||
Brian Granger
|
r4508 | }; | ||
jon
|
r17210 | var RawCell = function (options) { | ||
Jonathan Frederic
|
r19176 | /** | ||
* Constructor | ||||
* | ||||
* Parameters: | ||||
* options: dictionary | ||||
* Dictionary of keyword arguments. | ||||
* events: $(Events) instance | ||||
* config: dictionary | ||||
* keyboard_manager: KeyboardManager instance | ||||
* notebook: Notebook instance | ||||
*/ | ||||
jon
|
r17210 | options = options || {}; | ||
MinRK
|
r17764 | var config = utils.mergeopt(RawCell, options.config); | ||
jon
|
r17210 | TextCell.apply(this, [$.extend({}, options, {config: config})]); | ||
Brian E. Granger
|
r14016 | |||
jon
|
r17210 | this.cell_type = 'raw'; | ||
Brian Granger
|
r4508 | }; | ||
Matthias BUSSONNIER
|
r10165 | RawCell.options_default = { | ||
Paul Ivanov
|
r15859 | placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " + | ||
"It will not be rendered in the notebook. " + | ||||
MinRK
|
r13668 | "When passing through nbconvert, a Raw Cell's content is added to the output unmodified." | ||
Matthias BUSSONNIER
|
r10165 | }; | ||
Matthias Bussonnier
|
r18377 | RawCell.prototype = Object.create(TextCell.prototype); | ||
Brian Granger
|
r4508 | |||
Brian E. Granger
|
r14015 | /** @method bind_events **/ | ||
RawCell.prototype.bind_events = function () { | ||||
TextCell.prototype.bind_events.apply(this); | ||||
Jonathan Frederic
|
r15497 | var that = this; | ||
Brian E. Granger
|
r14015 | this.element.focusout(function() { | ||
that.auto_highlight(); | ||||
Paul Ivanov
|
r15858 | that.render(); | ||
Brian E. Granger
|
r14015 | }); | ||
Paul Ivanov
|
r15768 | |||
this.code_mirror.on('focus', function() { that.unrender(); }); | ||||
Brian E. Granger
|
r14015 | }; | ||
Matthias BUSSONNIER
|
r8711 | /** | ||
* Trigger autodetection of highlight scheme for current cell | ||||
* @method auto_highlight | ||||
*/ | ||||
Matthias BUSSONNIER
|
r8202 | RawCell.prototype.auto_highlight = function () { | ||
Jonathan Frederic
|
r17200 | this._auto_highlight(this.config.raw_cell_highlight); | ||
Matthias BUSSONNIER
|
r8202 | }; | ||
Brian Granger
|
r4508 | |||
Matthias BUSSONNIER
|
r8711 | /** @method render **/ | ||
MinRK
|
r6248 | RawCell.prototype.render = function () { | ||
Jonathan Frederic
|
r17196 | var cont = TextCell.prototype.render.apply(this); | ||
Paul Ivanov
|
r15768 | if (cont){ | ||
var text = this.get_text(); | ||||
if (text === "") { text = this.placeholder; } | ||||
this.set_text(text); | ||||
Paul Ivanov
|
r15858 | this.element.removeClass('rendered'); | ||
Brian E. Granger
|
r14015 | } | ||
Paul Ivanov
|
r15768 | return cont; | ||
Brian Granger
|
r6017 | }; | ||
Jonathan Frederic
|
r17196 | // Backwards compatability. | ||
Brian Granger
|
r4508 | IPython.TextCell = TextCell; | ||
IPython.MarkdownCell = MarkdownCell; | ||||
MinRK
|
r6248 | IPython.RawCell = RawCell; | ||
Brian Granger
|
r4508 | |||
Jonathan Frederic
|
r17214 | var textcell = { | ||
MinRK
|
r18596 | TextCell: TextCell, | ||
MarkdownCell: MarkdownCell, | ||||
Min RK
|
r18999 | RawCell: RawCell | ||
Jonathan Frederic
|
r17196 | }; | ||
Jonathan Frederic
|
r17214 | return textcell; | ||
Jonathan Frederic
|
r17196 | }); | ||