##// END OF EJS Templates
call unrender() when setting text of cells
Paul Ivanov -
Show More
@@ -1,465 +1,465 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'notebook/js/cell',
8 8 'base/js/security',
9 9 'notebook/js/mathjaxutils',
10 10 'notebook/js/celltoolbar',
11 11 'components/marked/lib/marked',
12 12 ], function(IPython, $, cell, security, mathjaxutils, celltoolbar, marked) {
13 13 "use strict";
14 14 var Cell = cell.Cell;
15 15
16 16 var TextCell = function (options) {
17 17 // Constructor
18 18 //
19 19 // Construct a new TextCell, codemirror mode is by default 'htmlmixed',
20 20 // and cell type is 'text' cell start as not redered.
21 21 //
22 22 // Parameters:
23 23 // options: dictionary
24 24 // Dictionary of keyword arguments.
25 25 // events: $(Events) instance
26 26 // config: dictionary
27 27 // keyboard_manager: KeyboardManager instance
28 28 // notebook: Notebook instance
29 29 options = options || {};
30 30
31 31 // in all TextCell/Cell subclasses
32 32 // do not assign most of members here, just pass it down
33 33 // in the options dict potentially overwriting what you wish.
34 34 // they will be assigned in the base class.
35 35 this.notebook = options.notebook;
36 36 this.events = options.events;
37 37 this.config = options.config;
38 38
39 39 // we cannot put this as a class key as it has handle to "this".
40 40 var cm_overwrite_options = {
41 41 onKeyEvent: $.proxy(this.handle_keyevent,this)
42 42 };
43 43 var config = this.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options});
44 44 Cell.apply(this, [{
45 45 config: config,
46 46 keyboard_manager: options.keyboard_manager,
47 47 events: this.events}]);
48 48
49 49 this.cell_type = this.cell_type || 'text';
50 50 mathjaxutils = mathjaxutils;
51 51 this.rendered = false;
52 52 };
53 53
54 54 TextCell.prototype = new Cell();
55 55
56 56 TextCell.options_default = {
57 57 cm_config : {
58 58 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
59 59 mode: 'htmlmixed',
60 60 lineWrapping : true,
61 61 }
62 62 };
63 63
64 64
65 65 /**
66 66 * Create the DOM element of the TextCell
67 67 * @method create_element
68 68 * @private
69 69 */
70 70 TextCell.prototype.create_element = function () {
71 71 Cell.prototype.create_element.apply(this, arguments);
72 72
73 73 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
74 74 cell.attr('tabindex','2');
75 75
76 76 var prompt = $('<div/>').addClass('prompt input_prompt');
77 77 cell.append(prompt);
78 78 var inner_cell = $('<div/>').addClass('inner_cell');
79 79 this.celltoolbar = new celltoolbar.CellToolbar({
80 80 cell: this,
81 81 events: this.events,
82 82 notebook: this.notebook});
83 83 inner_cell.append(this.celltoolbar.element);
84 84 var input_area = $('<div/>').addClass('input_area');
85 85 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
86 86 // The tabindex=-1 makes this div focusable.
87 87 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
88 88 addClass('rendered_html').attr('tabindex','-1');
89 89 inner_cell.append(input_area).append(render_area);
90 90 cell.append(inner_cell);
91 91 this.element = cell;
92 92 };
93 93
94 94
95 95 /**
96 96 * Bind the DOM evet to cell actions
97 97 * Need to be called after TextCell.create_element
98 98 * @private
99 99 * @method bind_event
100 100 */
101 101 TextCell.prototype.bind_events = function () {
102 102 Cell.prototype.bind_events.apply(this);
103 103 var that = this;
104 104
105 105 this.element.dblclick(function () {
106 106 if (that.selected === false) {
107 107 this.events.trigger('select.Cell', {'cell':that});
108 108 }
109 109 var cont = that.unrender();
110 110 if (cont) {
111 111 that.focus_editor();
112 112 }
113 113 });
114 114 };
115 115
116 116 // Cell level actions
117 117
118 118 TextCell.prototype.select = function () {
119 119 var cont = Cell.prototype.select.apply(this);
120 120 if (cont) {
121 121 if (this.mode === 'edit') {
122 122 this.code_mirror.refresh();
123 123 }
124 124 }
125 125 return cont;
126 126 };
127 127
128 128 TextCell.prototype.unrender = function () {
129 129 if (this.read_only) return;
130 130 var cont = Cell.prototype.unrender.apply(this);
131 131 if (cont) {
132 132 var text_cell = this.element;
133 133 var output = text_cell.find("div.text_cell_render");
134 134 output.hide();
135 135 text_cell.find('div.input_area').show();
136 136 if (this.get_text() === this.placeholder) {
137 137 this.set_text('');
138 138 }
139 139 this.refresh();
140 140 }
141 141 if (this.celltoolbar.ui_controls_list.length) {
142 142 this.celltoolbar.show();
143 143 }
144 144 return cont;
145 145 };
146 146
147 147 TextCell.prototype.execute = function () {
148 148 this.render();
149 149 };
150 150
151 151 /**
152 152 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
153 153 * @method get_text
154 154 * @retrun {string} CodeMirror current text value
155 155 */
156 156 TextCell.prototype.get_text = function() {
157 157 return this.code_mirror.getValue();
158 158 };
159 159
160 160 /**
161 161 * @param {string} text - Codemiror text value
162 162 * @see TextCell#get_text
163 163 * @method set_text
164 164 * */
165 165 TextCell.prototype.set_text = function(text) {
166 166 this.code_mirror.setValue(text);
167 this.unrender();
167 168 this.code_mirror.refresh();
168 this.rendered = false;
169 169 };
170 170
171 171 /**
172 172 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
173 173 * @method get_rendered
174 174 * */
175 175 TextCell.prototype.get_rendered = function() {
176 176 return this.element.find('div.text_cell_render').html();
177 177 };
178 178
179 179 /**
180 180 * @method set_rendered
181 181 */
182 182 TextCell.prototype.set_rendered = function(text) {
183 183 this.element.find('div.text_cell_render').html(text);
184 184 this.celltoolbar.hide();
185 185 };
186 186
187 187
188 188 /**
189 189 * Create Text cell from JSON
190 190 * @param {json} data - JSON serialized text-cell
191 191 * @method fromJSON
192 192 */
193 193 TextCell.prototype.fromJSON = function (data) {
194 194 Cell.prototype.fromJSON.apply(this, arguments);
195 195 if (data.cell_type === this.cell_type) {
196 196 if (data.source !== undefined) {
197 197 this.set_text(data.source);
198 198 // make this value the starting point, so that we can only undo
199 199 // to this state, instead of a blank cell
200 200 this.code_mirror.clearHistory();
201 201 // TODO: This HTML needs to be treated as potentially dangerous
202 202 // user input and should be handled before set_rendered.
203 203 this.set_rendered(data.rendered || '');
204 204 this.rendered = false;
205 205 this.render();
206 206 }
207 207 }
208 208 };
209 209
210 210 /** Generate JSON from cell
211 211 * @return {object} cell data serialised to json
212 212 */
213 213 TextCell.prototype.toJSON = function () {
214 214 var data = Cell.prototype.toJSON.apply(this);
215 215 data.source = this.get_text();
216 216 if (data.source == this.placeholder) {
217 217 data.source = "";
218 218 }
219 219 return data;
220 220 };
221 221
222 222
223 223 var MarkdownCell = function (options) {
224 224 // Constructor
225 225 //
226 226 // Parameters:
227 227 // options: dictionary
228 228 // Dictionary of keyword arguments.
229 229 // events: $(Events) instance
230 230 // config: dictionary
231 231 // keyboard_manager: KeyboardManager instance
232 232 // notebook: Notebook instance
233 233 options = options || {};
234 234 var config = this.mergeopt(MarkdownCell, options.config);
235 235 TextCell.apply(this, [$.extend({}, options, {config: config})]);
236 236
237 237 this.cell_type = 'markdown';
238 238 };
239 239
240 240 MarkdownCell.options_default = {
241 241 cm_config: {
242 242 mode: 'ipythongfm'
243 243 },
244 244 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
245 245 };
246 246
247 247 MarkdownCell.prototype = new TextCell();
248 248
249 249 /**
250 250 * @method render
251 251 */
252 252 MarkdownCell.prototype.render = function () {
253 253 var cont = TextCell.prototype.render.apply(this);
254 254 if (cont) {
255 255 var text = this.get_text();
256 256 var math = null;
257 257 if (text === "") { text = this.placeholder; }
258 258 var text_and_math = mathjaxutils.remove_math(text);
259 259 text = text_and_math[0];
260 260 math = text_and_math[1];
261 261 var html = marked.parser(marked.lexer(text));
262 262 html = mathjaxutils.replace_math(html, math);
263 263 html = security.sanitize_html(html);
264 264 html = $($.parseHTML(html));
265 265 // links in markdown cells should open in new tabs
266 266 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
267 267 this.set_rendered(html);
268 268 this.element.find('div.input_area').hide();
269 269 this.element.find("div.text_cell_render").show();
270 270 this.typeset();
271 271 }
272 272 return cont;
273 273 };
274 274
275 275
276 276 var RawCell = function (options) {
277 277 // Constructor
278 278 //
279 279 // Parameters:
280 280 // options: dictionary
281 281 // Dictionary of keyword arguments.
282 282 // events: $(Events) instance
283 283 // config: dictionary
284 284 // keyboard_manager: KeyboardManager instance
285 285 // notebook: Notebook instance
286 286 options = options || {};
287 287 var config = this.mergeopt(RawCell, options.config);
288 288 TextCell.apply(this, [$.extend({}, options, {config: config})]);
289 289
290 290 // RawCell should always hide its rendered div
291 291 this.element.find('div.text_cell_render').hide();
292 292 this.cell_type = 'raw';
293 293 };
294 294
295 295 RawCell.options_default = {
296 296 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
297 297 "It will not be rendered in the notebook. " +
298 298 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
299 299 };
300 300
301 301 RawCell.prototype = new TextCell();
302 302
303 303 /** @method bind_events **/
304 304 RawCell.prototype.bind_events = function () {
305 305 TextCell.prototype.bind_events.apply(this);
306 306 var that = this;
307 307 this.element.focusout(function() {
308 308 that.auto_highlight();
309 309 that.render();
310 310 });
311 311
312 312 this.code_mirror.on('focus', function() { that.unrender(); });
313 313 };
314 314
315 315 /**
316 316 * Trigger autodetection of highlight scheme for current cell
317 317 * @method auto_highlight
318 318 */
319 319 RawCell.prototype.auto_highlight = function () {
320 320 this._auto_highlight(this.config.raw_cell_highlight);
321 321 };
322 322
323 323 /** @method render **/
324 324 RawCell.prototype.render = function () {
325 325 var cont = TextCell.prototype.render.apply(this);
326 326 if (cont){
327 327 var text = this.get_text();
328 328 if (text === "") { text = this.placeholder; }
329 329 this.set_text(text);
330 330 this.element.removeClass('rendered');
331 331 }
332 332 return cont;
333 333 };
334 334
335 335
336 336 var HeadingCell = function (options) {
337 337 // Constructor
338 338 //
339 339 // Parameters:
340 340 // options: dictionary
341 341 // Dictionary of keyword arguments.
342 342 // events: $(Events) instance
343 343 // config: dictionary
344 344 // keyboard_manager: KeyboardManager instance
345 345 // notebook: Notebook instance
346 346 options = options || {};
347 347 var config = this.mergeopt(HeadingCell, options.config);
348 348 TextCell.apply(this, [$.extend({}, options, {config: config})]);
349 349
350 350 this.level = 1;
351 351 this.cell_type = 'heading';
352 352 };
353 353
354 354 HeadingCell.options_default = {
355 355 placeholder: "Type Heading Here"
356 356 };
357 357
358 358 HeadingCell.prototype = new TextCell();
359 359
360 360 /** @method fromJSON */
361 361 HeadingCell.prototype.fromJSON = function (data) {
362 362 if (data.level !== undefined){
363 363 this.level = data.level;
364 364 }
365 365 TextCell.prototype.fromJSON.apply(this, arguments);
366 366 };
367 367
368 368
369 369 /** @method toJSON */
370 370 HeadingCell.prototype.toJSON = function () {
371 371 var data = TextCell.prototype.toJSON.apply(this);
372 372 data.level = this.get_level();
373 373 return data;
374 374 };
375 375
376 376 /**
377 377 * can the cell be split into two cells
378 378 * @method is_splittable
379 379 **/
380 380 HeadingCell.prototype.is_splittable = function () {
381 381 return false;
382 382 };
383 383
384 384
385 385 /**
386 386 * can the cell be merged with other cells
387 387 * @method is_mergeable
388 388 **/
389 389 HeadingCell.prototype.is_mergeable = function () {
390 390 return false;
391 391 };
392 392
393 393 /**
394 394 * Change heading level of cell, and re-render
395 395 * @method set_level
396 396 */
397 397 HeadingCell.prototype.set_level = function (level) {
398 398 this.level = level;
399 399 if (this.rendered) {
400 400 this.rendered = false;
401 401 this.render();
402 402 }
403 403 };
404 404
405 405 /** The depth of header cell, based on html (h1 to h6)
406 406 * @method get_level
407 407 * @return {integer} level - for 1 to 6
408 408 */
409 409 HeadingCell.prototype.get_level = function () {
410 410 return this.level;
411 411 };
412 412
413 413
414 414 HeadingCell.prototype.get_rendered = function () {
415 415 var r = this.element.find("div.text_cell_render");
416 416 return r.children().first().html();
417 417 };
418 418
419 419 HeadingCell.prototype.render = function () {
420 420 var cont = TextCell.prototype.render.apply(this);
421 421 if (cont) {
422 422 var text = this.get_text();
423 423 var math = null;
424 424 // Markdown headings must be a single line
425 425 text = text.replace(/\n/g, ' ');
426 426 if (text === "") { text = this.placeholder; }
427 427 text = new Array(this.level + 1).join("#") + " " + text;
428 428 var text_and_math = mathjaxutils.remove_math(text);
429 429 text = text_and_math[0];
430 430 math = text_and_math[1];
431 431 var html = marked.parser(marked.lexer(text));
432 432 html = mathjaxutils.replace_math(html, math);
433 433 html = security.sanitize_html(html);
434 434 var h = $($.parseHTML(html));
435 435 // add id and linkback anchor
436 436 var hash = h.text().replace(/ /g, '-');
437 437 h.attr('id', hash);
438 438 h.append(
439 439 $('<a/>')
440 440 .addClass('anchor-link')
441 441 .attr('href', '#' + hash)
442 442 .text('¶')
443 443 );
444 444 this.set_rendered(h);
445 445 this.element.find('div.input_area').hide();
446 446 this.element.find("div.text_cell_render").show();
447 447 this.typeset();
448 448 }
449 449 return cont;
450 450 };
451 451
452 452 // Backwards compatability.
453 453 IPython.TextCell = TextCell;
454 454 IPython.MarkdownCell = MarkdownCell;
455 455 IPython.RawCell = RawCell;
456 456 IPython.HeadingCell = HeadingCell;
457 457
458 458 var textcell = {
459 459 'TextCell': TextCell,
460 460 'MarkdownCell': MarkdownCell,
461 461 'RawCell': RawCell,
462 462 'HeadingCell': HeadingCell,
463 463 };
464 464 return textcell;
465 465 });
General Comments 0
You need to be logged in to leave comments. Login now