##// END OF EJS Templates
Treat set_rendered as unsafe.
Jonathan Frederic -
Show More
@@ -1,564 +1,568
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2012 The IPython Development Team
2 // Copyright (C) 2008-2012 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // TextCell
9 // TextCell
10 //============================================================================
10 //============================================================================
11
11
12
12
13
13
14 /**
14 /**
15 A module that allow to create different type of Text Cell
15 A module that allow to create different type of Text Cell
16 @module IPython
16 @module IPython
17 @namespace IPython
17 @namespace IPython
18 */
18 */
19 var IPython = (function (IPython) {
19 var IPython = (function (IPython) {
20 "use strict";
20 "use strict";
21
21
22 // TextCell base class
22 // TextCell base class
23 var key = IPython.utils.keycodes;
23 var key = IPython.utils.keycodes;
24
24
25 /**
25 /**
26 * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
26 * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
27 * cell start as not redered.
27 * cell start as not redered.
28 *
28 *
29 * @class TextCell
29 * @class TextCell
30 * @constructor TextCell
30 * @constructor TextCell
31 * @extend IPython.Cell
31 * @extend IPython.Cell
32 * @param {object|undefined} [options]
32 * @param {object|undefined} [options]
33 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend/overwrite default config
33 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend/overwrite default config
34 * @param [options.placeholder] {string} default string to use when souce in empty for rendering (only use in some TextCell subclass)
34 * @param [options.placeholder] {string} default string to use when souce in empty for rendering (only use in some TextCell subclass)
35 */
35 */
36 var TextCell = function (options) {
36 var TextCell = function (options) {
37 // in all TextCell/Cell subclasses
37 // in all TextCell/Cell subclasses
38 // do not assign most of members here, just pass it down
38 // do not assign most of members here, just pass it down
39 // in the options dict potentially overwriting what you wish.
39 // in the options dict potentially overwriting what you wish.
40 // they will be assigned in the base class.
40 // they will be assigned in the base class.
41
41
42 // we cannot put this as a class key as it has handle to "this".
42 // we cannot put this as a class key as it has handle to "this".
43 var cm_overwrite_options = {
43 var cm_overwrite_options = {
44 onKeyEvent: $.proxy(this.handle_keyevent,this)
44 onKeyEvent: $.proxy(this.handle_keyevent,this)
45 };
45 };
46
46
47 options = this.mergeopt(TextCell,options,{cm_config:cm_overwrite_options});
47 options = this.mergeopt(TextCell,options,{cm_config:cm_overwrite_options});
48
48
49 this.cell_type = this.cell_type || 'text';
49 this.cell_type = this.cell_type || 'text';
50
50
51 IPython.Cell.apply(this, [options]);
51 IPython.Cell.apply(this, [options]);
52
52
53 this.rendered = false;
53 this.rendered = false;
54 };
54 };
55
55
56 TextCell.prototype = new IPython.Cell();
56 TextCell.prototype = new IPython.Cell();
57
57
58 TextCell.options_default = {
58 TextCell.options_default = {
59 cm_config : {
59 cm_config : {
60 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
60 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
61 mode: 'htmlmixed',
61 mode: 'htmlmixed',
62 lineWrapping : true,
62 lineWrapping : true,
63 }
63 }
64 };
64 };
65
65
66
66
67 /**
67 /**
68 * Create the DOM element of the TextCell
68 * Create the DOM element of the TextCell
69 * @method create_element
69 * @method create_element
70 * @private
70 * @private
71 */
71 */
72 TextCell.prototype.create_element = function () {
72 TextCell.prototype.create_element = function () {
73 IPython.Cell.prototype.create_element.apply(this, arguments);
73 IPython.Cell.prototype.create_element.apply(this, arguments);
74
74
75 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
75 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
76 cell.attr('tabindex','2');
76 cell.attr('tabindex','2');
77
77
78 var prompt = $('<div/>').addClass('prompt input_prompt');
78 var prompt = $('<div/>').addClass('prompt input_prompt');
79 cell.append(prompt);
79 cell.append(prompt);
80 var inner_cell = $('<div/>').addClass('inner_cell');
80 var inner_cell = $('<div/>').addClass('inner_cell');
81 this.celltoolbar = new IPython.CellToolbar(this);
81 this.celltoolbar = new IPython.CellToolbar(this);
82 inner_cell.append(this.celltoolbar.element);
82 inner_cell.append(this.celltoolbar.element);
83 var input_area = $('<div/>').addClass('text_cell_input border-box-sizing');
83 var input_area = $('<div/>').addClass('text_cell_input border-box-sizing');
84 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
84 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
85 // The tabindex=-1 makes this div focusable.
85 // The tabindex=-1 makes this div focusable.
86 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
86 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
87 addClass('rendered_html').attr('tabindex','-1');
87 addClass('rendered_html').attr('tabindex','-1');
88 inner_cell.append(input_area).append(render_area);
88 inner_cell.append(input_area).append(render_area);
89 cell.append(inner_cell);
89 cell.append(inner_cell);
90 this.element = cell;
90 this.element = cell;
91 };
91 };
92
92
93
93
94 /**
94 /**
95 * Bind the DOM evet to cell actions
95 * Bind the DOM evet to cell actions
96 * Need to be called after TextCell.create_element
96 * Need to be called after TextCell.create_element
97 * @private
97 * @private
98 * @method bind_event
98 * @method bind_event
99 */
99 */
100 TextCell.prototype.bind_events = function () {
100 TextCell.prototype.bind_events = function () {
101 IPython.Cell.prototype.bind_events.apply(this);
101 IPython.Cell.prototype.bind_events.apply(this);
102 var that = this;
102 var that = this;
103
103
104 this.element.dblclick(function () {
104 this.element.dblclick(function () {
105 if (that.selected === false) {
105 if (that.selected === false) {
106 $([IPython.events]).trigger('select.Cell', {'cell':that});
106 $([IPython.events]).trigger('select.Cell', {'cell':that});
107 };
107 };
108 $([IPython.events]).trigger('edit_mode.Cell', {cell: that});
108 $([IPython.events]).trigger('edit_mode.Cell', {cell: that});
109 });
109 });
110 };
110 };
111
111
112 TextCell.prototype.handle_keyevent = function (editor, event) {
112 TextCell.prototype.handle_keyevent = function (editor, event) {
113
113
114 // console.log('CM', this.mode, event.which, event.type)
114 // console.log('CM', this.mode, event.which, event.type)
115
115
116 if (this.mode === 'command') {
116 if (this.mode === 'command') {
117 return true;
117 return true;
118 } else if (this.mode === 'edit') {
118 } else if (this.mode === 'edit') {
119 return this.handle_codemirror_keyevent(editor, event);
119 return this.handle_codemirror_keyevent(editor, event);
120 }
120 }
121 };
121 };
122
122
123 /**
123 /**
124 * This method gets called in CodeMirror's onKeyDown/onKeyPress
124 * This method gets called in CodeMirror's onKeyDown/onKeyPress
125 * handlers and is used to provide custom key handling.
125 * handlers and is used to provide custom key handling.
126 *
126 *
127 * Subclass should override this method to have custom handeling
127 * Subclass should override this method to have custom handeling
128 *
128 *
129 * @method handle_codemirror_keyevent
129 * @method handle_codemirror_keyevent
130 * @param {CodeMirror} editor - The codemirror instance bound to the cell
130 * @param {CodeMirror} editor - The codemirror instance bound to the cell
131 * @param {event} event -
131 * @param {event} event -
132 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
132 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
133 */
133 */
134 TextCell.prototype.handle_codemirror_keyevent = function (editor, event) {
134 TextCell.prototype.handle_codemirror_keyevent = function (editor, event) {
135 var that = this;
135 var that = this;
136
136
137 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey || event.altKey)) {
137 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey || event.altKey)) {
138 // Always ignore shift-enter in CodeMirror as we handle it.
138 // Always ignore shift-enter in CodeMirror as we handle it.
139 return true;
139 return true;
140 } else if (event.which === key.UPARROW && event.type === 'keydown') {
140 } else if (event.which === key.UPARROW && event.type === 'keydown') {
141 // If we are not at the top, let CM handle the up arrow and
141 // If we are not at the top, let CM handle the up arrow and
142 // prevent the global keydown handler from handling it.
142 // prevent the global keydown handler from handling it.
143 if (!that.at_top()) {
143 if (!that.at_top()) {
144 event.stop();
144 event.stop();
145 return false;
145 return false;
146 } else {
146 } else {
147 return true;
147 return true;
148 };
148 };
149 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
149 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
150 // If we are not at the bottom, let CM handle the down arrow and
150 // If we are not at the bottom, let CM handle the down arrow and
151 // prevent the global keydown handler from handling it.
151 // prevent the global keydown handler from handling it.
152 if (!that.at_bottom()) {
152 if (!that.at_bottom()) {
153 event.stop();
153 event.stop();
154 return false;
154 return false;
155 } else {
155 } else {
156 return true;
156 return true;
157 };
157 };
158 } else if (event.which === key.ESC && event.type === 'keydown') {
158 } else if (event.which === key.ESC && event.type === 'keydown') {
159 if (that.code_mirror.options.keyMap === "vim-insert") {
159 if (that.code_mirror.options.keyMap === "vim-insert") {
160 // vim keyMap is active and in insert mode. In this case we leave vim
160 // vim keyMap is active and in insert mode. In this case we leave vim
161 // insert mode, but remain in notebook edit mode.
161 // insert mode, but remain in notebook edit mode.
162 // Let' CM handle this event and prevent global handling.
162 // Let' CM handle this event and prevent global handling.
163 event.stop();
163 event.stop();
164 return false;
164 return false;
165 } else {
165 } else {
166 // vim keyMap is not active. Leave notebook edit mode.
166 // vim keyMap is not active. Leave notebook edit mode.
167 // Don't let CM handle the event, defer to global handling.
167 // Don't let CM handle the event, defer to global handling.
168 return true;
168 return true;
169 }
169 }
170 }
170 }
171 return false;
171 return false;
172 };
172 };
173
173
174 // Cell level actions
174 // Cell level actions
175
175
176 TextCell.prototype.select = function () {
176 TextCell.prototype.select = function () {
177 var cont = IPython.Cell.prototype.select.apply(this);
177 var cont = IPython.Cell.prototype.select.apply(this);
178 if (cont) {
178 if (cont) {
179 if (this.mode === 'edit') {
179 if (this.mode === 'edit') {
180 this.code_mirror.refresh();
180 this.code_mirror.refresh();
181 }
181 }
182 };
182 };
183 return cont;
183 return cont;
184 };
184 };
185
185
186 TextCell.prototype.unrender = function () {
186 TextCell.prototype.unrender = function () {
187 if (this.read_only) return;
187 if (this.read_only) return;
188 var cont = IPython.Cell.prototype.unrender.apply(this);
188 var cont = IPython.Cell.prototype.unrender.apply(this);
189 if (cont) {
189 if (cont) {
190 var text_cell = this.element;
190 var text_cell = this.element;
191 var output = text_cell.find("div.text_cell_render");
191 var output = text_cell.find("div.text_cell_render");
192 output.hide();
192 output.hide();
193 text_cell.find('div.text_cell_input').show();
193 text_cell.find('div.text_cell_input').show();
194 if (this.get_text() === this.placeholder) {
194 if (this.get_text() === this.placeholder) {
195 this.set_text('');
195 this.set_text('');
196 this.refresh();
196 this.refresh();
197 }
197 }
198
198
199 };
199 };
200 return cont;
200 return cont;
201 };
201 };
202
202
203 TextCell.prototype.execute = function () {
203 TextCell.prototype.execute = function () {
204 this.render();
204 this.render();
205 };
205 };
206
206
207 TextCell.prototype.edit_mode = function () {
207 TextCell.prototype.edit_mode = function () {
208 var cont = IPython.Cell.prototype.edit_mode.apply(this);
208 var cont = IPython.Cell.prototype.edit_mode.apply(this);
209 if (cont) {
209 if (cont) {
210 this.unrender();
210 this.unrender();
211 this.focus_editor();
211 this.focus_editor();
212 };
212 };
213 return cont;
213 return cont;
214 }
214 }
215
215
216 /**
216 /**
217 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
217 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
218 * @method get_text
218 * @method get_text
219 * @retrun {string} CodeMirror current text value
219 * @retrun {string} CodeMirror current text value
220 */
220 */
221 TextCell.prototype.get_text = function() {
221 TextCell.prototype.get_text = function() {
222 return this.code_mirror.getValue();
222 return this.code_mirror.getValue();
223 };
223 };
224
224
225 /**
225 /**
226 * @param {string} text - Codemiror text value
226 * @param {string} text - Codemiror text value
227 * @see TextCell#get_text
227 * @see TextCell#get_text
228 * @method set_text
228 * @method set_text
229 * */
229 * */
230 TextCell.prototype.set_text = function(text) {
230 TextCell.prototype.set_text = function(text) {
231 this.code_mirror.setValue(text);
231 this.code_mirror.setValue(text);
232 this.code_mirror.refresh();
232 this.code_mirror.refresh();
233 };
233 };
234
234
235 /**
235 /**
236 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
236 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
237 * @method get_rendered
237 * @method get_rendered
238 * @return {html} html of rendered element
238 * @return {html} html of rendered element
239 * */
239 * */
240 TextCell.prototype.get_rendered = function() {
240 TextCell.prototype.get_rendered = function() {
241 return this.element.find('div.text_cell_render').html();
241 return this.element.find('div.text_cell_render').html();
242 };
242 };
243
243
244 /**
244 /**
245 * @method set_rendered
245 * @method set_rendered
246 */
246 */
247 TextCell.prototype.set_rendered = function(text) {
247 TextCell.prototype.set_rendered = function(text) {
248 // TODO: This HTML needs to be treated as potentially dangerous
249 // user input.
250 this.element.find('div.text_cell_render').html(text);
248 this.element.find('div.text_cell_render').html(text);
251 };
249 };
252
250
253 /**
251 /**
254 * @method at_top
252 * @method at_top
255 * @return {Boolean}
253 * @return {Boolean}
256 */
254 */
257 TextCell.prototype.at_top = function () {
255 TextCell.prototype.at_top = function () {
258 if (this.rendered) {
256 if (this.rendered) {
259 return true;
257 return true;
260 } else {
258 } else {
261 var cursor = this.code_mirror.getCursor();
259 var cursor = this.code_mirror.getCursor();
262 if (cursor.line === 0 && cursor.ch === 0) {
260 if (cursor.line === 0 && cursor.ch === 0) {
263 return true;
261 return true;
264 } else {
262 } else {
265 return false;
263 return false;
266 };
264 };
267 };
265 };
268 };
266 };
269
267
270 /**
268 /**
271 * @method at_bottom
269 * @method at_bottom
272 * @return {Boolean}
270 * @return {Boolean}
273 * */
271 * */
274 TextCell.prototype.at_bottom = function () {
272 TextCell.prototype.at_bottom = function () {
275 if (this.rendered) {
273 if (this.rendered) {
276 return true;
274 return true;
277 } else {
275 } else {
278 var cursor = this.code_mirror.getCursor();
276 var cursor = this.code_mirror.getCursor();
279 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
277 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
280 return true;
278 return true;
281 } else {
279 } else {
282 return false;
280 return false;
283 };
281 };
284 };
282 };
285 };
283 };
286
284
287 /**
285 /**
288 * Create Text cell from JSON
286 * Create Text cell from JSON
289 * @param {json} data - JSON serialized text-cell
287 * @param {json} data - JSON serialized text-cell
290 * @method fromJSON
288 * @method fromJSON
291 */
289 */
292 TextCell.prototype.fromJSON = function (data) {
290 TextCell.prototype.fromJSON = function (data) {
293 IPython.Cell.prototype.fromJSON.apply(this, arguments);
291 IPython.Cell.prototype.fromJSON.apply(this, arguments);
294 if (data.cell_type === this.cell_type) {
292 if (data.cell_type === this.cell_type) {
295 if (data.source !== undefined) {
293 if (data.source !== undefined) {
296 this.set_text(data.source);
294 this.set_text(data.source);
297 // make this value the starting point, so that we can only undo
295 // make this value the starting point, so that we can only undo
298 // to this state, instead of a blank cell
296 // to this state, instead of a blank cell
299 this.code_mirror.clearHistory();
297 this.code_mirror.clearHistory();
298 // TODO: This HTML needs to be treated as potentially dangerous
299 // user input and should be handled before set_rendered.
300 this.set_rendered(data.rendered || '');
300 this.set_rendered(data.rendered || '');
301 this.rendered = false;
301 this.rendered = false;
302 this.render();
302 this.render();
303 }
303 }
304 }
304 }
305 };
305 };
306
306
307 /** Generate JSON from cell
307 /** Generate JSON from cell
308 * @return {object} cell data serialised to json
308 * @return {object} cell data serialised to json
309 */
309 */
310 TextCell.prototype.toJSON = function () {
310 TextCell.prototype.toJSON = function () {
311 var data = IPython.Cell.prototype.toJSON.apply(this);
311 var data = IPython.Cell.prototype.toJSON.apply(this);
312 data.source = this.get_text();
312 data.source = this.get_text();
313 if (data.source == this.placeholder) {
313 if (data.source == this.placeholder) {
314 data.source = "";
314 data.source = "";
315 }
315 }
316 return data;
316 return data;
317 };
317 };
318
318
319
319
320 /**
320 /**
321 * @class MarkdownCell
321 * @class MarkdownCell
322 * @constructor MarkdownCell
322 * @constructor MarkdownCell
323 * @extends IPython.HTMLCell
323 * @extends IPython.HTMLCell
324 */
324 */
325 var MarkdownCell = function (options) {
325 var MarkdownCell = function (options) {
326 options = this.mergeopt(MarkdownCell, options);
326 options = this.mergeopt(MarkdownCell, options);
327
327
328 this.cell_type = 'markdown';
328 this.cell_type = 'markdown';
329 TextCell.apply(this, [options]);
329 TextCell.apply(this, [options]);
330 };
330 };
331
331
332 MarkdownCell.options_default = {
332 MarkdownCell.options_default = {
333 cm_config: {
333 cm_config: {
334 mode: 'gfm'
334 mode: 'gfm'
335 },
335 },
336 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
336 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
337 }
337 }
338
338
339 MarkdownCell.prototype = new TextCell();
339 MarkdownCell.prototype = new TextCell();
340
340
341 /**
341 /**
342 * @method render
342 * @method render
343 */
343 */
344 MarkdownCell.prototype.render = function () {
344 MarkdownCell.prototype.render = function () {
345 var cont = IPython.TextCell.prototype.render.apply(this);
345 var cont = IPython.TextCell.prototype.render.apply(this);
346 if (cont) {
346 if (cont) {
347 var text = this.get_text();
347 var text = this.get_text();
348 var math = null;
348 var math = null;
349 if (text === "") { text = this.placeholder; }
349 if (text === "") { text = this.placeholder; }
350 var text_and_math = IPython.mathjaxutils.remove_math(text);
350 var text_and_math = IPython.mathjaxutils.remove_math(text);
351 text = text_and_math[0];
351 text = text_and_math[0];
352 math = text_and_math[1];
352 math = text_and_math[1];
353 var html = marked.parser(marked.lexer(text));
353 var html = marked.parser(marked.lexer(text));
354 html = $(IPython.mathjaxutils.replace_math(html, math));
354 html = $(IPython.mathjaxutils.replace_math(html, math));
355 // Links in markdown cells should open in new tabs.
355 // Links in markdown cells should open in new tabs.
356 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
356 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
357 try {
357 try {
358 // TODO: This HTML needs to be treated as potentially dangerous
359 // user input and should be handled before set_rendered.
358 this.set_rendered(html);
360 this.set_rendered(html);
359 } catch (e) {
361 } catch (e) {
360 console.log("Error running Javascript in Markdown:");
362 console.log("Error running Javascript in Markdown:");
361 console.log(e);
363 console.log(e);
362 this.set_rendered($("<div/>").addClass("js-error").html(
364 rendered.empty();
363 "Error rendering Markdown!<br/>" + e.toString())
365 rendered.append(
366 $("<div/>")
367 .append($("<div/>").text('Error rendering Markdown!').addClass("js-error"))
368 .append($("<div/>").text(e.toString()).addClass("js-error"))
364 );
369 );
365 }
370 }
366 this.element.find('div.text_cell_input').hide();
371 this.element.find('div.text_cell_input').hide();
367 this.element.find("div.text_cell_render").show();
372 this.element.find("div.text_cell_render").show();
368 this.typeset()
373 this.typeset()
369 };
374 };
370 return cont;
375 return cont;
371 };
376 };
372
377
373
378
374 // RawCell
379 // RawCell
375
380
376 /**
381 /**
377 * @class RawCell
382 * @class RawCell
378 * @constructor RawCell
383 * @constructor RawCell
379 * @extends IPython.TextCell
384 * @extends IPython.TextCell
380 */
385 */
381 var RawCell = function (options) {
386 var RawCell = function (options) {
382
387
383 options = this.mergeopt(RawCell,options)
388 options = this.mergeopt(RawCell,options)
384 TextCell.apply(this, [options]);
389 TextCell.apply(this, [options]);
385 this.cell_type = 'raw';
390 this.cell_type = 'raw';
386 // RawCell should always hide its rendered div
391 // RawCell should always hide its rendered div
387 this.element.find('div.text_cell_render').hide();
392 this.element.find('div.text_cell_render').hide();
388 };
393 };
389
394
390 RawCell.options_default = {
395 RawCell.options_default = {
391 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert.\n" +
396 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert.\n" +
392 "It will not be rendered in the notebook.\n" +
397 "It will not be rendered in the notebook.\n" +
393 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
398 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
394 };
399 };
395
400
396 RawCell.prototype = new TextCell();
401 RawCell.prototype = new TextCell();
397
402
398 /** @method bind_events **/
403 /** @method bind_events **/
399 RawCell.prototype.bind_events = function () {
404 RawCell.prototype.bind_events = function () {
400 TextCell.prototype.bind_events.apply(this);
405 TextCell.prototype.bind_events.apply(this);
401 var that = this
406 var that = this
402 this.element.focusout(function() {
407 this.element.focusout(function() {
403 that.auto_highlight();
408 that.auto_highlight();
404 });
409 });
405 };
410 };
406
411
407 /**
412 /**
408 * Trigger autodetection of highlight scheme for current cell
413 * Trigger autodetection of highlight scheme for current cell
409 * @method auto_highlight
414 * @method auto_highlight
410 */
415 */
411 RawCell.prototype.auto_highlight = function () {
416 RawCell.prototype.auto_highlight = function () {
412 this._auto_highlight(IPython.config.raw_cell_highlight);
417 this._auto_highlight(IPython.config.raw_cell_highlight);
413 };
418 };
414
419
415 /** @method render **/
420 /** @method render **/
416 RawCell.prototype.render = function () {
421 RawCell.prototype.render = function () {
417 // Make sure that this cell type can never be rendered
422 // Make sure that this cell type can never be rendered
418 if (this.rendered) {
423 if (this.rendered) {
419 this.unrender();
424 this.unrender();
420 }
425 }
421 var text = this.get_text();
426 var text = this.get_text();
422 if (text === "") { text = this.placeholder; }
427 if (text === "") { text = this.placeholder; }
423 this.set_text(text);
428 this.set_text(text);
424 };
429 };
425
430
426
431
427 /**
432 /**
428 * @class HeadingCell
433 * @class HeadingCell
429 * @extends IPython.TextCell
434 * @extends IPython.TextCell
430 */
435 */
431
436
432 /**
437 /**
433 * @constructor HeadingCell
438 * @constructor HeadingCell
434 * @extends IPython.TextCell
439 * @extends IPython.TextCell
435 */
440 */
436 var HeadingCell = function (options) {
441 var HeadingCell = function (options) {
437 options = this.mergeopt(HeadingCell, options);
442 options = this.mergeopt(HeadingCell, options);
438
443
439 this.level = 1;
444 this.level = 1;
440 this.cell_type = 'heading';
445 this.cell_type = 'heading';
441 TextCell.apply(this, [options]);
446 TextCell.apply(this, [options]);
442
447
443 /**
448 /**
444 * heading level of the cell, use getter and setter to access
449 * heading level of the cell, use getter and setter to access
445 * @property level
450 * @property level
446 */
451 */
447 };
452 };
448
453
449 HeadingCell.options_default = {
454 HeadingCell.options_default = {
450 placeholder: "Type Heading Here"
455 placeholder: "Type Heading Here"
451 };
456 };
452
457
453 HeadingCell.prototype = new TextCell();
458 HeadingCell.prototype = new TextCell();
454
459
455 /** @method fromJSON */
460 /** @method fromJSON */
456 HeadingCell.prototype.fromJSON = function (data) {
461 HeadingCell.prototype.fromJSON = function (data) {
457 if (data.level != undefined){
462 if (data.level != undefined){
458 this.level = data.level;
463 this.level = data.level;
459 }
464 }
460 TextCell.prototype.fromJSON.apply(this, arguments);
465 TextCell.prototype.fromJSON.apply(this, arguments);
461 };
466 };
462
467
463
468
464 /** @method toJSON */
469 /** @method toJSON */
465 HeadingCell.prototype.toJSON = function () {
470 HeadingCell.prototype.toJSON = function () {
466 var data = TextCell.prototype.toJSON.apply(this);
471 var data = TextCell.prototype.toJSON.apply(this);
467 data.level = this.get_level();
472 data.level = this.get_level();
468 return data;
473 return data;
469 };
474 };
470
475
471 /**
476 /**
472 * can the cell be split into two cells
477 * can the cell be split into two cells
473 * @method is_splittable
478 * @method is_splittable
474 **/
479 **/
475 HeadingCell.prototype.is_splittable = function () {
480 HeadingCell.prototype.is_splittable = function () {
476 return false;
481 return false;
477 };
482 };
478
483
479
484
480 /**
485 /**
481 * can the cell be merged with other cells
486 * can the cell be merged with other cells
482 * @method is_mergeable
487 * @method is_mergeable
483 **/
488 **/
484 HeadingCell.prototype.is_mergeable = function () {
489 HeadingCell.prototype.is_mergeable = function () {
485 return false;
490 return false;
486 };
491 };
487
492
488 /**
493 /**
489 * Change heading level of cell, and re-render
494 * Change heading level of cell, and re-render
490 * @method set_level
495 * @method set_level
491 */
496 */
492 HeadingCell.prototype.set_level = function (level) {
497 HeadingCell.prototype.set_level = function (level) {
493 this.level = level;
498 this.level = level;
494 if (this.rendered) {
499 if (this.rendered) {
495 this.rendered = false;
500 this.rendered = false;
496 this.render();
501 this.render();
497 };
502 };
498 };
503 };
499
504
500 /** The depth of header cell, based on html (h1 to h6)
505 /** The depth of header cell, based on html (h1 to h6)
501 * @method get_level
506 * @method get_level
502 * @return {integer} level - for 1 to 6
507 * @return {integer} level - for 1 to 6
503 */
508 */
504 HeadingCell.prototype.get_level = function () {
509 HeadingCell.prototype.get_level = function () {
505 return this.level;
510 return this.level;
506 };
511 };
507
512
508
513
509 HeadingCell.prototype.set_rendered = function (html) {
514 HeadingCell.prototype.set_rendered = function (html) {
510 // TODO: This HTML needs to be treated as potentially dangerous
511 // user input.
512 this.element.find("div.text_cell_render").html(html);
515 this.element.find("div.text_cell_render").html(html);
513 };
516 };
514
517
515
518
516 HeadingCell.prototype.get_rendered = function () {
519 HeadingCell.prototype.get_rendered = function () {
517 var r = this.element.find("div.text_cell_render");
520 var r = this.element.find("div.text_cell_render");
518 return r.children().first().html();
521 return r.children().first().html();
519 };
522 };
520
523
521
524
522 HeadingCell.prototype.render = function () {
525 HeadingCell.prototype.render = function () {
523 var cont = IPython.TextCell.prototype.render.apply(this);
526 var cont = IPython.TextCell.prototype.render.apply(this);
524 if (cont) {
527 if (cont) {
525 var text = this.get_text();
528 var text = this.get_text();
526 var math = null;
529 var math = null;
527 // Markdown headings must be a single line
530 // Markdown headings must be a single line
528 text = text.replace(/\n/g, ' ');
531 text = text.replace(/\n/g, ' ');
529 if (text === "") { text = this.placeholder; }
532 if (text === "") { text = this.placeholder; }
530 text = Array(this.level + 1).join("#") + " " + text;
533 text = Array(this.level + 1).join("#") + " " + text;
531 var text_and_math = IPython.mathjaxutils.remove_math(text);
534 var text_and_math = IPython.mathjaxutils.remove_math(text);
532 text = text_and_math[0];
535 text = text_and_math[0];
533 math = text_and_math[1];
536 math = text_and_math[1];
534 var html = marked.parser(marked.lexer(text));
537 var html = marked.parser(marked.lexer(text));
535 var h = $(IPython.mathjaxutils.replace_math(html, math));
538 var h = $(IPython.mathjaxutils.replace_math(html, math));
536 // add id and linkback anchor
539 // add id and linkback anchor
537 var hash = h.text().replace(/ /g, '-');
540 var hash = h.text().replace(/ /g, '-');
538 h.attr('id', hash);
541 h.attr('id', hash);
539 h.append(
542 h.append(
540 $('<a/>')
543 $('<a/>')
541 .addClass('anchor-link')
544 .addClass('anchor-link')
542 .attr('href', '#' + hash)
545 .attr('href', '#' + hash)
543 .text('¶')
546 .text('¶')
544 );
547 );
545
548 // TODO: This HTML needs to be treated as potentially dangerous
549 // user input and should be handled before set_rendered.
546 this.set_rendered(h);
550 this.set_rendered(h);
547 this.typeset();
551 this.typeset();
548 this.element.find('div.text_cell_input').hide();
552 this.element.find('div.text_cell_input').hide();
549 this.element.find("div.text_cell_render").show();
553 this.element.find("div.text_cell_render").show();
550
554
551 };
555 };
552 return cont;
556 return cont;
553 };
557 };
554
558
555 IPython.TextCell = TextCell;
559 IPython.TextCell = TextCell;
556 IPython.MarkdownCell = MarkdownCell;
560 IPython.MarkdownCell = MarkdownCell;
557 IPython.RawCell = RawCell;
561 IPython.RawCell = RawCell;
558 IPython.HeadingCell = HeadingCell;
562 IPython.HeadingCell = HeadingCell;
559
563
560
564
561 return IPython;
565 return IPython;
562
566
563 }(IPython));
567 }(IPython));
564
568
General Comments 0
You need to be logged in to leave comments. Login now