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